src/cmd/pack/pack.go GO 354 lines View on github.com → Search inside
1// Copyright 2014 The Go Authors. All rights reserved.2// Use of this source code is governed by a BSD-style3// license that can be found in the LICENSE file.45package main67import (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)1819const usageMessage = `Usage: pack op file.a [name....]20Where op is one of cprtx optionally followed by v for verbose output.21For compatibility with old Go build environments the op string grc is22accepted as a synonym for c.2324For more information, run25	go doc cmd/pack`2627func usage() {28	fmt.Fprintln(os.Stderr, usageMessage)29	os.Exit(2)30}3132func 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 *Archive47	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}7374// 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.78var (79	op      rune80	verbose bool81)8283// setOp parses the operation string (first argument).84func setOp(arg string) {85	// Recognize 'go tool pack grc' because that was the86	// formerly canonical way to build a new archive87	// from a set of input files. Accepting it keeps old88	// build systems working with both Go 1.2 and Go 1.3.89	if arg == "grc" {90		arg = "c"91	}9293	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 = r101		case 'v':102			if verbose {103				// Can be set only once.104				usage()105			}106			verbose = true107		default:108			usage()109		}110	}111}112113const (114	arHeader = "!<arch>\n"115)116117// An Archive represents an open archive file. It is always scanned sequentially118// from start to end, without backing up.119type Archive struct {120	a        *archive.Archive121	files    []string // Explicit list of files to be processed.122	pad      int      // Padding bytes required at end of current archive file123	matchAll bool     // match all files in archive124}125126// archive opens (and if necessary creates) the named archive.127func 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.Archive133	if mode&os.O_TRUNC != 0 { // the c command134		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 command138			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}155156// scan scans the archive and executes the specified action on each entry.157func (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}163164// listEntry prints to standard output a line describing the entry.165func 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}172173// output copies the entry to the specified writer.174func (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}184185// match reports whether the entry matches the argument list.186// If it does, it also drops the file from the to-be-processed list.187func (ar *Archive) match(e *archive.Entry) bool {188	if ar.matchAll {189		return true190	}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 true196		}197	}198	return false199}200201// addFiles adds files to the archive. The archive is known to be202// sane and we are positioned at the end. No attempt is made203// to check for existing files.204func (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		}212213		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 close222		}223224		for _, e := range aro.Entries {225			if e.Type != archive.EntryGoObj || e.Name != "_go_.o" {226				continue227			}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 = nil234}235236// FileLike abstracts the few methods we need, so we can test without needing real files.237type FileLike interface {238	Name() string239	Stat() (fs.FileInfo, error)240	Read([]byte) (int, error)241	Close() error242}243244// addFile adds a single file to the archive245func (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 := 0255	gid := 0256	ar.a.AddEntry(archive.EntryNativeObj, info.Name(), mtime, uid, gid, info.Mode(), info.Size(), fd)257}258259// addPkgdef adds the __.PKGDEF file to the archive, copied260// from the first Go object file on the file list, if any.261// The archive is known to be empty.262func (ar *Archive) addPkgdef() {263	done := false264	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 close272		}273274		for _, e := range aro.Entries {275			if e.Type != archive.EntryPkgDef {276				continue277			}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 = true283		}284	close:285		f.Close()286		if done {287			break288		}289	}290}291292// Finally, the actual commands. Each is an action.293294// can be modified for testing.295var stdout io.Writer = os.Stdout296297// printContents implements the 'p' command.298func (ar *Archive) printContents(e *archive.Entry) {299	ar.extractContents1(e, stdout)300}301302// tableOfContents implements the 't' command.303func (ar *Archive) tableOfContents(e *archive.Entry) {304	if ar.match(e) {305		listEntry(e, verbose)306	}307}308309// extractContents implements the 'x' command.310func (ar *Archive) extractContents(e *archive.Entry) {311	ar.extractContents1(e, nil)312}313314func (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 = f326		}327		ar.output(e, out)328	}329}330331// isGoCompilerObjFile reports whether file is an object file created332// by the Go compiler, which is an archive file with exactly one entry333// of __.PKGDEF, or _go_.o, or both entries.334func 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 bool341		for _, e := range a.Entries {342			if e.Type == archive.EntryPkgDef && e.Name == "__.PKGDEF" {343				foundPkgDef = true344			}345			if e.Type == archive.EntryGoObj && e.Name == "_go_.o" {346				foundGo = true347			}348		}349		return foundPkgDef && foundGo350	default:351		return false352	}353}

Code quality findings 4

Goroutine without waitgroup or channel; risks resource leaks or race conditions
warning correctness goroutine-without-sync
go doc cmd/pack`
Range over slice copies each element by value; use index or pointer receiver for large structs to avoid copies
info performance copy-large-struct
for i, name := range ar.files {
Formatted output to console; prefer structured logging for consistency
info correctness fmt-printf
fmt.Printf("%s\n", file)
Formatted output to console; prefer structured logging for consistency
info correctness fmt-printf
fmt.Printf("__.PKGDEF # %s\n", file)

Security findings 1

Ensure restrictive umask values
security permissive-file-mode
f, err := os.OpenFile(name, mode, 0666)

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.