]> Cypherpunks repositories - gostls13.git/commitdiff
archive/tar: convert Reader.Next to be loop based
authorJoe Tsai <joetsai@digital-static.net>
Wed, 16 Sep 2015 07:58:56 +0000 (00:58 -0700)
committerBrad Fitzpatrick <bradfitz@golang.org>
Wed, 2 Dec 2015 02:27:27 +0000 (02:27 +0000)
Motivation for change:
* Recursive logic is hard to follow, since it tends to apply
things in reverse. On the other hand, the tar formats tend to
describe meta headers as affecting the next entry.
* Recursion also applies changes in the wrong order. Two test
files are attached that use multiple headers. The previous Go
behavior differs from what GNU and BSD tar do.

Change-Id: Ic1557256fc1363c5cb26570e5d0b9f65a9e57341
Reviewed-on: https://go-review.googlesource.com/14624
Run-TryBot: Joe Tsai <joetsai@digital-static.net>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
src/archive/tar/reader.go
src/archive/tar/reader_test.go
src/archive/tar/testdata/gnu-multi-hdrs.tar [new file with mode: 0644]
src/archive/tar/testdata/pax-multi-hdrs.tar [new file with mode: 0644]

index da944359d1e47bee51138c88ad979dee31edb5fa..5319f2b629e51a7816cc6dee15a3cb6eeed8bd0e 100644 (file)
@@ -117,92 +117,82 @@ func NewReader(r io.Reader) *Reader { return &Reader{r: r} }
 //
 // io.EOF is returned at the end of the input.
 func (tr *Reader) Next() (*Header, error) {
-       var p parser
-       var hdr *Header
-       if tr.err == nil {
-               tr.skipUnread()
-       }
        if tr.err != nil {
-               return hdr, tr.err
+               return nil, tr.err
        }
-       hdr = tr.readHeader()
-       if hdr == nil {
-               return hdr, tr.err
-       }
-       // Check for PAX/GNU header.
-       switch hdr.Typeflag {
-       case TypeXHeader:
-               //  PAX extended header
-               headers, err := parsePAX(tr)
-               if err != nil {
-                       return nil, err
-               }
-               // We actually read the whole file,
-               // but this skips alignment padding
-               tr.skipUnread()
+
+       var hdr *Header
+       var extHdrs map[string]string
+
+       // Externally, Next iterates through the tar archive as if it is a series of
+       // files. Internally, the tar format often uses fake "files" to add meta
+       // data that describes the next file. These meta data "files" should not
+       // normally be visible to the outside. As such, this loop iterates through
+       // one or more "header files" until it finds a "normal file".
+loop:
+       for {
+               tr.err = tr.skipUnread()
                if tr.err != nil {
                        return nil, tr.err
                }
+
                hdr = tr.readHeader()
-               if hdr == nil {
+               if tr.err != nil {
                        return nil, tr.err
                }
-               mergePAX(hdr, headers)
 
-               // Check for a PAX format sparse file
-               sp, err := tr.checkForGNUSparsePAXHeaders(hdr, headers)
-               if err != nil {
-                       tr.err = err
-                       return nil, err
-               }
-               if sp != nil {
-                       // Sparse files do not make sense when applied to the special header
-                       // types that never have a data section.
-                       if isHeaderOnlyType(hdr.Typeflag) {
-                               tr.err = ErrHeader
+               // Check for PAX/GNU special headers and files.
+               switch hdr.Typeflag {
+               case TypeXHeader:
+                       extHdrs, tr.err = parsePAX(tr)
+                       if tr.err != nil {
                                return nil, tr.err
                        }
-
-                       // Current file is a PAX format GNU sparse file.
-                       // Set the current file reader to a sparse file reader.
-                       tr.curr, tr.err = newSparseFileReader(tr.curr, sp, hdr.Size)
+                       continue loop // This is a meta header affecting the next header
+               case TypeGNULongName, TypeGNULongLink:
+                       var realname []byte
+                       realname, tr.err = ioutil.ReadAll(tr)
                        if tr.err != nil {
                                return nil, tr.err
                        }
+
+                       // Convert GNU extensions to use PAX headers.
+                       if extHdrs == nil {
+                               extHdrs = make(map[string]string)
+                       }
+                       var p parser
+                       switch hdr.Typeflag {
+                       case TypeGNULongName:
+                               extHdrs[paxPath] = p.parseString(realname)
+                       case TypeGNULongLink:
+                               extHdrs[paxLinkpath] = p.parseString(realname)
+                       }
+                       if p.err != nil {
+                               tr.err = p.err
+                               return nil, tr.err
+                       }
+                       continue loop // This is a meta header affecting the next header
+               default:
+                       mergePAX(hdr, extHdrs)
+
+                       // Check for a PAX format sparse file
+                       sp, err := tr.checkForGNUSparsePAXHeaders(hdr, extHdrs)
+                       if err != nil {
+                               tr.err = err
+                               return nil, err
+                       }
+                       if sp != nil {
+                               // Current file is a PAX format GNU sparse file.
+                               // Set the current file reader to a sparse file reader.
+                               tr.curr, tr.err = newSparseFileReader(tr.curr, sp, hdr.Size)
+                               if tr.err != nil {
+                                       return nil, tr.err
+                               }
+                       }
+                       break loop // This is a file, so stop
                }
-               return hdr, nil
-       case TypeGNULongName:
-               // We have a GNU long name header. Its contents are the real file name.
-               realname, err := ioutil.ReadAll(tr)
-               if err != nil {
-                       return nil, err
-               }
-               hdr, tr.err = tr.Next()
-               if tr.err != nil {
-                       return nil, tr.err
-               }
-               hdr.Name = p.parseString(realname)
-               if p.err != nil {
-                       return nil, p.err
-               }
-               return hdr, nil
-       case TypeGNULongLink:
-               // We have a GNU long link header.
-               realname, err := ioutil.ReadAll(tr)
-               if err != nil {
-                       return nil, err
-               }
-               hdr, tr.err = tr.Next()
-               if tr.err != nil {
-                       return nil, tr.err
-               }
-               hdr.Linkname = p.parseString(realname)
-               if p.err != nil {
-                       return nil, p.err
-               }
-               return hdr, nil
        }
-       return hdr, tr.err
+       return hdr, nil
 }
 
 // checkForGNUSparsePAXHeaders checks the PAX headers for GNU sparse headers. If they are found, then
index b3fc68cc7bd0434babb7e0dac32dd4c642691907..21d51ebc0e0a07f5b63a7902facf6bea4f7e02ff 100644 (file)
@@ -288,6 +288,30 @@ var untarTests = []*untarTest{
                        },
                },
        },
+       {
+               // Matches the behavior of GNU, BSD, and STAR tar utilities.
+               file: "testdata/gnu-multi-hdrs.tar",
+               headers: []*Header{
+                       {
+                               Name:     "GNU2/GNU2/long-path-name",
+                               Linkname: "GNU4/GNU4/long-linkpath-name",
+                               ModTime:  time.Unix(0, 0),
+                               Typeflag: '2',
+                       },
+               },
+       },
+       {
+               // Matches the behavior of GNU and BSD tar utilities.
+               file: "testdata/pax-multi-hdrs.tar",
+               headers: []*Header{
+                       {
+                               Name:     "bar",
+                               Linkname: "PAX4/PAX4/long-linkpath-name",
+                               ModTime:  time.Unix(0, 0),
+                               Typeflag: '2',
+                       },
+               },
+       },
        {
                file: "testdata/neg-size.tar",
                err:  ErrHeader,
diff --git a/src/archive/tar/testdata/gnu-multi-hdrs.tar b/src/archive/tar/testdata/gnu-multi-hdrs.tar
new file mode 100644 (file)
index 0000000..8bcad55
Binary files /dev/null and b/src/archive/tar/testdata/gnu-multi-hdrs.tar differ
diff --git a/src/archive/tar/testdata/pax-multi-hdrs.tar b/src/archive/tar/testdata/pax-multi-hdrs.tar
new file mode 100644 (file)
index 0000000..14bc759
Binary files /dev/null and b/src/archive/tar/testdata/pax-multi-hdrs.tar differ