1// Copyright 2014 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package macho
6
7import (
8	"encoding/binary"
9	"fmt"
10	"internal/saferio"
11	"io"
12	"os"
13)
14
15// A FatFile is a Mach-O universal binary that contains at least one architecture.
16type FatFile struct {
17	Magic  uint32
18	Arches []FatArch
19	closer io.Closer
20}
21
22// A FatArchHeader represents a fat header for a specific image architecture.
23type FatArchHeader struct {
24	Cpu    Cpu
25	SubCpu uint32
26	Offset uint32
27	Size   uint32
28	Align  uint32
29}
30
31const fatArchHeaderSize = 5 * 4
32
33// A FatArch is a Mach-O File inside a FatFile.
34type FatArch struct {
35	FatArchHeader
36	*File
37}
38
39// ErrNotFat is returned from [NewFatFile] or [OpenFat] when the file is not a
40// universal binary but may be a thin binary, based on its magic number.
41var ErrNotFat = &FormatError{0, "not a fat Mach-O file", nil}
42
43// NewFatFile creates a new [FatFile] for accessing all the Mach-O images in a
44// universal binary. The Mach-O binary is expected to start at position 0 in
45// the ReaderAt.
46func NewFatFile(r io.ReaderAt) (*FatFile, error) {
47	var ff FatFile
48	sr := io.NewSectionReader(r, 0, 1<<63-1)
49
50	// Read the fat_header struct, which is always in big endian.
51	// Start with the magic number.
52	err := binary.Read(sr, binary.BigEndian, &ff.Magic)
53	if err != nil {
54		return nil, &FormatError{0, "error reading magic number", nil}
55	} else if ff.Magic != MagicFat {
56		// See if this is a Mach-O file via its magic number. The magic
57		// must be converted to little endian first though.
58		var buf [4]byte
59		binary.BigEndian.PutUint32(buf[:], ff.Magic)
60		leMagic := binary.LittleEndian.Uint32(buf[:])
61		if leMagic == Magic32 || leMagic == Magic64 {
62			return nil, ErrNotFat
63		} else {
64			return nil, &FormatError{0, "invalid magic number", nil}
65		}
66	}
67	offset := int64(4)
68
69	// Read the number of FatArchHeaders that come after the fat_header.
70	var narch uint32
71	err = binary.Read(sr, binary.BigEndian, &narch)
72	if err != nil {
73		return nil, &FormatError{offset, "invalid fat_header", nil}
74	}
75	offset += 4
76
77	if narch < 1 {
78		return nil, &FormatError{offset, "file contains no images", nil}
79	}
80
81	// Combine the Cpu and SubCpu (both uint32) into a uint64 to make sure
82	// there are not duplicate architectures.
83	seenArches := make(map[uint64]bool)
84	// Make sure that all images are for the same MH_ type.
85	var machoType Type
86
87	// Following the fat_header comes narch fat_arch structs that index
88	// Mach-O images further in the file.
89	c := saferio.SliceCap[FatArch](uint64(narch))
90	if c < 0 {
91		return nil, &FormatError{offset, "too many images", nil}
92	}
93	ff.Arches = make([]FatArch, 0, c)
94	for i := uint32(0); i < narch; i++ {
95		var fa FatArch
96		err = binary.Read(sr, binary.BigEndian, &fa.FatArchHeader)
97		if err != nil {
98			return nil, &FormatError{offset, "invalid fat_arch header", nil}
99		}
100		offset += fatArchHeaderSize
101
102		fr := io.NewSectionReader(r, int64(fa.Offset), int64(fa.Size))
103		fa.File, err = NewFile(fr)
104		if err != nil {
105			return nil, err
106		}
107
108		// Make sure the architecture for this image is not duplicate.
109		seenArch := (uint64(fa.Cpu) << 32) | uint64(fa.SubCpu)
110		if o, k := seenArches[seenArch]; o || k {
111			return nil, &FormatError{offset, fmt.Sprintf("duplicate architecture cpu=%v, subcpu=%#x", fa.Cpu, fa.SubCpu), nil}
112		}
113		seenArches[seenArch] = true
114
115		// Make sure the Mach-O type matches that of the first image.
116		if i == 0 {
117			machoType = fa.Type
118		} else {
119			if fa.Type != machoType {
120				return nil, &FormatError{offset, fmt.Sprintf("Mach-O type for architecture #%d (type=%#x) does not match first (type=%#x)", i, fa.Type, machoType), nil}
121			}
122		}
123
124		ff.Arches = append(ff.Arches, fa)
125	}
126
127	return &ff, nil
128}
129
130// OpenFat opens the named file using [os.Open] and prepares it for use as a Mach-O
131// universal binary.
132func OpenFat(name string) (*FatFile, error) {
133	f, err := os.Open(name)
134	if err != nil {
135		return nil, err
136	}
137	ff, err := NewFatFile(f)
138	if err != nil {
139		f.Close()
140		return nil, err
141	}
142	ff.closer = f
143	return ff, nil
144}
145
146func (ff *FatFile) Close() error {
147	var err error
148	if ff.closer != nil {
149		err = ff.closer.Close()
150		ff.closer = nil
151	}
152	return err
153}
154