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
5package tar
6
7import (
8	"bytes"
9	"errors"
10	"fmt"
11	"internal/testenv"
12	"io"
13	"io/fs"
14	"math"
15	"os"
16	"path"
17	"path/filepath"
18	"reflect"
19	"strings"
20	"testing"
21	"time"
22)
23
24type testError struct{ error }
25
26type 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.
30type testFile struct {
31	ops fileOps
32	pos int64
33}
34
35func (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
57func (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
81func (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
101func equalSparseEntries(x, y []sparseEntry) bool {
102	return (len(x) == 0 && len(y) == 0) || reflect.DeepEqual(x, y)
103}
104
105func 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
211func 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
238func 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
262func TestFileInfoHeaderSymlink(t *testing.T) {
263	testenv.MustHaveSymlink(t)
264
265	tmpdir, err := os.MkdirTemp("", "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
297func 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		Typeflag:   TypeReg,
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 := io.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
339type headerRoundTripTest struct {
340	h  *Header
341	fm fs.FileMode
342}
343
344func 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 | fs.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 | fs.ModeDevice | fs.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 | fs.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 | fs.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 | fs.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 | fs.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 | fs.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 | fs.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
522func 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: "foo/", Typeflag: TypeDir},
738		formats: FormatUSTAR | FormatPAX | FormatGNU,
739	}, {
740		header:  &Header{Name: "foo/", Typeflag: TypeReg},
741		formats: FormatUnknown,
742	}, {
743		header:  &Header{Name: "foo/", Typeflag: TypeSymlink},
744		formats: FormatUSTAR | FormatPAX | FormatGNU,
745	}}
746
747	for i, v := range vectors {
748		formats, paxHdrs, err := v.header.allowedFormats()
749		if formats != v.formats {
750			t.Errorf("test %d, allowedFormats(): got %v, want %v", i, formats, v.formats)
751		}
752		if formats&FormatPAX > 0 && !reflect.DeepEqual(paxHdrs, v.paxHdrs) && !(len(paxHdrs) == 0 && len(v.paxHdrs) == 0) {
753			t.Errorf("test %d, allowedFormats():\ngot  %v\nwant %s", i, paxHdrs, v.paxHdrs)
754		}
755		if (formats != FormatUnknown) && (err != nil) {
756			t.Errorf("test %d, unexpected error: %v", i, err)
757		}
758		if (formats == FormatUnknown) && (err == nil) {
759			t.Errorf("test %d, got nil-error, want non-nil error", i)
760		}
761	}
762}
763
764func Benchmark(b *testing.B) {
765	type file struct {
766		hdr  *Header
767		body []byte
768	}
769
770	vectors := []struct {
771		label string
772		files []file
773	}{{
774		"USTAR",
775		[]file{{
776			&Header{Name: "bar", Mode: 0640, Size: int64(3)},
777			[]byte("foo"),
778		}, {
779			&Header{Name: "world", Mode: 0640, Size: int64(5)},
780			[]byte("hello"),
781		}},
782	}, {
783		"GNU",
784		[]file{{
785			&Header{Name: "bar", Mode: 0640, Size: int64(3), Devmajor: -1},
786			[]byte("foo"),
787		}, {
788			&Header{Name: "world", Mode: 0640, Size: int64(5), Devmajor: -1},
789			[]byte("hello"),
790		}},
791	}, {
792		"PAX",
793		[]file{{
794			&Header{Name: "bar", Mode: 0640, Size: int64(3), Xattrs: map[string]string{"foo": "bar"}},
795			[]byte("foo"),
796		}, {
797			&Header{Name: "world", Mode: 0640, Size: int64(5), Xattrs: map[string]string{"foo": "bar"}},
798			[]byte("hello"),
799		}},
800	}}
801
802	b.Run("Writer", func(b *testing.B) {
803		for _, v := range vectors {
804			b.Run(v.label, func(b *testing.B) {
805				b.ReportAllocs()
806				for i := 0; i < b.N; i++ {
807					// Writing to io.Discard because we want to
808					// test purely the writer code and not bring in disk performance into this.
809					tw := NewWriter(io.Discard)
810					for _, file := range v.files {
811						if err := tw.WriteHeader(file.hdr); err != nil {
812							b.Errorf("unexpected WriteHeader error: %v", err)
813						}
814						if _, err := tw.Write(file.body); err != nil {
815							b.Errorf("unexpected Write error: %v", err)
816						}
817					}
818					if err := tw.Close(); err != nil {
819						b.Errorf("unexpected Close error: %v", err)
820					}
821				}
822			})
823		}
824	})
825
826	b.Run("Reader", func(b *testing.B) {
827		for _, v := range vectors {
828			var buf bytes.Buffer
829			var r bytes.Reader
830
831			// Write the archive to a byte buffer.
832			tw := NewWriter(&buf)
833			for _, file := range v.files {
834				tw.WriteHeader(file.hdr)
835				tw.Write(file.body)
836			}
837			tw.Close()
838			b.Run(v.label, func(b *testing.B) {
839				b.ReportAllocs()
840				// Read from the byte buffer.
841				for i := 0; i < b.N; i++ {
842					r.Reset(buf.Bytes())
843					tr := NewReader(&r)
844					if _, err := tr.Next(); err != nil {
845						b.Errorf("unexpected Next error: %v", err)
846					}
847					if _, err := io.Copy(io.Discard, tr); err != nil {
848						b.Errorf("unexpected Copy error : %v", err)
849					}
850				}
851			})
852		}
853	})
854
855}
856