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