summaryrefslogtreecommitdiff
path: root/src/cmd/pack/pack.go
diff options
context:
space:
mode:
Diffstat (limited to 'src/cmd/pack/pack.go')
-rw-r--r--src/cmd/pack/pack.go486
1 files changed, 486 insertions, 0 deletions
diff --git a/src/cmd/pack/pack.go b/src/cmd/pack/pack.go
new file mode 100644
index 000000000..594433712
--- /dev/null
+++ b/src/cmd/pack/pack.go
@@ -0,0 +1,486 @@
+// 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
+ godoc 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 (or if necessary creates) the named archive.
+func archive(name string, mode int, files []string) *Archive {
+ fd, err := os.OpenFile(name, mode, 0)
+ if err != nil && mode&^os.O_TRUNC == os.O_RDWR && os.IsNotExist(err) {
+ fd, err = create(name)
+ }
+ if err != nil {
+ log.Fatal(err)
+ }
+ mustBeArchive(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) (*os.File, error) {
+ fd, err := os.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
+ if err != nil {
+ return nil, err
+ }
+ fmt.Fprint(fd, arHeader)
+ fd.Seek(0, 0)
+ return fd, nil
+}
+
+// mustBeArchive verifies the header of the file. It assumes the file offset
+// is 0 coming in, and leaves it positioned immediately after the header.
+func mustBeArchive(fd *os.File) {
+ buf := make([]byte, len(arHeader))
+ _, err := io.ReadFull(fd, buf)
+ if err != nil || string(buf) != arHeader {
+ log.Fatal("file is not an archive: bad header")
+ }
+}
+
+// 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, 1)
+ 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, 1)
+ 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)
+ }
+}