| // Copyright 2018 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package xcoff |
| |
| import ( |
| "encoding/binary" |
| "fmt" |
| "io" |
| "os" |
| "strconv" |
| "strings" |
| ) |
| |
| const ( |
| SAIAMAG = 0x8 |
| AIAFMAG = "`\n" |
| AIAMAG = "<aiaff>\n" |
| AIAMAGBIG = "<bigaf>\n" |
| |
| // Sizeof |
| FL_HSZ_BIG = 0x80 |
| AR_HSZ_BIG = 0x70 |
| ) |
| |
| type bigarFileHeader struct { |
| Flmagic [SAIAMAG]byte // Archive magic string |
| Flmemoff [20]byte // Member table offset |
| Flgstoff [20]byte // 32-bits global symtab offset |
| Flgst64off [20]byte // 64-bits global symtab offset |
| Flfstmoff [20]byte // First member offset |
| Fllstmoff [20]byte // Last member offset |
| Flfreeoff [20]byte // First member on free list offset |
| } |
| |
| type bigarMemberHeader struct { |
| Arsize [20]byte // File member size |
| Arnxtmem [20]byte // Next member pointer |
| Arprvmem [20]byte // Previous member pointer |
| Ardate [12]byte // File member date |
| Aruid [12]byte // File member uid |
| Argid [12]byte // File member gid |
| Armode [12]byte // File member mode (octal) |
| Arnamlen [4]byte // File member name length |
| // _ar_nam is removed because it's easier to get name without it. |
| } |
| |
| // Archive represents an open AIX big archive. |
| type Archive struct { |
| ArchiveHeader |
| Members []*Member |
| |
| closer io.Closer |
| } |
| |
| // MemberHeader holds information about a big archive file header |
| type ArchiveHeader struct { |
| magic string |
| } |
| |
| // Member represents a member of an AIX big archive. |
| type Member struct { |
| MemberHeader |
| sr *io.SectionReader |
| } |
| |
| // MemberHeader holds information about a big archive member |
| type MemberHeader struct { |
| Name string |
| Size uint64 |
| } |
| |
| // OpenArchive opens the named archive using os.Open and prepares it for use |
| // as an AIX big archive. |
| func OpenArchive(name string) (*Archive, error) { |
| f, err := os.Open(name) |
| if err != nil { |
| return nil, err |
| } |
| arch, err := NewArchive(f) |
| if err != nil { |
| f.Close() |
| return nil, err |
| } |
| arch.closer = f |
| return arch, nil |
| } |
| |
| // Close closes the Archive. |
| // If the Archive was created using NewArchive directly instead of OpenArchive, |
| // Close has no effect. |
| func (a *Archive) Close() error { |
| var err error |
| if a.closer != nil { |
| err = a.closer.Close() |
| a.closer = nil |
| } |
| return err |
| } |
| |
| // NewArchive creates a new Archive for accessing an AIX big archive in an underlying reader. |
| func NewArchive(r io.ReaderAt) (*Archive, error) { |
| parseDecimalBytes := func(b []byte) (int64, error) { |
| return strconv.ParseInt(strings.TrimSpace(string(b)), 10, 64) |
| } |
| sr := io.NewSectionReader(r, 0, 1<<63-1) |
| |
| // Read File Header |
| var magic [SAIAMAG]byte |
| if _, err := sr.ReadAt(magic[:], 0); err != nil { |
| return nil, err |
| } |
| |
| arch := new(Archive) |
| switch string(magic[:]) { |
| case AIAMAGBIG: |
| arch.magic = string(magic[:]) |
| case AIAMAG: |
| return nil, fmt.Errorf("small AIX archive not supported") |
| default: |
| return nil, fmt.Errorf("unrecognised archive magic: 0x%x", magic) |
| } |
| |
| var fhdr bigarFileHeader |
| if _, err := sr.Seek(0, io.SeekStart); err != nil { |
| return nil, err |
| } |
| if err := binary.Read(sr, binary.BigEndian, &fhdr); err != nil { |
| return nil, err |
| } |
| |
| off, err := parseDecimalBytes(fhdr.Flfstmoff[:]) |
| if err != nil { |
| return nil, fmt.Errorf("error parsing offset of first member in archive header(%q); %v", fhdr, err) |
| } |
| |
| if off == 0 { |
| // Occurs if the archive is empty. |
| return arch, nil |
| } |
| |
| lastoff, err := parseDecimalBytes(fhdr.Fllstmoff[:]) |
| if err != nil { |
| return nil, fmt.Errorf("error parsing offset of first member in archive header(%q); %v", fhdr, err) |
| } |
| |
| // Read members |
| for { |
| // Read Member Header |
| // The member header is normally 2 bytes larger. But it's easier |
| // to read the name if the header is read without _ar_nam. |
| // However, AIAFMAG must be read afterward. |
| if _, err := sr.Seek(off, io.SeekStart); err != nil { |
| return nil, err |
| } |
| |
| var mhdr bigarMemberHeader |
| if err := binary.Read(sr, binary.BigEndian, &mhdr); err != nil { |
| return nil, err |
| } |
| |
| member := new(Member) |
| arch.Members = append(arch.Members, member) |
| |
| size, err := parseDecimalBytes(mhdr.Arsize[:]) |
| if err != nil { |
| return nil, fmt.Errorf("error parsing size in member header(%q); %v", mhdr, err) |
| } |
| member.Size = uint64(size) |
| |
| // Read name |
| namlen, err := parseDecimalBytes(mhdr.Arnamlen[:]) |
| if err != nil { |
| return nil, fmt.Errorf("error parsing name length in member header(%q); %v", mhdr, err) |
| } |
| name := make([]byte, namlen) |
| if err := binary.Read(sr, binary.BigEndian, name); err != nil { |
| return nil, err |
| } |
| member.Name = string(name) |
| |
| fileoff := off + AR_HSZ_BIG + namlen |
| if fileoff&1 != 0 { |
| fileoff++ |
| if _, err := sr.Seek(1, io.SeekCurrent); err != nil { |
| return nil, err |
| } |
| } |
| |
| // Read AIAFMAG string |
| var fmag [2]byte |
| if err := binary.Read(sr, binary.BigEndian, &fmag); err != nil { |
| return nil, err |
| } |
| if string(fmag[:]) != AIAFMAG { |
| return nil, fmt.Errorf("AIAFMAG not found after member header") |
| } |
| |
| fileoff += 2 // Add the two bytes of AIAFMAG |
| member.sr = io.NewSectionReader(sr, fileoff, size) |
| |
| if off == lastoff { |
| break |
| } |
| off, err = parseDecimalBytes(mhdr.Arnxtmem[:]) |
| if err != nil { |
| return nil, fmt.Errorf("error parsing offset of first member in archive header(%q); %v", fhdr, err) |
| } |
| |
| } |
| |
| return arch, nil |
| |
| } |
| |
| // GetFile returns the XCOFF file defined by member name. |
| // FIXME: This doesn't work if an archive has two members with the same |
| // name which can occur if a archive has both 32-bits and 64-bits files. |
| func (arch *Archive) GetFile(name string) (*File, error) { |
| for _, mem := range arch.Members { |
| if mem.Name == name { |
| return NewFile(mem.sr) |
| } |
| } |
| return nil, fmt.Errorf("unknown member %s in archive", name) |
| |
| } |