...
Run Format

Source file src/cmd/pack/pack.go

Documentation: cmd/pack

  // Copyright 2014 The Go Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style
  // license that can be found in the LICENSE file.
  
  package main
  
  import (
  	"bufio"
  	"bytes"
  	"errors"
  	"fmt"
  	"io"
  	"log"
  	"os"
  	"strconv"
  	"strings"
  	"time"
  	"unicode/utf8"
  )
  
  /*
  The archive format is:
  
  First, on a line by itself
  	!<arch>
  
  Then zero or more file records. Each file record has a fixed-size one-line header
  followed by data bytes followed by an optional padding byte. The header is:
  
  	%-16s%-12d%-6d%-6d%-8o%-10d`
  	name mtime uid gid mode size
  
  (note the trailing backquote). The %-16s here means at most 16 *bytes* of
  the name, and if shorter, space padded on the right.
  */
  
  const usageMessage = `Usage: pack op file.a [name....]
  Where op is one of cprtx optionally followed by v for verbose output.
  For compatibility with old Go build environments the op string grc is
  accepted as a synonym for c.
  
  For more information, run
  	go doc cmd/pack`
  
  func usage() {
  	fmt.Fprintln(os.Stderr, usageMessage)
  	os.Exit(2)
  }
  
  func main() {
  	log.SetFlags(0)
  	log.SetPrefix("pack: ")
  	// need "pack op archive" at least.
  	if len(os.Args) < 3 {
  		log.Print("not enough arguments")
  		fmt.Fprintln(os.Stderr)
  		usage()
  	}
  	setOp(os.Args[1])
  	var ar *Archive
  	switch op {
  	case 'p':
  		ar = archive(os.Args[2], os.O_RDONLY, os.Args[3:])
  		ar.scan(ar.printContents)
  	case 'r':
  		ar = archive(os.Args[2], os.O_RDWR, os.Args[3:])
  		ar.scan(ar.skipContents)
  		ar.addFiles()
  	case 'c':
  		ar = archive(os.Args[2], os.O_RDWR|os.O_TRUNC, os.Args[3:])
  		ar.addPkgdef()
  		ar.addFiles()
  	case 't':
  		ar = archive(os.Args[2], os.O_RDONLY, os.Args[3:])
  		ar.scan(ar.tableOfContents)
  	case 'x':
  		ar = archive(os.Args[2], os.O_RDONLY, os.Args[3:])
  		ar.scan(ar.extractContents)
  	default:
  		log.Printf("invalid operation %q", os.Args[1])
  		fmt.Fprintln(os.Stderr)
  		usage()
  	}
  	if len(ar.files) > 0 {
  		log.Fatalf("file %q not in archive", ar.files[0])
  	}
  }
  
  // The unusual ancestry means the arguments are not Go-standard.
  // These variables hold the decoded operation specified by the first argument.
  // op holds the operation we are doing (prtx).
  // verbose tells whether the 'v' option was specified.
  var (
  	op      rune
  	verbose bool
  )
  
  // setOp parses the operation string (first argument).
  func setOp(arg string) {
  	// Recognize 'go tool pack grc' because that was the
  	// formerly canonical way to build a new archive
  	// from a set of input files. Accepting it keeps old
  	// build systems working with both Go 1.2 and Go 1.3.
  	if arg == "grc" {
  		arg = "c"
  	}
  
  	for _, r := range arg {
  		switch r {
  		case 'c', 'p', 'r', 't', 'x':
  			if op != 0 {
  				// At most one can be set.
  				usage()
  			}
  			op = r
  		case 'v':
  			if verbose {
  				// Can be set only once.
  				usage()
  			}
  			verbose = true
  		default:
  			usage()
  		}
  	}
  }
  
  const (
  	arHeader    = "!<arch>\n"
  	entryHeader = "%s%-12d%-6d%-6d%-8o%-10d`\n"
  	// In entryHeader the first entry, the name, is always printed as 16 bytes right-padded.
  	entryLen   = 16 + 12 + 6 + 6 + 8 + 10 + 1 + 1
  	timeFormat = "Jan _2 15:04 2006"
  )
  
  // An Archive represents an open archive file. It is always scanned sequentially
  // from start to end, without backing up.
  type Archive struct {
  	fd       *os.File // Open file descriptor.
  	files    []string // Explicit list of files to be processed.
  	pad      int      // Padding bytes required at end of current archive file
  	matchAll bool     // match all files in archive
  }
  
  // archive opens (and if necessary creates) the named archive.
  func archive(name string, mode int, files []string) *Archive {
  	// If the file exists, it must be an archive. If it doesn't exist, or if
  	// we're doing the c command, indicated by O_TRUNC, truncate the archive.
  	if !existingArchive(name) || mode&os.O_TRUNC != 0 {
  		create(name)
  		mode &^= os.O_TRUNC
  	}
  	fd, err := os.OpenFile(name, mode, 0)
  	if err != nil {
  		log.Fatal(err)
  	}
  	checkHeader(fd)
  	return &Archive{
  		fd:       fd,
  		files:    files,
  		matchAll: len(files) == 0,
  	}
  }
  
  // create creates and initializes an archive that does not exist.
  func create(name string) {
  	fd, err := os.Create(name)
  	if err != nil {
  		log.Fatal(err)
  	}
  	_, err = fmt.Fprint(fd, arHeader)
  	if err != nil {
  		log.Fatal(err)
  	}
  	fd.Close()
  }
  
  // existingArchive reports whether the file exists and is a valid archive.
  // If it exists but is not an archive, existingArchive will exit.
  func existingArchive(name string) bool {
  	fd, err := os.Open(name)
  	if err != nil {
  		if os.IsNotExist(err) {
  			return false
  		}
  		log.Fatalf("cannot open file: %s", err)
  	}
  	checkHeader(fd)
  	fd.Close()
  	return true
  }
  
  // checkHeader verifies the header of the file. It assumes the file
  // is positioned at 0 and leaves it positioned at the end of the header.
  func checkHeader(fd *os.File) {
  	buf := make([]byte, len(arHeader))
  	_, err := io.ReadFull(fd, buf)
  	if err != nil || string(buf) != arHeader {
  		log.Fatalf("%s is not an archive: bad header", fd.Name())
  	}
  }
  
  // An Entry is the internal representation of the per-file header information of one entry in the archive.
  type Entry struct {
  	name  string
  	mtime int64
  	uid   int
  	gid   int
  	mode  os.FileMode
  	size  int64
  }
  
  func (e *Entry) String() string {
  	return fmt.Sprintf("%s %6d/%-6d %12d %s %s",
  		(e.mode & 0777).String(),
  		e.uid,
  		e.gid,
  		e.size,
  		time.Unix(e.mtime, 0).Format(timeFormat),
  		e.name)
  }
  
  // readMetadata reads and parses the metadata for the next entry in the archive.
  func (ar *Archive) readMetadata() *Entry {
  	buf := make([]byte, entryLen)
  	_, err := io.ReadFull(ar.fd, buf)
  	if err == io.EOF {
  		// No entries left.
  		return nil
  	}
  	if err != nil || buf[entryLen-2] != '`' || buf[entryLen-1] != '\n' {
  		log.Fatal("file is not an archive: bad entry")
  	}
  	entry := new(Entry)
  	entry.name = strings.TrimRight(string(buf[:16]), " ")
  	if len(entry.name) == 0 {
  		log.Fatal("file is not an archive: bad name")
  	}
  	buf = buf[16:]
  	str := string(buf)
  	get := func(width, base, bitsize int) int64 {
  		v, err := strconv.ParseInt(strings.TrimRight(str[:width], " "), base, bitsize)
  		if err != nil {
  			log.Fatal("file is not an archive: bad number in entry: ", err)
  		}
  		str = str[width:]
  		return v
  	}
  	// %-16s%-12d%-6d%-6d%-8o%-10d`
  	entry.mtime = get(12, 10, 64)
  	entry.uid = int(get(6, 10, 32))
  	entry.gid = int(get(6, 10, 32))
  	entry.mode = os.FileMode(get(8, 8, 32))
  	entry.size = get(10, 10, 64)
  	return entry
  }
  
  // scan scans the archive and executes the specified action on each entry.
  // When action returns, the file offset is at the start of the next entry.
  func (ar *Archive) scan(action func(*Entry)) {
  	for {
  		entry := ar.readMetadata()
  		if entry == nil {
  			break
  		}
  		action(entry)
  	}
  }
  
  // listEntry prints to standard output a line describing the entry.
  func listEntry(ar *Archive, entry *Entry, verbose bool) {
  	if verbose {
  		fmt.Fprintf(stdout, "%s\n", entry)
  	} else {
  		fmt.Fprintf(stdout, "%s\n", entry.name)
  	}
  }
  
  // output copies the entry to the specified writer.
  func (ar *Archive) output(entry *Entry, w io.Writer) {
  	n, err := io.Copy(w, io.LimitReader(ar.fd, entry.size))
  	if err != nil {
  		log.Fatal(err)
  	}
  	if n != entry.size {
  		log.Fatal("short file")
  	}
  	if entry.size&1 == 1 {
  		_, err := ar.fd.Seek(1, io.SeekCurrent)
  		if err != nil {
  			log.Fatal(err)
  		}
  	}
  }
  
  // skip skips the entry without reading it.
  func (ar *Archive) skip(entry *Entry) {
  	size := entry.size
  	if size&1 == 1 {
  		size++
  	}
  	_, err := ar.fd.Seek(size, io.SeekCurrent)
  	if err != nil {
  		log.Fatal(err)
  	}
  }
  
  // match reports whether the entry matches the argument list.
  // If it does, it also drops the file from the to-be-processed list.
  func (ar *Archive) match(entry *Entry) bool {
  	if ar.matchAll {
  		return true
  	}
  	for i, name := range ar.files {
  		if entry.name == name {
  			copy(ar.files[i:], ar.files[i+1:])
  			ar.files = ar.files[:len(ar.files)-1]
  			return true
  		}
  	}
  	return false
  }
  
  // addFiles adds files to the archive. The archive is known to be
  // sane and we are positioned at the end. No attempt is made
  // to check for existing files.
  func (ar *Archive) addFiles() {
  	if len(ar.files) == 0 {
  		usage()
  	}
  	for _, file := range ar.files {
  		if verbose {
  			fmt.Printf("%s\n", file)
  		}
  		fd, err := os.Open(file)
  		if err != nil {
  			log.Fatal(err)
  		}
  		ar.addFile(fd)
  	}
  	ar.files = nil
  }
  
  // FileLike abstracts the few methods we need, so we can test without needing real files.
  type FileLike interface {
  	Name() string
  	Stat() (os.FileInfo, error)
  	Read([]byte) (int, error)
  	Close() error
  }
  
  // addFile adds a single file to the archive
  func (ar *Archive) addFile(fd FileLike) {
  	defer fd.Close()
  	// Format the entry.
  	// First, get its info.
  	info, err := fd.Stat()
  	if err != nil {
  		log.Fatal(err)
  	}
  	// mtime, uid, gid are all zero so repeated builds produce identical output.
  	mtime := int64(0)
  	uid := 0
  	gid := 0
  	ar.startFile(info.Name(), mtime, uid, gid, info.Mode(), info.Size())
  	n64, err := io.Copy(ar.fd, fd)
  	if err != nil {
  		log.Fatal("writing file: ", err)
  	}
  	if n64 != info.Size() {
  		log.Fatalf("writing file: wrote %d bytes; file is size %d", n64, info.Size())
  	}
  	ar.endFile()
  }
  
  // startFile writes the archive entry header.
  func (ar *Archive) startFile(name string, mtime int64, uid, gid int, mode os.FileMode, size int64) {
  	n, err := fmt.Fprintf(ar.fd, entryHeader, exactly16Bytes(name), mtime, uid, gid, mode, size)
  	if err != nil || n != entryLen {
  		log.Fatal("writing entry header: ", err)
  	}
  	ar.pad = int(size & 1)
  }
  
  // endFile writes the archive entry tail (a single byte of padding, if the file size was odd).
  func (ar *Archive) endFile() {
  	if ar.pad != 0 {
  		_, err := ar.fd.Write([]byte{0})
  		if err != nil {
  			log.Fatal("writing archive: ", err)
  		}
  		ar.pad = 0
  	}
  }
  
  // addPkgdef adds the __.PKGDEF file to the archive, copied
  // from the first Go object file on the file list, if any.
  // The archive is known to be empty.
  func (ar *Archive) addPkgdef() {
  	for _, file := range ar.files {
  		pkgdef, err := readPkgdef(file)
  		if err != nil {
  			continue
  		}
  		if verbose {
  			fmt.Printf("__.PKGDEF # %s\n", file)
  		}
  		ar.startFile("__.PKGDEF", 0, 0, 0, 0644, int64(len(pkgdef)))
  		_, err = ar.fd.Write(pkgdef)
  		if err != nil {
  			log.Fatal("writing __.PKGDEF: ", err)
  		}
  		ar.endFile()
  		break
  	}
  }
  
  // readPkgdef extracts the __.PKGDEF data from a Go object file.
  func readPkgdef(file string) (data []byte, err error) {
  	f, err := os.Open(file)
  	if err != nil {
  		return nil, err
  	}
  	defer f.Close()
  
  	// Read from file, collecting header for __.PKGDEF.
  	// The header is from the beginning of the file until a line
  	// containing just "!". The first line must begin with "go object ".
  	rbuf := bufio.NewReader(f)
  	var wbuf bytes.Buffer
  	for {
  		line, err := rbuf.ReadBytes('\n')
  		if err != nil {
  			return nil, err
  		}
  		if wbuf.Len() == 0 && !bytes.HasPrefix(line, []byte("go object ")) {
  			return nil, errors.New("not a Go object file")
  		}
  		if bytes.Equal(line, []byte("!\n")) {
  			break
  		}
  		wbuf.Write(line)
  	}
  	return wbuf.Bytes(), nil
  }
  
  // exactly16Bytes truncates the string if necessary so it is at most 16 bytes long,
  // then pads the result with spaces to be exactly 16 bytes.
  // Fmt uses runes for its width calculation, but we need bytes in the entry header.
  func exactly16Bytes(s string) string {
  	for len(s) > 16 {
  		_, wid := utf8.DecodeLastRuneInString(s)
  		s = s[:len(s)-wid]
  	}
  	const sixteenSpaces = "                "
  	s += sixteenSpaces[:16-len(s)]
  	return s
  }
  
  // Finally, the actual commands. Each is an action.
  
  // can be modified for testing.
  var stdout io.Writer = os.Stdout
  
  // printContents implements the 'p' command.
  func (ar *Archive) printContents(entry *Entry) {
  	if ar.match(entry) {
  		if verbose {
  			listEntry(ar, entry, false)
  		}
  		ar.output(entry, stdout)
  	} else {
  		ar.skip(entry)
  	}
  }
  
  // skipContents implements the first part of the 'r' command.
  // It just scans the archive to make sure it's intact.
  func (ar *Archive) skipContents(entry *Entry) {
  	ar.skip(entry)
  }
  
  // tableOfContents implements the 't' command.
  func (ar *Archive) tableOfContents(entry *Entry) {
  	if ar.match(entry) {
  		listEntry(ar, entry, verbose)
  	}
  	ar.skip(entry)
  }
  
  // extractContents implements the 'x' command.
  func (ar *Archive) extractContents(entry *Entry) {
  	if ar.match(entry) {
  		if verbose {
  			listEntry(ar, entry, false)
  		}
  		fd, err := os.OpenFile(entry.name, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, entry.mode)
  		if err != nil {
  			log.Fatal(err)
  		}
  		ar.output(entry, fd)
  		fd.Close()
  	} else {
  		ar.skip(entry)
  	}
  }
  

View as plain text