From 21da8e6022d1d148741ba3c08cdc555df500520e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Chigot?= Date: Mon, 10 Dec 2018 10:53:39 +0100 Subject: [PATCH] internal/xcoff: add big archive support This commit adds support to read AIX big archive inside internal/xcoff package. Change-Id: I4317b40824b24312a69c918dfc6438dc3aff7be7 Reviewed-on: https://go-review.googlesource.com/c/153398 Run-TryBot: Ian Lance Taylor TryBot-Result: Gobot Gobot Reviewed-by: Ian Lance Taylor --- src/internal/xcoff/ar.go | 228 +++++++++++++++++++++++ src/internal/xcoff/ar_test.go | 79 ++++++++ src/internal/xcoff/testdata/bigar-empty | 2 + src/internal/xcoff/testdata/bigar-ppc64 | Bin 0 -> 2468 bytes src/internal/xcoff/testdata/printbye.c | 5 + src/internal/xcoff/testdata/printhello.c | 5 + 6 files changed, 319 insertions(+) create mode 100644 src/internal/xcoff/ar.go create mode 100644 src/internal/xcoff/ar_test.go create mode 100644 src/internal/xcoff/testdata/bigar-empty create mode 100644 src/internal/xcoff/testdata/bigar-ppc64 create mode 100644 src/internal/xcoff/testdata/printbye.c create mode 100644 src/internal/xcoff/testdata/printhello.c diff --git a/src/internal/xcoff/ar.go b/src/internal/xcoff/ar.go new file mode 100644 index 00000000000..0fb410f7dd7 --- /dev/null +++ b/src/internal/xcoff/ar.go @@ -0,0 +1,228 @@ +// 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) + +} diff --git a/src/internal/xcoff/ar_test.go b/src/internal/xcoff/ar_test.go new file mode 100644 index 00000000000..03c2fd1c5a2 --- /dev/null +++ b/src/internal/xcoff/ar_test.go @@ -0,0 +1,79 @@ +// 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 ( + "reflect" + "testing" +) + +type archiveTest struct { + file string + hdr ArchiveHeader + members []*MemberHeader + membersFileHeader []FileHeader +} + +var archTest = []archiveTest{ + { + "testdata/bigar-ppc64", + ArchiveHeader{AIAMAGBIG}, + []*MemberHeader{ + {"printbye.o", 836}, + {"printhello.o", 860}, + }, + []FileHeader{ + FileHeader{U64_TOCMAGIC}, + FileHeader{U64_TOCMAGIC}, + }, + }, + { + "testdata/bigar-empty", + ArchiveHeader{AIAMAGBIG}, + []*MemberHeader{}, + []FileHeader{}, + }, +} + +func TestOpenArchive(t *testing.T) { + for i := range archTest { + tt := &archTest[i] + arch, err := OpenArchive(tt.file) + if err != nil { + t.Error(err) + continue + } + if !reflect.DeepEqual(arch.ArchiveHeader, tt.hdr) { + t.Errorf("open archive %s:\n\thave %#v\n\twant %#v\n", tt.file, arch.ArchiveHeader, tt.hdr) + continue + } + + for i, mem := range arch.Members { + if i >= len(tt.members) { + break + } + have := &mem.MemberHeader + want := tt.members[i] + if !reflect.DeepEqual(have, want) { + t.Errorf("open %s, member %d:\n\thave %#v\n\twant %#v\n", tt.file, i, have, want) + } + + f, err := arch.GetFile(mem.Name) + if err != nil { + t.Error(err) + continue + } + if !reflect.DeepEqual(f.FileHeader, tt.membersFileHeader[i]) { + t.Errorf("open %s, member file header %d:\n\thave %#v\n\twant %#v\n", tt.file, i, f.FileHeader, tt.membersFileHeader[i]) + } + } + tn := len(tt.members) + an := len(arch.Members) + if tn != an { + t.Errorf("open %s: len(Members) = %d, want %d", tt.file, an, tn) + } + + } +} diff --git a/src/internal/xcoff/testdata/bigar-empty b/src/internal/xcoff/testdata/bigar-empty new file mode 100644 index 00000000000..851ccc51236 --- /dev/null +++ b/src/internal/xcoff/testdata/bigar-empty @@ -0,0 +1,2 @@ + +0 0 0 0 0 0 \ No newline at end of file diff --git a/src/internal/xcoff/testdata/bigar-ppc64 b/src/internal/xcoff/testdata/bigar-ppc64 new file mode 100644 index 0000000000000000000000000000000000000000..a8d4979d121f71266b57bc33c0e55e2b8a679cbb GIT binary patch literal 2468 zcmcIm&ubG=5T4ye3F)CxN@=}x=FfVZC|q z;8DSI>A&Tz;z96YpkkbPZ{FJ<*`^xsL3U=|``*mFdHc=gPP22+*uR%4<|`#DHu5o> z%wn-pj87MePJGHLB!A9EdG5W1&Cot<2w0SZ%Abb90G9Kq9Z%5U|;aL;l(PZ+~#dZ2QRq)RcGfFR3QLur%k~P&>q`r4t)etP-C&E(c(uv~^iaua%SS7v zEsB!X78;P6{08&DP`TPKXcEUSDa12FPm@j+9p@o4OQ${}3}=2#E`~8Hgb{wY;(UP$ zIioo*ZK-s{0Dkng&!iVb^8v-`(;po~C~@AS@-EqJ_T z+j(%xVYW#gCiRlU9`UdI#m*x#I&62lUWmcpWg(2uQeMj7F5^ylU!83d`~u*u z?NhQu7yx6LWlKu&{67rN1m|B51*=m*j=g?2oI$YuF;FAgE?X|kS&SwQ2aFz~6)^e( zS^=Xe+Qp2PcPBYLKchMGIT?LZGkOto#SvpPowcYLv??HRMib^PVP-Iz>QdK!n2EV4 zqp4ck8Y4r+8BLgH8Z*u4-HRA4ip`vsh1aZ>OTp{J?|O*Y!G}|SUK79mmwf5N{b82w zW~TmFVBEyMkq}XJD%+F2N}kpOC&6hr3w5-lo)yOZt?qJf*I#g-uasq~Chb%$=(#F) MY{~TRWa?)A0AGJZeE + +void printbye(){ + printf("Goodbye\n"); +} diff --git a/src/internal/xcoff/testdata/printhello.c b/src/internal/xcoff/testdata/printhello.c new file mode 100644 index 00000000000..182aa09728a --- /dev/null +++ b/src/internal/xcoff/testdata/printhello.c @@ -0,0 +1,5 @@ +#include + +void printhello(){ + printf("Helloworld\n"); +}