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

archive/tar: partially revert sparse file support

This CL removes the following APIs:
	type SparseEntry struct{ ... }
	type Header struct{ SparseHoles []SparseEntry; ... }
	func (*Header) DetectSparseHoles(f *os.File) error
	func (*Header) PunchSparseHoles(f *os.File) error
	func (*Reader) WriteTo(io.Writer) (int, error)
	func (*Writer) ReadFrom(io.Reader) (int, error)

This API was added during the Go1.10 dev cycle, and are safe to remove.

The rationale for reverting is because Header.DetectSparseHoles and
Header.PunchSparseHoles are functionality that probably better belongs in
the os package itself.

The other API like Header.SparseHoles, Reader.WriteTo, and Writer.ReadFrom
perform no OS specific logic and only perform the actual business logic of
reading and writing sparse archives. Since we do know know what the API added to
package os may look like, we preemptively revert these non-OS specific changes
as well by simply commenting them out.

Updates #13548
Updates #22735

Change-Id: I77842acd39a43de63e5c754bfa1c26cc24687b70
Reviewed-on: https://go-review.googlesource.com/78030Reviewed-by: default avatarRuss Cox <rsc@golang.org>
parent ca886e06
...@@ -13,7 +13,6 @@ package tar ...@@ -13,7 +13,6 @@ package tar
import ( import (
"errors" "errors"
"fmt" "fmt"
"io"
"math" "math"
"os" "os"
"path" "path"
...@@ -82,7 +81,6 @@ const ( ...@@ -82,7 +81,6 @@ const (
TypeXGlobalHeader = 'g' TypeXGlobalHeader = 'g'
// Type 'S' indicates a sparse file in the GNU format. // Type 'S' indicates a sparse file in the GNU format.
// Header.SparseHoles should be populated when using this type.
TypeGNUSparse = 'S' TypeGNUSparse = 'S'
// Types 'L' and 'K' are used by the GNU format for a meta file // Types 'L' and 'K' are used by the GNU format for a meta file
...@@ -164,19 +162,6 @@ type Header struct { ...@@ -164,19 +162,6 @@ type Header struct {
Devmajor int64 // Major device number (valid for TypeChar or TypeBlock) Devmajor int64 // Major device number (valid for TypeChar or TypeBlock)
Devminor int64 // Minor device number (valid for TypeChar or TypeBlock) Devminor int64 // Minor device number (valid for TypeChar or TypeBlock)
// SparseHoles represents a sequence of holes in a sparse file.
//
// A file is sparse if len(SparseHoles) > 0 or Typeflag is TypeGNUSparse.
// If TypeGNUSparse is set, then the format is GNU, otherwise
// the format is PAX (by using GNU-specific PAX records).
//
// A sparse file consists of fragments of data, intermixed with holes
// (described by this field). A hole is semantically a block of NUL-bytes,
// but does not actually exist within the tar file.
// The holes must be sorted in ascending order,
// not overlap with each other, and not extend past the specified Size.
SparseHoles []SparseEntry
// Xattrs stores extended attributes as PAX records under the // Xattrs stores extended attributes as PAX records under the
// "SCHILY.xattr." namespace. // "SCHILY.xattr." namespace.
// //
...@@ -214,10 +199,10 @@ type Header struct { ...@@ -214,10 +199,10 @@ type Header struct {
Format Format Format Format
} }
// SparseEntry represents a Length-sized fragment at Offset in the file. // sparseEntry represents a Length-sized fragment at Offset in the file.
type SparseEntry struct{ Offset, Length int64 } type sparseEntry struct{ Offset, Length int64 }
func (s SparseEntry) endOffset() int64 { return s.Offset + s.Length } func (s sparseEntry) endOffset() int64 { return s.Offset + s.Length }
// A sparse file can be represented as either a sparseDatas or a sparseHoles. // A sparse file can be represented as either a sparseDatas or a sparseHoles.
// As long as the total size is known, they are equivalent and one can be // As long as the total size is known, they are equivalent and one can be
...@@ -240,7 +225,7 @@ func (s SparseEntry) endOffset() int64 { return s.Offset + s.Length } ...@@ -240,7 +225,7 @@ func (s SparseEntry) endOffset() int64 { return s.Offset + s.Length }
// {Offset: 2, Length: 5}, // Data fragment for 2..6 // {Offset: 2, Length: 5}, // Data fragment for 2..6
// {Offset: 18, Length: 3}, // Data fragment for 18..20 // {Offset: 18, Length: 3}, // Data fragment for 18..20
// } // }
// var sph sparseHoles = []SparseEntry{ // var sph sparseHoles = []sparseEntry{
// {Offset: 0, Length: 2}, // Hole fragment for 0..1 // {Offset: 0, Length: 2}, // Hole fragment for 0..1
// {Offset: 7, Length: 11}, // Hole fragment for 7..17 // {Offset: 7, Length: 11}, // Hole fragment for 7..17
// {Offset: 21, Length: 4}, // Hole fragment for 21..24 // {Offset: 21, Length: 4}, // Hole fragment for 21..24
...@@ -249,19 +234,19 @@ func (s SparseEntry) endOffset() int64 { return s.Offset + s.Length } ...@@ -249,19 +234,19 @@ func (s SparseEntry) endOffset() int64 { return s.Offset + s.Length }
// Then the content of the resulting sparse file with a Header.Size of 25 is: // Then the content of the resulting sparse file with a Header.Size of 25 is:
// var sparseFile = "\x00"*2 + "abcde" + "\x00"*11 + "fgh" + "\x00"*4 // var sparseFile = "\x00"*2 + "abcde" + "\x00"*11 + "fgh" + "\x00"*4
type ( type (
sparseDatas []SparseEntry sparseDatas []sparseEntry
sparseHoles []SparseEntry sparseHoles []sparseEntry
) )
// validateSparseEntries reports whether sp is a valid sparse map. // validateSparseEntries reports whether sp is a valid sparse map.
// It does not matter whether sp represents data fragments or hole fragments. // It does not matter whether sp represents data fragments or hole fragments.
func validateSparseEntries(sp []SparseEntry, size int64) bool { func validateSparseEntries(sp []sparseEntry, size int64) bool {
// Validate all sparse entries. These are the same checks as performed by // Validate all sparse entries. These are the same checks as performed by
// the BSD tar utility. // the BSD tar utility.
if size < 0 { if size < 0 {
return false return false
} }
var pre SparseEntry var pre sparseEntry
for _, cur := range sp { for _, cur := range sp {
switch { switch {
case cur.Offset < 0 || cur.Length < 0: case cur.Offset < 0 || cur.Length < 0:
...@@ -285,7 +270,7 @@ func validateSparseEntries(sp []SparseEntry, size int64) bool { ...@@ -285,7 +270,7 @@ func validateSparseEntries(sp []SparseEntry, size int64) bool {
// Even though the Go tar Reader and the BSD tar utility can handle entries // Even though the Go tar Reader and the BSD tar utility can handle entries
// with arbitrary offsets and lengths, the GNU tar utility can only handle // with arbitrary offsets and lengths, the GNU tar utility can only handle
// offsets and lengths that are multiples of blockSize. // offsets and lengths that are multiples of blockSize.
func alignSparseEntries(src []SparseEntry, size int64) []SparseEntry { func alignSparseEntries(src []sparseEntry, size int64) []sparseEntry {
dst := src[:0] dst := src[:0]
for _, s := range src { for _, s := range src {
pos, end := s.Offset, s.endOffset() pos, end := s.Offset, s.endOffset()
...@@ -294,7 +279,7 @@ func alignSparseEntries(src []SparseEntry, size int64) []SparseEntry { ...@@ -294,7 +279,7 @@ func alignSparseEntries(src []SparseEntry, size int64) []SparseEntry {
end -= blockPadding(-end) // Round-down to nearest blockSize end -= blockPadding(-end) // Round-down to nearest blockSize
} }
if pos < end { if pos < end {
dst = append(dst, SparseEntry{Offset: pos, Length: end - pos}) dst = append(dst, sparseEntry{Offset: pos, Length: end - pos})
} }
} }
return dst return dst
...@@ -308,9 +293,9 @@ func alignSparseEntries(src []SparseEntry, size int64) []SparseEntry { ...@@ -308,9 +293,9 @@ func alignSparseEntries(src []SparseEntry, size int64) []SparseEntry {
// * adjacent fragments are coalesced together // * adjacent fragments are coalesced together
// * only the last fragment may be empty // * only the last fragment may be empty
// * the endOffset of the last fragment is the total size // * the endOffset of the last fragment is the total size
func invertSparseEntries(src []SparseEntry, size int64) []SparseEntry { func invertSparseEntries(src []sparseEntry, size int64) []sparseEntry {
dst := src[:0] dst := src[:0]
var pre SparseEntry var pre sparseEntry
for _, cur := range src { for _, cur := range src {
if cur.Length == 0 { if cur.Length == 0 {
continue // Skip empty fragments continue // Skip empty fragments
...@@ -491,24 +476,28 @@ func (h Header) allowedFormats() (format Format, paxHdrs map[string]string, err ...@@ -491,24 +476,28 @@ func (h Header) allowedFormats() (format Format, paxHdrs map[string]string, err
} }
} }
// Check sparse files. // TODO(dsnet): Re-enable this when adding sparse support.
if len(h.SparseHoles) > 0 || h.Typeflag == TypeGNUSparse { // See https://golang.org/issue/22735
if isHeaderOnlyType(h.Typeflag) { /*
return FormatUnknown, nil, headerError{"header-only type cannot be sparse"} // Check sparse files.
} if len(h.SparseHoles) > 0 || h.Typeflag == TypeGNUSparse {
if !validateSparseEntries(h.SparseHoles, h.Size) { if isHeaderOnlyType(h.Typeflag) {
return FormatUnknown, nil, headerError{"invalid sparse holes"} return FormatUnknown, nil, headerError{"header-only type cannot be sparse"}
} }
if h.Typeflag == TypeGNUSparse { if !validateSparseEntries(h.SparseHoles, h.Size) {
whyOnlyGNU = "only GNU supports TypeGNUSparse" return FormatUnknown, nil, headerError{"invalid sparse holes"}
format.mayOnlyBe(FormatGNU) }
} else { if h.Typeflag == TypeGNUSparse {
whyNoGNU = "GNU supports sparse files only with TypeGNUSparse" whyOnlyGNU = "only GNU supports TypeGNUSparse"
format.mustNotBe(FormatGNU) format.mayOnlyBe(FormatGNU)
} else {
whyNoGNU = "GNU supports sparse files only with TypeGNUSparse"
format.mustNotBe(FormatGNU)
}
whyNoUSTAR = "USTAR does not support sparse files"
format.mustNotBe(FormatUSTAR)
} }
whyNoUSTAR = "USTAR does not support sparse files" */
format.mustNotBe(FormatUSTAR)
}
// Check desired format. // Check desired format.
if wantFormat := h.Format; wantFormat != FormatUnknown { if wantFormat := h.Format; wantFormat != FormatUnknown {
...@@ -532,66 +521,6 @@ func (h Header) allowedFormats() (format Format, paxHdrs map[string]string, err ...@@ -532,66 +521,6 @@ func (h Header) allowedFormats() (format Format, paxHdrs map[string]string, err
return format, paxHdrs, err return format, paxHdrs, err
} }
var sysSparseDetect func(f *os.File) (sparseHoles, error)
var sysSparsePunch func(f *os.File, sph sparseHoles) error
// DetectSparseHoles searches for holes within f to populate SparseHoles
// on supported operating systems and filesystems.
// The file offset is cleared to zero.
//
// When packing a sparse file, DetectSparseHoles should be called prior to
// serializing the header to the archive with Writer.WriteHeader.
func (h *Header) DetectSparseHoles(f *os.File) (err error) {
defer func() {
if _, serr := f.Seek(0, io.SeekStart); err == nil {
err = serr
}
}()
h.SparseHoles = nil
if sysSparseDetect != nil {
sph, err := sysSparseDetect(f)
h.SparseHoles = sph
return err
}
return nil
}
// PunchSparseHoles destroys the contents of f, and prepares a sparse file
// (on supported operating systems and filesystems)
// with holes punched according to SparseHoles.
// The file offset is cleared to zero.
//
// When extracting a sparse file, PunchSparseHoles should be called prior to
// populating the content of a file with Reader.WriteTo.
func (h *Header) PunchSparseHoles(f *os.File) (err error) {
defer func() {
if _, serr := f.Seek(0, io.SeekStart); err == nil {
err = serr
}
}()
if err := f.Truncate(0); err != nil {
return err
}
var size int64
if len(h.SparseHoles) > 0 {
size = h.SparseHoles[len(h.SparseHoles)-1].endOffset()
}
if !validateSparseEntries(h.SparseHoles, size) {
return errors.New("archive/tar: invalid sparse holes")
}
if size == 0 {
return nil // For non-sparse files, do nothing (other than Truncate)
}
if sysSparsePunch != nil {
return sysSparsePunch(f, h.SparseHoles)
}
return f.Truncate(size)
}
// FileInfo returns an os.FileInfo for the Header. // FileInfo returns an os.FileInfo for the Header.
func (h *Header) FileInfo() os.FileInfo { func (h *Header) FileInfo() os.FileInfo {
return headerFileInfo{h} return headerFileInfo{h}
...@@ -693,9 +622,6 @@ const ( ...@@ -693,9 +622,6 @@ const (
// Since os.FileInfo's Name method only returns the base name of // Since os.FileInfo's Name method only returns the base name of
// the file it describes, it may be necessary to modify Header.Name // the file it describes, it may be necessary to modify Header.Name
// to provide the full path name of the file. // to provide the full path name of the file.
//
// This function does not populate Header.SparseHoles;
// for sparse file support, additionally call Header.DetectSparseHoles.
func FileInfoHeader(fi os.FileInfo, link string) (*Header, error) { func FileInfoHeader(fi os.FileInfo, link string) (*Header, error) {
if fi == nil { if fi == nil {
return nil, errors.New("archive/tar: FileInfo is nil") return nil, errors.New("archive/tar: FileInfo is nil")
...@@ -761,9 +687,6 @@ func FileInfoHeader(fi os.FileInfo, link string) (*Header, error) { ...@@ -761,9 +687,6 @@ func FileInfoHeader(fi os.FileInfo, link string) (*Header, error) {
h.Size = 0 h.Size = 0
h.Linkname = sys.Linkname h.Linkname = sys.Linkname
} }
if sys.SparseHoles != nil {
h.SparseHoles = append([]SparseEntry{}, sys.SparseHoles...)
}
if sys.PAXRecords != nil { if sys.PAXRecords != nil {
h.PAXRecords = make(map[string]string) h.PAXRecords = make(map[string]string)
for k, v := range sys.PAXRecords { for k, v := range sys.PAXRecords {
......
...@@ -7,13 +7,10 @@ package tar_test ...@@ -7,13 +7,10 @@ package tar_test
import ( import (
"archive/tar" "archive/tar"
"bytes" "bytes"
"crypto/md5"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"log" "log"
"os" "os"
"strings"
) )
func Example_minimal() { func Example_minimal() {
...@@ -72,179 +69,3 @@ func Example_minimal() { ...@@ -72,179 +69,3 @@ func Example_minimal() {
// Contents of todo.txt: // Contents of todo.txt:
// Get animal handling license. // Get animal handling license.
} }
// A sparse file can efficiently represent a large file that is mostly empty.
// When packing an archive, Header.DetectSparseHoles can be used to populate
// the sparse map, while Header.PunchSparseHoles can be used to create a
// sparse file on disk when extracting an archive.
func Example_sparseAutomatic() {
// Create the source sparse file.
src, err := ioutil.TempFile("", "sparse.db")
if err != nil {
log.Fatal(err)
}
defer os.Remove(src.Name()) // Best-effort cleanup
defer func() {
if err := src.Close(); err != nil {
log.Fatal(err)
}
}()
if err := src.Truncate(10e6); err != nil {
log.Fatal(err)
}
for i := 0; i < 10; i++ {
if _, err := src.Seek(1e6-1e3, io.SeekCurrent); err != nil {
log.Fatal(err)
}
if _, err := src.Write(bytes.Repeat([]byte{'0' + byte(i)}, 1e3)); err != nil {
log.Fatal(err)
}
}
// Create an archive and pack the source sparse file to it.
var buf bytes.Buffer
tw := tar.NewWriter(&buf)
fi, err := src.Stat()
if err != nil {
log.Fatal(err)
}
hdr, err := tar.FileInfoHeader(fi, "")
if err != nil {
log.Fatal(err)
}
if err := hdr.DetectSparseHoles(src); err != nil {
log.Fatal(err)
}
if err := tw.WriteHeader(hdr); err != nil {
log.Fatal(err)
}
if _, err := io.Copy(tw, src); err != nil {
log.Fatal(err)
}
if err := tw.Close(); err != nil {
log.Fatal(err)
}
// Create the destination sparse file.
dst, err := ioutil.TempFile("", "sparse.db")
if err != nil {
log.Fatal(err)
}
defer os.Remove(dst.Name()) // Best-effort cleanup
defer func() {
if err := dst.Close(); err != nil {
log.Fatal(err)
}
}()
// Open the archive and extract the sparse file into the destination file.
tr := tar.NewReader(&buf)
hdr, err = tr.Next()
if err != nil {
log.Fatal(err)
}
if err := hdr.PunchSparseHoles(dst); err != nil {
log.Fatal(err)
}
if _, err := io.Copy(dst, tr); err != nil {
log.Fatal(err)
}
// Verify that the sparse files are identical.
want, err := ioutil.ReadFile(src.Name())
if err != nil {
log.Fatal(err)
}
got, err := ioutil.ReadFile(dst.Name())
if err != nil {
log.Fatal(err)
}
fmt.Printf("Src MD5: %08x\n", md5.Sum(want))
fmt.Printf("Dst MD5: %08x\n", md5.Sum(got))
// Output:
// Src MD5: 33820d648d42cb3da2515da229149f74
// Dst MD5: 33820d648d42cb3da2515da229149f74
}
// The SparseHoles can be manually constructed without Header.DetectSparseHoles.
func Example_sparseManual() {
// Define a sparse file to add to the archive.
// This sparse files contains 5 data fragments, and 4 hole fragments.
// The logical size of the file is 16 KiB, while the physical size of the
// file is only 3 KiB (not counting the header data).
hdr := &tar.Header{
Name: "sparse.db",
Size: 16384,
SparseHoles: []tar.SparseEntry{
// Data fragment at 0..1023
{Offset: 1024, Length: 1024 - 512}, // Hole fragment at 1024..1535
// Data fragment at 1536..2047
{Offset: 2048, Length: 2048 - 512}, // Hole fragment at 2048..3583
// Data fragment at 3584..4095
{Offset: 4096, Length: 4096 - 512}, // Hole fragment at 4096..7679
// Data fragment at 7680..8191
{Offset: 8192, Length: 8192 - 512}, // Hole fragment at 8192..15871
// Data fragment at 15872..16383
},
}
// The regions marked as a sparse hole are filled with NUL-bytes.
// The total length of the body content must match the specified Size field.
body := "" +
strings.Repeat("A", 1024) +
strings.Repeat("\x00", 1024-512) +
strings.Repeat("B", 512) +
strings.Repeat("\x00", 2048-512) +
strings.Repeat("C", 512) +
strings.Repeat("\x00", 4096-512) +
strings.Repeat("D", 512) +
strings.Repeat("\x00", 8192-512) +
strings.Repeat("E", 512)
h := md5.Sum([]byte(body))
fmt.Printf("Write content of %s, Size: %d, MD5: %08x\n", hdr.Name, len(body), h)
fmt.Printf("Write SparseHoles of %s:\n\t%v\n\n", hdr.Name, hdr.SparseHoles)
// Create a new archive and write the sparse file.
var buf bytes.Buffer
tw := tar.NewWriter(&buf)
if err := tw.WriteHeader(hdr); err != nil {
log.Fatal(err)
}
if _, err := tw.Write([]byte(body)); err != nil {
log.Fatal(err)
}
if err := tw.Close(); err != nil {
log.Fatal(err)
}
// Open and iterate through the files in the archive.
tr := tar.NewReader(&buf)
for {
hdr, err := tr.Next()
if err == io.EOF {
break
}
if err != nil {
log.Fatal(err)
}
body, err := ioutil.ReadAll(tr)
if err != nil {
log.Fatal(err)
}
h := md5.Sum([]byte(body))
fmt.Printf("Read content of %s, Size: %d, MD5: %08x\n", hdr.Name, len(body), h)
fmt.Printf("Read SparseHoles of %s:\n\t%v\n\n", hdr.Name, hdr.SparseHoles)
}
// Output:
// Write content of sparse.db, Size: 16384, MD5: 9b4e2cfae0f9303d30237718e891e9f9
// Write SparseHoles of sparse.db:
// [{1024 512} {2048 1536} {4096 3584} {8192 7680}]
//
// Read content of sparse.db, Size: 16384, MD5: 9b4e2cfae0f9303d30237718e891e9f9
// Read SparseHoles of sparse.db:
// [{1024 512} {2048 1536} {4096 3584} {8192 7680} {16384 0}]
}
...@@ -41,6 +41,8 @@ import "strings" ...@@ -41,6 +41,8 @@ import "strings"
// The table's lower portion shows specialized features of each format, // The table's lower portion shows specialized features of each format,
// such as supported string encodings, support for sub-second timestamps, // such as supported string encodings, support for sub-second timestamps,
// or support for sparse files. // or support for sparse files.
//
// The Writer currently provides no support for sparse files.
type Format int type Format int
// Constants to identify various tar formats. // Constants to identify various tar formats.
......
...@@ -192,7 +192,6 @@ func (tr *Reader) handleSparseFile(hdr *Header, rawHdr *block) error { ...@@ -192,7 +192,6 @@ func (tr *Reader) handleSparseFile(hdr *Header, rawHdr *block) error {
} }
sph := invertSparseEntries(spd, hdr.Size) sph := invertSparseEntries(spd, hdr.Size)
tr.curr = &sparseFileReader{tr.curr, sph, 0} tr.curr = &sparseFileReader{tr.curr, sph, 0}
hdr.SparseHoles = append([]SparseEntry{}, sph...)
} }
return err return err
} }
...@@ -486,7 +485,7 @@ func (tr *Reader) readOldGNUSparseMap(hdr *Header, blk *block) (sparseDatas, err ...@@ -486,7 +485,7 @@ func (tr *Reader) readOldGNUSparseMap(hdr *Header, blk *block) (sparseDatas, err
if p.err != nil { if p.err != nil {
return nil, p.err return nil, p.err
} }
spd = append(spd, SparseEntry{Offset: offset, Length: length}) spd = append(spd, sparseEntry{Offset: offset, Length: length})
} }
if s.IsExtended()[0] > 0 { if s.IsExtended()[0] > 0 {
...@@ -566,7 +565,7 @@ func readGNUSparseMap1x0(r io.Reader) (sparseDatas, error) { ...@@ -566,7 +565,7 @@ func readGNUSparseMap1x0(r io.Reader) (sparseDatas, error) {
if err1 != nil || err2 != nil { if err1 != nil || err2 != nil {
return nil, ErrHeader return nil, ErrHeader
} }
spd = append(spd, SparseEntry{Offset: offset, Length: length}) spd = append(spd, sparseEntry{Offset: offset, Length: length})
} }
return spd, nil return spd, nil
} }
...@@ -600,7 +599,7 @@ func readGNUSparseMap0x1(paxHdrs map[string]string) (sparseDatas, error) { ...@@ -600,7 +599,7 @@ func readGNUSparseMap0x1(paxHdrs map[string]string) (sparseDatas, error) {
if err1 != nil || err2 != nil { if err1 != nil || err2 != nil {
return nil, ErrHeader return nil, ErrHeader
} }
spd = append(spd, SparseEntry{Offset: offset, Length: length}) spd = append(spd, sparseEntry{Offset: offset, Length: length})
sparseMap = sparseMap[2:] sparseMap = sparseMap[2:]
} }
return spd, nil return spd, nil
...@@ -627,14 +626,17 @@ func (tr *Reader) Read(b []byte) (int, error) { ...@@ -627,14 +626,17 @@ func (tr *Reader) Read(b []byte) (int, error) {
return n, err return n, err
} }
// WriteTo writes the content of the current file to w. // writeTo writes the content of the current file to w.
// The bytes written matches the number of remaining bytes in the current file. // The bytes written matches the number of remaining bytes in the current file.
// //
// If the current file is sparse and w is an io.WriteSeeker, // If the current file is sparse and w is an io.WriteSeeker,
// then WriteTo uses Seek to skip past holes defined in Header.SparseHoles, // then writeTo uses Seek to skip past holes defined in Header.SparseHoles,
// assuming that skipped regions are filled with NULs. // assuming that skipped regions are filled with NULs.
// This always writes the last byte to ensure w is the right size. // This always writes the last byte to ensure w is the right size.
func (tr *Reader) WriteTo(w io.Writer) (int64, error) { //
// TODO(dsnet): Re-export this when adding sparse file support.
// See https://golang.org/issue/22735
func (tr *Reader) writeTo(w io.Writer) (int64, error) {
if tr.err != nil { if tr.err != nil {
return 0, tr.err return 0, tr.err
} }
......
...@@ -71,24 +71,7 @@ func TestReader(t *testing.T) { ...@@ -71,24 +71,7 @@ func TestReader(t *testing.T) {
Gname: "david", Gname: "david",
Devmajor: 0, Devmajor: 0,
Devminor: 0, Devminor: 0,
SparseHoles: []SparseEntry{ Format: FormatGNU,
{0, 1}, {2, 1}, {4, 1}, {6, 1}, {8, 1}, {10, 1}, {12, 1}, {14, 1},
{16, 1}, {18, 1}, {20, 1}, {22, 1}, {24, 1}, {26, 1}, {28, 1},
{30, 1}, {32, 1}, {34, 1}, {36, 1}, {38, 1}, {40, 1}, {42, 1},
{44, 1}, {46, 1}, {48, 1}, {50, 1}, {52, 1}, {54, 1}, {56, 1},
{58, 1}, {60, 1}, {62, 1}, {64, 1}, {66, 1}, {68, 1}, {70, 1},
{72, 1}, {74, 1}, {76, 1}, {78, 1}, {80, 1}, {82, 1}, {84, 1},
{86, 1}, {88, 1}, {90, 1}, {92, 1}, {94, 1}, {96, 1}, {98, 1},
{100, 1}, {102, 1}, {104, 1}, {106, 1}, {108, 1}, {110, 1},
{112, 1}, {114, 1}, {116, 1}, {118, 1}, {120, 1}, {122, 1},
{124, 1}, {126, 1}, {128, 1}, {130, 1}, {132, 1}, {134, 1},
{136, 1}, {138, 1}, {140, 1}, {142, 1}, {144, 1}, {146, 1},
{148, 1}, {150, 1}, {152, 1}, {154, 1}, {156, 1}, {158, 1},
{160, 1}, {162, 1}, {164, 1}, {166, 1}, {168, 1}, {170, 1},
{172, 1}, {174, 1}, {176, 1}, {178, 1}, {180, 1}, {182, 1},
{184, 1}, {186, 1}, {188, 1}, {190, 10},
},
Format: FormatGNU,
}, { }, {
Name: "sparse-posix-0.0", Name: "sparse-posix-0.0",
Mode: 420, Mode: 420,
...@@ -102,23 +85,6 @@ func TestReader(t *testing.T) { ...@@ -102,23 +85,6 @@ func TestReader(t *testing.T) {
Gname: "david", Gname: "david",
Devmajor: 0, Devmajor: 0,
Devminor: 0, Devminor: 0,
SparseHoles: []SparseEntry{
{0, 1}, {2, 1}, {4, 1}, {6, 1}, {8, 1}, {10, 1}, {12, 1}, {14, 1},
{16, 1}, {18, 1}, {20, 1}, {22, 1}, {24, 1}, {26, 1}, {28, 1},
{30, 1}, {32, 1}, {34, 1}, {36, 1}, {38, 1}, {40, 1}, {42, 1},
{44, 1}, {46, 1}, {48, 1}, {50, 1}, {52, 1}, {54, 1}, {56, 1},
{58, 1}, {60, 1}, {62, 1}, {64, 1}, {66, 1}, {68, 1}, {70, 1},
{72, 1}, {74, 1}, {76, 1}, {78, 1}, {80, 1}, {82, 1}, {84, 1},
{86, 1}, {88, 1}, {90, 1}, {92, 1}, {94, 1}, {96, 1}, {98, 1},
{100, 1}, {102, 1}, {104, 1}, {106, 1}, {108, 1}, {110, 1},
{112, 1}, {114, 1}, {116, 1}, {118, 1}, {120, 1}, {122, 1},
{124, 1}, {126, 1}, {128, 1}, {130, 1}, {132, 1}, {134, 1},
{136, 1}, {138, 1}, {140, 1}, {142, 1}, {144, 1}, {146, 1},
{148, 1}, {150, 1}, {152, 1}, {154, 1}, {156, 1}, {158, 1},
{160, 1}, {162, 1}, {164, 1}, {166, 1}, {168, 1}, {170, 1},
{172, 1}, {174, 1}, {176, 1}, {178, 1}, {180, 1}, {182, 1},
{184, 1}, {186, 1}, {188, 1}, {190, 10},
},
PAXRecords: map[string]string{ PAXRecords: map[string]string{
"GNU.sparse.size": "200", "GNU.sparse.size": "200",
"GNU.sparse.numblocks": "95", "GNU.sparse.numblocks": "95",
...@@ -138,23 +104,6 @@ func TestReader(t *testing.T) { ...@@ -138,23 +104,6 @@ func TestReader(t *testing.T) {
Gname: "david", Gname: "david",
Devmajor: 0, Devmajor: 0,
Devminor: 0, Devminor: 0,
SparseHoles: []SparseEntry{
{0, 1}, {2, 1}, {4, 1}, {6, 1}, {8, 1}, {10, 1}, {12, 1}, {14, 1},
{16, 1}, {18, 1}, {20, 1}, {22, 1}, {24, 1}, {26, 1}, {28, 1},
{30, 1}, {32, 1}, {34, 1}, {36, 1}, {38, 1}, {40, 1}, {42, 1},
{44, 1}, {46, 1}, {48, 1}, {50, 1}, {52, 1}, {54, 1}, {56, 1},
{58, 1}, {60, 1}, {62, 1}, {64, 1}, {66, 1}, {68, 1}, {70, 1},
{72, 1}, {74, 1}, {76, 1}, {78, 1}, {80, 1}, {82, 1}, {84, 1},
{86, 1}, {88, 1}, {90, 1}, {92, 1}, {94, 1}, {96, 1}, {98, 1},
{100, 1}, {102, 1}, {104, 1}, {106, 1}, {108, 1}, {110, 1},
{112, 1}, {114, 1}, {116, 1}, {118, 1}, {120, 1}, {122, 1},
{124, 1}, {126, 1}, {128, 1}, {130, 1}, {132, 1}, {134, 1},
{136, 1}, {138, 1}, {140, 1}, {142, 1}, {144, 1}, {146, 1},
{148, 1}, {150, 1}, {152, 1}, {154, 1}, {156, 1}, {158, 1},
{160, 1}, {162, 1}, {164, 1}, {166, 1}, {168, 1}, {170, 1},
{172, 1}, {174, 1}, {176, 1}, {178, 1}, {180, 1}, {182, 1},
{184, 1}, {186, 1}, {188, 1}, {190, 10},
},
PAXRecords: map[string]string{ PAXRecords: map[string]string{
"GNU.sparse.size": "200", "GNU.sparse.size": "200",
"GNU.sparse.numblocks": "95", "GNU.sparse.numblocks": "95",
...@@ -175,23 +124,6 @@ func TestReader(t *testing.T) { ...@@ -175,23 +124,6 @@ func TestReader(t *testing.T) {
Gname: "david", Gname: "david",
Devmajor: 0, Devmajor: 0,
Devminor: 0, Devminor: 0,
SparseHoles: []SparseEntry{
{0, 1}, {2, 1}, {4, 1}, {6, 1}, {8, 1}, {10, 1}, {12, 1}, {14, 1},
{16, 1}, {18, 1}, {20, 1}, {22, 1}, {24, 1}, {26, 1}, {28, 1},
{30, 1}, {32, 1}, {34, 1}, {36, 1}, {38, 1}, {40, 1}, {42, 1},
{44, 1}, {46, 1}, {48, 1}, {50, 1}, {52, 1}, {54, 1}, {56, 1},
{58, 1}, {60, 1}, {62, 1}, {64, 1}, {66, 1}, {68, 1}, {70, 1},
{72, 1}, {74, 1}, {76, 1}, {78, 1}, {80, 1}, {82, 1}, {84, 1},
{86, 1}, {88, 1}, {90, 1}, {92, 1}, {94, 1}, {96, 1}, {98, 1},
{100, 1}, {102, 1}, {104, 1}, {106, 1}, {108, 1}, {110, 1},
{112, 1}, {114, 1}, {116, 1}, {118, 1}, {120, 1}, {122, 1},
{124, 1}, {126, 1}, {128, 1}, {130, 1}, {132, 1}, {134, 1},
{136, 1}, {138, 1}, {140, 1}, {142, 1}, {144, 1}, {146, 1},
{148, 1}, {150, 1}, {152, 1}, {154, 1}, {156, 1}, {158, 1},
{160, 1}, {162, 1}, {164, 1}, {166, 1}, {168, 1}, {170, 1},
{172, 1}, {174, 1}, {176, 1}, {178, 1}, {180, 1}, {182, 1},
{184, 1}, {186, 1}, {188, 1}, {190, 10},
},
PAXRecords: map[string]string{ PAXRecords: map[string]string{
"GNU.sparse.major": "1", "GNU.sparse.major": "1",
"GNU.sparse.minor": "0", "GNU.sparse.minor": "0",
...@@ -493,19 +425,18 @@ func TestReader(t *testing.T) { ...@@ -493,19 +425,18 @@ func TestReader(t *testing.T) {
ChangeTime: time.Unix(1441973436, 0), ChangeTime: time.Unix(1441973436, 0),
Format: FormatGNU, Format: FormatGNU,
}, { }, {
Name: "test2/sparse", Name: "test2/sparse",
Mode: 33188, Mode: 33188,
Uid: 1000, Uid: 1000,
Gid: 1000, Gid: 1000,
Size: 536870912, Size: 536870912,
ModTime: time.Unix(1441973427, 0), ModTime: time.Unix(1441973427, 0),
Typeflag: 'S', Typeflag: 'S',
Uname: "rawr", Uname: "rawr",
Gname: "dsnet", Gname: "dsnet",
AccessTime: time.Unix(1441991948, 0), AccessTime: time.Unix(1441991948, 0),
ChangeTime: time.Unix(1441973436, 0), ChangeTime: time.Unix(1441973436, 0),
SparseHoles: []SparseEntry{{0, 536870912}}, Format: FormatGNU,
Format: FormatGNU,
}}, }},
}, { }, {
// Matches the behavior of GNU and BSD tar utilities. // Matches the behavior of GNU and BSD tar utilities.
...@@ -621,33 +552,30 @@ func TestReader(t *testing.T) { ...@@ -621,33 +552,30 @@ func TestReader(t *testing.T) {
// Generated by Go, works on BSD tar v3.1.2 and GNU tar v.1.27.1. // Generated by Go, works on BSD tar v3.1.2 and GNU tar v.1.27.1.
file: "testdata/gnu-nil-sparse-data.tar", file: "testdata/gnu-nil-sparse-data.tar",
headers: []*Header{{ headers: []*Header{{
Name: "sparse.db", Name: "sparse.db",
Typeflag: TypeGNUSparse, Typeflag: TypeGNUSparse,
Size: 1000, Size: 1000,
ModTime: time.Unix(0, 0), ModTime: time.Unix(0, 0),
SparseHoles: []SparseEntry{{Offset: 1000, Length: 0}}, Format: FormatGNU,
Format: FormatGNU,
}}, }},
}, { }, {
// Generated by Go, works on BSD tar v3.1.2 and GNU tar v.1.27.1. // Generated by Go, works on BSD tar v3.1.2 and GNU tar v.1.27.1.
file: "testdata/gnu-nil-sparse-hole.tar", file: "testdata/gnu-nil-sparse-hole.tar",
headers: []*Header{{ headers: []*Header{{
Name: "sparse.db", Name: "sparse.db",
Typeflag: TypeGNUSparse, Typeflag: TypeGNUSparse,
Size: 1000, Size: 1000,
ModTime: time.Unix(0, 0), ModTime: time.Unix(0, 0),
SparseHoles: []SparseEntry{{Offset: 0, Length: 1000}}, Format: FormatGNU,
Format: FormatGNU,
}}, }},
}, { }, {
// Generated by Go, works on BSD tar v3.1.2 and GNU tar v.1.27.1. // Generated by Go, works on BSD tar v3.1.2 and GNU tar v.1.27.1.
file: "testdata/pax-nil-sparse-data.tar", file: "testdata/pax-nil-sparse-data.tar",
headers: []*Header{{ headers: []*Header{{
Name: "sparse.db", Name: "sparse.db",
Typeflag: TypeReg, Typeflag: TypeReg,
Size: 1000, Size: 1000,
ModTime: time.Unix(0, 0), ModTime: time.Unix(0, 0),
SparseHoles: []SparseEntry{{Offset: 1000, Length: 0}},
PAXRecords: map[string]string{ PAXRecords: map[string]string{
"size": "1512", "size": "1512",
"GNU.sparse.major": "1", "GNU.sparse.major": "1",
...@@ -661,11 +589,10 @@ func TestReader(t *testing.T) { ...@@ -661,11 +589,10 @@ func TestReader(t *testing.T) {
// Generated by Go, works on BSD tar v3.1.2 and GNU tar v.1.27.1. // Generated by Go, works on BSD tar v3.1.2 and GNU tar v.1.27.1.
file: "testdata/pax-nil-sparse-hole.tar", file: "testdata/pax-nil-sparse-hole.tar",
headers: []*Header{{ headers: []*Header{{
Name: "sparse.db", Name: "sparse.db",
Typeflag: TypeReg, Typeflag: TypeReg,
Size: 1000, Size: 1000,
ModTime: time.Unix(0, 0), ModTime: time.Unix(0, 0),
SparseHoles: []SparseEntry{{Offset: 0, Length: 1000}},
PAXRecords: map[string]string{ PAXRecords: map[string]string{
"size": "512", "size": "512",
"GNU.sparse.major": "1", "GNU.sparse.major": "1",
...@@ -935,7 +862,7 @@ func TestReadTruncation(t *testing.T) { ...@@ -935,7 +862,7 @@ func TestReadTruncation(t *testing.T) {
} }
cnt++ cnt++
if s2 == "manual" { if s2 == "manual" {
if _, err = tr.WriteTo(ioutil.Discard); err != nil { if _, err = tr.writeTo(ioutil.Discard); err != nil {
break break
} }
} }
...@@ -1123,7 +1050,7 @@ func TestReadOldGNUSparseMap(t *testing.T) { ...@@ -1123,7 +1050,7 @@ func TestReadOldGNUSparseMap(t *testing.T) {
return out return out
} }
makeSparseStrings := func(sp []SparseEntry) (out []string) { makeSparseStrings := func(sp []sparseEntry) (out []string) {
var f formatter var f formatter
for _, s := range sp { for _, s := range sp {
var b [24]byte var b [24]byte
...@@ -1377,7 +1304,7 @@ func TestReadGNUSparsePAXHeaders(t *testing.T) { ...@@ -1377,7 +1304,7 @@ func TestReadGNUSparsePAXHeaders(t *testing.T) {
inputHdrs: map[string]string{paxGNUSparseMajor: "1", paxGNUSparseMinor: "0"}, inputHdrs: map[string]string{paxGNUSparseMajor: "1", paxGNUSparseMinor: "0"},
wantMap: func() (spd sparseDatas) { wantMap: func() (spd sparseDatas) {
for i := 0; i < 100; i++ { for i := 0; i < 100; i++ {
spd = append(spd, SparseEntry{int64(i) << 30, 512}) spd = append(spd, sparseEntry{int64(i) << 30, 512})
} }
return spd return spd
}(), }(),
......
// Copyright 2017 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.
// +build linux darwin dragonfly freebsd openbsd netbsd solaris
package tar
import (
"io"
"os"
"runtime"
"syscall"
)
func init() {
sysSparseDetect = sparseDetectUnix
}
func sparseDetectUnix(f *os.File) (sph sparseHoles, err error) {
// SEEK_DATA and SEEK_HOLE originated from Solaris and support for it
// has been added to most of the other major Unix systems.
var seekData, seekHole = 3, 4 // SEEK_DATA/SEEK_HOLE from unistd.h
if runtime.GOOS == "darwin" {
// Darwin has the constants swapped, compared to all other UNIX.
seekData, seekHole = 4, 3
}
// Check for seekData/seekHole support.
// Different OS and FS may differ in the exact errno that is returned when
// there is no support. Rather than special-casing every possible errno
// representing "not supported", just assume that a non-nil error means
// that seekData/seekHole is not supported.
if _, err := f.Seek(0, seekHole); err != nil {
return nil, nil
}
// Populate the SparseHoles.
var last, pos int64 = -1, 0
for {
// Get the location of the next hole section.
if pos, err = fseek(f, pos, seekHole); pos == last || err != nil {
return sph, err
}
offset := pos
last = pos
// Get the location of the next data section.
if pos, err = fseek(f, pos, seekData); pos == last || err != nil {
return sph, err
}
length := pos - offset
last = pos
if length > 0 {
sph = append(sph, SparseEntry{offset, length})
}
}
}
func fseek(f *os.File, pos int64, whence int) (int64, error) {
pos, err := f.Seek(pos, whence)
if errno(err) == syscall.ENXIO {
// SEEK_DATA returns ENXIO when past the last data fragment,
// which makes determining the size of the last hole difficult.
pos, err = f.Seek(0, io.SeekEnd)
}
return pos, err
}
func errno(err error) error {
if perr, ok := err.(*os.PathError); ok {
return perr.Err
}
return err
}
// Copyright 2017 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.
// +build windows
package tar
import (
"os"
"syscall"
"unsafe"
)
var errInvalidFunc = syscall.Errno(1) // ERROR_INVALID_FUNCTION from WinError.h
func init() {
sysSparseDetect = sparseDetectWindows
sysSparsePunch = sparsePunchWindows
}
func sparseDetectWindows(f *os.File) (sph sparseHoles, err error) {
const queryAllocRanges = 0x000940CF // FSCTL_QUERY_ALLOCATED_RANGES from WinIoCtl.h
type allocRangeBuffer struct{ offset, length int64 } // FILE_ALLOCATED_RANGE_BUFFER from WinIoCtl.h
s, err := f.Stat()
if err != nil {
return nil, err
}
queryRange := allocRangeBuffer{0, s.Size()}
allocRanges := make([]allocRangeBuffer, 64)
// Repeatedly query for ranges until the input buffer is large enough.
var bytesReturned uint32
for {
err := syscall.DeviceIoControl(
syscall.Handle(f.Fd()), queryAllocRanges,
(*byte)(unsafe.Pointer(&queryRange)), uint32(unsafe.Sizeof(queryRange)),
(*byte)(unsafe.Pointer(&allocRanges[0])), uint32(len(allocRanges)*int(unsafe.Sizeof(allocRanges[0]))),
&bytesReturned, nil,
)
if err == syscall.ERROR_MORE_DATA {
allocRanges = make([]allocRangeBuffer, 2*len(allocRanges))
continue
}
if err == errInvalidFunc {
return nil, nil // Sparse file not supported on this FS
}
if err != nil {
return nil, err
}
break
}
n := bytesReturned / uint32(unsafe.Sizeof(allocRanges[0]))
allocRanges = append(allocRanges[:n], allocRangeBuffer{s.Size(), 0})
// Invert the data fragments into hole fragments.
var pos int64
for _, r := range allocRanges {
if r.offset > pos {
sph = append(sph, SparseEntry{pos, r.offset - pos})
}
pos = r.offset + r.length
}
return sph, nil
}
func sparsePunchWindows(f *os.File, sph sparseHoles) error {
const setSparse = 0x000900C4 // FSCTL_SET_SPARSE from WinIoCtl.h
const setZeroData = 0x000980C8 // FSCTL_SET_ZERO_DATA from WinIoCtl.h
type zeroDataInfo struct{ start, end int64 } // FILE_ZERO_DATA_INFORMATION from WinIoCtl.h
// Set the file as being sparse.
var bytesReturned uint32
devErr := syscall.DeviceIoControl(
syscall.Handle(f.Fd()), setSparse,
nil, 0, nil, 0,
&bytesReturned, nil,
)
if devErr != nil && devErr != errInvalidFunc {
return devErr
}
// Set the file to the right size.
var size int64
if len(sph) > 0 {
size = sph[len(sph)-1].endOffset()
}
if err := f.Truncate(size); err != nil {
return err
}
if devErr == errInvalidFunc {
// Sparse file not supported on this FS.
// Call sparsePunchManual since SetEndOfFile does not guarantee that
// the extended space is filled with zeros.
return sparsePunchManual(f, sph)
}
// Punch holes for all relevant fragments.
for _, s := range sph {
zdi := zeroDataInfo{s.Offset, s.endOffset()}
err := syscall.DeviceIoControl(
syscall.Handle(f.Fd()), setZeroData,
(*byte)(unsafe.Pointer(&zdi)), uint32(unsafe.Sizeof(zdi)),
nil, 0,
&bytesReturned, nil,
)
if err != nil {
return err
}
}
return nil
}
// sparsePunchManual writes zeros into each hole.
func sparsePunchManual(f *os.File, sph sparseHoles) error {
const chunkSize = 32 << 10
zbuf := make([]byte, chunkSize)
for _, s := range sph {
for pos := s.Offset; pos < s.endOffset(); pos += chunkSize {
n := min(chunkSize, s.endOffset()-pos)
if _, err := f.WriteAt(zbuf[:n], pos); err != nil {
return err
}
}
}
return nil
}
This diff is collapsed.
...@@ -10,7 +10,6 @@ import ( ...@@ -10,7 +10,6 @@ import (
"io" "io"
"path" "path"
"sort" "sort"
"strconv"
"strings" "strings"
"time" "time"
) )
...@@ -46,7 +45,7 @@ type fileWriter interface { ...@@ -46,7 +45,7 @@ type fileWriter interface {
// Flush finishes writing the current file's block padding. // Flush finishes writing the current file's block padding.
// The current file must be fully written before Flush can be called. // The current file must be fully written before Flush can be called.
// //
// Deprecated: This is unnecessary as the next call to WriteHeader or Close // This is unnecessary as the next call to WriteHeader or Close
// will implicitly flush out the file's padding. // will implicitly flush out the file's padding.
func (tw *Writer) Flush() error { func (tw *Writer) Flush() error {
if tw.err != nil { if tw.err != nil {
...@@ -120,36 +119,41 @@ func (tw *Writer) writeUSTARHeader(hdr *Header) error { ...@@ -120,36 +119,41 @@ func (tw *Writer) writeUSTARHeader(hdr *Header) error {
func (tw *Writer) writePAXHeader(hdr *Header, paxHdrs map[string]string) error { func (tw *Writer) writePAXHeader(hdr *Header, paxHdrs map[string]string) error {
realName, realSize := hdr.Name, hdr.Size realName, realSize := hdr.Name, hdr.Size
// Handle sparse files. // TODO(dsnet): Re-enable this when adding sparse support.
var spd sparseDatas // See https://golang.org/issue/22735
var spb []byte /*
if len(hdr.SparseHoles) > 0 { // Handle sparse files.
sph := append([]SparseEntry{}, hdr.SparseHoles...) // Copy sparse map var spd sparseDatas
sph = alignSparseEntries(sph, hdr.Size) var spb []byte
spd = invertSparseEntries(sph, hdr.Size) if len(hdr.SparseHoles) > 0 {
sph := append([]sparseEntry{}, hdr.SparseHoles...) // Copy sparse map
// Format the sparse map. sph = alignSparseEntries(sph, hdr.Size)
hdr.Size = 0 // Replace with encoded size spd = invertSparseEntries(sph, hdr.Size)
spb = append(strconv.AppendInt(spb, int64(len(spd)), 10), '\n')
for _, s := range spd { // Format the sparse map.
hdr.Size += s.Length hdr.Size = 0 // Replace with encoded size
spb = append(strconv.AppendInt(spb, s.Offset, 10), '\n') spb = append(strconv.AppendInt(spb, int64(len(spd)), 10), '\n')
spb = append(strconv.AppendInt(spb, s.Length, 10), '\n') for _, s := range spd {
} hdr.Size += s.Length
pad := blockPadding(int64(len(spb))) spb = append(strconv.AppendInt(spb, s.Offset, 10), '\n')
spb = append(spb, zeroBlock[:pad]...) spb = append(strconv.AppendInt(spb, s.Length, 10), '\n')
hdr.Size += int64(len(spb)) // Accounts for encoded sparse map }
pad := blockPadding(int64(len(spb)))
spb = append(spb, zeroBlock[:pad]...)
hdr.Size += int64(len(spb)) // Accounts for encoded sparse map
// Add and modify appropriate PAX records. // Add and modify appropriate PAX records.
dir, file := path.Split(realName) dir, file := path.Split(realName)
hdr.Name = path.Join(dir, "GNUSparseFile.0", file) hdr.Name = path.Join(dir, "GNUSparseFile.0", file)
paxHdrs[paxGNUSparseMajor] = "1" paxHdrs[paxGNUSparseMajor] = "1"
paxHdrs[paxGNUSparseMinor] = "0" paxHdrs[paxGNUSparseMinor] = "0"
paxHdrs[paxGNUSparseName] = realName paxHdrs[paxGNUSparseName] = realName
paxHdrs[paxGNUSparseRealSize] = strconv.FormatInt(realSize, 10) paxHdrs[paxGNUSparseRealSize] = strconv.FormatInt(realSize, 10)
paxHdrs[paxSize] = strconv.FormatInt(hdr.Size, 10) paxHdrs[paxSize] = strconv.FormatInt(hdr.Size, 10)
delete(paxHdrs, paxPath) // Recorded by paxGNUSparseName delete(paxHdrs, paxPath) // Recorded by paxGNUSparseName
} }
*/
_ = realSize
// Write PAX records to the output. // Write PAX records to the output.
isGlobal := hdr.Typeflag == TypeXGlobalHeader isGlobal := hdr.Typeflag == TypeXGlobalHeader
...@@ -197,14 +201,18 @@ func (tw *Writer) writePAXHeader(hdr *Header, paxHdrs map[string]string) error { ...@@ -197,14 +201,18 @@ func (tw *Writer) writePAXHeader(hdr *Header, paxHdrs map[string]string) error {
return err return err
} }
// Write the sparse map and setup the sparse writer if necessary. // TODO(dsnet): Re-enable this when adding sparse support.
if len(spd) > 0 { // See https://golang.org/issue/22735
// Use tw.curr since the sparse map is accounted for in hdr.Size. /*
if _, err := tw.curr.Write(spb); err != nil { // Write the sparse map and setup the sparse writer if necessary.
return err if len(spd) > 0 {
// Use tw.curr since the sparse map is accounted for in hdr.Size.
if _, err := tw.curr.Write(spb); err != nil {
return err
}
tw.curr = &sparseFileWriter{tw.curr, spd, 0}
} }
tw.curr = &sparseFileWriter{tw.curr, spd, 0} */
}
return nil return nil
} }
...@@ -235,40 +243,44 @@ func (tw *Writer) writeGNUHeader(hdr *Header) error { ...@@ -235,40 +243,44 @@ func (tw *Writer) writeGNUHeader(hdr *Header) error {
if !hdr.ChangeTime.IsZero() { if !hdr.ChangeTime.IsZero() {
f.formatNumeric(blk.GNU().ChangeTime(), hdr.ChangeTime.Unix()) f.formatNumeric(blk.GNU().ChangeTime(), hdr.ChangeTime.Unix())
} }
if hdr.Typeflag == TypeGNUSparse { // TODO(dsnet): Re-enable this when adding sparse support.
sph := append([]SparseEntry{}, hdr.SparseHoles...) // Copy sparse map // See https://golang.org/issue/22735
sph = alignSparseEntries(sph, hdr.Size) /*
spd = invertSparseEntries(sph, hdr.Size) if hdr.Typeflag == TypeGNUSparse {
sph := append([]sparseEntry{}, hdr.SparseHoles...) // Copy sparse map
// Format the sparse map. sph = alignSparseEntries(sph, hdr.Size)
formatSPD := func(sp sparseDatas, sa sparseArray) sparseDatas { spd = invertSparseEntries(sph, hdr.Size)
for i := 0; len(sp) > 0 && i < sa.MaxEntries(); i++ {
f.formatNumeric(sa.Entry(i).Offset(), sp[0].Offset) // Format the sparse map.
f.formatNumeric(sa.Entry(i).Length(), sp[0].Length) formatSPD := func(sp sparseDatas, sa sparseArray) sparseDatas {
sp = sp[1:] for i := 0; len(sp) > 0 && i < sa.MaxEntries(); i++ {
f.formatNumeric(sa.Entry(i).Offset(), sp[0].Offset)
f.formatNumeric(sa.Entry(i).Length(), sp[0].Length)
sp = sp[1:]
}
if len(sp) > 0 {
sa.IsExtended()[0] = 1
}
return sp
} }
if len(sp) > 0 { sp2 := formatSPD(spd, blk.GNU().Sparse())
sa.IsExtended()[0] = 1 for len(sp2) > 0 {
var spHdr block
sp2 = formatSPD(sp2, spHdr.Sparse())
spb = append(spb, spHdr[:]...)
} }
return sp
}
sp2 := formatSPD(spd, blk.GNU().Sparse())
for len(sp2) > 0 {
var spHdr block
sp2 = formatSPD(sp2, spHdr.Sparse())
spb = append(spb, spHdr[:]...)
}
// Update size fields in the header block. // Update size fields in the header block.
realSize := hdr.Size realSize := hdr.Size
hdr.Size = 0 // Encoded size; does not account for encoded sparse map hdr.Size = 0 // Encoded size; does not account for encoded sparse map
for _, s := range spd { for _, s := range spd {
hdr.Size += s.Length hdr.Size += s.Length
}
copy(blk.V7().Size(), zeroBlock[:]) // Reset field
f.formatNumeric(blk.V7().Size(), hdr.Size)
f.formatNumeric(blk.GNU().RealSize(), realSize)
} }
copy(blk.V7().Size(), zeroBlock[:]) // Reset field */
f.formatNumeric(blk.V7().Size(), hdr.Size)
f.formatNumeric(blk.GNU().RealSize(), realSize)
}
blk.SetFormat(FormatGNU) blk.SetFormat(FormatGNU)
if err := tw.writeRawHeader(blk, hdr.Size, hdr.Typeflag); err != nil { if err := tw.writeRawHeader(blk, hdr.Size, hdr.Typeflag); err != nil {
return err return err
...@@ -401,9 +413,6 @@ func splitUSTARPath(name string) (prefix, suffix string, ok bool) { ...@@ -401,9 +413,6 @@ func splitUSTARPath(name string) (prefix, suffix string, ok bool) {
// Write returns the error ErrWriteTooLong if more than // Write returns the error ErrWriteTooLong if more than
// Header.Size bytes are written after WriteHeader. // Header.Size bytes are written after WriteHeader.
// //
// If the current file is sparse, then the regions marked as a hole
// must be written as NUL-bytes.
//
// Calling Write on special types like TypeLink, TypeSymlink, TypeChar, // Calling Write on special types like TypeLink, TypeSymlink, TypeChar,
// TypeBlock, TypeDir, and TypeFifo returns (0, ErrWriteTooLong) regardless // TypeBlock, TypeDir, and TypeFifo returns (0, ErrWriteTooLong) regardless
// of what the Header.Size claims. // of what the Header.Size claims.
...@@ -418,14 +427,17 @@ func (tw *Writer) Write(b []byte) (int, error) { ...@@ -418,14 +427,17 @@ func (tw *Writer) Write(b []byte) (int, error) {
return n, err return n, err
} }
// ReadFrom populates the content of the current file by reading from r. // readFrom populates the content of the current file by reading from r.
// The bytes read must match the number of remaining bytes in the current file. // The bytes read must match the number of remaining bytes in the current file.
// //
// If the current file is sparse and r is an io.ReadSeeker, // If the current file is sparse and r is an io.ReadSeeker,
// then ReadFrom uses Seek to skip past holes defined in Header.SparseHoles, // then readFrom uses Seek to skip past holes defined in Header.SparseHoles,
// assuming that skipped regions are all NULs. // assuming that skipped regions are all NULs.
// This always reads the last byte to ensure r is the right size. // This always reads the last byte to ensure r is the right size.
func (tw *Writer) ReadFrom(r io.Reader) (int64, error) { //
// TODO(dsnet): Re-export this when adding sparse file support.
// See https://golang.org/issue/22735
func (tw *Writer) readFrom(r io.Reader) (int64, error) {
if tw.err != nil { if tw.err != nil {
return 0, tw.err return 0, tw.err
} }
......
...@@ -339,118 +339,122 @@ func TestWriter(t *testing.T) { ...@@ -339,118 +339,122 @@ func TestWriter(t *testing.T) {
}, nil}, }, nil},
testClose{nil}, testClose{nil},
}, },
}, { // TODO(dsnet): Re-enable this test when adding sparse support.
file: "testdata/gnu-nil-sparse-data.tar", // See https://golang.org/issue/22735
tests: []testFnc{ /*
testHeader{Header{ }, {
Typeflag: TypeGNUSparse, file: "testdata/gnu-nil-sparse-data.tar",
Name: "sparse.db", tests: []testFnc{
Size: 1000, testHeader{Header{
SparseHoles: []SparseEntry{{Offset: 1000, Length: 0}}, Typeflag: TypeGNUSparse,
}, nil}, Name: "sparse.db",
testWrite{strings.Repeat("0123456789", 100), 1000, nil}, Size: 1000,
testClose{}, SparseHoles: []sparseEntry{{Offset: 1000, Length: 0}},
}, }, nil},
}, { testWrite{strings.Repeat("0123456789", 100), 1000, nil},
file: "testdata/gnu-nil-sparse-hole.tar", testClose{},
tests: []testFnc{
testHeader{Header{
Typeflag: TypeGNUSparse,
Name: "sparse.db",
Size: 1000,
SparseHoles: []SparseEntry{{Offset: 0, Length: 1000}},
}, nil},
testWrite{strings.Repeat("\x00", 1000), 1000, nil},
testClose{},
},
}, {
file: "testdata/pax-nil-sparse-data.tar",
tests: []testFnc{
testHeader{Header{
Typeflag: TypeReg,
Name: "sparse.db",
Size: 1000,
SparseHoles: []SparseEntry{{Offset: 1000, Length: 0}},
}, nil},
testWrite{strings.Repeat("0123456789", 100), 1000, nil},
testClose{},
},
}, {
file: "testdata/pax-nil-sparse-hole.tar",
tests: []testFnc{
testHeader{Header{
Typeflag: TypeReg,
Name: "sparse.db",
Size: 1000,
SparseHoles: []SparseEntry{{Offset: 0, Length: 1000}},
}, nil},
testWrite{strings.Repeat("\x00", 1000), 1000, nil},
testClose{},
},
}, {
file: "testdata/gnu-sparse-big.tar",
tests: []testFnc{
testHeader{Header{
Typeflag: TypeGNUSparse,
Name: "gnu-sparse",
Size: 6e10,
SparseHoles: []SparseEntry{
{Offset: 0e10, Length: 1e10 - 100},
{Offset: 1e10, Length: 1e10 - 100},
{Offset: 2e10, Length: 1e10 - 100},
{Offset: 3e10, Length: 1e10 - 100},
{Offset: 4e10, Length: 1e10 - 100},
{Offset: 5e10, Length: 1e10 - 100},
}, },
}, nil}, }, {
testReadFrom{fileOps{ file: "testdata/gnu-nil-sparse-hole.tar",
int64(1e10 - blockSize), tests: []testFnc{
strings.Repeat("\x00", blockSize-100) + strings.Repeat("0123456789", 10), testHeader{Header{
int64(1e10 - blockSize), Typeflag: TypeGNUSparse,
strings.Repeat("\x00", blockSize-100) + strings.Repeat("0123456789", 10), Name: "sparse.db",
int64(1e10 - blockSize), Size: 1000,
strings.Repeat("\x00", blockSize-100) + strings.Repeat("0123456789", 10), SparseHoles: []sparseEntry{{Offset: 0, Length: 1000}},
int64(1e10 - blockSize), }, nil},
strings.Repeat("\x00", blockSize-100) + strings.Repeat("0123456789", 10), testWrite{strings.Repeat("\x00", 1000), 1000, nil},
int64(1e10 - blockSize), testClose{},
strings.Repeat("\x00", blockSize-100) + strings.Repeat("0123456789", 10),
int64(1e10 - blockSize),
strings.Repeat("\x00", blockSize-100) + strings.Repeat("0123456789", 10),
}, 6e10, nil},
testClose{nil},
},
}, {
file: "testdata/pax-sparse-big.tar",
tests: []testFnc{
testHeader{Header{
Typeflag: TypeReg,
Name: "pax-sparse",
Size: 6e10,
SparseHoles: []SparseEntry{
{Offset: 0e10, Length: 1e10 - 100},
{Offset: 1e10, Length: 1e10 - 100},
{Offset: 2e10, Length: 1e10 - 100},
{Offset: 3e10, Length: 1e10 - 100},
{Offset: 4e10, Length: 1e10 - 100},
{Offset: 5e10, Length: 1e10 - 100},
}, },
}, nil}, }, {
testReadFrom{fileOps{ file: "testdata/pax-nil-sparse-data.tar",
int64(1e10 - blockSize), tests: []testFnc{
strings.Repeat("\x00", blockSize-100) + strings.Repeat("0123456789", 10), testHeader{Header{
int64(1e10 - blockSize), Typeflag: TypeReg,
strings.Repeat("\x00", blockSize-100) + strings.Repeat("0123456789", 10), Name: "sparse.db",
int64(1e10 - blockSize), Size: 1000,
strings.Repeat("\x00", blockSize-100) + strings.Repeat("0123456789", 10), SparseHoles: []sparseEntry{{Offset: 1000, Length: 0}},
int64(1e10 - blockSize), }, nil},
strings.Repeat("\x00", blockSize-100) + strings.Repeat("0123456789", 10), testWrite{strings.Repeat("0123456789", 100), 1000, nil},
int64(1e10 - blockSize), testClose{},
strings.Repeat("\x00", blockSize-100) + strings.Repeat("0123456789", 10), },
int64(1e10 - blockSize), }, {
strings.Repeat("\x00", blockSize-100) + strings.Repeat("0123456789", 10), file: "testdata/pax-nil-sparse-hole.tar",
}, 6e10, nil}, tests: []testFnc{
testClose{nil}, testHeader{Header{
}, Typeflag: TypeReg,
Name: "sparse.db",
Size: 1000,
SparseHoles: []sparseEntry{{Offset: 0, Length: 1000}},
}, nil},
testWrite{strings.Repeat("\x00", 1000), 1000, nil},
testClose{},
},
}, {
file: "testdata/gnu-sparse-big.tar",
tests: []testFnc{
testHeader{Header{
Typeflag: TypeGNUSparse,
Name: "gnu-sparse",
Size: 6e10,
SparseHoles: []sparseEntry{
{Offset: 0e10, Length: 1e10 - 100},
{Offset: 1e10, Length: 1e10 - 100},
{Offset: 2e10, Length: 1e10 - 100},
{Offset: 3e10, Length: 1e10 - 100},
{Offset: 4e10, Length: 1e10 - 100},
{Offset: 5e10, Length: 1e10 - 100},
},
}, nil},
testReadFrom{fileOps{
int64(1e10 - blockSize),
strings.Repeat("\x00", blockSize-100) + strings.Repeat("0123456789", 10),
int64(1e10 - blockSize),
strings.Repeat("\x00", blockSize-100) + strings.Repeat("0123456789", 10),
int64(1e10 - blockSize),
strings.Repeat("\x00", blockSize-100) + strings.Repeat("0123456789", 10),
int64(1e10 - blockSize),
strings.Repeat("\x00", blockSize-100) + strings.Repeat("0123456789", 10),
int64(1e10 - blockSize),
strings.Repeat("\x00", blockSize-100) + strings.Repeat("0123456789", 10),
int64(1e10 - blockSize),
strings.Repeat("\x00", blockSize-100) + strings.Repeat("0123456789", 10),
}, 6e10, nil},
testClose{nil},
},
}, {
file: "testdata/pax-sparse-big.tar",
tests: []testFnc{
testHeader{Header{
Typeflag: TypeReg,
Name: "pax-sparse",
Size: 6e10,
SparseHoles: []sparseEntry{
{Offset: 0e10, Length: 1e10 - 100},
{Offset: 1e10, Length: 1e10 - 100},
{Offset: 2e10, Length: 1e10 - 100},
{Offset: 3e10, Length: 1e10 - 100},
{Offset: 4e10, Length: 1e10 - 100},
{Offset: 5e10, Length: 1e10 - 100},
},
}, nil},
testReadFrom{fileOps{
int64(1e10 - blockSize),
strings.Repeat("\x00", blockSize-100) + strings.Repeat("0123456789", 10),
int64(1e10 - blockSize),
strings.Repeat("\x00", blockSize-100) + strings.Repeat("0123456789", 10),
int64(1e10 - blockSize),
strings.Repeat("\x00", blockSize-100) + strings.Repeat("0123456789", 10),
int64(1e10 - blockSize),
strings.Repeat("\x00", blockSize-100) + strings.Repeat("0123456789", 10),
int64(1e10 - blockSize),
strings.Repeat("\x00", blockSize-100) + strings.Repeat("0123456789", 10),
int64(1e10 - blockSize),
strings.Repeat("\x00", blockSize-100) + strings.Repeat("0123456789", 10),
}, 6e10, nil},
testClose{nil},
},
*/
}, { }, {
file: "testdata/trailing-slash.tar", file: "testdata/trailing-slash.tar",
tests: []testFnc{ tests: []testFnc{
...@@ -487,7 +491,7 @@ func TestWriter(t *testing.T) { ...@@ -487,7 +491,7 @@ func TestWriter(t *testing.T) {
} }
case testReadFrom: case testReadFrom:
f := &testFile{ops: tf.ops} f := &testFile{ops: tf.ops}
got, err := tw.ReadFrom(f) got, err := tw.readFrom(f)
if _, ok := err.(testError); ok { if _, ok := err.(testError); ok {
t.Errorf("test %d, ReadFrom(): %v", i, err) t.Errorf("test %d, ReadFrom(): %v", i, err)
} else if got != tf.wantCnt || !equalError(err, tf.wantErr) { } else if got != tf.wantCnt || !equalError(err, tf.wantErr) {
......
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