github.com/go-asm/go@v1.21.1-0.20240213172139-40c5ead50c48/cmd/pack/pack.go (about)

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