// and then it can be treated as an io.Reader to access the file's data.
type Reader struct {
r io.Reader
- err error
pad int64 // amount of padding (ignored) after current file entry
curr numBytesReader // reader for current file entry
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 {
if tr.err != nil {
return nil, tr.err
}
+ hdr, err := tr.next()
+ tr.err = err
+ return hdr, err
+}
- var hdr *Header
- var rawHdr *block
+func (tr *Reader) next() (*Header, error) {
var extHdrs map[string]string
// Externally, Next iterates through the tar archive as if it is a series of
// 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
+ if err := tr.skipUnread(); err != nil {
+ return nil, err
}
-
- hdr, rawHdr = tr.readHeader()
- if tr.err != nil {
- return nil, tr.err
+ hdr, rawHdr, err := tr.readHeader()
+ if err != nil {
+ return nil, err
}
-
- tr.err = tr.handleRegularFile(hdr)
- if tr.err != nil {
- return nil, tr.err
+ if err := tr.handleRegularFile(hdr); err != nil {
+ return nil, err
}
// 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
+ extHdrs, err = parsePAX(tr)
+ if err != nil {
+ return nil, err
}
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
+ realname, err := ioutil.ReadAll(tr)
+ if err != nil {
+ return nil, err
}
// Convert GNU extensions to use PAX headers.
extHdrs[paxLinkpath] = p.parseString(realname)
}
if p.err != nil {
- tr.err = p.err
- return nil, tr.err
+ return nil, p.err
}
continue loop // This is a meta header affecting the next header
default:
// The old GNU sparse format is handled here since it is technically
// just a regular file with additional attributes.
- // TODO(dsnet): We should handle errors reported by mergePAX.
- mergePAX(hdr, extHdrs)
+ if err := mergePAX(hdr, extHdrs); err != nil {
+ return nil, err
+ }
// TODO(dsnet): The extended headers may have updated the size.
// Thus, we must setup the regFileReader again here.
//
// See golang.org/issue/15573
- tr.err = tr.handleSparseFile(hdr, rawHdr, extHdrs)
- if tr.err != nil {
- return nil, tr.err
+ if err := tr.handleSparseFile(hdr, rawHdr, extHdrs); err != nil {
+ return nil, 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
return p.err
}
- sp = tr.readOldGNUSparseMap(rawHdr)
- if tr.err != nil {
- return tr.err
+ sp, err = tr.readOldGNUSparseMap(rawHdr)
+ if err != nil {
+ return err
}
} else {
sp, err = tr.checkForGNUSparsePAXHeaders(hdr, extHdrs)
// in the header struct overwrite those found in the header
// struct with higher precision or longer values. Esp. useful
// 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 {
switch k {
case paxPath:
hdr.Name = v
case paxLinkpath:
hdr.Linkname = v
- case paxGname:
- hdr.Gname = v
case paxUname:
hdr.Uname = v
+ case paxGname:
+ hdr.Gname = v
case paxUid:
- uid, err := strconv.ParseInt(v, 10, 0)
- if err != nil {
- return err
- }
- hdr.Uid = int(uid)
+ id64, err = strconv.ParseInt(v, 10, 0)
+ hdr.Uid = int(id64)
case paxGid:
- gid, err := strconv.ParseInt(v, 10, 0)
- if err != nil {
- return err
- }
- hdr.Gid = int(gid)
+ id64, err = strconv.ParseInt(v, 10, 0)
+ hdr.Gid = int(id64)
case paxAtime:
- t, err := parsePAXTime(v)
- if err != nil {
- return err
- }
- hdr.AccessTime = t
+ hdr.AccessTime, err = parsePAXTime(v)
case paxMtime:
- t, err := parsePAXTime(v)
- if err != nil {
- return err
- }
- hdr.ModTime = t
+ hdr.ModTime, err = parsePAXTime(v)
case paxCtime:
- t, err := parsePAXTime(v)
- if err != nil {
- return err
- }
- hdr.ChangeTime = t
+ hdr.ChangeTime, err = parsePAXTime(v)
case paxSize:
- size, err := strconv.ParseInt(v, 10, 0)
- if err != nil {
- return err
- }
- hdr.Size = size
+ hdr.Size, err = strconv.ParseInt(v, 10, 0)
default:
if strings.HasPrefix(k, paxXattr) {
if hdr.Xattrs == nil {
hdr.Xattrs[k[len(paxXattr):]] = v
}
}
+ if err != nil {
+ return ErrHeader
+ }
}
return nil
}
// Seek seems supported, so perform the real Seek.
pos2, err := sr.Seek(dataSkip-1, io.SeekCurrent)
if err != nil {
- tr.err = err
- return tr.err
+ return err
}
seekSkipped = pos2 - pos1
}
}
- var copySkipped int64 // Number of bytes skipped via CopyN
- copySkipped, tr.err = io.CopyN(ioutil.Discard, tr.r, totalSkip-seekSkipped)
- if tr.err == io.EOF && seekSkipped+copySkipped < dataSkip {
- tr.err = io.ErrUnexpectedEOF
+ copySkipped, err := io.CopyN(ioutil.Discard, tr.r, totalSkip-seekSkipped)
+ if err == io.EOF && seekSkipped+copySkipped < dataSkip {
+ err = io.ErrUnexpectedEOF
}
- return tr.err
+ return err
}
// readHeader reads the next block header and assumes that the underlying reader
// * Exactly 0 bytes are read and EOF is hit.
// * Exactly 1 block of zeros is read and EOF is hit.
// * At least 2 blocks of zeros are read.
-func (tr *Reader) readHeader() (*Header, *block) {
- if _, tr.err = io.ReadFull(tr.r, tr.blk[:]); tr.err != nil {
- return nil, nil // io.EOF is okay here
- }
-
+func (tr *Reader) readHeader() (*Header, *block, error) {
// 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 _, tr.err = io.ReadFull(tr.r, tr.blk[:]); tr.err != nil {
- return nil, nil // io.EOF is okay here
+ if _, err := io.ReadFull(tr.r, tr.blk[:]); err != nil {
+ return nil, nil, err // EOF is okay here; exactly 1 block of zeros read
}
if bytes.Equal(tr.blk[:], zeroBlock[:]) {
- tr.err = io.EOF
- } else {
- tr.err = ErrHeader // zero block and then non-zero block
+ return nil, nil, io.EOF // normal EOF; exactly 2 block of zeros read
}
- return nil, nil
+ return nil, nil, ErrHeader // Zero block and then non-zero block
}
// Verify the header matches a known format.
format := tr.blk.GetFormat()
if format == formatUnknown {
- tr.err = ErrHeader
- return nil, nil
+ return nil, nil, ErrHeader
}
var p parser
hdr.Name = prefix + "/" + hdr.Name
}
}
-
- // Check for parsing errors.
- if p.err != nil {
- tr.err = p.err
- return nil, nil
- }
- return hdr, &tr.blk
+ return hdr, &tr.blk, p.err
}
// 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,
// 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 s sparseArray = blk.GNU().Sparse()
var sp = make([]sparseEntry, 0, s.MaxEntries())
offset := p.parseOctal(s.Entry(i).Offset())
numBytes := p.parseOctal(s.Entry(i).NumBytes())
if p.err != nil {
- tr.err = p.err
- return nil
+ return nil, p.err
}
if offset == 0 && numBytes == 0 {
break
for s.IsExtended()[0] > 0 {
// There are more entries. Read an extension header and parse its entries.
var blk block
- if _, tr.err = io.ReadFull(tr.r, blk[:]); tr.err != nil {
- return nil
+ if _, err := io.ReadFull(tr.r, blk[:]); err != nil {
+ return nil, err
}
s = blk.Sparse()
offset := p.parseOctal(s.Entry(i).Offset())
numBytes := p.parseOctal(s.Entry(i).NumBytes())
if p.err != nil {
- tr.err = p.err
- return nil
+ return nil, p.err
}
if offset == 0 && numBytes == 0 {
break
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
// Calling Read on special types like TypeLink, TypeSymLink, TypeChar,
// TypeBlock, TypeDir, and TypeFifo returns 0, io.EOF regardless of what
// 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 {
return 0, tr.err
}
return 0, io.EOF
}
- n, err = tr.curr.Read(b)
+ n, err := tr.curr.Read(b)
if err != nil && err != io.EOF {
tr.err = err
}
- return
+ return n, err
}
func (rfr *regFileReader) Read(b []byte) (n int, err error) {