Commit cd0ba4c1 authored by Joe Tsai's avatar Joe Tsai Committed by Joe Tsai

archive/tar: make Reader error handling consistent

The tar.Reader guarantees stickiness of errors. Ensuring this property means
that the methods of Reader need to be consistent about whose responsibility it
is to actually ensure that errors are sticky.

In this CL, we make it only the responsibility of the exported methods
(Next and Read) to store tr.err. All other methods just return the error as is.

As part of this change, we also check the error value of mergePAX (and test
that it properly detects invalid PAX files). Since the value of mergePAX was
never used before, we change it such that it always returns ErrHeader instead
of strconv.SyntaxError. This keeps it consistent with other usages of strconv
in the same tar package.

Change-Id: Ia1c31da71f1de4c175da89a385dec665d3edd167
Reviewed-on: https://go-review.googlesource.com/28215
Run-TryBot: Joe Tsai <thebrokentoaster@gmail.com>
Reviewed-by: default avatarBrad Fitzpatrick <bradfitz@golang.org>
parent d1a19235
...@@ -30,10 +30,14 @@ const maxNanoSecondIntSize = 9 ...@@ -30,10 +30,14 @@ const maxNanoSecondIntSize = 9
// and then it can be treated as an io.Reader to access the file's data. // and then it can be treated as an io.Reader to access the file's data.
type Reader struct { type Reader struct {
r io.Reader r io.Reader
err error
pad int64 // amount of padding (ignored) after current file entry pad int64 // amount of padding (ignored) after current file entry
curr numBytesReader // reader for current file entry curr numBytesReader // reader for current file entry
blk block // buffer to use as temporary local storage blk block // buffer to use as temporary local storage
// err is a persistent error.
// It is only the responsibility of every exported method of Reader to
// ensure that this error is sticky.
err error
} }
type parser struct { type parser struct {
...@@ -108,9 +112,12 @@ func (tr *Reader) Next() (*Header, error) { ...@@ -108,9 +112,12 @@ func (tr *Reader) Next() (*Header, error) {
if tr.err != nil { if tr.err != nil {
return nil, tr.err return nil, tr.err
} }
hdr, err := tr.next()
tr.err = err
return hdr, err
}
var hdr *Header func (tr *Reader) next() (*Header, error) {
var rawHdr *block
var extHdrs map[string]string var extHdrs map[string]string
// Externally, Next iterates through the tar archive as if it is a series of // Externally, Next iterates through the tar archive as if it is a series of
...@@ -120,34 +127,29 @@ func (tr *Reader) Next() (*Header, error) { ...@@ -120,34 +127,29 @@ func (tr *Reader) Next() (*Header, error) {
// one or more "header files" until it finds a "normal file". // one or more "header files" until it finds a "normal file".
loop: loop:
for { for {
tr.err = tr.skipUnread() if err := tr.skipUnread(); err != nil {
if tr.err != nil { return nil, err
return nil, tr.err
} }
hdr, rawHdr, err := tr.readHeader()
hdr, rawHdr = tr.readHeader() if err != nil {
if tr.err != nil { return nil, err
return nil, tr.err
} }
if err := tr.handleRegularFile(hdr); err != nil {
tr.err = tr.handleRegularFile(hdr) return nil, err
if tr.err != nil {
return nil, tr.err
} }
// Check for PAX/GNU special headers and files. // Check for PAX/GNU special headers and files.
switch hdr.Typeflag { switch hdr.Typeflag {
case TypeXHeader: case TypeXHeader:
extHdrs, tr.err = parsePAX(tr) extHdrs, err = parsePAX(tr)
if tr.err != nil { if err != nil {
return nil, tr.err return nil, err
} }
continue loop // This is a meta header affecting the next header continue loop // This is a meta header affecting the next header
case TypeGNULongName, TypeGNULongLink: case TypeGNULongName, TypeGNULongLink:
var realname []byte realname, err := ioutil.ReadAll(tr)
realname, tr.err = ioutil.ReadAll(tr) if err != nil {
if tr.err != nil { return nil, err
return nil, tr.err
} }
// Convert GNU extensions to use PAX headers. // Convert GNU extensions to use PAX headers.
...@@ -162,30 +164,28 @@ loop: ...@@ -162,30 +164,28 @@ loop:
extHdrs[paxLinkpath] = p.parseString(realname) extHdrs[paxLinkpath] = p.parseString(realname)
} }
if p.err != nil { if p.err != nil {
tr.err = p.err return nil, p.err
return nil, tr.err
} }
continue loop // This is a meta header affecting the next header continue loop // This is a meta header affecting the next header
default: default:
// The old GNU sparse format is handled here since it is technically // The old GNU sparse format is handled here since it is technically
// just a regular file with additional attributes. // just a regular file with additional attributes.
// TODO(dsnet): We should handle errors reported by mergePAX. if err := mergePAX(hdr, extHdrs); err != nil {
mergePAX(hdr, extHdrs) return nil, err
}
// TODO(dsnet): The extended headers may have updated the size. // TODO(dsnet): The extended headers may have updated the size.
// Thus, we must setup the regFileReader again here. // Thus, we must setup the regFileReader again here.
// //
// See golang.org/issue/15573 // See golang.org/issue/15573
tr.err = tr.handleSparseFile(hdr, rawHdr, extHdrs) if err := tr.handleSparseFile(hdr, rawHdr, extHdrs); err != nil {
if tr.err != nil { return nil, err
return nil, tr.err
} }
break loop // This is a file, so stop return hdr, nil // This is a file, so stop
} }
} }
return hdr, nil
} }
// handleRegularFile sets up the current file reader and padding such that it // handleRegularFile sets up the current file reader and padding such that it
...@@ -217,9 +217,9 @@ func (tr *Reader) handleSparseFile(hdr *Header, rawHdr *block, extHdrs map[strin ...@@ -217,9 +217,9 @@ func (tr *Reader) handleSparseFile(hdr *Header, rawHdr *block, extHdrs map[strin
return p.err return p.err
} }
sp = tr.readOldGNUSparseMap(rawHdr) sp, err = tr.readOldGNUSparseMap(rawHdr)
if tr.err != nil { if err != nil {
return tr.err return err
} }
} else { } else {
sp, err = tr.checkForGNUSparsePAXHeaders(hdr, extHdrs) sp, err = tr.checkForGNUSparsePAXHeaders(hdr, extHdrs)
...@@ -302,53 +302,32 @@ func (tr *Reader) checkForGNUSparsePAXHeaders(hdr *Header, headers map[string]st ...@@ -302,53 +302,32 @@ func (tr *Reader) checkForGNUSparsePAXHeaders(hdr *Header, headers map[string]st
// in the header struct overwrite those found in the header // in the header struct overwrite those found in the header
// struct with higher precision or longer values. Esp. useful // struct with higher precision or longer values. Esp. useful
// for name and linkname fields. // for name and linkname fields.
func mergePAX(hdr *Header, headers map[string]string) error { func mergePAX(hdr *Header, headers map[string]string) (err error) {
var id64 int64
for k, v := range headers { for k, v := range headers {
switch k { switch k {
case paxPath: case paxPath:
hdr.Name = v hdr.Name = v
case paxLinkpath: case paxLinkpath:
hdr.Linkname = v hdr.Linkname = v
case paxGname:
hdr.Gname = v
case paxUname: case paxUname:
hdr.Uname = v hdr.Uname = v
case paxGname:
hdr.Gname = v
case paxUid: case paxUid:
uid, err := strconv.ParseInt(v, 10, 0) id64, err = strconv.ParseInt(v, 10, 0)
if err != nil { hdr.Uid = int(id64)
return err
}
hdr.Uid = int(uid)
case paxGid: case paxGid:
gid, err := strconv.ParseInt(v, 10, 0) id64, err = strconv.ParseInt(v, 10, 0)
if err != nil { hdr.Gid = int(id64)
return err
}
hdr.Gid = int(gid)
case paxAtime: case paxAtime:
t, err := parsePAXTime(v) hdr.AccessTime, err = parsePAXTime(v)
if err != nil {
return err
}
hdr.AccessTime = t
case paxMtime: case paxMtime:
t, err := parsePAXTime(v) hdr.ModTime, err = parsePAXTime(v)
if err != nil {
return err
}
hdr.ModTime = t
case paxCtime: case paxCtime:
t, err := parsePAXTime(v) hdr.ChangeTime, err = parsePAXTime(v)
if err != nil {
return err
}
hdr.ChangeTime = t
case paxSize: case paxSize:
size, err := strconv.ParseInt(v, 10, 0) hdr.Size, err = strconv.ParseInt(v, 10, 0)
if err != nil {
return err
}
hdr.Size = size
default: default:
if strings.HasPrefix(k, paxXattr) { if strings.HasPrefix(k, paxXattr) {
if hdr.Xattrs == nil { if hdr.Xattrs == nil {
...@@ -357,6 +336,9 @@ func mergePAX(hdr *Header, headers map[string]string) error { ...@@ -357,6 +336,9 @@ func mergePAX(hdr *Header, headers map[string]string) error {
hdr.Xattrs[k[len(paxXattr):]] = v hdr.Xattrs[k[len(paxXattr):]] = v
} }
} }
if err != nil {
return ErrHeader
}
} }
return nil return nil
} }
...@@ -569,19 +551,17 @@ func (tr *Reader) skipUnread() error { ...@@ -569,19 +551,17 @@ func (tr *Reader) skipUnread() error {
// Seek seems supported, so perform the real Seek. // Seek seems supported, so perform the real Seek.
pos2, err := sr.Seek(dataSkip-1, io.SeekCurrent) pos2, err := sr.Seek(dataSkip-1, io.SeekCurrent)
if err != nil { if err != nil {
tr.err = err return err
return tr.err
} }
seekSkipped = pos2 - pos1 seekSkipped = pos2 - pos1
} }
} }
var copySkipped int64 // Number of bytes skipped via CopyN copySkipped, err := io.CopyN(ioutil.Discard, tr.r, totalSkip-seekSkipped)
copySkipped, tr.err = io.CopyN(ioutil.Discard, tr.r, totalSkip-seekSkipped) if err == io.EOF && seekSkipped+copySkipped < dataSkip {
if tr.err == io.EOF && seekSkipped+copySkipped < dataSkip { err = io.ErrUnexpectedEOF
tr.err = io.ErrUnexpectedEOF
} }
return tr.err return err
} }
// readHeader reads the next block header and assumes that the underlying reader // readHeader reads the next block header and assumes that the underlying reader
...@@ -592,29 +572,25 @@ func (tr *Reader) skipUnread() error { ...@@ -592,29 +572,25 @@ func (tr *Reader) skipUnread() error {
// * Exactly 0 bytes are read and EOF is hit. // * Exactly 0 bytes are read and EOF is hit.
// * Exactly 1 block of zeros is read and EOF is hit. // * Exactly 1 block of zeros is read and EOF is hit.
// * At least 2 blocks of zeros are read. // * At least 2 blocks of zeros are read.
func (tr *Reader) readHeader() (*Header, *block) { func (tr *Reader) readHeader() (*Header, *block, error) {
if _, tr.err = io.ReadFull(tr.r, tr.blk[:]); tr.err != nil {
return nil, nil // io.EOF is okay here
}
// Two blocks of zero bytes marks the end of the archive. // Two blocks of zero bytes marks the end of the archive.
if _, err := io.ReadFull(tr.r, tr.blk[:]); err != nil {
return nil, nil, err // EOF is okay here; exactly 0 bytes read
}
if bytes.Equal(tr.blk[:], zeroBlock[:]) { if bytes.Equal(tr.blk[:], zeroBlock[:]) {
if _, tr.err = io.ReadFull(tr.r, tr.blk[:]); tr.err != nil { if _, err := io.ReadFull(tr.r, tr.blk[:]); err != nil {
return nil, nil // io.EOF is okay here return nil, nil, err // EOF is okay here; exactly 1 block of zeros read
} }
if bytes.Equal(tr.blk[:], zeroBlock[:]) { if bytes.Equal(tr.blk[:], zeroBlock[:]) {
tr.err = io.EOF return nil, nil, io.EOF // normal EOF; exactly 2 block of zeros read
} else {
tr.err = ErrHeader // zero block and then non-zero block
} }
return nil, nil return nil, nil, ErrHeader // Zero block and then non-zero block
} }
// Verify the header matches a known format. // Verify the header matches a known format.
format := tr.blk.GetFormat() format := tr.blk.GetFormat()
if format == formatUnknown { if format == formatUnknown {
tr.err = ErrHeader return nil, nil, ErrHeader
return nil, nil
} }
var p parser var p parser
...@@ -658,19 +634,13 @@ func (tr *Reader) readHeader() (*Header, *block) { ...@@ -658,19 +634,13 @@ func (tr *Reader) readHeader() (*Header, *block) {
hdr.Name = prefix + "/" + hdr.Name hdr.Name = prefix + "/" + hdr.Name
} }
} }
return hdr, &tr.blk, p.err
// Check for parsing errors.
if p.err != nil {
tr.err = p.err
return nil, nil
}
return hdr, &tr.blk
} }
// readOldGNUSparseMap reads the sparse map as stored in the old GNU sparse format. // readOldGNUSparseMap reads the sparse map as stored in the old GNU sparse format.
// The sparse map is stored in the tar header if it's small enough. If it's larger than four entries, // The sparse map is stored in the tar header if it's small enough. If it's larger than four entries,
// then one or more extension headers are used to store the rest of the sparse map. // then one or more extension headers are used to store the rest of the sparse map.
func (tr *Reader) readOldGNUSparseMap(blk *block) []sparseEntry { func (tr *Reader) readOldGNUSparseMap(blk *block) ([]sparseEntry, error) {
var p parser var p parser
var s sparseArray = blk.GNU().Sparse() var s sparseArray = blk.GNU().Sparse()
var sp = make([]sparseEntry, 0, s.MaxEntries()) var sp = make([]sparseEntry, 0, s.MaxEntries())
...@@ -678,8 +648,7 @@ func (tr *Reader) readOldGNUSparseMap(blk *block) []sparseEntry { ...@@ -678,8 +648,7 @@ func (tr *Reader) readOldGNUSparseMap(blk *block) []sparseEntry {
offset := p.parseOctal(s.Entry(i).Offset()) offset := p.parseOctal(s.Entry(i).Offset())
numBytes := p.parseOctal(s.Entry(i).NumBytes()) numBytes := p.parseOctal(s.Entry(i).NumBytes())
if p.err != nil { if p.err != nil {
tr.err = p.err return nil, p.err
return nil
} }
if offset == 0 && numBytes == 0 { if offset == 0 && numBytes == 0 {
break break
...@@ -690,8 +659,8 @@ func (tr *Reader) readOldGNUSparseMap(blk *block) []sparseEntry { ...@@ -690,8 +659,8 @@ func (tr *Reader) readOldGNUSparseMap(blk *block) []sparseEntry {
for s.IsExtended()[0] > 0 { for s.IsExtended()[0] > 0 {
// There are more entries. Read an extension header and parse its entries. // There are more entries. Read an extension header and parse its entries.
var blk block var blk block
if _, tr.err = io.ReadFull(tr.r, blk[:]); tr.err != nil { if _, err := io.ReadFull(tr.r, blk[:]); err != nil {
return nil return nil, err
} }
s = blk.Sparse() s = blk.Sparse()
...@@ -699,8 +668,7 @@ func (tr *Reader) readOldGNUSparseMap(blk *block) []sparseEntry { ...@@ -699,8 +668,7 @@ func (tr *Reader) readOldGNUSparseMap(blk *block) []sparseEntry {
offset := p.parseOctal(s.Entry(i).Offset()) offset := p.parseOctal(s.Entry(i).Offset())
numBytes := p.parseOctal(s.Entry(i).NumBytes()) numBytes := p.parseOctal(s.Entry(i).NumBytes())
if p.err != nil { if p.err != nil {
tr.err = p.err return nil, p.err
return nil
} }
if offset == 0 && numBytes == 0 { if offset == 0 && numBytes == 0 {
break break
...@@ -708,7 +676,7 @@ func (tr *Reader) readOldGNUSparseMap(blk *block) []sparseEntry { ...@@ -708,7 +676,7 @@ func (tr *Reader) readOldGNUSparseMap(blk *block) []sparseEntry {
sp = append(sp, sparseEntry{offset: offset, numBytes: numBytes}) sp = append(sp, sparseEntry{offset: offset, numBytes: numBytes})
} }
} }
return sp return sp, nil
} }
// readGNUSparseMap1x0 reads the sparse map as stored in GNU's PAX sparse format // readGNUSparseMap1x0 reads the sparse map as stored in GNU's PAX sparse format
...@@ -836,7 +804,7 @@ func (tr *Reader) numBytes() int64 { ...@@ -836,7 +804,7 @@ func (tr *Reader) numBytes() int64 {
// Calling Read on special types like TypeLink, TypeSymLink, TypeChar, // Calling Read on special types like TypeLink, TypeSymLink, TypeChar,
// TypeBlock, TypeDir, and TypeFifo returns 0, io.EOF regardless of what // TypeBlock, TypeDir, and TypeFifo returns 0, io.EOF regardless of what
// the Header.Size claims. // the Header.Size claims.
func (tr *Reader) Read(b []byte) (n int, err error) { func (tr *Reader) Read(b []byte) (int, error) {
if tr.err != nil { if tr.err != nil {
return 0, tr.err return 0, tr.err
} }
...@@ -844,11 +812,11 @@ func (tr *Reader) Read(b []byte) (n int, err error) { ...@@ -844,11 +812,11 @@ func (tr *Reader) Read(b []byte) (n int, err error) {
return 0, io.EOF return 0, io.EOF
} }
n, err = tr.curr.Read(b) n, err := tr.curr.Read(b)
if err != nil && err != io.EOF { if err != nil && err != io.EOF {
tr.err = err tr.err = err
} }
return return n, err
} }
func (rfr *regFileReader) Read(b []byte) (n int, err error) { func (rfr *regFileReader) Read(b []byte) (n int, err error) {
......
...@@ -229,6 +229,14 @@ var untarTests = []*untarTest{ ...@@ -229,6 +229,14 @@ var untarTests = []*untarTest{
}, },
}, },
}, },
{
file: "testdata/pax-bad-hdr-file.tar",
err: ErrHeader,
},
{
file: "testdata/pax-bad-mtime-file.tar",
err: ErrHeader,
},
{ {
file: "testdata/nil-uid.tar", // golang.org/issue/5290 file: "testdata/nil-uid.tar", // golang.org/issue/5290
headers: []*Header{ headers: []*Header{
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment