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