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 }