| // 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 |
| } |
| } |