1// Copyright 2018 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5// Except for this comment, this file is a verbatim copy of the file
6// with the same name in $GOROOT/src/go/internal/gccgoimporter.
7
8package gccgoimporter
9
10import (
11	"bytes"
12	"debug/elf"
13	"errors"
14	"fmt"
15	"io"
16	"strconv"
17	"strings"
18)
19
20// Magic strings for different archive file formats.
21const (
22	armag  = "!<arch>\n"
23	armagt = "!<thin>\n"
24	armagb = "<bigaf>\n"
25)
26
27// Offsets and sizes for fields in a standard archive header.
28const (
29	arNameOff  = 0
30	arNameSize = 16
31	arDateOff  = arNameOff + arNameSize
32	arDateSize = 12
33	arUIDOff   = arDateOff + arDateSize
34	arUIDSize  = 6
35	arGIDOff   = arUIDOff + arUIDSize
36	arGIDSize  = 6
37	arModeOff  = arGIDOff + arGIDSize
38	arModeSize = 8
39	arSizeOff  = arModeOff + arModeSize
40	arSizeSize = 10
41	arFmagOff  = arSizeOff + arSizeSize
42	arFmagSize = 2
43
44	arHdrSize = arFmagOff + arFmagSize
45)
46
47// The contents of the fmag field of a standard archive header.
48const arfmag = "`\n"
49
50// arExportData takes an archive file and returns a ReadSeeker for the
51// export data in that file. This assumes that there is only one
52// object in the archive containing export data, which is not quite
53// what gccgo does; gccgo concatenates together all the export data
54// for all the objects in the file.  In practice that case does not arise.
55func arExportData(archive io.ReadSeeker) (io.ReadSeeker, error) {
56	if _, err := archive.Seek(0, io.SeekStart); err != nil {
57		return nil, err
58	}
59
60	var buf [len(armag)]byte
61	if _, err := archive.Read(buf[:]); err != nil {
62		return nil, err
63	}
64
65	switch string(buf[:]) {
66	case armag:
67		return standardArExportData(archive)
68	case armagt:
69		return nil, errors.New("unsupported thin archive")
70	case armagb:
71		return nil, errors.New("unsupported AIX big archive")
72	default:
73		return nil, fmt.Errorf("unrecognized archive file format %q", buf[:])
74	}
75}
76
77// standardArExportData returns export data form a standard archive.
78func standardArExportData(archive io.ReadSeeker) (io.ReadSeeker, error) {
79	off := int64(len(armag))
80	for {
81		var hdrBuf [arHdrSize]byte
82		if _, err := archive.Read(hdrBuf[:]); err != nil {
83			return nil, err
84		}
85		off += arHdrSize
86
87		if bytes.Compare(hdrBuf[arFmagOff:arFmagOff+arFmagSize], []byte(arfmag)) != 0 {
88			return nil, fmt.Errorf("archive header format header (%q)", hdrBuf[:])
89		}
90
91		size, err := strconv.ParseInt(strings.TrimSpace(string(hdrBuf[arSizeOff:arSizeOff+arSizeSize])), 10, 64)
92		if err != nil {
93			return nil, fmt.Errorf("error parsing size in archive header (%q): %v", hdrBuf[:], err)
94		}
95
96		fn := hdrBuf[arNameOff : arNameOff+arNameSize]
97		if fn[0] == '/' && (fn[1] == ' ' || fn[1] == '/' || bytes.Compare(fn[:8], []byte("/SYM64/ ")) == 0) {
98			// Archive symbol table or extended name table,
99			// which we don't care about.
100		} else {
101			archiveAt := readerAtFromSeeker(archive)
102			ret, err := elfFromAr(io.NewSectionReader(archiveAt, off, size))
103			if ret != nil || err != nil {
104				return ret, err
105			}
106		}
107
108		if size&1 != 0 {
109			size++
110		}
111		off += size
112		if _, err := archive.Seek(off, io.SeekStart); err != nil {
113			return nil, err
114		}
115	}
116}
117
118// elfFromAr tries to get export data from an archive member as an ELF file.
119// If there is no export data, this returns nil, nil.
120func elfFromAr(member *io.SectionReader) (io.ReadSeeker, error) {
121	ef, err := elf.NewFile(member)
122	if err != nil {
123		return nil, err
124	}
125	sec := ef.Section(".go_export")
126	if sec == nil {
127		return nil, nil
128	}
129	return sec.Open(), nil
130}
131
132// readerAtFromSeeker turns an io.ReadSeeker into an io.ReaderAt.
133// This is only safe because there won't be any concurrent seeks
134// while this code is executing.
135func readerAtFromSeeker(rs io.ReadSeeker) io.ReaderAt {
136	if ret, ok := rs.(io.ReaderAt); ok {
137		return ret
138	}
139	return seekerReadAt{rs}
140}
141
142type seekerReadAt struct {
143	seeker io.ReadSeeker
144}
145
146func (sra seekerReadAt) ReadAt(p []byte, off int64) (int, error) {
147	if _, err := sra.seeker.Seek(off, io.SeekStart); err != nil {
148		return 0, err
149	}
150	return sra.seeker.Read(p)
151}
152