Goroutine without waitgroup or channel; risks resource leaks or race conditions
go doc cmd/pack`
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}
Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.