summaryrefslogtreecommitdiffstats
path: root/src/cmd/pack/pack.go
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/cmd/pack/pack.go342
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
+ }
+}