Index: common/archive/ar/reader.go |
diff --git a/common/archive/ar/reader.go b/common/archive/ar/reader.go |
new file mode 100644 |
index 0000000000000000000000000000000000000000..c884e1d7a9958cbbbef1c4a2ab01bfd32d4ab96a |
--- /dev/null |
+++ b/common/archive/ar/reader.go |
@@ -0,0 +1,274 @@ |
+// Copyright 2016 The LUCI Authors. All rights reserved. |
+// Use of this source code is governed under the Apache License, Version 2.0 |
+// that can be found in the LICENSE file. |
+ |
+// Read an ar file with BSD formatted file names. |
+ |
+package ar |
+ |
+import ( |
+ "bytes" |
+ "errors" |
+ "fmt" |
+ "io" |
+ "os" |
+ "time" |
+) |
+ |
+type arFileInfo struct { |
+ // os.FileInfo parts |
+ name string |
+ size int64 |
+ mode uint32 |
+ modtime uint64 |
+ // Extra parts |
+ uid int |
+ gid int |
+} |
+ |
+// os.FileInfo interface |
+func (fi *arFileInfo) Name() string { return fi.name } |
+func (fi *arFileInfo) Size() int64 { return fi.size } |
+func (fi *arFileInfo) Mode() os.FileMode { return os.FileMode(fi.mode) } |
+func (fi *arFileInfo) ModTime() time.Time { return time.Unix(int64(fi.modtime), 0) } |
+func (fi *arFileInfo) IsDir() bool { return fi.Mode().IsDir() } |
+func (fi *arFileInfo) Sys() interface{} { return fi } |
+ |
+// Extra |
+func (fi *arFileInfo) UserId() int { return fi.uid } |
+func (fi *arFileInfo) GroupId() int { return fi.gid } |
+ |
+var ( |
+ ErrHeader = errors.New("archive/ar: invalid ar header") |
+) |
+ |
+type ReaderStage uint |
+ |
+const ( |
+ READ_HEADER ReaderStage = iota |
+ READ_BODY |
+ READ_CLOSED |
+) |
+ |
+type Reader struct { |
+ stage ReaderStage |
+ r io.Reader |
+ bytesrequired int64 |
+ needspadding bool |
+} |
+ |
+func NewReader(r io.Reader) (*Reader, error) { |
+ reader := Reader{r: r, bytesrequired: 0, needspadding: false} |
+ err := reader.checkBytes("header", []byte("!<arch>\n")) |
+ if err != nil { |
+ return nil, err |
+ } else { |
+ return &reader, nil |
+ } |
+} |
+ |
+func (ar *Reader) checkBytes(name string, str []byte) error { |
+ buffer := make([]byte, len(str)) |
+ |
+ count, err := io.ReadFull(ar.r, buffer) |
+ if err != nil { |
+ return err |
+ } |
+ |
+ if count != len(buffer) { |
+ return errors.New(fmt.Sprintf("%s: Not enough data read (only %d, needed %d)", name, count, len(buffer))) |
+ } |
+ |
+ if bytes.Equal(str, buffer) { |
+ return nil |
+ } else { |
+ return errors.New(fmt.Sprintf("%s: error in bytes (wanted: %v got: %v)", name, buffer, str)) |
+ } |
+} |
+ |
+func (ar *Reader) Close() error { |
+ switch ar.stage { |
+ case READ_HEADER: |
+ // Good |
+ case READ_BODY: |
+ return errors.New("Usage error, reading a file.") |
+ case READ_CLOSED: |
+ return errors.New("Usage error, archive already closed.") |
+ default: |
+ panic(fmt.Sprintf("Unknown reader mode: %d", ar.stage)) |
+ } |
+ //ar.r.Close() |
+ ar.stage = READ_CLOSED |
+ return nil |
+} |
+ |
+func (ar *Reader) readBytes(numbytes int64) error { |
+ if numbytes > ar.bytesrequired { |
+ return errors.New(fmt.Sprintf("To much data read! Needed %d, got %d", ar.bytesrequired, numbytes)) |
+ } |
+ |
+ ar.bytesrequired -= numbytes |
+ if ar.bytesrequired != 0 { |
+ return nil |
+ } |
+ |
+ // Padding to 16bit boundary |
+ if ar.needspadding { |
+ err := ar.checkBytes("padding", []byte("\n")) |
+ if err != nil { |
+ return err |
+ } |
+ ar.needspadding = false |
+ } |
+ ar.stage = READ_HEADER |
+ return nil |
+} |
+ |
+// Check you can write bytes to the ar at this moment. |
+func (ar *Reader) checkRead() error { |
+ switch ar.stage { |
+ case READ_HEADER: |
+ return errors.New("Usage error, need to read header first.") |
+ // Good |
+ case READ_BODY: |
+ return nil |
+ case READ_CLOSED: |
+ return errors.New("Usage error, archive closed.") |
+ default: |
+ panic(fmt.Sprintf("Unknown reader mode: %d", ar.stage)) |
+ } |
+} |
+ |
+// Check we have finished writing bytes |
+func (ar *Reader) checkFinished() error { |
+ if ar.bytesrequired != 0 { |
+ return errors.New(fmt.Sprintf("Didn't read enough bytes %d still needed!", ar.bytesrequired)) |
+ } |
+ return nil |
+} |
+ |
+func (ar *Reader) readPartial(name string, data []byte) error { |
+ err := ar.checkRead() |
+ if err != nil { |
+ return err |
+ } |
+ |
+ datalen := int64(len(data)) |
+ if datalen > ar.bytesrequired { |
+ return errors.New(fmt.Sprintf("To much data! Wanted %d, but had %d", ar.bytesrequired, datalen)) |
+ } |
+ |
+ count, err := io.ReadFull(ar.r, data) |
+ ar.readBytes(int64(count)) |
+ return nil |
+} |
+ |
+func (ar *Reader) readHeaderBytes(name string, bytes int, formatstr string) (int64, error) { |
+ data := make([]byte, bytes) |
+ _, err := io.ReadFull(ar.r, data) |
+ if err != nil { |
+ return -1, err |
+ } |
+ |
+ var output int64 = 0 |
+ _, err = fmt.Sscanf(string(data), formatstr, &output) |
+ if err != nil { |
+ return -1, err |
+ } |
+ |
+ if output <= 0 { |
+ return -1, errors.New(fmt.Sprintf("%s: bad value %d", name, output)) |
+ } |
+ return output, nil |
+} |
+ |
+func (ar *Reader) readHeader() (*arFileInfo, error) { |
+ switch ar.stage { |
+ case READ_HEADER: |
+ // Good |
+ case READ_BODY: |
+ return nil, errors.New("Usage error, already writing a file.") |
+ case READ_CLOSED: |
+ return nil, errors.New("Usage error, archive closed.") |
+ default: |
+ panic(fmt.Sprintf("Unknown writer mode: %d", ar.stage)) |
+ } |
+ |
+ var fi arFileInfo |
+ |
+ // File name length prefixed with '#1/' (BSD variant), 16 bytes |
+ namelen, err := ar.readHeaderBytes("filename length", 16, "#1/%13d") |
+ if err != nil { |
+ return nil, err |
+ } |
+ |
+ // Modtime, 12 bytes |
+ modtime, err := ar.readHeaderBytes("modtime", 12, "%12d") |
+ if err != nil { |
+ return nil, err |
+ } |
+ fi.modtime = uint64(modtime) |
+ |
+ // Owner ID, 6 bytes |
+ ownerid, err := ar.readHeaderBytes("ownerid", 6, "%6d") |
+ if err != nil { |
+ return nil, err |
+ } |
+ fi.uid = int(ownerid) |
+ |
+ // Group ID, 6 bytes |
+ groupid, err := ar.readHeaderBytes("groupid", 6, "%6d") |
+ if err != nil { |
+ return nil, err |
+ } |
+ fi.gid = int(groupid) |
+ |
+ // File mode, 8 bytes |
+ filemod, err := ar.readHeaderBytes("groupid", 8, "%8o") |
+ if err != nil { |
+ return nil, err |
+ } |
+ fi.mode = uint32(filemod) |
+ |
+ // File size, 10 bytes |
+ size, err := ar.readHeaderBytes("datasize", 10, "%10d") |
+ if err != nil { |
+ return nil, err |
+ } |
+ fi.size = size - namelen |
+ |
+ ar.stage = READ_BODY |
+ ar.bytesrequired = size |
+ ar.needspadding = (ar.bytesrequired%2 != 0) |
+ |
+ // File magic, 2 bytes |
+ err = ar.checkBytes("filemagic", []byte("\x60\n")) |
+ if err != nil { |
+ return nil, err |
+ } |
+ |
+ // Filename - BSD variant |
+ filename := make([]byte, namelen) |
+ err = ar.readPartial("filename", filename) |
+ if err != nil { |
+ return nil, err |
+ } |
+ fi.name = string(filename) |
+ |
+ return &fi, nil |
+} |
+ |
+func (ar *Reader) Read(b []byte) (n int, err error) { |
+ err = ar.readPartial("data", b) |
+ if err != nil { |
+ return -1, err |
+ } |
+ err = ar.checkFinished() |
+ if err != nil { |
+ return -1, err |
+ } |
+ return len(b), nil |
+} |
+func (ar *Reader) Next() (*arFileInfo, error) { |
+ return ar.readHeader() |
+} |