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