| // Copyright 2014 The Go Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style | |
| // license that can be found in the LICENSE file. | |
| package main | |
| import ( | |
| "bufio" | |
| "bytes" | |
| "errors" | |
| "fmt" | |
| "io" | |
| "log" | |
| "os" | |
| "strconv" | |
| "strings" | |
| "time" | |
| "unicode/utf8" | |
| ) | |
| /* | |
| The archive format is: | |
| First, on a line by itself | |
| !<arch> | |
| Then zero or more file records. Each file record has a fixed-size one-line header | |
| followed by data bytes followed by an optional padding byte. The header is: | |
| %-16s%-12d%-6d%-6d%-8o%-10d` | |
| name mtime uid gid mode size | |
| (note the trailing backquote). The %-16s here means at most 16 *bytes* of | |
| the name, and if shorter, space padded on the right. | |
| */ | |
| const usageMessage = `Usage: pack op file.a [name....] | |
| Where op is one of cprtx optionally followed by v for verbose output. | |
| For compatibility with old Go build environments the op string grc is | |
| accepted as a synonym for c. | |
| For more information, run | |
| go doc cmd/pack` | |
| func usage() { | |
| fmt.Fprintln(os.Stderr, usageMessage) | |
| os.Exit(2) | |
| } | |
| func main() { | |
| log.SetFlags(0) | |
| log.SetPrefix("pack: ") | |
| // need "pack op archive" at least. | |
| if len(os.Args) < 3 { | |
| log.Print("not enough arguments") | |
| fmt.Fprintln(os.Stderr) | |
| usage() | |
| } | |
| setOp(os.Args[1]) | |
| var ar *Archive | |
| switch op { | |
| case 'p': | |
| ar = archive(os.Args[2], os.O_RDONLY, os.Args[3:]) | |
| ar.scan(ar.printContents) | |
| case 'r': | |
| ar = archive(os.Args[2], os.O_RDWR, os.Args[3:]) | |
| ar.scan(ar.skipContents) | |
| ar.addFiles() | |
| case 'c': | |
| ar = archive(os.Args[2], os.O_RDWR|os.O_TRUNC, os.Args[3:]) | |
| ar.addPkgdef() | |
| ar.addFiles() | |
| case 't': | |
| ar = archive(os.Args[2], os.O_RDONLY, os.Args[3:]) | |
| ar.scan(ar.tableOfContents) | |
| case 'x': | |
| ar = archive(os.Args[2], os.O_RDONLY, os.Args[3:]) | |
| ar.scan(ar.extractContents) | |
| default: | |
| log.Printf("invalid operation %q", os.Args[1]) | |
| fmt.Fprintln(os.Stderr) | |
| usage() | |
| } | |
| if len(ar.files) > 0 { | |
| log.Fatalf("file %q not in archive", ar.files[0]) | |
| } | |
| } | |
| // The unusual ancestry means the arguments are not Go-standard. | |
| // These variables hold the decoded operation specified by the first argument. | |
| // op holds the operation we are doing (prtx). | |
| // verbose tells whether the 'v' option was specified. | |
| var ( | |
| op rune | |
| verbose bool | |
| ) | |
| // setOp parses the operation string (first argument). | |
| func setOp(arg string) { | |
| // Recognize 'go tool pack grc' because that was the | |
| // formerly canonical way to build a new archive | |
| // from a set of input files. Accepting it keeps old | |
| // build systems working with both Go 1.2 and Go 1.3. | |
| if arg == "grc" { | |
| arg = "c" | |
| } | |
| for _, r := range arg { | |
| switch r { | |
| case 'c', 'p', 'r', 't', 'x': | |
| if op != 0 { | |
| // At most one can be set. | |
| usage() | |
| } | |
| op = r | |
| case 'v': | |
| if verbose { | |
| // Can be set only once. | |
| usage() | |
| } | |
| verbose = true | |
| default: | |
| usage() | |
| } | |
| } | |
| } | |
| const ( | |
| arHeader = "!<arch>\n" | |
| entryHeader = "%s%-12d%-6d%-6d%-8o%-10d`\n" | |
| // In entryHeader the first entry, the name, is always printed as 16 bytes right-padded. | |
| entryLen = 16 + 12 + 6 + 6 + 8 + 10 + 1 + 1 | |
| timeFormat = "Jan _2 15:04 2006" | |
| ) | |
| // An Archive represents an open archive file. It is always scanned sequentially | |
| // from start to end, without backing up. | |
| type Archive struct { | |
| fd *os.File // Open file descriptor. | |
| files []string // Explicit list of files to be processed. | |
| pad int // Padding bytes required at end of current archive file | |
| matchAll bool // match all files in archive | |
| } | |
| // archive opens (and if necessary creates) the named archive. | |
| func archive(name string, mode int, files []string) *Archive { | |
| // If the file exists, it must be an archive. If it doesn't exist, or if | |
| // we're doing the c command, indicated by O_TRUNC, truncate the archive. | |
| if !existingArchive(name) || mode&os.O_TRUNC != 0 { | |
| create(name) | |
| mode &^= os.O_TRUNC | |
| } | |
| fd, err := os.OpenFile(name, mode, 0) | |
| if err != nil { | |
| log.Fatal(err) | |
| } | |
| checkHeader(fd) | |
| return &Archive{ | |
| fd: fd, | |
| files: files, | |
| matchAll: len(files) == 0, | |
| } | |
| } | |
| // create creates and initializes an archive that does not exist. | |
| func create(name string) { | |
| fd, err := os.Create(name) | |
| if err != nil { | |
| log.Fatal(err) | |
| } | |
| _, err = fmt.Fprint(fd, arHeader) | |
| if err != nil { | |
| log.Fatal(err) | |
| } | |
| fd.Close() | |
| } | |
| // existingArchive reports whether the file exists and is a valid archive. | |
| // If it exists but is not an archive, existingArchive will exit. | |
| func existingArchive(name string) bool { | |
| fd, err := os.Open(name) | |
| if err != nil { | |
| if os.IsNotExist(err) { | |
| return false | |
| } | |
| log.Fatalf("cannot open file: %s", err) | |
| } | |
| checkHeader(fd) | |
| fd.Close() | |
| return true | |
| } | |
| // checkHeader verifies the header of the file. It assumes the file | |
| // is positioned at 0 and leaves it positioned at the end of the header. | |
| func checkHeader(fd *os.File) { | |
| buf := make([]byte, len(arHeader)) | |
| _, err := io.ReadFull(fd, buf) | |
| if err != nil || string(buf) != arHeader { | |
| log.Fatalf("%s is not an archive: bad header", fd.Name()) | |
| } | |
| } | |
| // An Entry is the internal representation of the per-file header information of one entry in the archive. | |
| type Entry struct { | |
| name string | |
| mtime int64 | |
| uid int | |
| gid int | |
| mode os.FileMode | |
| size int64 | |
| } | |
| func (e *Entry) String() string { | |
| return fmt.Sprintf("%s %6d/%-6d %12d %s %s", | |
| (e.mode & 0777).String(), | |
| e.uid, | |
| e.gid, | |
| e.size, | |
| time.Unix(e.mtime, 0).Format(timeFormat), | |
| e.name) | |
| } | |
| // readMetadata reads and parses the metadata for the next entry in the archive. | |
| func (ar *Archive) readMetadata() *Entry { | |
| buf := make([]byte, entryLen) | |
| _, err := io.ReadFull(ar.fd, buf) | |
| if err == io.EOF { | |
| // No entries left. | |
| return nil | |
| } | |
| if err != nil || buf[entryLen-2] != '`' || buf[entryLen-1] != '\n' { | |
| log.Fatal("file is not an archive: bad entry") | |
| } | |
| entry := new(Entry) | |
| entry.name = strings.TrimRight(string(buf[:16]), " ") | |
| if len(entry.name) == 0 { | |
| log.Fatal("file is not an archive: bad name") | |
| } | |
| buf = buf[16:] | |
| str := string(buf) | |
| get := func(width, base, bitsize int) int64 { | |
| v, err := strconv.ParseInt(strings.TrimRight(str[:width], " "), base, bitsize) | |
| if err != nil { | |
| log.Fatal("file is not an archive: bad number in entry: ", err) | |
| } | |
| str = str[width:] | |
| return v | |
| } | |
| // %-16s%-12d%-6d%-6d%-8o%-10d` | |
| entry.mtime = get(12, 10, 64) | |
| entry.uid = int(get(6, 10, 32)) | |
| entry.gid = int(get(6, 10, 32)) | |
| entry.mode = os.FileMode(get(8, 8, 32)) | |
| entry.size = get(10, 10, 64) | |
| return entry | |
| } | |
| // scan scans the archive and executes the specified action on each entry. | |
| // When action returns, the file offset is at the start of the next entry. | |
| func (ar *Archive) scan(action func(*Entry)) { | |
| for { | |
| entry := ar.readMetadata() | |
| if entry == nil { | |
| break | |
| } | |
| action(entry) | |
| } | |
| } | |
| // listEntry prints to standard output a line describing the entry. | |
| func listEntry(ar *Archive, entry *Entry, verbose bool) { | |
| if verbose { | |
| fmt.Fprintf(stdout, "%s\n", entry) | |
| } else { | |
| fmt.Fprintf(stdout, "%s\n", entry.name) | |
| } | |
| } | |
| // output copies the entry to the specified writer. | |
| func (ar *Archive) output(entry *Entry, w io.Writer) { | |
| n, err := io.Copy(w, io.LimitReader(ar.fd, entry.size)) | |
| if err != nil { | |
| log.Fatal(err) | |
| } | |
| if n != entry.size { | |
| log.Fatal("short file") | |
| } | |
| if entry.size&1 == 1 { | |
| _, err := ar.fd.Seek(1, 1) | |
| if err != nil { | |
| log.Fatal(err) | |
| } | |
| } | |
| } | |
| // skip skips the entry without reading it. | |
| func (ar *Archive) skip(entry *Entry) { | |
| size := entry.size | |
| if size&1 == 1 { | |
| size++ | |
| } | |
| _, err := ar.fd.Seek(size, 1) | |
| if err != nil { | |
| log.Fatal(err) | |
| } | |
| } | |
| // match reports whether the entry matches the argument list. | |
| // If it does, it also drops the file from the to-be-processed list. | |
| func (ar *Archive) match(entry *Entry) bool { | |
| if ar.matchAll { | |
| return true | |
| } | |
| for i, name := range ar.files { | |
| if entry.name == name { | |
| copy(ar.files[i:], ar.files[i+1:]) | |
| ar.files = ar.files[:len(ar.files)-1] | |
| return true | |
| } | |
| } | |
| return false | |
| } | |
| // addFiles adds files to the archive. The archive is known to be | |
| // sane and we are positioned at the end. No attempt is made | |
| // to check for existing files. | |
| func (ar *Archive) addFiles() { | |
| if len(ar.files) == 0 { | |
| usage() | |
| } | |
| for _, file := range ar.files { | |
| if verbose { | |
| fmt.Printf("%s\n", file) | |
| } | |
| fd, err := os.Open(file) | |
| if err != nil { | |
| log.Fatal(err) | |
| } | |
| ar.addFile(fd) | |
| } | |
| ar.files = nil | |
| } | |
| // FileLike abstracts the few methods we need, so we can test without needing real files. | |
| type FileLike interface { | |
| Name() string | |
| Stat() (os.FileInfo, error) | |
| Read([]byte) (int, error) | |
| Close() error | |
| } | |
| // addFile adds a single file to the archive | |
| func (ar *Archive) addFile(fd FileLike) { | |
| defer fd.Close() | |
| // Format the entry. | |
| // First, get its info. | |
| info, err := fd.Stat() | |
| if err != nil { | |
| log.Fatal(err) | |
| } | |
| // mtime, uid, gid are all zero so repeated builds produce identical output. | |
| mtime := int64(0) | |
| uid := 0 | |
| gid := 0 | |
| ar.startFile(info.Name(), mtime, uid, gid, info.Mode(), info.Size()) | |
| n64, err := io.Copy(ar.fd, fd) | |
| if err != nil { | |
| log.Fatal("writing file: ", err) | |
| } | |
| if n64 != info.Size() { | |
| log.Fatalf("writing file: wrote %d bytes; file is size %d", n64, info.Size()) | |
| } | |
| ar.endFile() | |
| } | |
| // startFile writes the archive entry header. | |
| func (ar *Archive) startFile(name string, mtime int64, uid, gid int, mode os.FileMode, size int64) { | |
| n, err := fmt.Fprintf(ar.fd, entryHeader, exactly16Bytes(name), mtime, uid, gid, mode, size) | |
| if err != nil || n != entryLen { | |
| log.Fatal("writing entry header: ", err) | |
| } | |
| ar.pad = int(size & 1) | |
| } | |
| // endFile writes the archive entry tail (a single byte of padding, if the file size was odd). | |
| func (ar *Archive) endFile() { | |
| if ar.pad != 0 { | |
| _, err := ar.fd.Write([]byte{0}) | |
| if err != nil { | |
| log.Fatal("writing archive: ", err) | |
| } | |
| ar.pad = 0 | |
| } | |
| } | |
| // addPkgdef adds the __.PKGDEF file to the archive, copied | |
| // from the first Go object file on the file list, if any. | |
| // The archive is known to be empty. | |
| func (ar *Archive) addPkgdef() { | |
| for _, file := range ar.files { | |
| pkgdef, err := readPkgdef(file) | |
| if err != nil { | |
| continue | |
| } | |
| if verbose { | |
| fmt.Printf("__.PKGDEF # %s\n", file) | |
| } | |
| ar.startFile("__.PKGDEF", 0, 0, 0, 0644, int64(len(pkgdef))) | |
| _, err = ar.fd.Write(pkgdef) | |
| if err != nil { | |
| log.Fatal("writing __.PKGDEF: ", err) | |
| } | |
| ar.endFile() | |
| break | |
| } | |
| } | |
| // readPkgdef extracts the __.PKGDEF data from a Go object file. | |
| func readPkgdef(file string) (data []byte, err error) { | |
| f, err := os.Open(file) | |
| if err != nil { | |
| return nil, err | |
| } | |
| defer f.Close() | |
| // Read from file, collecting header for __.PKGDEF. | |
| // The header is from the beginning of the file until a line | |
| // containing just "!". The first line must begin with "go object ". | |
| rbuf := bufio.NewReader(f) | |
| var wbuf bytes.Buffer | |
| for { | |
| line, err := rbuf.ReadBytes('\n') | |
| if err != nil { | |
| return nil, err | |
| } | |
| if wbuf.Len() == 0 && !bytes.HasPrefix(line, []byte("go object ")) { | |
| return nil, errors.New("not a Go object file") | |
| } | |
| if bytes.Equal(line, []byte("!\n")) { | |
| break | |
| } | |
| wbuf.Write(line) | |
| } | |
| return wbuf.Bytes(), nil | |
| } | |
| // exactly16Bytes truncates the string if necessary so it is at most 16 bytes long, | |
| // then pads the result with spaces to be exactly 16 bytes. | |
| // Fmt uses runes for its width calculation, but we need bytes in the entry header. | |
| func exactly16Bytes(s string) string { | |
| for len(s) > 16 { | |
| _, wid := utf8.DecodeLastRuneInString(s) | |
| s = s[:len(s)-wid] | |
| } | |
| const sixteenSpaces = " " | |
| s += sixteenSpaces[:16-len(s)] | |
| return s | |
| } | |
| // Finally, the actual commands. Each is an action. | |
| // can be modified for testing. | |
| var stdout io.Writer = os.Stdout | |
| // printContents implements the 'p' command. | |
| func (ar *Archive) printContents(entry *Entry) { | |
| if ar.match(entry) { | |
| if verbose { | |
| listEntry(ar, entry, false) | |
| } | |
| ar.output(entry, stdout) | |
| } else { | |
| ar.skip(entry) | |
| } | |
| } | |
| // skipContents implements the first part of the 'r' command. | |
| // It just scans the archive to make sure it's intact. | |
| func (ar *Archive) skipContents(entry *Entry) { | |
| ar.skip(entry) | |
| } | |
| // tableOfContents implements the 't' command. | |
| func (ar *Archive) tableOfContents(entry *Entry) { | |
| if ar.match(entry) { | |
| listEntry(ar, entry, verbose) | |
| } | |
| ar.skip(entry) | |
| } | |
| // extractContents implements the 'x' command. | |
| func (ar *Archive) extractContents(entry *Entry) { | |
| if ar.match(entry) { | |
| if verbose { | |
| listEntry(ar, entry, false) | |
| } | |
| fd, err := os.OpenFile(entry.name, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, entry.mode) | |
| if err != nil { | |
| log.Fatal(err) | |
| } | |
| ar.output(entry, fd) | |
| fd.Close() | |
| } else { | |
| ar.skip(entry) | |
| } | |
| } |