| // Copyright 2013 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 archive implements reading of archive files generated by the Go |
| // toolchain. |
| package archive |
| |
| import ( |
| "bufio" |
| "bytes" |
| "cmd/internal/bio" |
| "cmd/internal/goobj" |
| "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. |
| */ |
| |
| // A Data is a reference to data stored in an object file. |
| // It records the offset and size of the data, so that a client can |
| // read the data only if necessary. |
| type Data struct { |
| Offset int64 |
| Size int64 |
| } |
| |
| type Archive struct { |
| f *os.File |
| Entries []Entry |
| } |
| |
| func (a *Archive) File() *os.File { return a.f } |
| |
| type Entry struct { |
| Name string |
| Type EntryType |
| Mtime int64 |
| Uid int |
| Gid int |
| Mode os.FileMode |
| Data |
| Obj *GoObj // nil if this entry is not a Go object file |
| } |
| |
| type EntryType int |
| |
| const ( |
| EntryPkgDef EntryType = iota |
| EntryGoObj |
| EntryNativeObj |
| ) |
| |
| 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) |
| } |
| |
| type GoObj struct { |
| TextHeader []byte |
| Arch string |
| Data |
| } |
| |
| const ( |
| 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" |
| ) |
| |
| var ( |
| archiveHeader = []byte("!<arch>\n") |
| archiveMagic = []byte("`\n") |
| goobjHeader = []byte("go objec") // truncated to size of archiveHeader |
| |
| errCorruptArchive = errors.New("corrupt archive") |
| errTruncatedArchive = errors.New("truncated archive") |
| errCorruptObject = errors.New("corrupt object file") |
| errNotObject = errors.New("unrecognized object file format") |
| ) |
| |
| type ErrGoObjOtherVersion struct{ magic []byte } |
| |
| func (e ErrGoObjOtherVersion) Error() string { |
| return fmt.Sprintf("go object of a different version: %q", e.magic) |
| } |
| |
| // An objReader is an object file reader. |
| type objReader struct { |
| a *Archive |
| b *bio.Reader |
| err error |
| offset int64 |
| limit int64 |
| tmp [256]byte |
| } |
| |
| func (r *objReader) init(f *os.File) { |
| r.a = &Archive{f, nil} |
| r.offset, _ = f.Seek(0, io.SeekCurrent) |
| r.limit, _ = f.Seek(0, io.SeekEnd) |
| f.Seek(r.offset, io.SeekStart) |
| r.b = bio.NewReader(f) |
| } |
| |
| // error records that an error occurred. |
| // It returns only the first error, so that an error |
| // caused by an earlier error does not discard information |
| // about the earlier error. |
| func (r *objReader) error(err error) error { |
| if r.err == nil { |
| if err == io.EOF { |
| err = io.ErrUnexpectedEOF |
| } |
| r.err = err |
| } |
| // panic("corrupt") // useful for debugging |
| return r.err |
| } |
| |
| // peek returns the next n bytes without advancing the reader. |
| func (r *objReader) peek(n int) ([]byte, error) { |
| if r.err != nil { |
| return nil, r.err |
| } |
| if r.offset >= r.limit { |
| r.error(io.ErrUnexpectedEOF) |
| return nil, r.err |
| } |
| b, err := r.b.Peek(n) |
| if err != nil { |
| if err != bufio.ErrBufferFull { |
| r.error(err) |
| } |
| } |
| return b, err |
| } |
| |
| // readByte reads and returns a byte from the input file. |
| // On I/O error or EOF, it records the error but returns byte 0. |
| // A sequence of 0 bytes will eventually terminate any |
| // parsing state in the object file. In particular, it ends the |
| // reading of a varint. |
| func (r *objReader) readByte() byte { |
| if r.err != nil { |
| return 0 |
| } |
| if r.offset >= r.limit { |
| r.error(io.ErrUnexpectedEOF) |
| return 0 |
| } |
| b, err := r.b.ReadByte() |
| if err != nil { |
| if err == io.EOF { |
| err = io.ErrUnexpectedEOF |
| } |
| r.error(err) |
| b = 0 |
| } else { |
| r.offset++ |
| } |
| return b |
| } |
| |
| // read reads exactly len(b) bytes from the input file. |
| // If an error occurs, read returns the error but also |
| // records it, so it is safe for callers to ignore the result |
| // as long as delaying the report is not a problem. |
| func (r *objReader) readFull(b []byte) error { |
| if r.err != nil { |
| return r.err |
| } |
| if r.offset+int64(len(b)) > r.limit { |
| return r.error(io.ErrUnexpectedEOF) |
| } |
| n, err := io.ReadFull(r.b, b) |
| r.offset += int64(n) |
| if err != nil { |
| return r.error(err) |
| } |
| return nil |
| } |
| |
| // skip skips n bytes in the input. |
| func (r *objReader) skip(n int64) { |
| if n < 0 { |
| r.error(fmt.Errorf("debug/goobj: internal error: misuse of skip")) |
| } |
| if n < int64(len(r.tmp)) { |
| // Since the data is so small, a just reading from the buffered |
| // reader is better than flushing the buffer and seeking. |
| r.readFull(r.tmp[:n]) |
| } else if n <= int64(r.b.Buffered()) { |
| // Even though the data is not small, it has already been read. |
| // Advance the buffer instead of seeking. |
| for n > int64(len(r.tmp)) { |
| r.readFull(r.tmp[:]) |
| n -= int64(len(r.tmp)) |
| } |
| r.readFull(r.tmp[:n]) |
| } else { |
| // Seek, giving up buffered data. |
| r.b.MustSeek(r.offset+n, io.SeekStart) |
| r.offset += n |
| } |
| } |
| |
| // New writes to f to make a new archive. |
| func New(f *os.File) (*Archive, error) { |
| _, err := f.Write(archiveHeader) |
| if err != nil { |
| return nil, err |
| } |
| return &Archive{f: f}, nil |
| } |
| |
| // Parse parses an object file or archive from f. |
| func Parse(f *os.File, verbose bool) (*Archive, error) { |
| var r objReader |
| r.init(f) |
| t, err := r.peek(8) |
| if err != nil { |
| if err == io.EOF { |
| err = io.ErrUnexpectedEOF |
| } |
| return nil, err |
| } |
| |
| switch { |
| default: |
| return nil, errNotObject |
| |
| case bytes.Equal(t, archiveHeader): |
| if err := r.parseArchive(verbose); err != nil { |
| return nil, err |
| } |
| case bytes.Equal(t, goobjHeader): |
| off := r.offset |
| o := &GoObj{} |
| if err := r.parseObject(o, r.limit-off); err != nil { |
| return nil, err |
| } |
| r.a.Entries = []Entry{{ |
| Name: f.Name(), |
| Type: EntryGoObj, |
| Data: Data{off, r.limit - off}, |
| Obj: o, |
| }} |
| } |
| |
| return r.a, nil |
| } |
| |
| // trimSpace removes trailing spaces from b and returns the corresponding string. |
| // This effectively parses the form used in archive headers. |
| func trimSpace(b []byte) string { |
| return string(bytes.TrimRight(b, " ")) |
| } |
| |
| // parseArchive parses a Unix archive of Go object files. |
| func (r *objReader) parseArchive(verbose bool) error { |
| r.readFull(r.tmp[:8]) // consume header (already checked) |
| for r.offset < r.limit { |
| if err := r.readFull(r.tmp[:60]); err != nil { |
| return err |
| } |
| data := r.tmp[:60] |
| |
| // Each file is preceded by this text header (slice indices in first column): |
| // 0:16 name |
| // 16:28 date |
| // 28:34 uid |
| // 34:40 gid |
| // 40:48 mode |
| // 48:58 size |
| // 58:60 magic - `\n |
| // We only care about name, size, and magic, unless in verbose mode. |
| // The fields are space-padded on the right. |
| // The size is in decimal. |
| // The file data - size bytes - follows the header. |
| // Headers are 2-byte aligned, so if size is odd, an extra padding |
| // byte sits between the file data and the next header. |
| // The file data that follows is padded to an even number of bytes: |
| // if size is odd, an extra padding byte is inserted betw the next header. |
| if len(data) < 60 { |
| return errTruncatedArchive |
| } |
| if !bytes.Equal(data[58:60], archiveMagic) { |
| return errCorruptArchive |
| } |
| name := trimSpace(data[0:16]) |
| var err error |
| get := func(start, end, base, bitsize int) int64 { |
| if err != nil { |
| return 0 |
| } |
| var v int64 |
| v, err = strconv.ParseInt(trimSpace(data[start:end]), base, bitsize) |
| return v |
| } |
| size := get(48, 58, 10, 64) |
| var ( |
| mtime int64 |
| uid, gid int |
| mode os.FileMode |
| ) |
| if verbose { |
| mtime = get(16, 28, 10, 64) |
| uid = int(get(28, 34, 10, 32)) |
| gid = int(get(34, 40, 10, 32)) |
| mode = os.FileMode(get(40, 48, 8, 32)) |
| } |
| if err != nil { |
| return errCorruptArchive |
| } |
| data = data[60:] |
| fsize := size + size&1 |
| if fsize < 0 || fsize < size { |
| return errCorruptArchive |
| } |
| switch name { |
| case "__.PKGDEF": |
| r.a.Entries = append(r.a.Entries, Entry{ |
| Name: name, |
| Type: EntryPkgDef, |
| Mtime: mtime, |
| Uid: uid, |
| Gid: gid, |
| Mode: mode, |
| Data: Data{r.offset, size}, |
| }) |
| r.skip(size) |
| default: |
| var typ EntryType |
| var o *GoObj |
| offset := r.offset |
| p, err := r.peek(8) |
| if err != nil { |
| return err |
| } |
| if bytes.Equal(p, goobjHeader) { |
| typ = EntryGoObj |
| o = &GoObj{} |
| r.parseObject(o, size) |
| } else { |
| typ = EntryNativeObj |
| r.skip(size) |
| } |
| r.a.Entries = append(r.a.Entries, Entry{ |
| Name: name, |
| Type: typ, |
| Mtime: mtime, |
| Uid: uid, |
| Gid: gid, |
| Mode: mode, |
| Data: Data{offset, size}, |
| Obj: o, |
| }) |
| } |
| if size&1 != 0 { |
| r.skip(1) |
| } |
| } |
| return nil |
| } |
| |
| // parseObject parses a single Go object file. |
| // The object file consists of a textual header ending in "\n!\n" |
| // and then the part we want to parse begins. |
| // The format of that part is defined in a comment at the top |
| // of cmd/internal/goobj/objfile.go. |
| func (r *objReader) parseObject(o *GoObj, size int64) error { |
| h := make([]byte, 0, 256) |
| var c1, c2, c3 byte |
| for { |
| c1, c2, c3 = c2, c3, r.readByte() |
| h = append(h, c3) |
| // The new export format can contain 0 bytes. |
| // Don't consider them errors, only look for r.err != nil. |
| if r.err != nil { |
| return errCorruptObject |
| } |
| if c1 == '\n' && c2 == '!' && c3 == '\n' { |
| break |
| } |
| } |
| o.TextHeader = h |
| hs := strings.Fields(string(h)) |
| if len(hs) >= 4 { |
| o.Arch = hs[3] |
| } |
| o.Offset = r.offset |
| o.Size = size - int64(len(h)) |
| |
| p, err := r.peek(8) |
| if err != nil { |
| return err |
| } |
| if !bytes.Equal(p, []byte(goobj.Magic)) { |
| if bytes.HasPrefix(p, []byte("\x00go1")) && bytes.HasSuffix(p, []byte("ld")) { |
| return r.error(ErrGoObjOtherVersion{p[1:]}) // strip the \x00 byte |
| } |
| return r.error(errCorruptObject) |
| } |
| r.skip(o.Size) |
| return nil |
| } |
| |
| // AddEntry adds an entry to the end of a, with the content from r. |
| func (a *Archive) AddEntry(typ EntryType, name string, mtime int64, uid, gid int, mode os.FileMode, size int64, r io.Reader) { |
| off, err := a.f.Seek(0, io.SeekEnd) |
| if err != nil { |
| log.Fatal(err) |
| } |
| n, err := fmt.Fprintf(a.f, entryHeader, exactly16Bytes(name), mtime, uid, gid, mode, size) |
| if err != nil || n != entryLen { |
| log.Fatal("writing entry header: ", err) |
| } |
| n1, _ := io.CopyN(a.f, r, size) |
| if n1 != size { |
| log.Fatal(err) |
| } |
| if (off+size)&1 != 0 { |
| a.f.Write([]byte{0}) // pad to even byte |
| } |
| a.Entries = append(a.Entries, Entry{ |
| Name: name, |
| Type: typ, |
| Mtime: mtime, |
| Uid: uid, |
| Gid: gid, |
| Mode: mode, |
| Data: Data{off + entryLen, size}, |
| }) |
| } |
| |
| // 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 |
| } |
| |
| // architecture-independent object file output |
| const HeaderSize = 60 |
| |
| func ReadHeader(b *bufio.Reader, name string) int { |
| var buf [HeaderSize]byte |
| if _, err := io.ReadFull(b, buf[:]); err != nil { |
| return -1 |
| } |
| aname := strings.Trim(string(buf[0:16]), " ") |
| if !strings.HasPrefix(aname, name) { |
| return -1 |
| } |
| asize := strings.Trim(string(buf[48:58]), " ") |
| i, _ := strconv.Atoi(asize) |
| return i |
| } |
| |
| func FormatHeader(arhdr []byte, name string, size int64) { |
| copy(arhdr[:], fmt.Sprintf("%-16s%-12d%-6d%-6d%-8o%-10d`\n", name, 0, 0, 0, 0644, size)) |
| } |