...

Source file src/cmd/pack/pack.go

Documentation: cmd/pack

     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  	return &Archive{
   143  		a:        a,
   144  		files:    files,
   145  		matchAll: len(files) == 0,
   146  	}
   147  }
   148  
   149  // scan scans the archive and executes the specified action on each entry.
   150  func (ar *Archive) scan(action func(*archive.Entry)) {
   151  	for i := range ar.a.Entries {
   152  		e := &ar.a.Entries[i]
   153  		action(e)
   154  	}
   155  }
   156  
   157  // listEntry prints to standard output a line describing the entry.
   158  func listEntry(e *archive.Entry, verbose bool) {
   159  	if verbose {
   160  		fmt.Fprintf(stdout, "%s\n", e.String())
   161  	} else {
   162  		fmt.Fprintf(stdout, "%s\n", e.Name)
   163  	}
   164  }
   165  
   166  // output copies the entry to the specified writer.
   167  func (ar *Archive) output(e *archive.Entry, w io.Writer) {
   168  	r := io.NewSectionReader(ar.a.File(), e.Offset, e.Size)
   169  	n, err := io.Copy(w, r)
   170  	if err != nil {
   171  		log.Fatal(err)
   172  	}
   173  	if n != e.Size {
   174  		log.Fatal("short file")
   175  	}
   176  }
   177  
   178  // match reports whether the entry matches the argument list.
   179  // If it does, it also drops the file from the to-be-processed list.
   180  func (ar *Archive) match(e *archive.Entry) bool {
   181  	if ar.matchAll {
   182  		return true
   183  	}
   184  	for i, name := range ar.files {
   185  		if e.Name == name {
   186  			copy(ar.files[i:], ar.files[i+1:])
   187  			ar.files = ar.files[:len(ar.files)-1]
   188  			return true
   189  		}
   190  	}
   191  	return false
   192  }
   193  
   194  // addFiles adds files to the archive. The archive is known to be
   195  // sane and we are positioned at the end. No attempt is made
   196  // to check for existing files.
   197  func (ar *Archive) addFiles() {
   198  	if len(ar.files) == 0 {
   199  		usage()
   200  	}
   201  	for _, file := range ar.files {
   202  		if verbose {
   203  			fmt.Printf("%s\n", file)
   204  		}
   205  
   206  		f, err := os.Open(file)
   207  		if err != nil {
   208  			log.Fatal(err)
   209  		}
   210  		aro, err := archive.Parse(f, false)
   211  		if err != nil || !isGoCompilerObjFile(aro) {
   212  			f.Seek(0, io.SeekStart)
   213  			ar.addFile(f)
   214  			goto close
   215  		}
   216  
   217  		for _, e := range aro.Entries {
   218  			if e.Type != archive.EntryGoObj || e.Name != "_go_.o" {
   219  				continue
   220  			}
   221  			ar.a.AddEntry(archive.EntryGoObj, filepath.Base(file), 0, 0, 0, 0644, e.Size, io.NewSectionReader(f, e.Offset, e.Size))
   222  		}
   223  	close:
   224  		f.Close()
   225  	}
   226  	ar.files = nil
   227  }
   228  
   229  // FileLike abstracts the few methods we need, so we can test without needing real files.
   230  type FileLike interface {
   231  	Name() string
   232  	Stat() (fs.FileInfo, error)
   233  	Read([]byte) (int, error)
   234  	Close() error
   235  }
   236  
   237  // addFile adds a single file to the archive
   238  func (ar *Archive) addFile(fd FileLike) {
   239  	// Format the entry.
   240  	// First, get its info.
   241  	info, err := fd.Stat()
   242  	if err != nil {
   243  		log.Fatal(err)
   244  	}
   245  	// mtime, uid, gid are all zero so repeated builds produce identical output.
   246  	mtime := int64(0)
   247  	uid := 0
   248  	gid := 0
   249  	ar.a.AddEntry(archive.EntryNativeObj, info.Name(), mtime, uid, gid, info.Mode(), info.Size(), fd)
   250  }
   251  
   252  // addPkgdef adds the __.PKGDEF file to the archive, copied
   253  // from the first Go object file on the file list, if any.
   254  // The archive is known to be empty.
   255  func (ar *Archive) addPkgdef() {
   256  	done := false
   257  	for _, file := range ar.files {
   258  		f, err := os.Open(file)
   259  		if err != nil {
   260  			log.Fatal(err)
   261  		}
   262  		aro, err := archive.Parse(f, false)
   263  		if err != nil || !isGoCompilerObjFile(aro) {
   264  			goto close
   265  		}
   266  
   267  		for _, e := range aro.Entries {
   268  			if e.Type != archive.EntryPkgDef {
   269  				continue
   270  			}
   271  			if verbose {
   272  				fmt.Printf("__.PKGDEF # %s\n", file)
   273  			}
   274  			ar.a.AddEntry(archive.EntryPkgDef, "__.PKGDEF", 0, 0, 0, 0644, e.Size, io.NewSectionReader(f, e.Offset, e.Size))
   275  			done = true
   276  		}
   277  	close:
   278  		f.Close()
   279  		if done {
   280  			break
   281  		}
   282  	}
   283  }
   284  
   285  // Finally, the actual commands. Each is an action.
   286  
   287  // can be modified for testing.
   288  var stdout io.Writer = os.Stdout
   289  
   290  // printContents implements the 'p' command.
   291  func (ar *Archive) printContents(e *archive.Entry) {
   292  	ar.extractContents1(e, stdout)
   293  }
   294  
   295  // tableOfContents implements the 't' command.
   296  func (ar *Archive) tableOfContents(e *archive.Entry) {
   297  	if ar.match(e) {
   298  		listEntry(e, verbose)
   299  	}
   300  }
   301  
   302  // extractContents implements the 'x' command.
   303  func (ar *Archive) extractContents(e *archive.Entry) {
   304  	ar.extractContents1(e, nil)
   305  }
   306  
   307  func (ar *Archive) extractContents1(e *archive.Entry, out io.Writer) {
   308  	if ar.match(e) {
   309  		if verbose {
   310  			listEntry(e, false)
   311  		}
   312  		if out == nil {
   313  			f, err := os.OpenFile(e.Name, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0444 /*e.Mode*/)
   314  			if err != nil {
   315  				log.Fatal(err)
   316  			}
   317  			defer f.Close()
   318  			out = f
   319  		}
   320  		ar.output(e, out)
   321  	}
   322  }
   323  
   324  // isGoCompilerObjFile reports whether file is an object file created
   325  // by the Go compiler, which is an archive file with exactly one entry
   326  // of __.PKGDEF, or _go_.o, or both entries.
   327  func isGoCompilerObjFile(a *archive.Archive) bool {
   328  	switch len(a.Entries) {
   329  	case 1:
   330  		return (a.Entries[0].Type == archive.EntryGoObj && a.Entries[0].Name == "_go_.o") ||
   331  			(a.Entries[0].Type == archive.EntryPkgDef && a.Entries[0].Name == "__.PKGDEF")
   332  	case 2:
   333  		var foundPkgDef, foundGo bool
   334  		for _, e := range a.Entries {
   335  			if e.Type == archive.EntryPkgDef && e.Name == "__.PKGDEF" {
   336  				foundPkgDef = true
   337  			}
   338  			if e.Type == archive.EntryGoObj && e.Name == "_go_.o" {
   339  				foundGo = true
   340  			}
   341  		}
   342  		return foundPkgDef && foundGo
   343  	default:
   344  		return false
   345  	}
   346  }
   347  

View as plain text