github.com/zach-klippenstein/go@v0.0.0-20150108044943-fcfbeb3adf58/src/cmd/pack/pack.go (about)

     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  	godoc 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, 1)
   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, 1)
   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  	rbuf := bufio.NewReader(f)
   430  	var wbuf bytes.Buffer
   431  	for {
   432  		line, err := rbuf.ReadBytes('\n')
   433  		if err != nil {
   434  			return nil, err
   435  		}
   436  		if wbuf.Len() == 0 && !bytes.HasPrefix(line, []byte("go object ")) {
   437  			return nil, errors.New("not a Go object file")
   438  		}
   439  		if bytes.Equal(line, []byte("!\n")) {
   440  			break
   441  		}
   442  		wbuf.Write(line)
   443  	}
   444  	return wbuf.Bytes(), nil
   445  }
   446  
   447  // exactly16Bytes truncates the string if necessary so it is at most 16 bytes long,
   448  // then pads the result with spaces to be exactly 16 bytes.
   449  // Fmt uses runes for its width calculation, but we need bytes in the entry header.
   450  func exactly16Bytes(s string) string {
   451  	for len(s) > 16 {
   452  		_, wid := utf8.DecodeLastRuneInString(s)
   453  		s = s[:len(s)-wid]
   454  	}
   455  	const sixteenSpaces = "                "
   456  	s += sixteenSpaces[:16-len(s)]
   457  	return s
   458  }
   459  
   460  // Finally, the actual commands. Each is an action.
   461  
   462  // can be modified for testing.
   463  var stdout io.Writer = os.Stdout
   464  
   465  // printContents implements the 'p' command.
   466  func (ar *Archive) printContents(entry *Entry) {
   467  	if ar.match(entry) {
   468  		if verbose {
   469  			listEntry(ar, entry, false)
   470  		}
   471  		ar.output(entry, stdout)
   472  	} else {
   473  		ar.skip(entry)
   474  	}
   475  }
   476  
   477  // skipContents implements the first part of the 'r' command.
   478  // It just scans the archive to make sure it's intact.
   479  func (ar *Archive) skipContents(entry *Entry) {
   480  	ar.skip(entry)
   481  }
   482  
   483  // tableOfContents implements the 't' command.
   484  func (ar *Archive) tableOfContents(entry *Entry) {
   485  	if ar.match(entry) {
   486  		listEntry(ar, entry, verbose)
   487  	}
   488  	ar.skip(entry)
   489  }
   490  
   491  // extractContents implements the 'x' command.
   492  func (ar *Archive) extractContents(entry *Entry) {
   493  	if ar.match(entry) {
   494  		if verbose {
   495  			listEntry(ar, entry, false)
   496  		}
   497  		fd, err := os.OpenFile(entry.name, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, entry.mode)
   498  		if err != nil {
   499  			log.Fatal(err)
   500  		}
   501  		ar.output(entry, fd)
   502  		fd.Close()
   503  	} else {
   504  		ar.skip(entry)
   505  	}
   506  }