1// Copyright 2014 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
5package main
6
7import (
8	"cmd/internal/archive"
9	"cmd/internal/telemetry/counter"
10	"fmt"
11	"io"
12	"io/fs"
13	"log"
14	"os"
15	"path/filepath"
16)
17
18const usageMessage = `Usage: pack op file.a [name....]
19Where op is one of cprtx optionally followed by v for verbose output.
20For compatibility with old Go build environments the op string grc is
21accepted as a synonym for c.
22
23For more information, run
24	go doc cmd/pack`
25
26func usage() {
27	fmt.Fprintln(os.Stderr, usageMessage)
28	os.Exit(2)
29}
30
31func main() {
32	log.SetFlags(0)
33	log.SetPrefix("pack: ")
34	counter.Open()
35	// need "pack op archive" at least.
36	if len(os.Args) < 3 {
37		log.Print("not enough arguments")
38		fmt.Fprintln(os.Stderr)
39		usage()
40	}
41	setOp(os.Args[1])
42	counter.Inc("pack/invocations")
43	counter.Inc("pack/op:" + string(op))
44	var ar *Archive
45	switch op {
46	case 'p':
47		ar = openArchive(os.Args[2], os.O_RDONLY, os.Args[3:])
48		ar.scan(ar.printContents)
49	case 'r':
50		ar = openArchive(os.Args[2], os.O_RDWR|os.O_CREATE, os.Args[3:])
51		ar.addFiles()
52	case 'c':
53		ar = openArchive(os.Args[2], os.O_RDWR|os.O_TRUNC|os.O_CREATE, os.Args[3:])
54		ar.addPkgdef()
55		ar.addFiles()
56	case 't':
57		ar = openArchive(os.Args[2], os.O_RDONLY, os.Args[3:])
58		ar.scan(ar.tableOfContents)
59	case 'x':
60		ar = openArchive(os.Args[2], os.O_RDONLY, os.Args[3:])
61		ar.scan(ar.extractContents)
62	default:
63		log.Printf("invalid operation %q", os.Args[1])
64		fmt.Fprintln(os.Stderr)
65		usage()
66	}
67	if len(ar.files) > 0 {
68		log.Fatalf("file %q not in archive", ar.files[0])
69	}
70}
71
72// The unusual ancestry means the arguments are not Go-standard.
73// These variables hold the decoded operation specified by the first argument.
74// op holds the operation we are doing (prtx).
75// verbose tells whether the 'v' option was specified.
76var (
77	op      rune
78	verbose bool
79)
80
81// setOp parses the operation string (first argument).
82func setOp(arg string) {
83	// Recognize 'go tool pack grc' because that was the
84	// formerly canonical way to build a new archive
85	// from a set of input files. Accepting it keeps old
86	// build systems working with both Go 1.2 and Go 1.3.
87	if arg == "grc" {
88		arg = "c"
89	}
90
91	for _, r := range arg {
92		switch r {
93		case 'c', 'p', 'r', 't', 'x':
94			if op != 0 {
95				// At most one can be set.
96				usage()
97			}
98			op = r
99		case 'v':
100			if verbose {
101				// Can be set only once.
102				usage()
103			}
104			verbose = true
105		default:
106			usage()
107		}
108	}
109}
110
111const (
112	arHeader = "!<arch>\n"
113)
114
115// An Archive represents an open archive file. It is always scanned sequentially
116// from start to end, without backing up.
117type Archive struct {
118	a        *archive.Archive
119	files    []string // Explicit list of files to be processed.
120	pad      int      // Padding bytes required at end of current archive file
121	matchAll bool     // match all files in archive
122}
123
124// archive opens (and if necessary creates) the named archive.
125func openArchive(name string, mode int, files []string) *Archive {
126	f, err := os.OpenFile(name, mode, 0666)
127	if err != nil {
128		log.Fatal(err)
129	}
130	var a *archive.Archive
131	if mode&os.O_TRUNC != 0 { // the c command
132		a, err = archive.New(f)
133	} else {
134		a, err = archive.Parse(f, verbose)
135		if err != nil && mode&os.O_CREATE != 0 { // the r command
136			a, err = archive.New(f)
137		}
138	}
139	if err != nil {
140		log.Fatal(err)
141	}
142	return &Archive{
143		a:        a,
144		files:    files,
145		matchAll: len(files) == 0,
146	}
147}
148
149// scan scans the archive and executes the specified action on each entry.
150func (ar *Archive) scan(action func(*archive.Entry)) {
151	for i := range ar.a.Entries {
152		e := &ar.a.Entries[i]
153		action(e)
154	}
155}
156
157// listEntry prints to standard output a line describing the entry.
158func listEntry(e *archive.Entry, verbose bool) {
159	if verbose {
160		fmt.Fprintf(stdout, "%s\n", e.String())
161	} else {
162		fmt.Fprintf(stdout, "%s\n", e.Name)
163	}
164}
165
166// output copies the entry to the specified writer.
167func (ar *Archive) output(e *archive.Entry, w io.Writer) {
168	r := io.NewSectionReader(ar.a.File(), e.Offset, e.Size)
169	n, err := io.Copy(w, r)
170	if err != nil {
171		log.Fatal(err)
172	}
173	if n != e.Size {
174		log.Fatal("short file")
175	}
176}
177
178// match reports whether the entry matches the argument list.
179// If it does, it also drops the file from the to-be-processed list.
180func (ar *Archive) match(e *archive.Entry) bool {
181	if ar.matchAll {
182		return true
183	}
184	for i, name := range ar.files {
185		if e.Name == name {
186			copy(ar.files[i:], ar.files[i+1:])
187			ar.files = ar.files[:len(ar.files)-1]
188			return true
189		}
190	}
191	return false
192}
193
194// addFiles adds files to the archive. The archive is known to be
195// sane and we are positioned at the end. No attempt is made
196// to check for existing files.
197func (ar *Archive) addFiles() {
198	if len(ar.files) == 0 {
199		usage()
200	}
201	for _, file := range ar.files {
202		if verbose {
203			fmt.Printf("%s\n", file)
204		}
205
206		f, err := os.Open(file)
207		if err != nil {
208			log.Fatal(err)
209		}
210		aro, err := archive.Parse(f, false)
211		if err != nil || !isGoCompilerObjFile(aro) {
212			f.Seek(0, io.SeekStart)
213			ar.addFile(f)
214			goto close
215		}
216
217		for _, e := range aro.Entries {
218			if e.Type != archive.EntryGoObj || e.Name != "_go_.o" {
219				continue
220			}
221			ar.a.AddEntry(archive.EntryGoObj, filepath.Base(file), 0, 0, 0, 0644, e.Size, io.NewSectionReader(f, e.Offset, e.Size))
222		}
223	close:
224		f.Close()
225	}
226	ar.files = nil
227}
228
229// FileLike abstracts the few methods we need, so we can test without needing real files.
230type FileLike interface {
231	Name() string
232	Stat() (fs.FileInfo, error)
233	Read([]byte) (int, error)
234	Close() error
235}
236
237// addFile adds a single file to the archive
238func (ar *Archive) addFile(fd FileLike) {
239	// Format the entry.
240	// First, get its info.
241	info, err := fd.Stat()
242	if err != nil {
243		log.Fatal(err)
244	}
245	// mtime, uid, gid are all zero so repeated builds produce identical output.
246	mtime := int64(0)
247	uid := 0
248	gid := 0
249	ar.a.AddEntry(archive.EntryNativeObj, info.Name(), mtime, uid, gid, info.Mode(), info.Size(), fd)
250}
251
252// addPkgdef adds the __.PKGDEF file to the archive, copied
253// from the first Go object file on the file list, if any.
254// The archive is known to be empty.
255func (ar *Archive) addPkgdef() {
256	done := false
257	for _, file := range ar.files {
258		f, err := os.Open(file)
259		if err != nil {
260			log.Fatal(err)
261		}
262		aro, err := archive.Parse(f, false)
263		if err != nil || !isGoCompilerObjFile(aro) {
264			goto close
265		}
266
267		for _, e := range aro.Entries {
268			if e.Type != archive.EntryPkgDef {
269				continue
270			}
271			if verbose {
272				fmt.Printf("__.PKGDEF # %s\n", file)
273			}
274			ar.a.AddEntry(archive.EntryPkgDef, "__.PKGDEF", 0, 0, 0, 0644, e.Size, io.NewSectionReader(f, e.Offset, e.Size))
275			done = true
276		}
277	close:
278		f.Close()
279		if done {
280			break
281		}
282	}
283}
284
285// Finally, the actual commands. Each is an action.
286
287// can be modified for testing.
288var stdout io.Writer = os.Stdout
289
290// printContents implements the 'p' command.
291func (ar *Archive) printContents(e *archive.Entry) {
292	ar.extractContents1(e, stdout)
293}
294
295// tableOfContents implements the 't' command.
296func (ar *Archive) tableOfContents(e *archive.Entry) {
297	if ar.match(e) {
298		listEntry(e, verbose)
299	}
300}
301
302// extractContents implements the 'x' command.
303func (ar *Archive) extractContents(e *archive.Entry) {
304	ar.extractContents1(e, nil)
305}
306
307func (ar *Archive) extractContents1(e *archive.Entry, out io.Writer) {
308	if ar.match(e) {
309		if verbose {
310			listEntry(e, false)
311		}
312		if out == nil {
313			f, err := os.OpenFile(e.Name, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0444 /*e.Mode*/)
314			if err != nil {
315				log.Fatal(err)
316			}
317			defer f.Close()
318			out = f
319		}
320		ar.output(e, out)
321	}
322}
323
324// isGoCompilerObjFile reports whether file is an object file created
325// by the Go compiler, which is an archive file with exactly one entry
326// of __.PKGDEF, or _go_.o, or both entries.
327func isGoCompilerObjFile(a *archive.Archive) bool {
328	switch len(a.Entries) {
329	case 1:
330		return (a.Entries[0].Type == archive.EntryGoObj && a.Entries[0].Name == "_go_.o") ||
331			(a.Entries[0].Type == archive.EntryPkgDef && a.Entries[0].Name == "__.PKGDEF")
332	case 2:
333		var foundPkgDef, foundGo bool
334		for _, e := range a.Entries {
335			if e.Type == archive.EntryPkgDef && e.Name == "__.PKGDEF" {
336				foundPkgDef = true
337			}
338			if e.Type == archive.EntryGoObj && e.Name == "_go_.o" {
339				foundGo = true
340			}
341		}
342		return foundPkgDef && foundGo
343	default:
344		return false
345	}
346}
347