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 <iant@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Ian Lance Taylor <iant@golang.org>
diff --git a/src/internal/xcoff/ar.go b/src/internal/xcoff/ar.go
new file mode 100644
index 0000000..0fb410f
--- /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    = "<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)
+
+}
diff --git a/src/internal/xcoff/ar_test.go b/src/internal/xcoff/ar_test.go
new file mode 100644
index 0000000..03c2fd1
--- /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 0000000..851ccc5
--- /dev/null
+++ b/src/internal/xcoff/testdata/bigar-empty
@@ -0,0 +1,2 @@
+<bigaf>
+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 0000000..a8d4979
--- /dev/null
+++ b/src/internal/xcoff/testdata/bigar-ppc64
Binary files differ
diff --git a/src/internal/xcoff/testdata/printbye.c b/src/internal/xcoff/testdata/printbye.c
new file mode 100644
index 0000000..9045079
--- /dev/null
+++ b/src/internal/xcoff/testdata/printbye.c
@@ -0,0 +1,5 @@
+#include <stdio.h>
+
+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 0000000..182aa09
--- /dev/null
+++ b/src/internal/xcoff/testdata/printhello.c
@@ -0,0 +1,5 @@
+#include <stdio.h>
+
+void printhello(){
+	printf("Helloworld\n");
+}