diff options
Diffstat (limited to 'src/cmd/pack/pack.go')
-rw-r--r-- | src/cmd/pack/pack.go | 486 |
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) + } +} |