xref: /aosp_15_r20/external/bazelbuild-rules_go/go/tools/builders/pack.go (revision 9bb1b549b6a84214c53be0924760be030e66b93a)
1// Copyright 2017 The Bazel Authors. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//    http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package main
16
17import (
18	"bufio"
19	"bytes"
20	"errors"
21	"fmt"
22	"io"
23	"io/ioutil"
24	"os"
25	"path/filepath"
26	"runtime"
27	"strconv"
28	"strings"
29)
30
31func copyFile(inPath, outPath string) error {
32	inFile, err := os.Open(inPath)
33	if err != nil {
34		return err
35	}
36	defer inFile.Close()
37	outFile, err := os.OpenFile(outPath, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0666)
38	if err != nil {
39		return err
40	}
41	defer outFile.Close()
42	_, err = io.Copy(outFile, inFile)
43	return err
44}
45
46func linkFile(inPath, outPath string) error {
47	inPath, err := filepath.Abs(inPath)
48	if err != nil {
49		return err
50	}
51	return os.Symlink(inPath, outPath)
52}
53
54func copyOrLinkFile(inPath, outPath string) error {
55	if runtime.GOOS == "windows" {
56		return copyFile(inPath, outPath)
57	} else {
58		return linkFile(inPath, outPath)
59	}
60}
61
62const (
63	// arHeader appears at the beginning of archives created by "ar" and
64	// "go tool pack" on all platforms.
65	arHeader = "!<arch>\n"
66
67	// entryLength is the size in bytes of the metadata preceding each file
68	// in an archive.
69	entryLength = 60
70
71	// pkgDef is the name of the export data file within an archive
72	pkgDef = "__.PKGDEF"
73
74	// nogoFact is the name of the nogo fact file
75	nogoFact = "nogo.out"
76)
77
78var zeroBytes = []byte("0                    ")
79
80type bufioReaderWithCloser struct {
81	// bufio.Reader is needed to skip bytes in archives
82	*bufio.Reader
83	io.Closer
84}
85
86func extractFiles(archive, dir string, names map[string]struct{}) (files []string, err error) {
87	rc, err := openArchive(archive)
88	if err != nil {
89		return nil, err
90	}
91	defer rc.Close()
92
93	var nameData []byte
94	bufReader := rc.Reader
95	for {
96		name, size, err := readMetadata(bufReader, &nameData)
97		if err == io.EOF {
98			return files, nil
99		}
100		if err != nil {
101			return nil, err
102		}
103		if !isObjectFile(name) {
104			if err := skipFile(bufReader, size); err != nil {
105				return nil, err
106			}
107			continue
108		}
109		name, err = simpleName(name, names)
110		if err != nil {
111			return nil, err
112		}
113		name = filepath.Join(dir, name)
114		if err := extractFile(bufReader, name, size); err != nil {
115			return nil, err
116		}
117		files = append(files, name)
118	}
119}
120
121func openArchive(archive string) (bufioReaderWithCloser, error) {
122	f, err := os.Open(archive)
123	if err != nil {
124		return bufioReaderWithCloser{}, err
125	}
126	r := bufio.NewReader(f)
127	header := make([]byte, len(arHeader))
128	if _, err := io.ReadFull(r, header); err != nil || string(header) != arHeader {
129		f.Close()
130		return bufioReaderWithCloser{}, fmt.Errorf("%s: bad header", archive)
131	}
132	return bufioReaderWithCloser{r, f}, nil
133}
134
135// readMetadata reads the relevant fields of an entry. Before calling,
136// r must be positioned at the beginning of an entry. Afterward, r will
137// be positioned at the beginning of the file data. io.EOF is returned if
138// there are no more files in the archive.
139//
140// Both BSD and GNU / SysV naming conventions are supported.
141func readMetadata(r *bufio.Reader, nameData *[]byte) (name string, size int64, err error) {
142retry:
143	// Each file is preceded by a 60-byte header that contains its metadata.
144	// We only care about two fields, name and size. Other fields (mtime,
145	// owner, group, mode) are ignored because they don't affect compilation.
146	var entry [entryLength]byte
147	if _, err := io.ReadFull(r, entry[:]); err != nil {
148		return "", 0, err
149	}
150
151	sizeField := strings.TrimSpace(string(entry[48:58]))
152	size, err = strconv.ParseInt(sizeField, 10, 64)
153	if err != nil {
154		return "", 0, err
155	}
156
157	nameField := strings.TrimRight(string(entry[:16]), " ")
158	switch {
159	case strings.HasPrefix(nameField, "#1/"):
160		// BSD-style name. The number of bytes in the name is written here in
161		// ASCII, right-padded with spaces. The actual name is stored at the
162		// beginning of the file data, left-padded with NUL bytes.
163		nameField = nameField[len("#1/"):]
164		nameLen, err := strconv.ParseInt(nameField, 10, 64)
165		if err != nil {
166			return "", 0, err
167		}
168		nameBuf := make([]byte, nameLen)
169		if _, err := io.ReadFull(r, nameBuf); err != nil {
170			return "", 0, err
171		}
172		name = strings.TrimRight(string(nameBuf), "\x00")
173		size -= nameLen
174
175	case nameField == "//":
176		// GNU / SysV-style name data. This is a fake file that contains names
177		// for files with long names. We read this into nameData, then read
178		// the next entry.
179		*nameData = make([]byte, size)
180		if _, err := io.ReadFull(r, *nameData); err != nil {
181			return "", 0, err
182		}
183		if size%2 != 0 {
184			// Files are aligned at 2-byte offsets. Discard the padding byte if the
185			// size was odd.
186			if _, err := r.ReadByte(); err != nil {
187				return "", 0, err
188			}
189		}
190		goto retry
191
192	case nameField == "/":
193		// GNU / SysV-style symbol lookup table. Skip.
194		if err := skipFile(r, size); err != nil {
195			return "", 0, err
196		}
197		goto retry
198
199	case strings.HasPrefix(nameField, "/"):
200		// GNU / SysV-style long file name. The number that follows the slash is
201		// an offset into the name data that should have been read earlier.
202		// The file name ends with a slash.
203		nameField = nameField[1:]
204		nameOffset, err := strconv.Atoi(nameField)
205		if err != nil {
206			return "", 0, err
207		}
208		if nameData == nil || nameOffset < 0 || nameOffset >= len(*nameData) {
209			return "", 0, fmt.Errorf("invalid name length: %d", nameOffset)
210		}
211		i := bytes.IndexByte((*nameData)[nameOffset:], '/')
212		if i < 0 {
213			return "", 0, errors.New("file name does not end with '/'")
214		}
215		name = string((*nameData)[nameOffset : nameOffset+i])
216
217	case strings.HasSuffix(nameField, "/"):
218		// GNU / SysV-style short file name.
219		name = nameField[:len(nameField)-1]
220
221	default:
222		// Common format name.
223		name = nameField
224	}
225
226	return name, size, err
227}
228
229// extractFile reads size bytes from r and writes them to a new file, name.
230func extractFile(r *bufio.Reader, name string, size int64) error {
231	w, err := os.Create(name)
232	if err != nil {
233		return err
234	}
235	defer w.Close()
236	_, err = io.CopyN(w, r, size)
237	if err != nil {
238		return err
239	}
240	if size%2 != 0 {
241		// Files are aligned at 2-byte offsets. Discard the padding byte if the
242		// size was odd.
243		if _, err := r.ReadByte(); err != nil {
244			return err
245		}
246	}
247	return nil
248}
249
250func skipFile(r *bufio.Reader, size int64) error {
251	if size%2 != 0 {
252		// Files are aligned at 2-byte offsets. Discard the padding byte if the
253		// size was odd.
254		size += 1
255	}
256	_, err := r.Discard(int(size))
257	return err
258}
259
260func isObjectFile(name string) bool {
261	return strings.HasSuffix(name, ".o")
262}
263
264// simpleName returns a file name which is at most 15 characters
265// and doesn't conflict with other names. If it is not possible to choose
266// such a name, simpleName will truncate the given name to 15 characters.
267// The original file extension will be preserved.
268func simpleName(name string, names map[string]struct{}) (string, error) {
269	if _, ok := names[name]; !ok && len(name) < 16 {
270		names[name] = struct{}{}
271		return name, nil
272	}
273	var stem, ext string
274	if i := strings.LastIndexByte(name, '.'); i < 0 {
275		stem = name
276	} else {
277		stem = strings.Replace(name[:i], ".", "_", -1)
278		ext = name[i:]
279	}
280	for n := 0; n < len(names)+1; n++ {
281		ns := strconv.Itoa(n)
282		stemLen := 15 - len(ext) - len(ns)
283		if stemLen < 0 {
284			break
285		}
286		if stemLen > len(stem) {
287			stemLen = len(stem)
288		}
289		candidate := stem[:stemLen] + ns + ext
290		if _, ok := names[candidate]; !ok {
291			names[candidate] = struct{}{}
292			return candidate, nil
293		}
294	}
295	return "", fmt.Errorf("cannot shorten file name: %q", name)
296}
297
298func appendFiles(goenv *env, archive string, files []string) error {
299	archive = abs(archive) // required for long filenames on Windows.
300
301	// Create an empty archive if one doesn't already exist.
302	// In Go 1.16, 'go tool pack r' reports an error if the archive doesn't exist.
303	// 'go tool pack c' copies export data in addition to creating the archive,
304	// so we don't want to use that directly.
305	_, err := os.Stat(archive)
306	if err != nil && !os.IsNotExist(err) {
307		return err
308	}
309	if os.IsNotExist(err) {
310		if err := ioutil.WriteFile(archive, []byte(arHeader), 0666); err != nil {
311			return err
312		}
313	}
314
315	// Append files to the archive.
316	// TODO(jayconrod): copy cmd/internal/archive and use that instead of
317	// shelling out to cmd/pack.
318	args := goenv.goTool("pack", "r", archive)
319	args = append(args, files...)
320	return goenv.runCommand(args)
321}
322
323type readWithCloser struct {
324	io.Reader
325	io.Closer
326}
327
328func readFileInArchive(fileName, archive string) (io.ReadCloser, error) {
329	rc, err := openArchive(archive)
330	if err != nil {
331		return nil, err
332	}
333	var nameData []byte
334	bufReader := rc.Reader
335	for err == nil {
336		// avoid shadowing err in the loop it can be returned correctly in the end
337		var (
338			name string
339			size int64
340		)
341		name, size, err = readMetadata(bufReader, &nameData)
342		if err != nil {
343			break
344		}
345		if name == fileName {
346			return readWithCloser{
347				Reader: io.LimitReader(rc, size),
348				Closer: rc,
349			}, nil
350		}
351		err = skipFile(bufReader, size)
352	}
353	if err == io.EOF {
354		err = os.ErrNotExist
355	}
356	rc.Close()
357	return nil, err
358}
359
360func extractFileFromArchive(archive, dir, name string) (err error) {
361	archiveReader, err := readFileInArchive(name, archive)
362	if err != nil {
363		return fmt.Errorf("error reading %s from %s: %v", name, archive, err)
364	}
365	defer func() {
366		e := archiveReader.Close()
367		if e != nil && err == nil {
368			err = fmt.Errorf("error closing %q: %v", archive, e)
369		}
370	}()
371	outPath := filepath.Join(dir, pkgDef)
372	outFile, err := os.Create(outPath)
373	if err != nil {
374		return fmt.Errorf("error creating %s: %v", outPath, err)
375	}
376	defer func() {
377		e := outFile.Close()
378		if e != nil && err == nil {
379			err = fmt.Errorf("error closing %q: %v", outPath, e)
380		}
381	}()
382	if size, err := io.Copy(outFile, archiveReader); err != nil {
383		return fmt.Errorf("error writing %s: %v", outPath, err)
384	} else if size == 0 {
385		return fmt.Errorf("%s is empty in %s", name, archive)
386	}
387	return err
388}
389