github.com/mattn/go@v0.0.0-20171011075504-07f7db3ea99f/src/archive/tar/tar_test.go (about)

     1  // Copyright 2012 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package tar
     6  
     7  import (
     8  	"bytes"
     9  	"errors"
    10  	"fmt"
    11  	"internal/testenv"
    12  	"io"
    13  	"io/ioutil"
    14  	"math"
    15  	"os"
    16  	"path"
    17  	"path/filepath"
    18  	"reflect"
    19  	"runtime"
    20  	"strings"
    21  	"testing"
    22  	"time"
    23  )
    24  
    25  type testError struct{ error }
    26  
    27  type fileOps []interface{} // []T where T is (string | int64)
    28  
    29  // testFile is an io.ReadWriteSeeker where the IO operations performed
    30  // on it must match the list of operations in ops.
    31  type testFile struct {
    32  	ops fileOps
    33  	pos int64
    34  }
    35  
    36  func (f *testFile) Read(b []byte) (int, error) {
    37  	if len(b) == 0 {
    38  		return 0, nil
    39  	}
    40  	if len(f.ops) == 0 {
    41  		return 0, io.EOF
    42  	}
    43  	s, ok := f.ops[0].(string)
    44  	if !ok {
    45  		return 0, errors.New("unexpected Read operation")
    46  	}
    47  
    48  	n := copy(b, s)
    49  	if len(s) > n {
    50  		f.ops[0] = s[n:]
    51  	} else {
    52  		f.ops = f.ops[1:]
    53  	}
    54  	f.pos += int64(len(b))
    55  	return n, nil
    56  }
    57  
    58  func (f *testFile) Write(b []byte) (int, error) {
    59  	if len(b) == 0 {
    60  		return 0, nil
    61  	}
    62  	if len(f.ops) == 0 {
    63  		return 0, errors.New("unexpected Write operation")
    64  	}
    65  	s, ok := f.ops[0].(string)
    66  	if !ok {
    67  		return 0, errors.New("unexpected Write operation")
    68  	}
    69  
    70  	if !strings.HasPrefix(s, string(b)) {
    71  		return 0, testError{fmt.Errorf("got Write(%q), want Write(%q)", b, s)}
    72  	}
    73  	if len(s) > len(b) {
    74  		f.ops[0] = s[len(b):]
    75  	} else {
    76  		f.ops = f.ops[1:]
    77  	}
    78  	f.pos += int64(len(b))
    79  	return len(b), nil
    80  }
    81  
    82  func (f *testFile) Seek(pos int64, whence int) (int64, error) {
    83  	if pos == 0 && whence == io.SeekCurrent {
    84  		return f.pos, nil
    85  	}
    86  	if len(f.ops) == 0 {
    87  		return 0, errors.New("unexpected Seek operation")
    88  	}
    89  	s, ok := f.ops[0].(int64)
    90  	if !ok {
    91  		return 0, errors.New("unexpected Seek operation")
    92  	}
    93  
    94  	if s != pos || whence != io.SeekCurrent {
    95  		return 0, testError{fmt.Errorf("got Seek(%d, %d), want Seek(%d, %d)", pos, whence, s, io.SeekCurrent)}
    96  	}
    97  	f.pos += s
    98  	f.ops = f.ops[1:]
    99  	return f.pos, nil
   100  }
   101  
   102  func equalSparseEntries(x, y []SparseEntry) bool {
   103  	return (len(x) == 0 && len(y) == 0) || reflect.DeepEqual(x, y)
   104  }
   105  
   106  func TestSparseEntries(t *testing.T) {
   107  	vectors := []struct {
   108  		in   []SparseEntry
   109  		size int64
   110  
   111  		wantValid    bool          // Result of validateSparseEntries
   112  		wantAligned  []SparseEntry // Result of alignSparseEntries
   113  		wantInverted []SparseEntry // Result of invertSparseEntries
   114  	}{{
   115  		in: []SparseEntry{}, size: 0,
   116  		wantValid:    true,
   117  		wantInverted: []SparseEntry{{0, 0}},
   118  	}, {
   119  		in: []SparseEntry{}, size: 5000,
   120  		wantValid:    true,
   121  		wantInverted: []SparseEntry{{0, 5000}},
   122  	}, {
   123  		in: []SparseEntry{{0, 5000}}, size: 5000,
   124  		wantValid:    true,
   125  		wantAligned:  []SparseEntry{{0, 5000}},
   126  		wantInverted: []SparseEntry{{5000, 0}},
   127  	}, {
   128  		in: []SparseEntry{{1000, 4000}}, size: 5000,
   129  		wantValid:    true,
   130  		wantAligned:  []SparseEntry{{1024, 3976}},
   131  		wantInverted: []SparseEntry{{0, 1000}, {5000, 0}},
   132  	}, {
   133  		in: []SparseEntry{{0, 3000}}, size: 5000,
   134  		wantValid:    true,
   135  		wantAligned:  []SparseEntry{{0, 2560}},
   136  		wantInverted: []SparseEntry{{3000, 2000}},
   137  	}, {
   138  		in: []SparseEntry{{3000, 2000}}, size: 5000,
   139  		wantValid:    true,
   140  		wantAligned:  []SparseEntry{{3072, 1928}},
   141  		wantInverted: []SparseEntry{{0, 3000}, {5000, 0}},
   142  	}, {
   143  		in: []SparseEntry{{2000, 2000}}, size: 5000,
   144  		wantValid:    true,
   145  		wantAligned:  []SparseEntry{{2048, 1536}},
   146  		wantInverted: []SparseEntry{{0, 2000}, {4000, 1000}},
   147  	}, {
   148  		in: []SparseEntry{{0, 2000}, {8000, 2000}}, size: 10000,
   149  		wantValid:    true,
   150  		wantAligned:  []SparseEntry{{0, 1536}, {8192, 1808}},
   151  		wantInverted: []SparseEntry{{2000, 6000}, {10000, 0}},
   152  	}, {
   153  		in: []SparseEntry{{0, 2000}, {2000, 2000}, {4000, 0}, {4000, 3000}, {7000, 1000}, {8000, 0}, {8000, 2000}}, size: 10000,
   154  		wantValid:    true,
   155  		wantAligned:  []SparseEntry{{0, 1536}, {2048, 1536}, {4096, 2560}, {7168, 512}, {8192, 1808}},
   156  		wantInverted: []SparseEntry{{10000, 0}},
   157  	}, {
   158  		in: []SparseEntry{{0, 0}, {1000, 0}, {2000, 0}, {3000, 0}, {4000, 0}, {5000, 0}}, size: 5000,
   159  		wantValid:    true,
   160  		wantInverted: []SparseEntry{{0, 5000}},
   161  	}, {
   162  		in: []SparseEntry{{1, 0}}, size: 0,
   163  		wantValid: false,
   164  	}, {
   165  		in: []SparseEntry{{-1, 0}}, size: 100,
   166  		wantValid: false,
   167  	}, {
   168  		in: []SparseEntry{{0, -1}}, size: 100,
   169  		wantValid: false,
   170  	}, {
   171  		in: []SparseEntry{{0, 0}}, size: -100,
   172  		wantValid: false,
   173  	}, {
   174  		in: []SparseEntry{{math.MaxInt64, 3}, {6, -5}}, size: 35,
   175  		wantValid: false,
   176  	}, {
   177  		in: []SparseEntry{{1, 3}, {6, -5}}, size: 35,
   178  		wantValid: false,
   179  	}, {
   180  		in: []SparseEntry{{math.MaxInt64, math.MaxInt64}}, size: math.MaxInt64,
   181  		wantValid: false,
   182  	}, {
   183  		in: []SparseEntry{{3, 3}}, size: 5,
   184  		wantValid: false,
   185  	}, {
   186  		in: []SparseEntry{{2, 0}, {1, 0}, {0, 0}}, size: 3,
   187  		wantValid: false,
   188  	}, {
   189  		in: []SparseEntry{{1, 3}, {2, 2}}, size: 10,
   190  		wantValid: false,
   191  	}}
   192  
   193  	for i, v := range vectors {
   194  		gotValid := validateSparseEntries(v.in, v.size)
   195  		if gotValid != v.wantValid {
   196  			t.Errorf("test %d, validateSparseEntries() = %v, want %v", i, gotValid, v.wantValid)
   197  		}
   198  		if !v.wantValid {
   199  			continue
   200  		}
   201  		gotAligned := alignSparseEntries(append([]SparseEntry{}, v.in...), v.size)
   202  		if !equalSparseEntries(gotAligned, v.wantAligned) {
   203  			t.Errorf("test %d, alignSparseEntries():\ngot  %v\nwant %v", i, gotAligned, v.wantAligned)
   204  		}
   205  		gotInverted := invertSparseEntries(append([]SparseEntry{}, v.in...), v.size)
   206  		if !equalSparseEntries(gotInverted, v.wantInverted) {
   207  			t.Errorf("test %d, inverseSparseEntries():\ngot  %v\nwant %v", i, gotInverted, v.wantInverted)
   208  		}
   209  	}
   210  }
   211  
   212  func TestFileInfoHeader(t *testing.T) {
   213  	fi, err := os.Stat("testdata/small.txt")
   214  	if err != nil {
   215  		t.Fatal(err)
   216  	}
   217  	h, err := FileInfoHeader(fi, "")
   218  	if err != nil {
   219  		t.Fatalf("FileInfoHeader: %v", err)
   220  	}
   221  	if g, e := h.Name, "small.txt"; g != e {
   222  		t.Errorf("Name = %q; want %q", g, e)
   223  	}
   224  	if g, e := h.Mode, int64(fi.Mode().Perm()); g != e {
   225  		t.Errorf("Mode = %#o; want %#o", g, e)
   226  	}
   227  	if g, e := h.Size, int64(5); g != e {
   228  		t.Errorf("Size = %v; want %v", g, e)
   229  	}
   230  	if g, e := h.ModTime, fi.ModTime(); !g.Equal(e) {
   231  		t.Errorf("ModTime = %v; want %v", g, e)
   232  	}
   233  	// FileInfoHeader should error when passing nil FileInfo
   234  	if _, err := FileInfoHeader(nil, ""); err == nil {
   235  		t.Fatalf("Expected error when passing nil to FileInfoHeader")
   236  	}
   237  }
   238  
   239  func TestFileInfoHeaderDir(t *testing.T) {
   240  	fi, err := os.Stat("testdata")
   241  	if err != nil {
   242  		t.Fatal(err)
   243  	}
   244  	h, err := FileInfoHeader(fi, "")
   245  	if err != nil {
   246  		t.Fatalf("FileInfoHeader: %v", err)
   247  	}
   248  	if g, e := h.Name, "testdata/"; g != e {
   249  		t.Errorf("Name = %q; want %q", g, e)
   250  	}
   251  	// Ignoring c_ISGID for golang.org/issue/4867
   252  	if g, e := h.Mode&^c_ISGID, int64(fi.Mode().Perm()); g != e {
   253  		t.Errorf("Mode = %#o; want %#o", g, e)
   254  	}
   255  	if g, e := h.Size, int64(0); g != e {
   256  		t.Errorf("Size = %v; want %v", g, e)
   257  	}
   258  	if g, e := h.ModTime, fi.ModTime(); !g.Equal(e) {
   259  		t.Errorf("ModTime = %v; want %v", g, e)
   260  	}
   261  }
   262  
   263  func TestFileInfoHeaderSymlink(t *testing.T) {
   264  	testenv.MustHaveSymlink(t)
   265  
   266  	tmpdir, err := ioutil.TempDir("", "TestFileInfoHeaderSymlink")
   267  	if err != nil {
   268  		t.Fatal(err)
   269  	}
   270  	defer os.RemoveAll(tmpdir)
   271  
   272  	link := filepath.Join(tmpdir, "link")
   273  	target := tmpdir
   274  	err = os.Symlink(target, link)
   275  	if err != nil {
   276  		t.Fatal(err)
   277  	}
   278  	fi, err := os.Lstat(link)
   279  	if err != nil {
   280  		t.Fatal(err)
   281  	}
   282  
   283  	h, err := FileInfoHeader(fi, target)
   284  	if err != nil {
   285  		t.Fatal(err)
   286  	}
   287  	if g, e := h.Name, fi.Name(); g != e {
   288  		t.Errorf("Name = %q; want %q", g, e)
   289  	}
   290  	if g, e := h.Linkname, target; g != e {
   291  		t.Errorf("Linkname = %q; want %q", g, e)
   292  	}
   293  	if g, e := h.Typeflag, byte(TypeSymlink); g != e {
   294  		t.Errorf("Typeflag = %v; want %v", g, e)
   295  	}
   296  }
   297  
   298  func TestRoundTrip(t *testing.T) {
   299  	data := []byte("some file contents")
   300  
   301  	var b bytes.Buffer
   302  	tw := NewWriter(&b)
   303  	hdr := &Header{
   304  		Name:       "file.txt",
   305  		Uid:        1 << 21, // Too big for 8 octal digits
   306  		Size:       int64(len(data)),
   307  		ModTime:    time.Now().Round(time.Second),
   308  		PAXRecords: map[string]string{"uid": "2097152"},
   309  		Format:     FormatPAX,
   310  	}
   311  	if err := tw.WriteHeader(hdr); err != nil {
   312  		t.Fatalf("tw.WriteHeader: %v", err)
   313  	}
   314  	if _, err := tw.Write(data); err != nil {
   315  		t.Fatalf("tw.Write: %v", err)
   316  	}
   317  	if err := tw.Close(); err != nil {
   318  		t.Fatalf("tw.Close: %v", err)
   319  	}
   320  
   321  	// Read it back.
   322  	tr := NewReader(&b)
   323  	rHdr, err := tr.Next()
   324  	if err != nil {
   325  		t.Fatalf("tr.Next: %v", err)
   326  	}
   327  	if !reflect.DeepEqual(rHdr, hdr) {
   328  		t.Errorf("Header mismatch.\n got %+v\nwant %+v", rHdr, hdr)
   329  	}
   330  	rData, err := ioutil.ReadAll(tr)
   331  	if err != nil {
   332  		t.Fatalf("Read: %v", err)
   333  	}
   334  	if !bytes.Equal(rData, data) {
   335  		t.Errorf("Data mismatch.\n got %q\nwant %q", rData, data)
   336  	}
   337  }
   338  
   339  type headerRoundTripTest struct {
   340  	h  *Header
   341  	fm os.FileMode
   342  }
   343  
   344  func TestHeaderRoundTrip(t *testing.T) {
   345  	vectors := []headerRoundTripTest{{
   346  		// regular file.
   347  		h: &Header{
   348  			Name:     "test.txt",
   349  			Mode:     0644,
   350  			Size:     12,
   351  			ModTime:  time.Unix(1360600916, 0),
   352  			Typeflag: TypeReg,
   353  		},
   354  		fm: 0644,
   355  	}, {
   356  		// symbolic link.
   357  		h: &Header{
   358  			Name:     "link.txt",
   359  			Mode:     0777,
   360  			Size:     0,
   361  			ModTime:  time.Unix(1360600852, 0),
   362  			Typeflag: TypeSymlink,
   363  		},
   364  		fm: 0777 | os.ModeSymlink,
   365  	}, {
   366  		// character device node.
   367  		h: &Header{
   368  			Name:     "dev/null",
   369  			Mode:     0666,
   370  			Size:     0,
   371  			ModTime:  time.Unix(1360578951, 0),
   372  			Typeflag: TypeChar,
   373  		},
   374  		fm: 0666 | os.ModeDevice | os.ModeCharDevice,
   375  	}, {
   376  		// block device node.
   377  		h: &Header{
   378  			Name:     "dev/sda",
   379  			Mode:     0660,
   380  			Size:     0,
   381  			ModTime:  time.Unix(1360578954, 0),
   382  			Typeflag: TypeBlock,
   383  		},
   384  		fm: 0660 | os.ModeDevice,
   385  	}, {
   386  		// directory.
   387  		h: &Header{
   388  			Name:     "dir/",
   389  			Mode:     0755,
   390  			Size:     0,
   391  			ModTime:  time.Unix(1360601116, 0),
   392  			Typeflag: TypeDir,
   393  		},
   394  		fm: 0755 | os.ModeDir,
   395  	}, {
   396  		// fifo node.
   397  		h: &Header{
   398  			Name:     "dev/initctl",
   399  			Mode:     0600,
   400  			Size:     0,
   401  			ModTime:  time.Unix(1360578949, 0),
   402  			Typeflag: TypeFifo,
   403  		},
   404  		fm: 0600 | os.ModeNamedPipe,
   405  	}, {
   406  		// setuid.
   407  		h: &Header{
   408  			Name:     "bin/su",
   409  			Mode:     0755 | c_ISUID,
   410  			Size:     23232,
   411  			ModTime:  time.Unix(1355405093, 0),
   412  			Typeflag: TypeReg,
   413  		},
   414  		fm: 0755 | os.ModeSetuid,
   415  	}, {
   416  		// setguid.
   417  		h: &Header{
   418  			Name:     "group.txt",
   419  			Mode:     0750 | c_ISGID,
   420  			Size:     0,
   421  			ModTime:  time.Unix(1360602346, 0),
   422  			Typeflag: TypeReg,
   423  		},
   424  		fm: 0750 | os.ModeSetgid,
   425  	}, {
   426  		// sticky.
   427  		h: &Header{
   428  			Name:     "sticky.txt",
   429  			Mode:     0600 | c_ISVTX,
   430  			Size:     7,
   431  			ModTime:  time.Unix(1360602540, 0),
   432  			Typeflag: TypeReg,
   433  		},
   434  		fm: 0600 | os.ModeSticky,
   435  	}, {
   436  		// hard link.
   437  		h: &Header{
   438  			Name:     "hard.txt",
   439  			Mode:     0644,
   440  			Size:     0,
   441  			Linkname: "file.txt",
   442  			ModTime:  time.Unix(1360600916, 0),
   443  			Typeflag: TypeLink,
   444  		},
   445  		fm: 0644,
   446  	}, {
   447  		// More information.
   448  		h: &Header{
   449  			Name:     "info.txt",
   450  			Mode:     0600,
   451  			Size:     0,
   452  			Uid:      1000,
   453  			Gid:      1000,
   454  			ModTime:  time.Unix(1360602540, 0),
   455  			Uname:    "slartibartfast",
   456  			Gname:    "users",
   457  			Typeflag: TypeReg,
   458  		},
   459  		fm: 0600,
   460  	}}
   461  
   462  	for i, v := range vectors {
   463  		fi := v.h.FileInfo()
   464  		h2, err := FileInfoHeader(fi, "")
   465  		if err != nil {
   466  			t.Error(err)
   467  			continue
   468  		}
   469  		if strings.Contains(fi.Name(), "/") {
   470  			t.Errorf("FileInfo of %q contains slash: %q", v.h.Name, fi.Name())
   471  		}
   472  		name := path.Base(v.h.Name)
   473  		if fi.IsDir() {
   474  			name += "/"
   475  		}
   476  		if got, want := h2.Name, name; got != want {
   477  			t.Errorf("i=%d: Name: got %v, want %v", i, got, want)
   478  		}
   479  		if got, want := h2.Size, v.h.Size; got != want {
   480  			t.Errorf("i=%d: Size: got %v, want %v", i, got, want)
   481  		}
   482  		if got, want := h2.Uid, v.h.Uid; got != want {
   483  			t.Errorf("i=%d: Uid: got %d, want %d", i, got, want)
   484  		}
   485  		if got, want := h2.Gid, v.h.Gid; got != want {
   486  			t.Errorf("i=%d: Gid: got %d, want %d", i, got, want)
   487  		}
   488  		if got, want := h2.Uname, v.h.Uname; got != want {
   489  			t.Errorf("i=%d: Uname: got %q, want %q", i, got, want)
   490  		}
   491  		if got, want := h2.Gname, v.h.Gname; got != want {
   492  			t.Errorf("i=%d: Gname: got %q, want %q", i, got, want)
   493  		}
   494  		if got, want := h2.Linkname, v.h.Linkname; got != want {
   495  			t.Errorf("i=%d: Linkname: got %v, want %v", i, got, want)
   496  		}
   497  		if got, want := h2.Typeflag, v.h.Typeflag; got != want {
   498  			t.Logf("%#v %#v", v.h, fi.Sys())
   499  			t.Errorf("i=%d: Typeflag: got %q, want %q", i, got, want)
   500  		}
   501  		if got, want := h2.Mode, v.h.Mode; got != want {
   502  			t.Errorf("i=%d: Mode: got %o, want %o", i, got, want)
   503  		}
   504  		if got, want := fi.Mode(), v.fm; got != want {
   505  			t.Errorf("i=%d: fi.Mode: got %o, want %o", i, got, want)
   506  		}
   507  		if got, want := h2.AccessTime, v.h.AccessTime; got != want {
   508  			t.Errorf("i=%d: AccessTime: got %v, want %v", i, got, want)
   509  		}
   510  		if got, want := h2.ChangeTime, v.h.ChangeTime; got != want {
   511  			t.Errorf("i=%d: ChangeTime: got %v, want %v", i, got, want)
   512  		}
   513  		if got, want := h2.ModTime, v.h.ModTime; got != want {
   514  			t.Errorf("i=%d: ModTime: got %v, want %v", i, got, want)
   515  		}
   516  		if sysh, ok := fi.Sys().(*Header); !ok || sysh != v.h {
   517  			t.Errorf("i=%d: Sys didn't return original *Header", i)
   518  		}
   519  	}
   520  }
   521  
   522  func TestHeaderAllowedFormats(t *testing.T) {
   523  	vectors := []struct {
   524  		header  *Header           // Input header
   525  		paxHdrs map[string]string // Expected PAX headers that may be needed
   526  		formats Format            // Expected formats that can encode the header
   527  	}{{
   528  		header:  &Header{},
   529  		formats: FormatUSTAR | FormatPAX | FormatGNU,
   530  	}, {
   531  		header:  &Header{Size: 077777777777},
   532  		formats: FormatUSTAR | FormatPAX | FormatGNU,
   533  	}, {
   534  		header:  &Header{Size: 077777777777, Format: FormatUSTAR},
   535  		formats: FormatUSTAR,
   536  	}, {
   537  		header:  &Header{Size: 077777777777, Format: FormatPAX},
   538  		formats: FormatUSTAR | FormatPAX,
   539  	}, {
   540  		header:  &Header{Size: 077777777777, Format: FormatGNU},
   541  		formats: FormatGNU,
   542  	}, {
   543  		header:  &Header{Size: 077777777777 + 1},
   544  		paxHdrs: map[string]string{paxSize: "8589934592"},
   545  		formats: FormatPAX | FormatGNU,
   546  	}, {
   547  		header:  &Header{Size: 077777777777 + 1, Format: FormatPAX},
   548  		paxHdrs: map[string]string{paxSize: "8589934592"},
   549  		formats: FormatPAX,
   550  	}, {
   551  		header:  &Header{Size: 077777777777 + 1, Format: FormatGNU},
   552  		paxHdrs: map[string]string{paxSize: "8589934592"},
   553  		formats: FormatGNU,
   554  	}, {
   555  		header:  &Header{Mode: 07777777},
   556  		formats: FormatUSTAR | FormatPAX | FormatGNU,
   557  	}, {
   558  		header:  &Header{Mode: 07777777 + 1},
   559  		formats: FormatGNU,
   560  	}, {
   561  		header:  &Header{Devmajor: -123},
   562  		formats: FormatGNU,
   563  	}, {
   564  		header:  &Header{Devmajor: 1<<56 - 1},
   565  		formats: FormatGNU,
   566  	}, {
   567  		header:  &Header{Devmajor: 1 << 56},
   568  		formats: FormatUnknown,
   569  	}, {
   570  		header:  &Header{Devmajor: -1 << 56},
   571  		formats: FormatGNU,
   572  	}, {
   573  		header:  &Header{Devmajor: -1<<56 - 1},
   574  		formats: FormatUnknown,
   575  	}, {
   576  		header:  &Header{Name: "用戶名", Devmajor: -1 << 56},
   577  		formats: FormatGNU,
   578  	}, {
   579  		header:  &Header{Size: math.MaxInt64},
   580  		paxHdrs: map[string]string{paxSize: "9223372036854775807"},
   581  		formats: FormatPAX | FormatGNU,
   582  	}, {
   583  		header:  &Header{Size: math.MinInt64},
   584  		paxHdrs: map[string]string{paxSize: "-9223372036854775808"},
   585  		formats: FormatUnknown,
   586  	}, {
   587  		header:  &Header{Uname: "0123456789abcdef0123456789abcdef"},
   588  		formats: FormatUSTAR | FormatPAX | FormatGNU,
   589  	}, {
   590  		header:  &Header{Uname: "0123456789abcdef0123456789abcdefx"},
   591  		paxHdrs: map[string]string{paxUname: "0123456789abcdef0123456789abcdefx"},
   592  		formats: FormatPAX,
   593  	}, {
   594  		header:  &Header{Name: "foobar"},
   595  		formats: FormatUSTAR | FormatPAX | FormatGNU,
   596  	}, {
   597  		header:  &Header{Name: strings.Repeat("a", nameSize)},
   598  		formats: FormatUSTAR | FormatPAX | FormatGNU,
   599  	}, {
   600  		header:  &Header{Name: strings.Repeat("a", nameSize+1)},
   601  		paxHdrs: map[string]string{paxPath: strings.Repeat("a", nameSize+1)},
   602  		formats: FormatPAX | FormatGNU,
   603  	}, {
   604  		header:  &Header{Linkname: "用戶名"},
   605  		paxHdrs: map[string]string{paxLinkpath: "用戶名"},
   606  		formats: FormatPAX | FormatGNU,
   607  	}, {
   608  		header:  &Header{Linkname: strings.Repeat("用戶名\x00", nameSize)},
   609  		paxHdrs: map[string]string{paxLinkpath: strings.Repeat("用戶名\x00", nameSize)},
   610  		formats: FormatUnknown,
   611  	}, {
   612  		header:  &Header{Linkname: "\x00hello"},
   613  		paxHdrs: map[string]string{paxLinkpath: "\x00hello"},
   614  		formats: FormatUnknown,
   615  	}, {
   616  		header:  &Header{Uid: 07777777},
   617  		formats: FormatUSTAR | FormatPAX | FormatGNU,
   618  	}, {
   619  		header:  &Header{Uid: 07777777 + 1},
   620  		paxHdrs: map[string]string{paxUid: "2097152"},
   621  		formats: FormatPAX | FormatGNU,
   622  	}, {
   623  		header:  &Header{Xattrs: nil},
   624  		formats: FormatUSTAR | FormatPAX | FormatGNU,
   625  	}, {
   626  		header:  &Header{Xattrs: map[string]string{"foo": "bar"}},
   627  		paxHdrs: map[string]string{paxSchilyXattr + "foo": "bar"},
   628  		formats: FormatPAX,
   629  	}, {
   630  		header:  &Header{Xattrs: map[string]string{"foo": "bar"}, Format: FormatGNU},
   631  		paxHdrs: map[string]string{paxSchilyXattr + "foo": "bar"},
   632  		formats: FormatUnknown,
   633  	}, {
   634  		header:  &Header{Xattrs: map[string]string{"用戶名": "\x00hello"}},
   635  		paxHdrs: map[string]string{paxSchilyXattr + "用戶名": "\x00hello"},
   636  		formats: FormatPAX,
   637  	}, {
   638  		header:  &Header{Xattrs: map[string]string{"foo=bar": "baz"}},
   639  		formats: FormatUnknown,
   640  	}, {
   641  		header:  &Header{Xattrs: map[string]string{"foo": ""}},
   642  		paxHdrs: map[string]string{paxSchilyXattr + "foo": ""},
   643  		formats: FormatPAX,
   644  	}, {
   645  		header:  &Header{ModTime: time.Unix(0, 0)},
   646  		formats: FormatUSTAR | FormatPAX | FormatGNU,
   647  	}, {
   648  		header:  &Header{ModTime: time.Unix(077777777777, 0)},
   649  		formats: FormatUSTAR | FormatPAX | FormatGNU,
   650  	}, {
   651  		header:  &Header{ModTime: time.Unix(077777777777+1, 0)},
   652  		paxHdrs: map[string]string{paxMtime: "8589934592"},
   653  		formats: FormatPAX | FormatGNU,
   654  	}, {
   655  		header:  &Header{ModTime: time.Unix(math.MaxInt64, 0)},
   656  		paxHdrs: map[string]string{paxMtime: "9223372036854775807"},
   657  		formats: FormatPAX | FormatGNU,
   658  	}, {
   659  		header:  &Header{ModTime: time.Unix(math.MaxInt64, 0), Format: FormatUSTAR},
   660  		paxHdrs: map[string]string{paxMtime: "9223372036854775807"},
   661  		formats: FormatUnknown,
   662  	}, {
   663  		header:  &Header{ModTime: time.Unix(-1, 0)},
   664  		paxHdrs: map[string]string{paxMtime: "-1"},
   665  		formats: FormatPAX | FormatGNU,
   666  	}, {
   667  		header:  &Header{ModTime: time.Unix(1, 500)},
   668  		paxHdrs: map[string]string{paxMtime: "1.0000005"},
   669  		formats: FormatUSTAR | FormatPAX | FormatGNU,
   670  	}, {
   671  		header:  &Header{ModTime: time.Unix(1, 0)},
   672  		formats: FormatUSTAR | FormatPAX | FormatGNU,
   673  	}, {
   674  		header:  &Header{ModTime: time.Unix(1, 0), Format: FormatPAX},
   675  		formats: FormatUSTAR | FormatPAX,
   676  	}, {
   677  		header:  &Header{ModTime: time.Unix(1, 500), Format: FormatUSTAR},
   678  		paxHdrs: map[string]string{paxMtime: "1.0000005"},
   679  		formats: FormatUSTAR,
   680  	}, {
   681  		header:  &Header{ModTime: time.Unix(1, 500), Format: FormatPAX},
   682  		paxHdrs: map[string]string{paxMtime: "1.0000005"},
   683  		formats: FormatPAX,
   684  	}, {
   685  		header:  &Header{ModTime: time.Unix(1, 500), Format: FormatGNU},
   686  		paxHdrs: map[string]string{paxMtime: "1.0000005"},
   687  		formats: FormatGNU,
   688  	}, {
   689  		header:  &Header{ModTime: time.Unix(-1, 500)},
   690  		paxHdrs: map[string]string{paxMtime: "-0.9999995"},
   691  		formats: FormatPAX | FormatGNU,
   692  	}, {
   693  		header:  &Header{ModTime: time.Unix(-1, 500), Format: FormatGNU},
   694  		paxHdrs: map[string]string{paxMtime: "-0.9999995"},
   695  		formats: FormatGNU,
   696  	}, {
   697  		header:  &Header{AccessTime: time.Unix(0, 0)},
   698  		paxHdrs: map[string]string{paxAtime: "0"},
   699  		formats: FormatPAX | FormatGNU,
   700  	}, {
   701  		header:  &Header{AccessTime: time.Unix(0, 0), Format: FormatUSTAR},
   702  		paxHdrs: map[string]string{paxAtime: "0"},
   703  		formats: FormatUnknown,
   704  	}, {
   705  		header:  &Header{AccessTime: time.Unix(0, 0), Format: FormatPAX},
   706  		paxHdrs: map[string]string{paxAtime: "0"},
   707  		formats: FormatPAX,
   708  	}, {
   709  		header:  &Header{AccessTime: time.Unix(0, 0), Format: FormatGNU},
   710  		paxHdrs: map[string]string{paxAtime: "0"},
   711  		formats: FormatGNU,
   712  	}, {
   713  		header:  &Header{AccessTime: time.Unix(-123, 0)},
   714  		paxHdrs: map[string]string{paxAtime: "-123"},
   715  		formats: FormatPAX | FormatGNU,
   716  	}, {
   717  		header:  &Header{AccessTime: time.Unix(-123, 0), Format: FormatPAX},
   718  		paxHdrs: map[string]string{paxAtime: "-123"},
   719  		formats: FormatPAX,
   720  	}, {
   721  		header:  &Header{ChangeTime: time.Unix(123, 456)},
   722  		paxHdrs: map[string]string{paxCtime: "123.000000456"},
   723  		formats: FormatPAX | FormatGNU,
   724  	}, {
   725  		header:  &Header{ChangeTime: time.Unix(123, 456), Format: FormatUSTAR},
   726  		paxHdrs: map[string]string{paxCtime: "123.000000456"},
   727  		formats: FormatUnknown,
   728  	}, {
   729  		header:  &Header{ChangeTime: time.Unix(123, 456), Format: FormatGNU},
   730  		paxHdrs: map[string]string{paxCtime: "123.000000456"},
   731  		formats: FormatGNU,
   732  	}, {
   733  		header:  &Header{ChangeTime: time.Unix(123, 456), Format: FormatPAX},
   734  		paxHdrs: map[string]string{paxCtime: "123.000000456"},
   735  		formats: FormatPAX,
   736  	}, {
   737  		header:  &Header{Name: "sparse.db", Size: 1000, SparseHoles: []SparseEntry{{0, 500}}},
   738  		formats: FormatPAX,
   739  	}, {
   740  		header:  &Header{Name: "sparse.db", Size: 1000, Typeflag: TypeGNUSparse, SparseHoles: []SparseEntry{{0, 500}}},
   741  		formats: FormatGNU,
   742  	}, {
   743  		header:  &Header{Name: "sparse.db", Size: 1000, SparseHoles: []SparseEntry{{0, 500}}, Format: FormatGNU},
   744  		formats: FormatUnknown,
   745  	}, {
   746  		header:  &Header{Name: "sparse.db", Size: 1000, Typeflag: TypeGNUSparse, SparseHoles: []SparseEntry{{0, 500}}, Format: FormatPAX},
   747  		formats: FormatUnknown,
   748  	}, {
   749  		header:  &Header{Name: "sparse.db", Size: 1000, SparseHoles: []SparseEntry{{0, 500}}, Format: FormatUSTAR},
   750  		formats: FormatUnknown,
   751  	}, {
   752  		header:  &Header{Name: "foo/", Typeflag: TypeDir},
   753  		formats: FormatUSTAR | FormatPAX | FormatGNU,
   754  	}, {
   755  		header:  &Header{Name: "foo/", Typeflag: TypeReg},
   756  		formats: FormatUnknown,
   757  	}, {
   758  		header:  &Header{Name: "foo/", Typeflag: TypeSymlink},
   759  		formats: FormatUSTAR | FormatPAX | FormatGNU,
   760  	}}
   761  
   762  	for i, v := range vectors {
   763  		formats, paxHdrs, err := v.header.allowedFormats()
   764  		if formats != v.formats {
   765  			t.Errorf("test %d, allowedFormats(): got %v, want %v", i, formats, v.formats)
   766  		}
   767  		if formats&FormatPAX > 0 && !reflect.DeepEqual(paxHdrs, v.paxHdrs) && !(len(paxHdrs) == 0 && len(v.paxHdrs) == 0) {
   768  			t.Errorf("test %d, allowedFormats():\ngot  %v\nwant %s", i, paxHdrs, v.paxHdrs)
   769  		}
   770  		if (formats != FormatUnknown) && (err != nil) {
   771  			t.Errorf("test %d, unexpected error: %v", i, err)
   772  		}
   773  		if (formats == FormatUnknown) && (err == nil) {
   774  			t.Errorf("test %d, got nil-error, want non-nil error", i)
   775  		}
   776  	}
   777  }
   778  
   779  func TestSparseFiles(t *testing.T) {
   780  	if runtime.GOOS == "plan9" {
   781  		t.Skip("skipping test on plan9; see https://golang.org/issue/21977")
   782  	}
   783  	// Only perform the tests for hole-detection on the builders,
   784  	// where we have greater control over the filesystem.
   785  	sparseSupport := testenv.Builder() != ""
   786  	switch runtime.GOOS + "-" + runtime.GOARCH {
   787  	case "linux-amd64", "linux-386", "windows-amd64", "windows-386":
   788  	default:
   789  		sparseSupport = false
   790  	}
   791  
   792  	vectors := []struct {
   793  		label     string
   794  		sparseMap sparseHoles
   795  	}{
   796  		{"EmptyFile", sparseHoles{{0, 0}}},
   797  		{"BigData", sparseHoles{{1e6, 0}}},
   798  		{"BigHole", sparseHoles{{0, 1e6}}},
   799  		{"DataFront", sparseHoles{{1e3, 1e6 - 1e3}}},
   800  		{"HoleFront", sparseHoles{{0, 1e6 - 1e3}, {1e6, 0}}},
   801  		{"DataMiddle", sparseHoles{{0, 5e5 - 1e3}, {5e5, 5e5}}},
   802  		{"HoleMiddle", sparseHoles{{1e3, 1e6 - 2e3}, {1e6, 0}}},
   803  		{"Multiple", func() (sph []SparseEntry) {
   804  			const chunkSize = 1e6
   805  			for i := 0; i < 100; i++ {
   806  				sph = append(sph, SparseEntry{chunkSize * int64(i), chunkSize - 1e3})
   807  			}
   808  			return append(sph, SparseEntry{int64(len(sph) * chunkSize), 0})
   809  		}()},
   810  	}
   811  
   812  	for _, v := range vectors {
   813  		sph := v.sparseMap
   814  		t.Run(v.label, func(t *testing.T) {
   815  			src, err := ioutil.TempFile("", "")
   816  			if err != nil {
   817  				t.Fatalf("unexpected TempFile error: %v", err)
   818  			}
   819  			defer os.Remove(src.Name())
   820  			dst, err := ioutil.TempFile("", "")
   821  			if err != nil {
   822  				t.Fatalf("unexpected TempFile error: %v", err)
   823  			}
   824  			defer os.Remove(dst.Name())
   825  
   826  			// Create the source sparse file.
   827  			hdr := Header{
   828  				Typeflag:    TypeReg,
   829  				Name:        "sparse.db",
   830  				Size:        sph[len(sph)-1].endOffset(),
   831  				SparseHoles: sph,
   832  			}
   833  			junk := bytes.Repeat([]byte{'Z'}, int(hdr.Size+1e3))
   834  			if _, err := src.Write(junk); err != nil {
   835  				t.Fatalf("unexpected Write error: %v", err)
   836  			}
   837  			if err := hdr.PunchSparseHoles(src); err != nil {
   838  				t.Fatalf("unexpected PunchSparseHoles error: %v", err)
   839  			}
   840  			var pos int64
   841  			for _, s := range sph {
   842  				b := bytes.Repeat([]byte{'X'}, int(s.Offset-pos))
   843  				if _, err := src.WriteAt(b, pos); err != nil {
   844  					t.Fatalf("unexpected WriteAt error: %v", err)
   845  				}
   846  				pos = s.endOffset()
   847  			}
   848  
   849  			// Round-trip the sparse file to/from a tar archive.
   850  			b := new(bytes.Buffer)
   851  			tw := NewWriter(b)
   852  			if err := tw.WriteHeader(&hdr); err != nil {
   853  				t.Fatalf("unexpected WriteHeader error: %v", err)
   854  			}
   855  			if _, err := tw.ReadFrom(src); err != nil {
   856  				t.Fatalf("unexpected ReadFrom error: %v", err)
   857  			}
   858  			if err := tw.Close(); err != nil {
   859  				t.Fatalf("unexpected Close error: %v", err)
   860  			}
   861  			tr := NewReader(b)
   862  			if _, err := tr.Next(); err != nil {
   863  				t.Fatalf("unexpected Next error: %v", err)
   864  			}
   865  			if err := hdr.PunchSparseHoles(dst); err != nil {
   866  				t.Fatalf("unexpected PunchSparseHoles error: %v", err)
   867  			}
   868  			if _, err := tr.WriteTo(dst); err != nil {
   869  				t.Fatalf("unexpected Copy error: %v", err)
   870  			}
   871  
   872  			// Verify the sparse file matches.
   873  			// Even if the OS and underlying FS do not support sparse files,
   874  			// the content should still match (i.e., holes read as zeros).
   875  			got, err := ioutil.ReadFile(dst.Name())
   876  			if err != nil {
   877  				t.Fatalf("unexpected ReadFile error: %v", err)
   878  			}
   879  			want, err := ioutil.ReadFile(src.Name())
   880  			if err != nil {
   881  				t.Fatalf("unexpected ReadFile error: %v", err)
   882  			}
   883  			if !bytes.Equal(got, want) {
   884  				t.Fatal("sparse files mismatch")
   885  			}
   886  
   887  			// Detect and compare the sparse holes.
   888  			if err := hdr.DetectSparseHoles(dst); err != nil {
   889  				t.Fatalf("unexpected DetectSparseHoles error: %v", err)
   890  			}
   891  			if sparseSupport && sysSparseDetect != nil {
   892  				if len(sph) > 0 && sph[len(sph)-1].Length == 0 {
   893  					sph = sph[:len(sph)-1]
   894  				}
   895  				if len(hdr.SparseHoles) != len(sph) {
   896  					t.Fatalf("len(SparseHoles) = %d, want %d", len(hdr.SparseHoles), len(sph))
   897  				}
   898  				for j, got := range hdr.SparseHoles {
   899  					// Each FS has their own block size, so these may not match.
   900  					want := sph[j]
   901  					if got.Offset < want.Offset {
   902  						t.Errorf("index %d, StartOffset = %d, want <%d", j, got.Offset, want.Offset)
   903  					}
   904  					if got.endOffset() > want.endOffset() {
   905  						t.Errorf("index %d, EndOffset = %d, want >%d", j, got.endOffset(), want.endOffset())
   906  					}
   907  				}
   908  			}
   909  		})
   910  	}
   911  }
   912  
   913  func Benchmark(b *testing.B) {
   914  	type file struct {
   915  		hdr  *Header
   916  		body []byte
   917  	}
   918  
   919  	vectors := []struct {
   920  		label string
   921  		files []file
   922  	}{{
   923  		"USTAR",
   924  		[]file{{
   925  			&Header{Name: "bar", Mode: 0640, Size: int64(3)},
   926  			[]byte("foo"),
   927  		}, {
   928  			&Header{Name: "world", Mode: 0640, Size: int64(5)},
   929  			[]byte("hello"),
   930  		}},
   931  	}, {
   932  		"GNU",
   933  		[]file{{
   934  			&Header{Name: "bar", Mode: 0640, Size: int64(3), Devmajor: -1},
   935  			[]byte("foo"),
   936  		}, {
   937  			&Header{Name: "world", Mode: 0640, Size: int64(5), Devmajor: -1},
   938  			[]byte("hello"),
   939  		}},
   940  	}, {
   941  		"PAX",
   942  		[]file{{
   943  			&Header{Name: "bar", Mode: 0640, Size: int64(3), Xattrs: map[string]string{"foo": "bar"}},
   944  			[]byte("foo"),
   945  		}, {
   946  			&Header{Name: "world", Mode: 0640, Size: int64(5), Xattrs: map[string]string{"foo": "bar"}},
   947  			[]byte("hello"),
   948  		}},
   949  	}}
   950  
   951  	b.Run("Writer", func(b *testing.B) {
   952  		for _, v := range vectors {
   953  			b.Run(v.label, func(b *testing.B) {
   954  				b.ReportAllocs()
   955  				for i := 0; i < b.N; i++ {
   956  					// Writing to ioutil.Discard because we want to
   957  					// test purely the writer code and not bring in disk performance into this.
   958  					tw := NewWriter(ioutil.Discard)
   959  					for _, file := range v.files {
   960  						if err := tw.WriteHeader(file.hdr); err != nil {
   961  							b.Errorf("unexpected WriteHeader error: %v", err)
   962  						}
   963  						if _, err := tw.Write(file.body); err != nil {
   964  							b.Errorf("unexpected Write error: %v", err)
   965  						}
   966  					}
   967  					if err := tw.Close(); err != nil {
   968  						b.Errorf("unexpected Close error: %v", err)
   969  					}
   970  				}
   971  			})
   972  		}
   973  	})
   974  
   975  	b.Run("Reader", func(b *testing.B) {
   976  		for _, v := range vectors {
   977  			var buf bytes.Buffer
   978  			var r bytes.Reader
   979  
   980  			// Write the archive to a byte buffer.
   981  			tw := NewWriter(&buf)
   982  			for _, file := range v.files {
   983  				tw.WriteHeader(file.hdr)
   984  				tw.Write(file.body)
   985  			}
   986  			tw.Close()
   987  			b.Run(v.label, func(b *testing.B) {
   988  				b.ReportAllocs()
   989  				// Read from the byte buffer.
   990  				for i := 0; i < b.N; i++ {
   991  					r.Reset(buf.Bytes())
   992  					tr := NewReader(&r)
   993  					if _, err := tr.Next(); err != nil {
   994  						b.Errorf("unexpected Next error: %v", err)
   995  					}
   996  					if _, err := io.Copy(ioutil.Discard, tr); err != nil {
   997  						b.Errorf("unexpected Copy error : %v", err)
   998  					}
   999  				}
  1000  			})
  1001  		}
  1002  	})
  1003  
  1004  }