// 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 = "\n" AIAMAGBIG = "\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, os.SEEK_SET); 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, os.SEEK_SET); 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, os.SEEK_CUR); 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) }