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	"fmt"
9	"io"
10	"log"
11	"os"
12	"path/filepath"
13	"strconv"
14	"strings"
15	"time"
16	"unicode/utf8"
17)
18
19/*
20The archive format is:
21
22First, on a line by itself
23	!<arch>
24
25Then zero or more file records. Each file record has a fixed-size one-line header
26followed by data bytes followed by an optional padding byte. The header is:
27
28	%-16s%-12d%-6d%-6d%-8o%-10d`
29	name mtime uid gid mode size
30
31(note the trailing backquote). The %-16s here means at most 16 *bytes* of
32the name, and if shorter, space padded on the right.
33*/
34
35const usageMessage = `Usage: pack op file.a [name....]
36Where op is one of cprtx optionally followed by v for verbose output.
37For compatibility with old Go build environments the op string grc is
38accepted as a synonym for c.
39
40For more information, run
41	go doc cmd/pack`
42
43func usage() {
44	fmt.Fprintln(os.Stderr, usageMessage)
45	os.Exit(2)
46}
47
48func main() {
49	log.SetFlags(0)
50	log.SetPrefix("pack: ")
51	// need "pack op archive" at least.
52	if len(os.Args) < 3 {
53		log.Print("not enough arguments")
54		fmt.Fprintln(os.Stderr)
55		usage()
56	}
57	setOp(os.Args[1])
58	var ar *Archive
59	switch op {
60	case 'p':
61		ar = archive(os.Args[2], os.O_RDONLY, os.Args[3:])
62		ar.scan(ar.printContents)
63	case 'r':
64		ar = archive(os.Args[2], os.O_RDWR, os.Args[3:])
65		ar.scan(ar.skipContents)
66		ar.addFiles()
67	case 'c':
68		ar = archive(os.Args[2], os.O_RDWR|os.O_TRUNC, os.Args[3:])
69		ar.addPkgdef()
70		ar.addFiles()
71	case 't':
72		ar = archive(os.Args[2], os.O_RDONLY, os.Args[3:])
73		ar.scan(ar.tableOfContents)
74	case 'x':
75		ar = archive(os.Args[2], os.O_RDONLY, os.Args[3:])
76		ar.scan(ar.extractContents)
77	default:
78		log.Printf("invalid operation %q", os.Args[1])
79		fmt.Fprintln(os.Stderr)
80		usage()
81	}
82	if len(ar.files) > 0 {
83		log.Fatalf("file %q not in archive", ar.files[0])
84	}
85}
86
87// The unusual ancestry means the arguments are not Go-standard.
88// These variables hold the decoded operation specified by the first argument.
89// op holds the operation we are doing (prtx).
90// verbose tells whether the 'v' option was specified.
91var (
92	op      rune
93	verbose bool
94)
95
96// setOp parses the operation string (first argument).
97func setOp(arg string) {
98	// Recognize 'go tool pack grc' because that was the
99	// formerly canonical way to build a new archive
100	// from a set of input files. Accepting it keeps old
101	// build systems working with both Go 1.2 and Go 1.3.
102	if arg == "grc" {
103		arg = "c"
104	}
105
106	for _, r := range arg {
107		switch r {
108		case 'c', 'p', 'r', 't', 'x':
109			if op != 0 {
110				// At most one can be set.
111				usage()
112			}
113			op = r
114		case 'v':
115			if verbose {
116				// Can be set only once.
117				usage()
118			}
119			verbose = true
120		default:
121			usage()
122		}
123	}
124}
125
126const (
127	arHeader    = "!<arch>\n"
128	entryHeader = "%s%-12d%-6d%-6d%-8o%-10d`\n"
129	// In entryHeader the first entry, the name, is always printed as 16 bytes right-padded.
130	entryLen   = 16 + 12 + 6 + 6 + 8 + 10 + 1 + 1
131	timeFormat = "Jan _2 15:04 2006"
132)
133
134// An Archive represents an open archive file. It is always scanned sequentially
135// from start to end, without backing up.
136type Archive struct {
137	fd       *os.File // Open file descriptor.
138	files    []string // Explicit list of files to be processed.
139	pad      int      // Padding bytes required at end of current archive file
140	matchAll bool     // match all files in archive
141}
142
143// archive opens (and if necessary creates) the named archive.
144func archive(name string, mode int, files []string) *Archive {
145	// If the file exists, it must be an archive. If it doesn't exist, or if
146	// we're doing the c command, indicated by O_TRUNC, truncate the archive.
147	if !existingArchive(name) || mode&os.O_TRUNC != 0 {
148		create(name)
149		mode &^= os.O_TRUNC
150	}
151	fd, err := os.OpenFile(name, mode, 0)
152	if err != nil {
153		log.Fatal(err)
154	}
155	checkHeader(fd)
156	return &Archive{
157		fd:       fd,
158		files:    files,
159		matchAll: len(files) == 0,
160	}
161}
162
163// create creates and initializes an archive that does not exist.
164func create(name string) {
165	fd, err := os.Create(name)
166	if err != nil {
167		log.Fatal(err)
168	}
169	_, err = fmt.Fprint(fd, arHeader)
170	if err != nil {
171		log.Fatal(err)
172	}
173	fd.Close()
174}
175
176// existingArchive reports whether the file exists and is a valid archive.
177// If it exists but is not an archive, existingArchive will exit.
178func existingArchive(name string) bool {
179	fd, err := os.Open(name)
180	if err != nil {
181		if os.IsNotExist(err) {
182			return false
183		}
184		log.Fatalf("cannot open file: %s", err)
185	}
186	checkHeader(fd)
187	fd.Close()
188	return true
189}
190
191// checkHeader verifies the header of the file. It assumes the file
192// is positioned at 0 and leaves it positioned at the end of the header.
193func checkHeader(fd *os.File) {
194	buf := make([]byte, len(arHeader))
195	_, err := io.ReadFull(fd, buf)
196	if err != nil || string(buf) != arHeader {
197		log.Fatalf("%s is not an archive: bad header", fd.Name())
198	}
199}
200
201// An Entry is the internal representation of the per-file header information of one entry in the archive.
202type Entry struct {
203	name  string
204	mtime int64
205	uid   int
206	gid   int
207	mode  os.FileMode
208	size  int64
209}
210
211func (e *Entry) String() string {
212	return fmt.Sprintf("%s %6d/%-6d %12d %s %s",
213		(e.mode & 0777).String(),
214		e.uid,
215		e.gid,
216		e.size,
217		time.Unix(e.mtime, 0).Format(timeFormat),
218		e.name)
219}
220
221// readMetadata reads and parses the metadata for the next entry in the archive.
222func (ar *Archive) readMetadata() *Entry {
223	buf := make([]byte, entryLen)
224	_, err := io.ReadFull(ar.fd, buf)
225	if err == io.EOF {
226		// No entries left.
227		return nil
228	}
229	if err != nil || buf[entryLen-2] != '`' || buf[entryLen-1] != '\n' {
230		log.Fatal("file is not an archive: bad entry")
231	}
232	entry := new(Entry)
233	entry.name = strings.TrimRight(string(buf[:16]), " ")
234	if len(entry.name) == 0 {
235		log.Fatal("file is not an archive: bad name")
236	}
237	buf = buf[16:]
238	str := string(buf)
239	get := func(width, base, bitsize int) int64 {
240		v, err := strconv.ParseInt(strings.TrimRight(str[:width], " "), base, bitsize)
241		if err != nil {
242			log.Fatal("file is not an archive: bad number in entry: ", err)
243		}
244		str = str[width:]
245		return v
246	}
247	// %-16s%-12d%-6d%-6d%-8o%-10d`
248	entry.mtime = get(12, 10, 64)
249	entry.uid = int(get(6, 10, 32))
250	entry.gid = int(get(6, 10, 32))
251	entry.mode = os.FileMode(get(8, 8, 32))
252	entry.size = get(10, 10, 64)
253	return entry
254}
255
256// scan scans the archive and executes the specified action on each entry.
257// When action returns, the file offset is at the start of the next entry.
258func (ar *Archive) scan(action func(*Entry)) {
259	for {
260		entry := ar.readMetadata()
261		if entry == nil {
262			break
263		}
264		action(entry)
265	}
266}
267
268// listEntry prints to standard output a line describing the entry.
269func listEntry(entry *Entry, verbose bool) {
270	if verbose {
271		fmt.Fprintf(stdout, "%s\n", entry)
272	} else {
273		fmt.Fprintf(stdout, "%s\n", entry.name)
274	}
275}
276
277// output copies the entry to the specified writer.
278func (ar *Archive) output(entry *Entry, w io.Writer) {
279	n, err := io.Copy(w, io.LimitReader(ar.fd, entry.size))
280	if err != nil {
281		log.Fatal(err)
282	}
283	if n != entry.size {
284		log.Fatal("short file")
285	}
286	if entry.size&1 == 1 {
287		_, err := ar.fd.Seek(1, io.SeekCurrent)
288		if err != nil {
289			log.Fatal(err)
290		}
291	}
292}
293
294// skip skips the entry without reading it.
295func (ar *Archive) skip(entry *Entry) {
296	size := entry.size
297	if size&1 == 1 {
298		size++
299	}
300	_, err := ar.fd.Seek(size, io.SeekCurrent)
301	if err != nil {
302		log.Fatal(err)
303	}
304}
305
306// match reports whether the entry matches the argument list.
307// If it does, it also drops the file from the to-be-processed list.
308func (ar *Archive) match(entry *Entry) bool {
309	if ar.matchAll {
310		return true
311	}
312	for i, name := range ar.files {
313		if entry.name == name {
314			copy(ar.files[i:], ar.files[i+1:])
315			ar.files = ar.files[:len(ar.files)-1]
316			return true
317		}
318	}
319	return false
320}
321
322// addFiles adds files to the archive. The archive is known to be
323// sane and we are positioned at the end. No attempt is made
324// to check for existing files.
325func (ar *Archive) addFiles() {
326	if len(ar.files) == 0 {
327		usage()
328	}
329	for _, file := range ar.files {
330		if verbose {
331			fmt.Printf("%s\n", file)
332		}
333
334		if !isGoCompilerObjFile(file) {
335			fd, err := os.Open(file)
336			if err != nil {
337				log.Fatal(err)
338			}
339			ar.addFile(fd)
340			continue
341		}
342
343		aro := archive(file, os.O_RDONLY, nil)
344		aro.scan(func(entry *Entry) {
345			if entry.name != "_go_.o" {
346				aro.skip(entry)
347				return
348			}
349			ar.startFile(filepath.Base(file), 0, 0, 0, 0644, entry.size)
350			aro.output(entry, ar.fd)
351			ar.endFile()
352		})
353	}
354	ar.files = nil
355}
356
357// FileLike abstracts the few methods we need, so we can test without needing real files.
358type FileLike interface {
359	Name() string
360	Stat() (os.FileInfo, error)
361	Read([]byte) (int, error)
362	Close() error
363}
364
365// addFile adds a single file to the archive
366func (ar *Archive) addFile(fd FileLike) {
367	defer fd.Close()
368	// Format the entry.
369	// First, get its info.
370	info, err := fd.Stat()
371	if err != nil {
372		log.Fatal(err)
373	}
374	// mtime, uid, gid are all zero so repeated builds produce identical output.
375	mtime := int64(0)
376	uid := 0
377	gid := 0
378	ar.startFile(info.Name(), mtime, uid, gid, info.Mode(), info.Size())
379	n64, err := io.Copy(ar.fd, fd)
380	if err != nil {
381		log.Fatal("writing file: ", err)
382	}
383	if n64 != info.Size() {
384		log.Fatalf("writing file: wrote %d bytes; file is size %d", n64, info.Size())
385	}
386	ar.endFile()
387}
388
389// startFile writes the archive entry header.
390func (ar *Archive) startFile(name string, mtime int64, uid, gid int, mode os.FileMode, size int64) {
391	n, err := fmt.Fprintf(ar.fd, entryHeader, exactly16Bytes(name), mtime, uid, gid, mode, size)
392	if err != nil || n != entryLen {
393		log.Fatal("writing entry header: ", err)
394	}
395	ar.pad = int(size & 1)
396}
397
398// endFile writes the archive entry tail (a single byte of padding, if the file size was odd).
399func (ar *Archive) endFile() {
400	if ar.pad != 0 {
401		_, err := ar.fd.Write([]byte{0})
402		if err != nil {
403			log.Fatal("writing archive: ", err)
404		}
405		ar.pad = 0
406	}
407}
408
409// addPkgdef adds the __.PKGDEF file to the archive, copied
410// from the first Go object file on the file list, if any.
411// The archive is known to be empty.
412func (ar *Archive) addPkgdef() {
413	done := false
414	for _, file := range ar.files {
415		if !isGoCompilerObjFile(file) {
416			continue
417		}
418		aro := archive(file, os.O_RDONLY, nil)
419		aro.scan(func(entry *Entry) {
420			if entry.name != "__.PKGDEF" {
421				aro.skip(entry)
422				return
423			}
424			if verbose {
425				fmt.Printf("__.PKGDEF # %s\n", file)
426			}
427			ar.startFile("__.PKGDEF", 0, 0, 0, 0644, entry.size)
428			aro.output(entry, ar.fd)
429			ar.endFile()
430			done = true
431		})
432		if done {
433			break
434		}
435	}
436}
437
438// exactly16Bytes truncates the string if necessary so it is at most 16 bytes long,
439// then pads the result with spaces to be exactly 16 bytes.
440// Fmt uses runes for its width calculation, but we need bytes in the entry header.
441func exactly16Bytes(s string) string {
442	for len(s) > 16 {
443		_, wid := utf8.DecodeLastRuneInString(s)
444		s = s[:len(s)-wid]
445	}
446	const sixteenSpaces = "                "
447	s += sixteenSpaces[:16-len(s)]
448	return s
449}
450
451// Finally, the actual commands. Each is an action.
452
453// can be modified for testing.
454var stdout io.Writer = os.Stdout
455
456// printContents implements the 'p' command.
457func (ar *Archive) printContents(entry *Entry) {
458	if ar.match(entry) {
459		if verbose {
460			listEntry(entry, false)
461		}
462		ar.output(entry, stdout)
463	} else {
464		ar.skip(entry)
465	}
466}
467
468// skipContents implements the first part of the 'r' command.
469// It just scans the archive to make sure it's intact.
470func (ar *Archive) skipContents(entry *Entry) {
471	ar.skip(entry)
472}
473
474// tableOfContents implements the 't' command.
475func (ar *Archive) tableOfContents(entry *Entry) {
476	if ar.match(entry) {
477		listEntry(entry, verbose)
478	}
479	ar.skip(entry)
480}
481
482// extractContents implements the 'x' command.
483func (ar *Archive) extractContents(entry *Entry) {
484	if ar.match(entry) {
485		if verbose {
486			listEntry(entry, false)
487		}
488		fd, err := os.OpenFile(entry.name, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, entry.mode)
489		if err != nil {
490			log.Fatal(err)
491		}
492		ar.output(entry, fd)
493		fd.Close()
494	} else {
495		ar.skip(entry)
496	}
497}
498
499// isGoCompilerObjFile reports whether file is an object file created
500// by the Go compiler.
501func isGoCompilerObjFile(file string) bool {
502	fd, err := os.Open(file)
503	if err != nil {
504		log.Fatal(err)
505	}
506
507	// Check for "!<arch>\n" header.
508	buf := make([]byte, len(arHeader))
509	_, err = io.ReadFull(fd, buf)
510	if err != nil {
511		if err == io.EOF {
512			return false
513		}
514		log.Fatal(err)
515	}
516	if string(buf) != arHeader {
517		return false
518	}
519
520	// Check for exactly two entries: "__.PKGDEF" and "_go_.o".
521	match := []string{"__.PKGDEF", "_go_.o"}
522	buf = make([]byte, entryLen)
523	for {
524		_, err := io.ReadFull(fd, buf)
525		if err != nil {
526			if err == io.EOF {
527				// No entries left.
528				return true
529			}
530			log.Fatal(err)
531		}
532		if buf[entryLen-2] != '`' || buf[entryLen-1] != '\n' {
533			return false
534		}
535
536		name := strings.TrimRight(string(buf[:16]), " ")
537		for {
538			if len(match) == 0 {
539				return false
540			}
541			var next string
542			next, match = match[0], match[1:]
543			if name == next {
544				break
545			}
546		}
547
548		size, err := strconv.ParseInt(strings.TrimRight(string(buf[48:58]), " "), 10, 64)
549		if err != nil {
550			return false
551		}
552		if size&1 != 0 {
553			size++
554		}
555
556		_, err = fd.Seek(size, io.SeekCurrent)
557		if err != nil {
558			log.Fatal(err)
559		}
560	}
561}
562