Source file src/cmd/pack/pack.go

     1  // Copyright 2014 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package main
     6  
     7  import (
     8  	"cmd/internal/archive"
     9  	"cmd/internal/telemetry/counter"
    10  	"fmt"
    11  	"io"
    12  	"io/fs"
    13  	"log"
    14  	"os"
    15  	"path/filepath"
    16  )
    17  
    18  const usageMessage = `Usage: pack op file.a [name....]
    19  Where op is one of cprtx optionally followed by v for verbose output.
    20  For compatibility with old Go build environments the op string grc is
    21  accepted as a synonym for c.
    22  
    23  For more information, run
    24  	go doc cmd/pack`
    25  
    26  func usage() {
    27  	fmt.Fprintln(os.Stderr, usageMessage)
    28  	os.Exit(2)
    29  }
    30  
    31  func main() {
    32  	log.SetFlags(0)
    33  	log.SetPrefix("pack: ")
    34  	counter.Open()
    35  	// need "pack op archive" at least.
    36  	if len(os.Args) < 3 {
    37  		log.Print("not enough arguments")
    38  		fmt.Fprintln(os.Stderr)
    39  		usage()
    40  	}
    41  	setOp(os.Args[1])
    42  	counter.Inc("pack/invocations")
    43  	counter.Inc("pack/op:" + string(op))
    44  	var ar *Archive
    45  	switch op {
    46  	case 'p':
    47  		ar = openArchive(os.Args[2], os.O_RDONLY, os.Args[3:])
    48  		ar.scan(ar.printContents)
    49  	case 'r':
    50  		ar = openArchive(os.Args[2], os.O_RDWR|os.O_CREATE, os.Args[3:])
    51  		ar.addFiles()
    52  	case 'c':
    53  		ar = openArchive(os.Args[2], os.O_RDWR|os.O_TRUNC|os.O_CREATE, os.Args[3:])
    54  		ar.addPkgdef()
    55  		ar.addFiles()
    56  	case 't':
    57  		ar = openArchive(os.Args[2], os.O_RDONLY, os.Args[3:])
    58  		ar.scan(ar.tableOfContents)
    59  	case 'x':
    60  		ar = openArchive(os.Args[2], os.O_RDONLY, os.Args[3:])
    61  		ar.scan(ar.extractContents)
    62  	default:
    63  		log.Printf("invalid operation %q", os.Args[1])
    64  		fmt.Fprintln(os.Stderr)
    65  		usage()
    66  	}
    67  	if len(ar.files) > 0 {
    68  		log.Fatalf("file %q not in archive", ar.files[0])
    69  	}
    70  }
    71  
    72  // The unusual ancestry means the arguments are not Go-standard.
    73  // These variables hold the decoded operation specified by the first argument.
    74  // op holds the operation we are doing (prtx).
    75  // verbose tells whether the 'v' option was specified.
    76  var (
    77  	op      rune
    78  	verbose bool
    79  )
    80  
    81  // setOp parses the operation string (first argument).
    82  func setOp(arg string) {
    83  	// Recognize 'go tool pack grc' because that was the
    84  	// formerly canonical way to build a new archive
    85  	// from a set of input files. Accepting it keeps old
    86  	// build systems working with both Go 1.2 and Go 1.3.
    87  	if arg == "grc" {
    88  		arg = "c"
    89  	}
    90  
    91  	for _, r := range arg {
    92  		switch r {
    93  		case 'c', 'p', 'r', 't', 'x':
    94  			if op != 0 {
    95  				// At most one can be set.
    96  				usage()
    97  			}
    98  			op = r
    99  		case 'v':
   100  			if verbose {
   101  				// Can be set only once.
   102  				usage()
   103  			}
   104  			verbose = true
   105  		default:
   106  			usage()
   107  		}
   108  	}
   109  }
   110  
   111  const (
   112  	arHeader = "!<arch>\n"
   113  )
   114  
   115  // An Archive represents an open archive file. It is always scanned sequentially
   116  // from start to end, without backing up.
   117  type Archive struct {
   118  	a        *archive.Archive
   119  	files    []string // Explicit list of files to be processed.
   120  	pad      int      // Padding bytes required at end of current archive file
   121  	matchAll bool     // match all files in archive
   122  }
   123  
   124  // archive opens (and if necessary creates) the named archive.
   125  func openArchive(name string, mode int, files []string) *Archive {
   126  	f, err := os.OpenFile(name, mode, 0666)
   127  	if err != nil {
   128  		log.Fatal(err)
   129  	}
   130  	var a *archive.Archive
   131  	if mode&os.O_TRUNC != 0 { // the c command
   132  		a, err = archive.New(f)
   133  	} else {
   134  		a, err = archive.Parse(f, verbose)
   135  		if err != nil && mode&os.O_CREATE != 0 { // the r command
   136  			a, err = archive.New(f)
   137  		}
   138  	}
   139  	if err != nil {
   140  		log.Fatal(err)
   141  	}
   142  	for _, f := range a.Entries {
   143  		if !filepath.IsLocal(f.Name) || filepath.Base(f.Name) != f.Name {
   144  			log.Fatalf("%q: invalid name", f.Name)
   145  		}
   146  	}
   147  	return &Archive{
   148  		a:        a,
   149  		files:    files,
   150  		matchAll: len(files) == 0,
   151  	}
   152  }
   153  
   154  // scan scans the archive and executes the specified action on each entry.
   155  func (ar *Archive) scan(action func(*archive.Entry)) {
   156  	for i := range ar.a.Entries {
   157  		e := &ar.a.Entries[i]
   158  		action(e)
   159  	}
   160  }
   161  
   162  // listEntry prints to standard output a line describing the entry.
   163  func listEntry(e *archive.Entry, verbose bool) {
   164  	if verbose {
   165  		fmt.Fprintf(stdout, "%s\n", e.String())
   166  	} else {
   167  		fmt.Fprintf(stdout, "%s\n", e.Name)
   168  	}
   169  }
   170  
   171  // output copies the entry to the specified writer.
   172  func (ar *Archive) output(e *archive.Entry, w io.Writer) {
   173  	r := io.NewSectionReader(ar.a.File(), e.Offset, e.Size)
   174  	n, err := io.Copy(w, r)
   175  	if err != nil {
   176  		log.Fatal(err)
   177  	}
   178  	if n != e.Size {
   179  		log.Fatal("short file")
   180  	}
   181  }
   182  
   183  // match reports whether the entry matches the argument list.
   184  // If it does, it also drops the file from the to-be-processed list.
   185  func (ar *Archive) match(e *archive.Entry) bool {
   186  	if ar.matchAll {
   187  		return true
   188  	}
   189  	for i, name := range ar.files {
   190  		if e.Name == name {
   191  			copy(ar.files[i:], ar.files[i+1:])
   192  			ar.files = ar.files[:len(ar.files)-1]
   193  			return true
   194  		}
   195  	}
   196  	return false
   197  }
   198  
   199  // addFiles adds files to the archive. The archive is known to be
   200  // sane and we are positioned at the end. No attempt is made
   201  // to check for existing files.
   202  func (ar *Archive) addFiles() {
   203  	if len(ar.files) == 0 {
   204  		usage()
   205  	}
   206  	for _, file := range ar.files {
   207  		if verbose {
   208  			fmt.Printf("%s\n", file)
   209  		}
   210  
   211  		f, err := os.Open(file)
   212  		if err != nil {
   213  			log.Fatal(err)
   214  		}
   215  		aro, err := archive.Parse(f, false)
   216  		if err != nil || !isGoCompilerObjFile(aro) {
   217  			f.Seek(0, io.SeekStart)
   218  			ar.addFile(f)
   219  			goto close
   220  		}
   221  
   222  		for _, e := range aro.Entries {
   223  			if e.Type != archive.EntryGoObj || e.Name != "_go_.o" {
   224  				continue
   225  			}
   226  			ar.a.AddEntry(archive.EntryGoObj, filepath.Base(file), 0, 0, 0, 0644, e.Size, io.NewSectionReader(f, e.Offset, e.Size))
   227  		}
   228  	close:
   229  		f.Close()
   230  	}
   231  	ar.files = nil
   232  }
   233  
   234  // FileLike abstracts the few methods we need, so we can test without needing real files.
   235  type FileLike interface {
   236  	Name() string
   237  	Stat() (fs.FileInfo, error)
   238  	Read([]byte) (int, error)
   239  	Close() error
   240  }
   241  
   242  // addFile adds a single file to the archive
   243  func (ar *Archive) addFile(fd FileLike) {
   244  	// Format the entry.
   245  	// First, get its info.
   246  	info, err := fd.Stat()
   247  	if err != nil {
   248  		log.Fatal(err)
   249  	}
   250  	// mtime, uid, gid are all zero so repeated builds produce identical output.
   251  	mtime := int64(0)
   252  	uid := 0
   253  	gid := 0
   254  	ar.a.AddEntry(archive.EntryNativeObj, info.Name(), mtime, uid, gid, info.Mode(), info.Size(), fd)
   255  }
   256  
   257  // addPkgdef adds the __.PKGDEF file to the archive, copied
   258  // from the first Go object file on the file list, if any.
   259  // The archive is known to be empty.
   260  func (ar *Archive) addPkgdef() {
   261  	done := false
   262  	for _, file := range ar.files {
   263  		f, err := os.Open(file)
   264  		if err != nil {
   265  			log.Fatal(err)
   266  		}
   267  		aro, err := archive.Parse(f, false)
   268  		if err != nil || !isGoCompilerObjFile(aro) {
   269  			goto close
   270  		}
   271  
   272  		for _, e := range aro.Entries {
   273  			if e.Type != archive.EntryPkgDef {
   274  				continue
   275  			}
   276  			if verbose {
   277  				fmt.Printf("__.PKGDEF # %s\n", file)
   278  			}
   279  			ar.a.AddEntry(archive.EntryPkgDef, "__.PKGDEF", 0, 0, 0, 0644, e.Size, io.NewSectionReader(f, e.Offset, e.Size))
   280  			done = true
   281  		}
   282  	close:
   283  		f.Close()
   284  		if done {
   285  			break
   286  		}
   287  	}
   288  }
   289  
   290  // Finally, the actual commands. Each is an action.
   291  
   292  // can be modified for testing.
   293  var stdout io.Writer = os.Stdout
   294  
   295  // printContents implements the 'p' command.
   296  func (ar *Archive) printContents(e *archive.Entry) {
   297  	ar.extractContents1(e, stdout)
   298  }
   299  
   300  // tableOfContents implements the 't' command.
   301  func (ar *Archive) tableOfContents(e *archive.Entry) {
   302  	if ar.match(e) {
   303  		listEntry(e, verbose)
   304  	}
   305  }
   306  
   307  // extractContents implements the 'x' command.
   308  func (ar *Archive) extractContents(e *archive.Entry) {
   309  	ar.extractContents1(e, nil)
   310  }
   311  
   312  func (ar *Archive) extractContents1(e *archive.Entry, out io.Writer) {
   313  	if ar.match(e) {
   314  		if verbose {
   315  			listEntry(e, false)
   316  		}
   317  		if out == nil {
   318  			f, err := os.OpenFile(e.Name, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0444 /*e.Mode*/)
   319  			if err != nil {
   320  				log.Fatal(err)
   321  			}
   322  			defer f.Close()
   323  			out = f
   324  		}
   325  		ar.output(e, out)
   326  	}
   327  }
   328  
   329  // isGoCompilerObjFile reports whether file is an object file created
   330  // by the Go compiler, which is an archive file with exactly one entry
   331  // of __.PKGDEF, or _go_.o, or both entries.
   332  func isGoCompilerObjFile(a *archive.Archive) bool {
   333  	switch len(a.Entries) {
   334  	case 1:
   335  		return (a.Entries[0].Type == archive.EntryGoObj && a.Entries[0].Name == "_go_.o") ||
   336  			(a.Entries[0].Type == archive.EntryPkgDef && a.Entries[0].Name == "__.PKGDEF")
   337  	case 2:
   338  		var foundPkgDef, foundGo bool
   339  		for _, e := range a.Entries {
   340  			if e.Type == archive.EntryPkgDef && e.Name == "__.PKGDEF" {
   341  				foundPkgDef = true
   342  			}
   343  			if e.Type == archive.EntryGoObj && e.Name == "_go_.o" {
   344  				foundGo = true
   345  			}
   346  		}
   347  		return foundPkgDef && foundGo
   348  	default:
   349  		return false
   350  	}
   351  }
   352  

View as plain text