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