Chromium Code Reviews| 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..57d71a4d891c9d2630d33050e25bbe81e1d1d4a5 |
| --- /dev/null |
| +++ b/common/archive/ar/reader.go |
| @@ -0,0 +1,275 @@ |
| +// 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 archive file. |
|
M-A Ruel
2016/06/06 23:01:05
Do not create 2 package comments
mithro
2016/06/07 12:28:52
So this comment is somehow associated with the pac
|
| + */ |
| +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 } |
|
M-A Ruel
2016/06/06 23:01:05
use pointer methods otherwise this copies the stru
mithro
2016/06/07 12:28:52
Done.
|
| +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 = iota |
|
M-A Ruel
2016/06/06 23:01:05
just iota for the first
mithro
2016/06/07 12:28:52
Any idea why gofmt doesn't pick that one up?
M-A Ruel
2016/06/08 20:25:31
I mean you can remove the complete "= iota". It's
|
| + READ_CLOSED = iota |
| +) |
| + |
| +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() |
| +} |