1// Copyright 2018 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 xcoff
6
7import (
8	"encoding/binary"
9	"fmt"
10	"io"
11	"os"
12	"strconv"
13	"strings"
14)
15
16const (
17	SAIAMAG   = 0x8
18	AIAFMAG   = "`\n"
19	AIAMAG    = "<aiaff>\n"
20	AIAMAGBIG = "<bigaf>\n"
21
22	// Sizeof
23	FL_HSZ_BIG = 0x80
24	AR_HSZ_BIG = 0x70
25)
26
27type bigarFileHeader struct {
28	Flmagic    [SAIAMAG]byte // Archive magic string
29	Flmemoff   [20]byte      // Member table offset
30	Flgstoff   [20]byte      // 32-bits global symtab offset
31	Flgst64off [20]byte      // 64-bits global symtab offset
32	Flfstmoff  [20]byte      // First member offset
33	Fllstmoff  [20]byte      // Last member offset
34	Flfreeoff  [20]byte      // First member on free list offset
35}
36
37type bigarMemberHeader struct {
38	Arsize   [20]byte // File member size
39	Arnxtmem [20]byte // Next member pointer
40	Arprvmem [20]byte // Previous member pointer
41	Ardate   [12]byte // File member date
42	Aruid    [12]byte // File member uid
43	Argid    [12]byte // File member gid
44	Armode   [12]byte // File member mode (octal)
45	Arnamlen [4]byte  // File member name length
46	// _ar_nam is removed because it's easier to get name without it.
47}
48
49// Archive represents an open AIX big archive.
50type Archive struct {
51	ArchiveHeader
52	Members []*Member
53
54	closer io.Closer
55}
56
57// ArchiveHeader holds information about a big archive file header
58type ArchiveHeader struct {
59	magic string
60}
61
62// Member represents a member of an AIX big archive.
63type Member struct {
64	MemberHeader
65	sr *io.SectionReader
66}
67
68// MemberHeader holds information about a big archive member
69type MemberHeader struct {
70	Name string
71	Size uint64
72}
73
74// OpenArchive opens the named archive using os.Open and prepares it for use
75// as an AIX big archive.
76func OpenArchive(name string) (*Archive, error) {
77	f, err := os.Open(name)
78	if err != nil {
79		return nil, err
80	}
81	arch, err := NewArchive(f)
82	if err != nil {
83		f.Close()
84		return nil, err
85	}
86	arch.closer = f
87	return arch, nil
88}
89
90// Close closes the Archive.
91// If the Archive was created using NewArchive directly instead of OpenArchive,
92// Close has no effect.
93func (a *Archive) Close() error {
94	var err error
95	if a.closer != nil {
96		err = a.closer.Close()
97		a.closer = nil
98	}
99	return err
100}
101
102// NewArchive creates a new Archive for accessing an AIX big archive in an underlying reader.
103func NewArchive(r io.ReaderAt) (*Archive, error) {
104	parseDecimalBytes := func(b []byte) (int64, error) {
105		return strconv.ParseInt(strings.TrimSpace(string(b)), 10, 64)
106	}
107	sr := io.NewSectionReader(r, 0, 1<<63-1)
108
109	// Read File Header
110	var magic [SAIAMAG]byte
111	if _, err := sr.ReadAt(magic[:], 0); err != nil {
112		return nil, err
113	}
114
115	arch := new(Archive)
116	switch string(magic[:]) {
117	case AIAMAGBIG:
118		arch.magic = string(magic[:])
119	case AIAMAG:
120		return nil, fmt.Errorf("small AIX archive not supported")
121	default:
122		return nil, fmt.Errorf("unrecognised archive magic: 0x%x", magic)
123	}
124
125	var fhdr bigarFileHeader
126	if _, err := sr.Seek(0, io.SeekStart); err != nil {
127		return nil, err
128	}
129	if err := binary.Read(sr, binary.BigEndian, &fhdr); err != nil {
130		return nil, err
131	}
132
133	off, err := parseDecimalBytes(fhdr.Flfstmoff[:])
134	if err != nil {
135		return nil, fmt.Errorf("error parsing offset of first member in archive header(%q); %v", fhdr, err)
136	}
137
138	if off == 0 {
139		// Occurs if the archive is empty.
140		return arch, nil
141	}
142
143	lastoff, err := parseDecimalBytes(fhdr.Fllstmoff[:])
144	if err != nil {
145		return nil, fmt.Errorf("error parsing offset of first member in archive header(%q); %v", fhdr, err)
146	}
147
148	// Read members
149	for {
150		// Read Member Header
151		// The member header is normally 2 bytes larger. But it's easier
152		// to read the name if the header is read without _ar_nam.
153		// However, AIAFMAG must be read afterward.
154		if _, err := sr.Seek(off, io.SeekStart); err != nil {
155			return nil, err
156		}
157
158		var mhdr bigarMemberHeader
159		if err := binary.Read(sr, binary.BigEndian, &mhdr); err != nil {
160			return nil, err
161		}
162
163		member := new(Member)
164		arch.Members = append(arch.Members, member)
165
166		size, err := parseDecimalBytes(mhdr.Arsize[:])
167		if err != nil {
168			return nil, fmt.Errorf("error parsing size in member header(%q); %v", mhdr, err)
169		}
170		member.Size = uint64(size)
171
172		// Read name
173		namlen, err := parseDecimalBytes(mhdr.Arnamlen[:])
174		if err != nil {
175			return nil, fmt.Errorf("error parsing name length in member header(%q); %v", mhdr, err)
176		}
177		name := make([]byte, namlen)
178		if err := binary.Read(sr, binary.BigEndian, name); err != nil {
179			return nil, err
180		}
181		member.Name = string(name)
182
183		fileoff := off + AR_HSZ_BIG + namlen
184		if fileoff&1 != 0 {
185			fileoff++
186			if _, err := sr.Seek(1, io.SeekCurrent); err != nil {
187				return nil, err
188			}
189		}
190
191		// Read AIAFMAG string
192		var fmag [2]byte
193		if err := binary.Read(sr, binary.BigEndian, &fmag); err != nil {
194			return nil, err
195		}
196		if string(fmag[:]) != AIAFMAG {
197			return nil, fmt.Errorf("AIAFMAG not found after member header")
198		}
199
200		fileoff += 2 // Add the two bytes of AIAFMAG
201		member.sr = io.NewSectionReader(sr, fileoff, size)
202
203		if off == lastoff {
204			break
205		}
206		off, err = parseDecimalBytes(mhdr.Arnxtmem[:])
207		if err != nil {
208			return nil, fmt.Errorf("error parsing offset of first member in archive header(%q); %v", fhdr, err)
209		}
210
211	}
212
213	return arch, nil
214}
215
216// GetFile returns the XCOFF file defined by member name.
217// FIXME: This doesn't work if an archive has two members with the same
218// name which can occur if an archive has both 32-bits and 64-bits files.
219func (arch *Archive) GetFile(name string) (*File, error) {
220	for _, mem := range arch.Members {
221		if mem.Name == name {
222			return NewFile(mem.sr)
223		}
224	}
225	return nil, fmt.Errorf("unknown member %s in archive", name)
226}
227