Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1077)

Unified Diff: common/archive/ar/reader.go

Issue 2043623002: luci-go: Tools for working with BSD style ar archives. (Closed) Base URL: https://github.com/luci/luci-go@master
Patch Set: Small update to a comment. Created 4 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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..84b3d4b05bb2b899ddfa29be762aedd206c0b7ca
--- /dev/null
+++ b/common/archive/ar/reader.go
@@ -0,0 +1,219 @@
+// 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"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "os"
+ "time"
+)
+
+// Special UsageError that indicates trying to read after closing.
+var (
+ ErrReadAfterClose = UsageError{msg: "read after file closed"}
+)
+
+// ReadDataIOError indicates an error while reading data from the archive.
+type ReadDataIOError struct {
+ IOError
+ wanted []byte
+ got []byte
+}
+
+func (e *ReadDataIOError) Error() string {
+ return fmt.Sprintf("%s (wanted '%s', got '%s') -- *archive corrupted*", e.IOError.Error(), e.wanted, e.got)
mcgreevy 2016/11/07 00:37:22 FYI, got idiomatically precedes want in Go (you wi
+}
+
+// Fatal is always true for ReadDataIOError.
+func (e *ReadDataIOError) Fatal() bool {
+ return true
+}
+
+// FileInfo contains information about a file inside the ar archive.
+type FileInfo interface {
+ os.FileInfo
+ UserID() int
+ GroupID() int
+}
+
+type arFileInfoData struct {
+ // os.FileInfo parts
+ name string
+ size int64
+ mode uint32
+ modtime uint64
+ // Extra parts
+ uid int
+ gid int
+}
+
+// os.FileInfo interface
+func (fi *arFileInfoData) Name() string { return fi.name }
+func (fi *arFileInfoData) Size() int64 { return fi.size }
+func (fi *arFileInfoData) Mode() os.FileMode { return os.FileMode(fi.mode) }
+func (fi *arFileInfoData) ModTime() time.Time { return time.Unix(int64(fi.modtime), 0) }
+func (fi *arFileInfoData) IsDir() bool { return fi.Mode().IsDir() }
+func (fi *arFileInfoData) Sys() interface{} { return fi }
+
+// Extra
+func (fi *arFileInfoData) UserID() int { return fi.uid }
+func (fi *arFileInfoData) GroupID() int { return fi.gid }
+
+type readerStage uint
+
+// Reader allows reading an existing ar archives.
+type Reader struct {
+ r io.Reader
+ bodyReader io.LimitedReader
+ bodyHasPadding bool
+}
+
+// NewReader allows reading an existing ar archives.
+func NewReader(r io.Reader) (*Reader, Error) {
+ reader := Reader{r: r}
+ if err := reader.checkBytes("header", []byte("!<arch>\n")); err != nil {
+ return nil, err
+ }
+ return &reader, nil
+}
+
+func (ar *Reader) checkBytes(section string, str []byte) Error {
+ buffer := make([]byte, len(str))
+
+ if _, err := io.ReadFull(ar.r, buffer); err != nil {
+ return &IOError{section: section, err: err}
+ }
+
+ if !bytes.Equal(str, buffer) {
+ return &ReadDataIOError{IOError: IOError{section: section}, wanted: str, got: buffer}
+ }
+
+ return nil
+}
+
+// Close the archive.
+func (ar *Reader) Close() Error {
+ if ar.r == nil {
+ return &ErrReadAfterClose
+ }
+ ar.r = nil
+ ar.bodyReader.R = nil
+ return nil
+}
+
+func (ar *Reader) readHeaderInt64(section string, bytes int, formatstr string) (int64, Error) {
+ data := make([]byte, bytes)
+ if _, err := io.ReadFull(ar.r, data); err != nil {
+ return -1, &IOError{section: section, err: err}
+ }
+
+ var output int64
+ if _, err := fmt.Sscanf(string(data), formatstr, &output); err != nil {
+ return -1, &ReadDataIOError{IOError: IOError{section: section, err: err}, wanted: []byte(formatstr), got: data}
+ }
+
+ if output <= 0 {
+ return -1, &ReadDataIOError{IOError: IOError{section: section}, wanted: []byte(formatstr), got: data}
+ }
+ return output, nil
+}
+
+// Next file in the archive.
+func (ar *Reader) Next() (FileInfo, Error) {
+ if ar.r == nil {
+ return nil, &ErrReadAfterClose
+ }
+
+ if ar.bodyReader.N > 0 {
+ // Skip over any remaining part of previous file.
+ if s, ok := ar.r.(io.Seeker); ok {
+ if _, err := s.Seek(ar.bodyReader.N, io.SeekCurrent); err != nil {
+ return nil, &ReadDataIOError{IOError: IOError{section: "body", err: err}, wanted: nil, got: nil}
+ }
+ } else {
+ if _, err := io.Copy(ioutil.Discard, &ar.bodyReader); err != nil {
+ return nil, &ReadDataIOError{IOError: IOError{section: "body", err: err}, wanted: nil, got: nil}
+ }
+ }
+
+ // Padding to 16bit boundary
+ if ar.bodyHasPadding {
+ if err := ar.checkBytes("body padding", []byte{'\n'}); err != nil {
+ return nil, err
+ }
+ ar.bodyHasPadding = false
+ }
+ }
+
+ var fi arFileInfoData
+
+ // File name length prefixed with '#1/' (BSD variant), 16 bytes
+ namelen, err := ar.readHeaderInt64("file header file path length", 16, "#1/%13d")
+ if err != nil {
+ return nil, err
+ }
+
+ // Modtime, 12 bytes
+ modtime, err := ar.readHeaderInt64("file modtime", 12, "%12d")
+ if err != nil {
+ return nil, err
+ }
+ fi.modtime = uint64(modtime)
+
+ // Owner ID, 6 bytes
+ ownerid, err := ar.readHeaderInt64("file header owner id", 6, "%6d")
+ if err != nil {
+ return nil, err
+ }
+ fi.uid = int(ownerid)
+
+ // Group ID, 6 bytes
+ groupid, err := ar.readHeaderInt64("file header group id", 6, "%6d")
+ if err != nil {
+ return nil, err
+ }
+ fi.gid = int(groupid)
+
+ // File mode, 8 bytes
+ filemod, err := ar.readHeaderInt64("file header file mode", 8, "%8o")
+ if err != nil {
+ return nil, err
+ }
+ fi.mode = uint32(filemod)
+
+ // File size, 10 bytes
+ size, err := ar.readHeaderInt64("file header data size", 10, "%10d")
+ if err != nil {
+ return nil, err
+ }
+ fi.size = size - namelen
+
+ // File magic, 2 bytes
+ if err = ar.checkBytes("file header file magic", []byte{'\x60', '\n'}); err != nil {
+ return nil, err
+ }
+
+ ar.bodyReader = io.LimitedReader{R: ar.r, N: size}
+ ar.bodyHasPadding = (size%2 != 0)
+
+ // Filename - BSD variant
+ filename := make([]byte, namelen)
+ if _, err := io.ReadFull(&ar.bodyReader, filename); err != nil {
+ return nil, &IOError{section: "body filename", err: err}
+ }
+ fi.name = string(filename)
+
+ return &fi, nil
+}
+
+// Body for the current file in the archive.
+func (ar *Reader) Body() io.Reader {
mcgreevy 2016/11/07 00:37:22 Is there a reason why the body can be returned fro
+ return &ar.bodyReader
+}

Powered by Google App Engine
This is Rietveld 408576698