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 5package main 6 7import ( 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 18const usageMessage = `Usage: pack op file.a [name....] 19Where op is one of cprtx optionally followed by v for verbose output. 20For compatibility with old Go build environments the op string grc is 21accepted as a synonym for c. 22 23For more information, run 24 go doc cmd/pack` 25 26func usage() { 27 fmt.Fprintln(os.Stderr, usageMessage) 28 os.Exit(2) 29} 30 31func 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. 76var ( 77 op rune 78 verbose bool 79) 80 81// setOp parses the operation string (first argument). 82func 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 111const ( 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. 117type 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. 125func 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. 150func (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. 158func 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. 167func (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. 180func (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. 197func (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. 230type 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 238func (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. 255func (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. 288var stdout io.Writer = os.Stdout 289 290// printContents implements the 'p' command. 291func (ar *Archive) printContents(e *archive.Entry) { 292 ar.extractContents1(e, stdout) 293} 294 295// tableOfContents implements the 't' command. 296func (ar *Archive) tableOfContents(e *archive.Entry) { 297 if ar.match(e) { 298 listEntry(e, verbose) 299 } 300} 301 302// extractContents implements the 'x' command. 303func (ar *Archive) extractContents(e *archive.Entry) { 304 ar.extractContents1(e, nil) 305} 306 307func (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. 327func 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