aboutsummaryrefslogtreecommitdiff
path: root/vendor/github.com/Microsoft/go-winio/backuptar/tar.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/Microsoft/go-winio/backuptar/tar.go')
-rw-r--r--vendor/github.com/Microsoft/go-winio/backuptar/tar.go74
1 files changed, 51 insertions, 23 deletions
diff --git a/vendor/github.com/Microsoft/go-winio/backuptar/tar.go b/vendor/github.com/Microsoft/go-winio/backuptar/tar.go
index cb461ca31..689e4da6b 100644
--- a/vendor/github.com/Microsoft/go-winio/backuptar/tar.go
+++ b/vendor/github.com/Microsoft/go-winio/backuptar/tar.go
@@ -5,7 +5,6 @@ package backuptar
import (
"archive/tar"
"encoding/base64"
- "errors"
"fmt"
"io"
"io/ioutil"
@@ -42,19 +41,14 @@ const (
hdrCreationTime = "LIBARCHIVE.creationtime"
)
-func writeZeroes(w io.Writer, count int64) error {
- buf := make([]byte, 8192)
- c := len(buf)
- for i := int64(0); i < count; i += int64(c) {
- if int64(c) > count-i {
- c = int(count - i)
- }
- _, err := w.Write(buf[:c])
- if err != nil {
- return err
- }
+// zeroReader is an io.Reader that always returns 0s.
+type zeroReader struct{}
+
+func (zr zeroReader) Read(b []byte) (int, error) {
+ for i := range b {
+ b[i] = 0
}
- return nil
+ return len(b), nil
}
func copySparse(t *tar.Writer, br *winio.BackupStreamReader) error {
@@ -71,16 +65,26 @@ func copySparse(t *tar.Writer, br *winio.BackupStreamReader) error {
return fmt.Errorf("unexpected stream %d", bhdr.Id)
}
+ // We can't seek backwards, since we have already written that data to the tar.Writer.
+ if bhdr.Offset < curOffset {
+ return fmt.Errorf("cannot seek back from %d to %d", curOffset, bhdr.Offset)
+ }
// archive/tar does not support writing sparse files
// so just write zeroes to catch up to the current offset.
- err = writeZeroes(t, bhdr.Offset-curOffset)
+ if _, err := io.CopyN(t, zeroReader{}, bhdr.Offset-curOffset); err != nil {
+ return fmt.Errorf("seek to offset %d: %s", bhdr.Offset, err)
+ }
if bhdr.Size == 0 {
+ // A sparse block with size = 0 is used to mark the end of the sparse blocks.
break
}
n, err := io.Copy(t, br)
if err != nil {
return err
}
+ if n != bhdr.Size {
+ return fmt.Errorf("copied %d bytes instead of %d at offset %d", n, bhdr.Size, bhdr.Offset)
+ }
curOffset = bhdr.Offset + n
}
return nil
@@ -221,20 +225,44 @@ func WriteTarFileFromBackupStream(t *tar.Writer, r io.Reader, name string, size
}
}
+ // The logic for copying file contents is fairly complicated due to the need for handling sparse files,
+ // and the weird ways they are represented by BackupRead. A normal file will always either have a data stream
+ // with size and content, or no data stream at all (if empty). However, for a sparse file, the content can also
+ // be represented using a series of sparse block streams following the data stream. Additionally, the way sparse
+ // files are handled by BackupRead has changed in the OS recently. The specifics of the representation are described
+ // in the list at the bottom of this block comment.
+ //
+ // Sparse files can be represented in four different ways, based on the specifics of the file.
+ // - Size = 0:
+ // Previously: BackupRead yields no data stream and no sparse block streams.
+ // Recently: BackupRead yields a data stream with size = 0. There are no following sparse block streams.
+ // - Size > 0, no allocated ranges:
+ // BackupRead yields a data stream with size = 0. Following is a single sparse block stream with
+ // size = 0 and offset = <file size>.
+ // - Size > 0, one allocated range:
+ // BackupRead yields a data stream with size = <file size> containing the file contents. There are no
+ // sparse block streams. This is the case if you take a normal file with contents and simply set the
+ // sparse flag on it.
+ // - Size > 0, multiple allocated ranges:
+ // BackupRead yields a data stream with size = 0. Following are sparse block streams for each allocated
+ // range of the file containing the range contents. Finally there is a sparse block stream with
+ // size = 0 and offset = <file size>.
+
if dataHdr != nil {
// A data stream was found. Copy the data.
- if (dataHdr.Attributes & winio.StreamSparseAttributes) == 0 {
+ // We assume that we will either have a data stream size > 0 XOR have sparse block streams.
+ if dataHdr.Size > 0 || (dataHdr.Attributes&winio.StreamSparseAttributes) == 0 {
if size != dataHdr.Size {
return fmt.Errorf("%s: mismatch between file size %d and header size %d", name, size, dataHdr.Size)
}
- _, err = io.Copy(t, br)
- if err != nil {
- return err
+ if _, err = io.Copy(t, br); err != nil {
+ return fmt.Errorf("%s: copying contents from data stream: %s", name, err)
}
- } else {
- err = copySparse(t, br)
- if err != nil {
- return err
+ } else if size > 0 {
+ // As of a recent OS change, BackupRead now returns a data stream for empty sparse files.
+ // These files have no sparse block streams, so skip the copySparse call if file size = 0.
+ if err = copySparse(t, br); err != nil {
+ return fmt.Errorf("%s: copying contents from sparse block stream: %s", name, err)
}
}
}
@@ -279,7 +307,7 @@ func WriteTarFileFromBackupStream(t *tar.Writer, r io.Reader, name string, size
} else {
// Unsupported for now, since the size of the alternate stream is not present
// in the backup stream until after the data has been read.
- return errors.New("tar of sparse alternate data streams is unsupported")
+ return fmt.Errorf("%s: tar of sparse alternate data streams is unsupported", name)
}
case winio.BackupEaData, winio.BackupLink, winio.BackupPropertyData, winio.BackupObjectId, winio.BackupTxfsData:
// ignore these streams