github.com/ader1990/go@v0.0.0-20140630135419-8c24447fa791/src/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 main 6 7 import ( 8 "bufio" 9 "bytes" 10 "errors" 11 "fmt" 12 "io" 13 "log" 14 "os" 15 "strconv" 16 "strings" 17 "time" 18 "unicode/utf8" 19 ) 20 21 /* 22 The archive format is: 23 24 First, on a line by itself 25 !<arch> 26 27 Then zero or more file records. Each file record has a fixed-size one-line header 28 followed by data bytes followed by an optional padding byte. The header is: 29 30 %-16s%-12d%-6d%-6d%-8o%-10d` 31 name mtime uid gid mode size 32 33 (note the trailing backquote). The %-16s here means at most 16 *bytes* of 34 the name, and if shorter, space padded on the right. 35 */ 36 37 const usageMessage = `Usage: pack op file.a [name....] 38 Where op is one of cprtx optionally followed by v for verbose output. 39 For compatibility with old Go build environments the op string grc is 40 accepted as a synonym for c. 41 42 For more information, run 43 godoc cmd/pack` 44 45 func usage() { 46 fmt.Fprintln(os.Stderr, usageMessage) 47 os.Exit(2) 48 } 49 50 func main() { 51 log.SetFlags(0) 52 log.SetPrefix("pack: ") 53 // need "pack op archive" at least. 54 if len(os.Args) < 3 { 55 log.Print("not enough arguments") 56 fmt.Fprintln(os.Stderr) 57 usage() 58 } 59 setOp(os.Args[1]) 60 var ar *Archive 61 switch op { 62 case 'p': 63 ar = archive(os.Args[2], os.O_RDONLY, os.Args[3:]) 64 ar.scan(ar.printContents) 65 case 'r': 66 ar = archive(os.Args[2], os.O_RDWR, os.Args[3:]) 67 ar.scan(ar.skipContents) 68 ar.addFiles() 69 case 'c': 70 ar = archive(os.Args[2], os.O_RDWR|os.O_TRUNC, os.Args[3:]) 71 ar.addPkgdef() 72 ar.addFiles() 73 case 't': 74 ar = archive(os.Args[2], os.O_RDONLY, os.Args[3:]) 75 ar.scan(ar.tableOfContents) 76 case 'x': 77 ar = archive(os.Args[2], os.O_RDONLY, os.Args[3:]) 78 ar.scan(ar.extractContents) 79 default: 80 log.Printf("invalid operation %q", os.Args[1]) 81 fmt.Fprintln(os.Stderr) 82 usage() 83 } 84 if len(ar.files) > 0 { 85 log.Fatalf("file %q not in archive", ar.files[0]) 86 } 87 } 88 89 // The unusual ancestry means the arguments are not Go-standard. 90 // These variables hold the decoded operation specified by the first argument. 91 // op holds the operation we are doing (prtx). 92 // verbose tells whether the 'v' option was specified. 93 var ( 94 op rune 95 verbose bool 96 ) 97 98 // setOp parses the operation string (first argument). 99 func setOp(arg string) { 100 // Recognize 'go tool pack grc' because that was the 101 // formerly canonical way to build a new archive 102 // from a set of input files. Accepting it keeps old 103 // build systems working with both Go 1.2 and Go 1.3. 104 if arg == "grc" { 105 arg = "c" 106 } 107 108 for _, r := range arg { 109 switch r { 110 case 'c', 'p', 'r', 't', 'x': 111 if op != 0 { 112 // At most one can be set. 113 usage() 114 } 115 op = r 116 case 'v': 117 if verbose { 118 // Can be set only once. 119 usage() 120 } 121 verbose = true 122 default: 123 usage() 124 } 125 } 126 } 127 128 const ( 129 arHeader = "!<arch>\n" 130 entryHeader = "%s%-12d%-6d%-6d%-8o%-10d`\n" 131 // In entryHeader the first entry, the name, is always printed as 16 bytes right-padded. 132 entryLen = 16 + 12 + 6 + 6 + 8 + 10 + 1 + 1 133 timeFormat = "Jan _2 15:04 2006" 134 ) 135 136 // An Archive represents an open archive file. It is always scanned sequentially 137 // from start to end, without backing up. 138 type Archive struct { 139 fd *os.File // Open file descriptor. 140 files []string // Explicit list of files to be processed. 141 pad int // Padding bytes required at end of current archive file 142 matchAll bool // match all files in archive 143 } 144 145 // archive opens (or if necessary creates) the named archive. 146 func archive(name string, mode int, files []string) *Archive { 147 fd, err := os.OpenFile(name, mode, 0) 148 if err != nil && mode&^os.O_TRUNC == os.O_RDWR && os.IsNotExist(err) { 149 fd, err = create(name) 150 } 151 if err != nil { 152 log.Fatal(err) 153 } 154 mustBeArchive(fd) 155 return &Archive{ 156 fd: fd, 157 files: files, 158 matchAll: len(files) == 0, 159 } 160 } 161 162 // create creates and initializes an archive that does not exist. 163 func create(name string) (*os.File, error) { 164 fd, err := os.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644) 165 if err != nil { 166 return nil, err 167 } 168 fmt.Fprint(fd, arHeader) 169 fd.Seek(0, 0) 170 return fd, nil 171 } 172 173 // mustBeArchive verifies the header of the file. It assumes the file offset 174 // is 0 coming in, and leaves it positioned immediately after the header. 175 func mustBeArchive(fd *os.File) { 176 buf := make([]byte, len(arHeader)) 177 _, err := io.ReadFull(fd, buf) 178 if err != nil || string(buf) != arHeader { 179 log.Fatal("file is not an archive: bad header") 180 } 181 } 182 183 // An Entry is the internal representation of the per-file header information of one entry in the archive. 184 type Entry struct { 185 name string 186 mtime int64 187 uid int 188 gid int 189 mode os.FileMode 190 size int64 191 } 192 193 func (e *Entry) String() string { 194 return fmt.Sprintf("%s %6d/%-6d %12d %s %s", 195 (e.mode & 0777).String(), 196 e.uid, 197 e.gid, 198 e.size, 199 time.Unix(e.mtime, 0).Format(timeFormat), 200 e.name) 201 } 202 203 // readMetadata reads and parses the metadata for the next entry in the archive. 204 func (ar *Archive) readMetadata() *Entry { 205 buf := make([]byte, entryLen) 206 _, err := io.ReadFull(ar.fd, buf) 207 if err == io.EOF { 208 // No entries left. 209 return nil 210 } 211 if err != nil || buf[entryLen-2] != '`' || buf[entryLen-1] != '\n' { 212 log.Fatal("file is not an archive: bad entry") 213 } 214 entry := new(Entry) 215 entry.name = strings.TrimRight(string(buf[:16]), " ") 216 if len(entry.name) == 0 { 217 log.Fatal("file is not an archive: bad name") 218 } 219 buf = buf[16:] 220 str := string(buf) 221 get := func(width, base, bitsize int) int64 { 222 v, err := strconv.ParseInt(strings.TrimRight(str[:width], " "), base, bitsize) 223 if err != nil { 224 log.Fatal("file is not an archive: bad number in entry: ", err) 225 } 226 str = str[width:] 227 return v 228 } 229 // %-16s%-12d%-6d%-6d%-8o%-10d` 230 entry.mtime = get(12, 10, 64) 231 entry.uid = int(get(6, 10, 32)) 232 entry.gid = int(get(6, 10, 32)) 233 entry.mode = os.FileMode(get(8, 8, 32)) 234 entry.size = get(10, 10, 64) 235 return entry 236 } 237 238 // scan scans the archive and executes the specified action on each entry. 239 // When action returns, the file offset is at the start of the next entry. 240 func (ar *Archive) scan(action func(*Entry)) { 241 for { 242 entry := ar.readMetadata() 243 if entry == nil { 244 break 245 } 246 action(entry) 247 } 248 } 249 250 // listEntry prints to standard output a line describing the entry. 251 func listEntry(ar *Archive, entry *Entry, verbose bool) { 252 if verbose { 253 fmt.Fprintf(stdout, "%s\n", entry) 254 } else { 255 fmt.Fprintf(stdout, "%s\n", entry.name) 256 } 257 } 258 259 // output copies the entry to the specified writer. 260 func (ar *Archive) output(entry *Entry, w io.Writer) { 261 n, err := io.Copy(w, io.LimitReader(ar.fd, entry.size)) 262 if err != nil { 263 log.Fatal(err) 264 } 265 if n != entry.size { 266 log.Fatal("short file") 267 } 268 if entry.size&1 == 1 { 269 _, err := ar.fd.Seek(1, 1) 270 if err != nil { 271 log.Fatal(err) 272 } 273 } 274 } 275 276 // skip skips the entry without reading it. 277 func (ar *Archive) skip(entry *Entry) { 278 size := entry.size 279 if size&1 == 1 { 280 size++ 281 } 282 _, err := ar.fd.Seek(size, 1) 283 if err != nil { 284 log.Fatal(err) 285 } 286 } 287 288 // match reports whether the entry matches the argument list. 289 // If it does, it also drops the file from the to-be-processed list. 290 func (ar *Archive) match(entry *Entry) bool { 291 if ar.matchAll { 292 return true 293 } 294 for i, name := range ar.files { 295 if entry.name == name { 296 copy(ar.files[i:], ar.files[i+1:]) 297 ar.files = ar.files[:len(ar.files)-1] 298 return true 299 } 300 } 301 return false 302 } 303 304 // addFiles adds files to the archive. The archive is known to be 305 // sane and we are positioned at the end. No attempt is made 306 // to check for existing files. 307 func (ar *Archive) addFiles() { 308 if len(ar.files) == 0 { 309 usage() 310 } 311 for _, file := range ar.files { 312 if verbose { 313 fmt.Printf("%s\n", file) 314 } 315 fd, err := os.Open(file) 316 if err != nil { 317 log.Fatal(err) 318 } 319 ar.addFile(fd) 320 } 321 ar.files = nil 322 } 323 324 // FileLike abstracts the few methods we need, so we can test without needing real files. 325 type FileLike interface { 326 Name() string 327 Stat() (os.FileInfo, error) 328 Read([]byte) (int, error) 329 Close() error 330 } 331 332 // addFile adds a single file to the archive 333 func (ar *Archive) addFile(fd FileLike) { 334 defer fd.Close() 335 // Format the entry. 336 // First, get its info. 337 info, err := fd.Stat() 338 if err != nil { 339 log.Fatal(err) 340 } 341 // mtime, uid, gid are all zero so repeated builds produce identical output. 342 mtime := int64(0) 343 uid := 0 344 gid := 0 345 ar.startFile(info.Name(), mtime, uid, gid, info.Mode(), info.Size()) 346 n64, err := io.Copy(ar.fd, fd) 347 if err != nil { 348 log.Fatal("writing file: ", err) 349 } 350 if n64 != info.Size() { 351 log.Fatalf("writing file: wrote %d bytes; file is size %d", n64, info.Size()) 352 } 353 ar.endFile() 354 } 355 356 // startFile writes the archive entry header. 357 func (ar *Archive) startFile(name string, mtime int64, uid, gid int, mode os.FileMode, size int64) { 358 n, err := fmt.Fprintf(ar.fd, entryHeader, exactly16Bytes(name), mtime, uid, gid, mode, size) 359 if err != nil || n != entryLen { 360 log.Fatal("writing entry header: ", err) 361 } 362 ar.pad = int(size & 1) 363 } 364 365 // endFile writes the archive entry tail (a single byte of padding, if the file size was odd). 366 func (ar *Archive) endFile() { 367 if ar.pad != 0 { 368 _, err := ar.fd.Write([]byte{0}) 369 if err != nil { 370 log.Fatal("writing archive: ", err) 371 } 372 ar.pad = 0 373 } 374 } 375 376 // addPkgdef adds the __.PKGDEF file to the archive, copied 377 // from the first Go object file on the file list, if any. 378 // The archive is known to be empty. 379 func (ar *Archive) addPkgdef() { 380 for _, file := range ar.files { 381 pkgdef, err := readPkgdef(file) 382 if err != nil { 383 continue 384 } 385 if verbose { 386 fmt.Printf("__.PKGDEF # %s\n", file) 387 } 388 ar.startFile("__.PKGDEF", 0, 0, 0, 0644, int64(len(pkgdef))) 389 _, err = ar.fd.Write(pkgdef) 390 if err != nil { 391 log.Fatal("writing __.PKGDEF: ", err) 392 } 393 ar.endFile() 394 break 395 } 396 } 397 398 // readPkgdef extracts the __.PKGDEF data from a Go object file. 399 func readPkgdef(file string) (data []byte, err error) { 400 f, err := os.Open(file) 401 if err != nil { 402 return nil, err 403 } 404 defer f.Close() 405 406 // Read from file, collecting header for __.PKGDEF. 407 // The header is from the beginning of the file until a line 408 // containing just "!". The first line must begin with "go object ". 409 rbuf := bufio.NewReader(f) 410 var wbuf bytes.Buffer 411 for { 412 line, err := rbuf.ReadBytes('\n') 413 if err != nil { 414 return nil, err 415 } 416 if wbuf.Len() == 0 && !bytes.HasPrefix(line, []byte("go object ")) { 417 return nil, errors.New("not a Go object file") 418 } 419 if bytes.Equal(line, []byte("!\n")) { 420 break 421 } 422 wbuf.Write(line) 423 } 424 return wbuf.Bytes(), nil 425 } 426 427 // exactly16Bytes truncates the string if necessary so it is at most 16 bytes long, 428 // then pads the result with spaces to be exactly 16 bytes. 429 // Fmt uses runes for its width calculation, but we need bytes in the entry header. 430 func exactly16Bytes(s string) string { 431 for len(s) > 16 { 432 _, wid := utf8.DecodeLastRuneInString(s) 433 s = s[:len(s)-wid] 434 } 435 const sixteenSpaces = " " 436 s += sixteenSpaces[:16-len(s)] 437 return s 438 } 439 440 // Finally, the actual commands. Each is an action. 441 442 // can be modified for testing. 443 var stdout io.Writer = os.Stdout 444 445 // printContents implements the 'p' command. 446 func (ar *Archive) printContents(entry *Entry) { 447 if ar.match(entry) { 448 if verbose { 449 listEntry(ar, entry, false) 450 } 451 ar.output(entry, stdout) 452 } else { 453 ar.skip(entry) 454 } 455 } 456 457 // skipContents implements the first part of the 'r' command. 458 // It just scans the archive to make sure it's intact. 459 func (ar *Archive) skipContents(entry *Entry) { 460 ar.skip(entry) 461 } 462 463 // tableOfContents implements the 't' command. 464 func (ar *Archive) tableOfContents(entry *Entry) { 465 if ar.match(entry) { 466 listEntry(ar, entry, verbose) 467 } 468 ar.skip(entry) 469 } 470 471 // extractContents implements the 'x' command. 472 func (ar *Archive) extractContents(entry *Entry) { 473 if ar.match(entry) { 474 if verbose { 475 listEntry(ar, entry, false) 476 } 477 fd, err := os.OpenFile(entry.name, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, entry.mode) 478 if err != nil { 479 log.Fatal(err) 480 } 481 ar.output(entry, fd) 482 fd.Close() 483 } else { 484 ar.skip(entry) 485 } 486 }