]> Cypherpunks repositories - gostls13.git/commitdiff
A basic tar package.
authorDavid Symonds <dsymonds@golang.org>
Tue, 9 Jun 2009 06:22:56 +0000 (23:22 -0700)
committerDavid Symonds <dsymonds@golang.org>
Tue, 9 Jun 2009 06:22:56 +0000 (23:22 -0700)
R=rsc
APPROVED=rsc
DELTA=371  (370 added, 0 deleted, 1 changed)
OCL=30029
CL=30084

src/lib/Make.deps
src/lib/Makefile
src/lib/archive/tar/Makefile [new file with mode: 0644]
src/lib/archive/tar/testdata/small.txt [new file with mode: 0644]
src/lib/archive/tar/testdata/small2.txt [new file with mode: 0644]
src/lib/archive/tar/testdata/test.tar [new file with mode: 0644]
src/lib/archive/tar/untar.go [new file with mode: 0644]
src/lib/archive/tar/untar_test.go [new file with mode: 0644]

index 77c4089a00f07fdf16c83904dc86de01e3662943..dd83e8b1cb5c38439f16f90fd746a55d78cf6bab 100644 (file)
@@ -1,7 +1,8 @@
+archive/tar.install: bufio.install bytes.install io.install os.install strconv.install
 bignum.install: fmt.install
 bufio.install: io.install os.install utf8.install
 bytes.install: utf8.install
-compress/flate.install: bufio.install fmt.install io.install os.install strconv.install
+compress/flate.install: bufio.install io.install os.install strconv.install
 compress/gzip.install: bufio.install compress/flate.install hash.install hash/crc32.install io.install os.install
 container/list.install:
 container/vector.install:
index 8b6b9a8eed6aa6642b9d670134bbb8e6ac869195..036a82e38b3bb5d128b722f76f3b74406cf5afa3 100644 (file)
@@ -12,6 +12,7 @@
 all: install
 
 DIRS=\
+       archive/tar\
        bignum\
        bufio\
        bytes\
@@ -65,8 +66,11 @@ DIRS=\
        utf8\
 
 TEST=\
+       archive/tar\
        bignum\
        bufio\
+       compress/flate\
+       compress/gzip\
        container/list\
        container/vector\
        crypto/aes\
diff --git a/src/lib/archive/tar/Makefile b/src/lib/archive/tar/Makefile
new file mode 100644 (file)
index 0000000..579ed4c
--- /dev/null
@@ -0,0 +1,60 @@
+# Copyright 2009 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.
+
+# DO NOT EDIT.  Automatically generated by gobuild.
+# gobuild -m >Makefile
+
+D=/archive/
+
+include $(GOROOT)/src/Make.$(GOARCH)
+AR=gopack
+
+default: packages
+
+clean:
+       rm -rf *.[$(OS)] *.a [$(OS)].out _obj
+
+test: packages
+       gotest
+
+coverage: packages
+       gotest
+       6cov -g `pwd` | grep -v '_test\.go:'
+
+%.$O: %.go
+       $(GC) -I_obj $*.go
+
+%.$O: %.c
+       $(CC) $*.c
+
+%.$O: %.s
+       $(AS) $*.s
+
+O1=\
+       untar.$O\
+
+
+phases: a1
+_obj$D/tar.a: phases
+
+a1: $(O1)
+       $(AR) grc _obj$D/tar.a untar.$O
+       rm -f $(O1)
+
+
+newpkg: clean
+       mkdir -p _obj$D
+       $(AR) grc _obj$D/tar.a
+
+$(O1): newpkg
+$(O2): a1
+
+nuke: clean
+       rm -f $(GOROOT)/pkg/$(GOOS)_$(GOARCH)$D/tar.a
+
+packages: _obj$D/tar.a
+
+install: packages
+       test -d $(GOROOT)/pkg && mkdir -p $(GOROOT)/pkg/$(GOOS)_$(GOARCH)$D
+       cp _obj$D/tar.a $(GOROOT)/pkg/$(GOOS)_$(GOARCH)$D/tar.a
diff --git a/src/lib/archive/tar/testdata/small.txt b/src/lib/archive/tar/testdata/small.txt
new file mode 100644 (file)
index 0000000..b249bfc
--- /dev/null
@@ -0,0 +1 @@
+Kilts
\ No newline at end of file
diff --git a/src/lib/archive/tar/testdata/small2.txt b/src/lib/archive/tar/testdata/small2.txt
new file mode 100644 (file)
index 0000000..394ee3e
--- /dev/null
@@ -0,0 +1 @@
+Google.com
diff --git a/src/lib/archive/tar/testdata/test.tar b/src/lib/archive/tar/testdata/test.tar
new file mode 100644 (file)
index 0000000..fc899dc
Binary files /dev/null and b/src/lib/archive/tar/testdata/test.tar differ
diff --git a/src/lib/archive/tar/untar.go b/src/lib/archive/tar/untar.go
new file mode 100644 (file)
index 0000000..300c0f9
--- /dev/null
@@ -0,0 +1,242 @@
+// Copyright 2009 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.
+
+// The tar package implements access to tar archives.
+// It aims to cover most of the variations, including those produced
+// by GNU and BSD tars (not yet started).
+//
+// References:
+//   http://www.freebsd.org/cgi/man.cgi?query=tar&sektion=5
+//   http://www.gnu.org/software/tar/manual/html_node/Standard.html
+package tar
+
+// TODO(dsymonds):
+// - Make it seekable.
+// - Extensions.
+
+import (
+       "bufio";
+       "bytes";
+       "io";
+       "os";
+       "strconv";
+)
+
+var (
+       HeaderError os.Error = os.ErrorString("invalid tar header");
+)
+
+// A tar archive consists of a sequence of files.
+// A Reader provides sequential access to the contents of a tar archive.
+// The Next method advances to the next file in the archive (including the first),
+// and then it can be treated as an io.Reader to access the file's data.
+//
+// Example:
+//     tr := NewTarReader(r);
+//     for {
+//             hdr, err := tr.Next();
+//             if err != nil {
+//                     // handle error
+//             }
+//             if hdr == nil {
+//                     // end of tar archive
+//                     break
+//             }
+//             io.Copy(tr, somewhere);
+//     }
+type Reader struct {
+       r io.Reader;
+       err os.Error;
+       nb int64;       // number of unread bytes for current file entry
+       pad int64;      // amount of padding (ignored) after current file entry
+}
+
+// A Header represents a single header in a tar archive.
+// Only some fields may be populated.
+type Header struct {
+       Name string;
+       Mode int64;
+       Uid int64;
+       Gid int64;
+       Size int64;
+       Mtime int64;
+       Typeflag byte;
+       Linkname string;
+       Uname string;
+       Gname string;
+       Devmajor int64;
+       Devminor int64;
+}
+
+func (tr *Reader) skipUnread()
+func (tr *Reader) readHeader() *Header
+
+// NewReader creates a new Reader reading the given io.Reader.
+func NewReader(r io.Reader) *Reader {
+       return &Reader{ r: r }
+}
+
+// Next advances to the next entry in the tar archive.
+func (tr *Reader) Next() (*Header, os.Error) {
+       var hdr *Header;
+       if tr.err == nil {
+               tr.skipUnread();
+       }
+       if tr.err == nil {
+               hdr = tr.readHeader();
+       }
+       return hdr, tr.err
+}
+
+const (
+       blockSize = 512;
+
+       // Types
+       TypeReg = '0';
+       TypeRegA = '\x00';
+       TypeLink = '1';
+       TypeSymlink = '2';
+       TypeChar = '3';
+       TypeBlock = '4';
+       TypeDir = '5';
+       TypeFifo = '6';
+       TypeCont = '7';
+       TypeXHeader = 'x';
+       TypeXGlobalHeader = 'g';
+)
+
+var zeroBlock = make([]byte, blockSize);
+
+// Parse bytes as a NUL-terminated C-style string.
+// If a NUL byte is not found then the whole slice is returned as a string.
+func cString(b []byte) string {
+       n := 0;
+       for n < len(b) && b[n] != 0 {
+               n++;
+       }
+       return string(b[0:n])
+}
+
+func (tr *Reader) octalNumber(b []byte) int64 {
+       x, err := strconv.Btoui64(cString(b), 8);
+       if err != nil {
+               tr.err = err;
+       }
+       return int64(x)
+}
+
+type ignoreWriter struct {}
+func (ignoreWriter) Write(b []byte) (n int, err os.Error) {
+       return len(b), nil
+}
+
+type seeker interface {
+       Seek(offset int64, whence int) (ret int64, err os.Error);
+}
+
+// Skip any unread bytes in the existing file entry, as well as any alignment padding.
+func (tr *Reader) skipUnread() {
+       nr := tr.nb + tr.pad;   // number of bytes to skip
+
+       var n int64;
+       if sr, ok := tr.r.(seeker); ok {
+               n, tr.err = sr.Seek(nr, 1);
+       } else {
+               n, tr.err = io.Copyn(tr.r, ignoreWriter{}, nr);
+       }
+       tr.nb, tr.pad = 0, 0;
+}
+
+func (tr *Reader) verifyChecksum(header []byte) bool {
+       given := tr.octalNumber(header[148:156]);
+       if tr.err != nil {
+               return false
+       }
+
+       var computed int64;
+       for i := 0; i < len(header); i++ {
+               if i == 148 {
+                       // The chksum field is special: it should be treated as space bytes.
+                       computed += ' ' * 8;
+                       i += 7;
+                       continue
+               }
+               computed += int64(header[i]);
+       }
+
+       return given == computed
+}
+
+type slicer []byte
+func (s *slicer) next(n int) (b []byte) {
+       b, *s = s[0:n], s[n:len(s)];
+       return
+}
+
+func (tr *Reader) readHeader() *Header {
+       header := make([]byte, blockSize);
+       var n int;
+       if n, tr.err = io.FullRead(tr.r, header); tr.err != nil {
+               return nil
+       }
+
+       // Two blocks of zero bytes marks the end of the archive.
+       if bytes.Equal(header, zeroBlock[0:blockSize]) {
+               if n, tr.err = io.FullRead(tr.r, header); tr.err != nil {
+                       return nil
+               }
+               if !bytes.Equal(header, zeroBlock[0:blockSize]) {
+                       tr.err = HeaderError;
+               }
+               return nil
+       }
+
+       if !tr.verifyChecksum(header) {
+               tr.err = HeaderError;
+               return nil
+       }
+
+       // Unpack
+       hdr := new(Header);
+       s := slicer(header);
+
+       // TODO(dsymonds): The format of the header depends on the value of magic (hdr[257:262]),
+       // so use that value to do the correct parsing below.
+
+       hdr.Name = cString(s.next(100));
+       hdr.Mode = tr.octalNumber(s.next(8));
+       hdr.Uid = tr.octalNumber(s.next(8));
+       hdr.Gid = tr.octalNumber(s.next(8));
+       hdr.Size = tr.octalNumber(s.next(12));
+       hdr.Mtime = tr.octalNumber(s.next(12));
+       s.next(8);  // chksum
+       hdr.Typeflag = s.next(1)[0];
+       hdr.Linkname = cString(s.next(100));
+       s.next(8);  // magic, version
+
+       if tr.err != nil {
+               tr.err = HeaderError;
+               return nil
+       }
+
+       // Maximum value of hdr.Size is 64 GB (12 octal digits),
+       // so there's no risk of int64 overflowing.
+       tr.nb = int64(hdr.Size);
+       tr.pad = -tr.nb & (blockSize - 1);  // blockSize is a power of two
+
+       return hdr
+}
+
+// Read reads from the current entry in the tar archive.
+// It returns 0, nil when it reaches the end of that entry,
+// until Next is called to advance to the next entry.
+func (tr *Reader) Read(b []uint8) (n int, err os.Error) {
+       if int64(len(b)) > tr.nb {
+               b = b[0:tr.nb];
+       }
+       n, err = tr.r.Read(b);
+       tr.nb -= int64(n);
+       tr.err = err;
+       return
+}
diff --git a/src/lib/archive/tar/untar_test.go b/src/lib/archive/tar/untar_test.go
new file mode 100644 (file)
index 0000000..a9c92db
--- /dev/null
@@ -0,0 +1,69 @@
+// Copyright 2009 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 tar
+
+import (
+       "archive/tar";
+       "bytes";
+       "fmt";
+       "io";
+       "os";
+       "testing";
+)
+
+func TestUntar(t *testing.T) {
+       f, err := os.Open("testdata/test.tar", os.O_RDONLY, 0444);
+       if err != nil {
+               t.Fatalf("Unexpected error: %v", err);
+       }
+       defer f.Close();
+
+       tr := NewReader(f);
+
+       // First file
+       hdr, err := tr.Next();
+       if err != nil || hdr == nil {
+               t.Fatalf("Didn't get first file: %v", err);
+       }
+       if hdr.Name != "small.txt" {
+               t.Errorf(`hdr.Name = %q, want "small.txt"`, hdr.Name);
+       }
+       if hdr.Mode != 0640 {
+               t.Errorf("hdr.Mode = %v, want 0640", hdr.Mode);
+       }
+       if hdr.Size != 5 {
+               t.Errorf("hdr.Size = %v, want 5", hdr.Size);
+       }
+
+       // Read the first four bytes; Next() should skip the last one.
+       buf := make([]byte, 4);
+       if n, err := io.FullRead(tr, buf); err != nil {
+               t.Fatalf("Unexpected error: %v", err);
+       }
+       if expected := io.StringBytes("Kilt"); !bytes.Equal(buf, expected) {
+               t.Errorf("Contents = %v, want %v", buf, expected);
+       }
+
+       // Second file
+       hdr, err = tr.Next();
+       if err != nil {
+               t.Fatalf("Didn't get second file: %v", err);
+       }
+       if hdr.Name != "small2.txt" {
+               t.Errorf(`hdr.Name = %q, want "small2.txt"`, hdr.Name);
+       }
+       if hdr.Mode != 0640 {
+               t.Errorf("hdr.Mode = %v, want 0640", hdr.Mode);
+       }
+       if hdr.Size != 11 {
+               t.Errorf("hdr.Size = %v, want 11", hdr.Size);
+       }
+
+
+       hdr, err = tr.Next();
+       if hdr != nil || err != nil {
+               t.Fatalf("Unexpected third file or error: %v", err);
+       }
+}