// 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, 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)
}