// 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 = "!\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 } }