diff options
Diffstat (limited to '')
-rw-r--r-- | src/cmd/pack/pack.go | 342 |
1 files changed, 342 insertions, 0 deletions
diff --git a/src/cmd/pack/pack.go b/src/cmd/pack/pack.go new file mode 100644 index 0000000..412ea36 --- /dev/null +++ b/src/cmd/pack/pack.go @@ -0,0 +1,342 @@ +// 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 ( + "cmd/internal/archive" + "fmt" + "io" + "io/fs" + "log" + "os" + "path/filepath" +) + +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 = openArchive(os.Args[2], os.O_RDONLY, os.Args[3:]) + ar.scan(ar.printContents) + case 'r': + ar = openArchive(os.Args[2], os.O_RDWR|os.O_CREATE, os.Args[3:]) + ar.addFiles() + case 'c': + ar = openArchive(os.Args[2], os.O_RDWR|os.O_TRUNC|os.O_CREATE, os.Args[3:]) + ar.addPkgdef() + ar.addFiles() + case 't': + ar = openArchive(os.Args[2], os.O_RDONLY, os.Args[3:]) + ar.scan(ar.tableOfContents) + case 'x': + ar = openArchive(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" +) + +// An Archive represents an open archive file. It is always scanned sequentially +// from start to end, without backing up. +type Archive struct { + a *archive.Archive + 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 openArchive(name string, mode int, files []string) *Archive { + f, err := os.OpenFile(name, mode, 0666) + if err != nil { + log.Fatal(err) + } + var a *archive.Archive + if mode&os.O_TRUNC != 0 { // the c command + a, err = archive.New(f) + } else { + a, err = archive.Parse(f, verbose) + if err != nil && mode&os.O_CREATE != 0 { // the r command + a, err = archive.New(f) + } + } + if err != nil { + log.Fatal(err) + } + return &Archive{ + a: a, + files: files, + matchAll: len(files) == 0, + } +} + +// scan scans the archive and executes the specified action on each entry. +func (ar *Archive) scan(action func(*archive.Entry)) { + for i := range ar.a.Entries { + e := &ar.a.Entries[i] + action(e) + } +} + +// listEntry prints to standard output a line describing the entry. +func listEntry(e *archive.Entry, verbose bool) { + if verbose { + fmt.Fprintf(stdout, "%s\n", e.String()) + } else { + fmt.Fprintf(stdout, "%s\n", e.Name) + } +} + +// output copies the entry to the specified writer. +func (ar *Archive) output(e *archive.Entry, w io.Writer) { + r := io.NewSectionReader(ar.a.File(), e.Offset, e.Size) + n, err := io.Copy(w, r) + if err != nil { + log.Fatal(err) + } + if n != e.Size { + log.Fatal("short file") + } +} + +// 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(e *archive.Entry) bool { + if ar.matchAll { + return true + } + for i, name := range ar.files { + if e.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) + } + + f, err := os.Open(file) + if err != nil { + log.Fatal(err) + } + aro, err := archive.Parse(f, false) + if err != nil || !isGoCompilerObjFile(aro) { + f.Seek(0, io.SeekStart) + ar.addFile(f) + goto close + } + + for _, e := range aro.Entries { + if e.Type != archive.EntryGoObj || e.Name != "_go_.o" { + continue + } + ar.a.AddEntry(archive.EntryGoObj, filepath.Base(file), 0, 0, 0, 0644, e.Size, io.NewSectionReader(f, e.Offset, e.Size)) + } + close: + f.Close() + } + ar.files = nil +} + +// FileLike abstracts the few methods we need, so we can test without needing real files. +type FileLike interface { + Name() string + Stat() (fs.FileInfo, error) + Read([]byte) (int, error) + Close() error +} + +// addFile adds a single file to the archive +func (ar *Archive) addFile(fd FileLike) { + // 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.a.AddEntry(archive.EntryNativeObj, info.Name(), mtime, uid, gid, info.Mode(), info.Size(), fd) +} + +// 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() { + done := false + for _, file := range ar.files { + f, err := os.Open(file) + if err != nil { + log.Fatal(err) + } + aro, err := archive.Parse(f, false) + if err != nil || !isGoCompilerObjFile(aro) { + goto close + } + + for _, e := range aro.Entries { + if e.Type != archive.EntryPkgDef { + continue + } + if verbose { + fmt.Printf("__.PKGDEF # %s\n", file) + } + ar.a.AddEntry(archive.EntryPkgDef, "__.PKGDEF", 0, 0, 0, 0644, e.Size, io.NewSectionReader(f, e.Offset, e.Size)) + done = true + } + close: + f.Close() + if done { + break + } + } +} + +// 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(e *archive.Entry) { + ar.extractContents1(e, stdout) +} + +// tableOfContents implements the 't' command. +func (ar *Archive) tableOfContents(e *archive.Entry) { + if ar.match(e) { + listEntry(e, verbose) + } +} + +// extractContents implements the 'x' command. +func (ar *Archive) extractContents(e *archive.Entry) { + ar.extractContents1(e, nil) +} + +func (ar *Archive) extractContents1(e *archive.Entry, out io.Writer) { + if ar.match(e) { + if verbose { + listEntry(e, false) + } + if out == nil { + f, err := os.OpenFile(e.Name, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0444 /*e.Mode*/) + if err != nil { + log.Fatal(err) + } + defer f.Close() + out = f + } + ar.output(e, out) + } +} + +// isGoCompilerObjFile reports whether file is an object file created +// by the Go compiler, which is an archive file with exactly one entry +// of __.PKGDEF, or _go_.o, or both entries. +func isGoCompilerObjFile(a *archive.Archive) bool { + switch len(a.Entries) { + case 1: + return (a.Entries[0].Type == archive.EntryGoObj && a.Entries[0].Name == "_go_.o") || + (a.Entries[0].Type == archive.EntryPkgDef && a.Entries[0].Name == "__.PKGDEF") + case 2: + var foundPkgDef, foundGo bool + for _, e := range a.Entries { + if e.Type == archive.EntryPkgDef && e.Name == "__.PKGDEF" { + foundPkgDef = true + } + if e.Type == archive.EntryGoObj && e.Name == "_go_.o" { + foundGo = true + } + } + return foundPkgDef && foundGo + default: + return false + } +} |