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

View as plain text