1
2
3
4
5 package tar
6
7 import (
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
24 type testError struct{ error }
25
26 type fileOps []any
27
28
29
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
111 wantAligned []sparseEntry
112 wantInverted []sparseEntry
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
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
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 := t.TempDir()
266
267 link := filepath.Join(tmpdir, "link")
268 target := tmpdir
269 if err := os.Symlink(target, link); err != nil {
270 t.Fatal(err)
271 }
272 fi, err := os.Lstat(link)
273 if err != nil {
274 t.Fatal(err)
275 }
276
277 h, err := FileInfoHeader(fi, target)
278 if err != nil {
279 t.Fatal(err)
280 }
281 if g, e := h.Name, fi.Name(); g != e {
282 t.Errorf("Name = %q; want %q", g, e)
283 }
284 if g, e := h.Linkname, target; g != e {
285 t.Errorf("Linkname = %q; want %q", g, e)
286 }
287 if g, e := h.Typeflag, byte(TypeSymlink); g != e {
288 t.Errorf("Typeflag = %v; want %v", g, e)
289 }
290 }
291
292 func TestRoundTrip(t *testing.T) {
293 data := []byte("some file contents")
294
295 var b bytes.Buffer
296 tw := NewWriter(&b)
297 hdr := &Header{
298 Name: "file.txt",
299 Uid: 1 << 21,
300 Size: int64(len(data)),
301 ModTime: time.Now().Round(time.Second),
302 PAXRecords: map[string]string{"uid": "2097152"},
303 Format: FormatPAX,
304 Typeflag: TypeReg,
305 }
306 if err := tw.WriteHeader(hdr); err != nil {
307 t.Fatalf("tw.WriteHeader: %v", err)
308 }
309 if _, err := tw.Write(data); err != nil {
310 t.Fatalf("tw.Write: %v", err)
311 }
312 if err := tw.Close(); err != nil {
313 t.Fatalf("tw.Close: %v", err)
314 }
315
316
317 tr := NewReader(&b)
318 rHdr, err := tr.Next()
319 if err != nil {
320 t.Fatalf("tr.Next: %v", err)
321 }
322 if !reflect.DeepEqual(rHdr, hdr) {
323 t.Errorf("Header mismatch.\n got %+v\nwant %+v", rHdr, hdr)
324 }
325 rData, err := io.ReadAll(tr)
326 if err != nil {
327 t.Fatalf("Read: %v", err)
328 }
329 if !bytes.Equal(rData, data) {
330 t.Errorf("Data mismatch.\n got %q\nwant %q", rData, data)
331 }
332 }
333
334 type headerRoundTripTest struct {
335 h *Header
336 fm fs.FileMode
337 }
338
339 func TestHeaderRoundTrip(t *testing.T) {
340 vectors := []headerRoundTripTest{{
341
342 h: &Header{
343 Name: "test.txt",
344 Mode: 0644,
345 Size: 12,
346 ModTime: time.Unix(1360600916, 0),
347 Typeflag: TypeReg,
348 },
349 fm: 0644,
350 }, {
351
352 h: &Header{
353 Name: "link.txt",
354 Mode: 0777,
355 Size: 0,
356 ModTime: time.Unix(1360600852, 0),
357 Typeflag: TypeSymlink,
358 },
359 fm: 0777 | fs.ModeSymlink,
360 }, {
361
362 h: &Header{
363 Name: "dev/null",
364 Mode: 0666,
365 Size: 0,
366 ModTime: time.Unix(1360578951, 0),
367 Typeflag: TypeChar,
368 },
369 fm: 0666 | fs.ModeDevice | fs.ModeCharDevice,
370 }, {
371
372 h: &Header{
373 Name: "dev/sda",
374 Mode: 0660,
375 Size: 0,
376 ModTime: time.Unix(1360578954, 0),
377 Typeflag: TypeBlock,
378 },
379 fm: 0660 | fs.ModeDevice,
380 }, {
381
382 h: &Header{
383 Name: "dir/",
384 Mode: 0755,
385 Size: 0,
386 ModTime: time.Unix(1360601116, 0),
387 Typeflag: TypeDir,
388 },
389 fm: 0755 | fs.ModeDir,
390 }, {
391
392 h: &Header{
393 Name: "dev/initctl",
394 Mode: 0600,
395 Size: 0,
396 ModTime: time.Unix(1360578949, 0),
397 Typeflag: TypeFifo,
398 },
399 fm: 0600 | fs.ModeNamedPipe,
400 }, {
401
402 h: &Header{
403 Name: "bin/su",
404 Mode: 0755 | c_ISUID,
405 Size: 23232,
406 ModTime: time.Unix(1355405093, 0),
407 Typeflag: TypeReg,
408 },
409 fm: 0755 | fs.ModeSetuid,
410 }, {
411
412 h: &Header{
413 Name: "group.txt",
414 Mode: 0750 | c_ISGID,
415 Size: 0,
416 ModTime: time.Unix(1360602346, 0),
417 Typeflag: TypeReg,
418 },
419 fm: 0750 | fs.ModeSetgid,
420 }, {
421
422 h: &Header{
423 Name: "sticky.txt",
424 Mode: 0600 | c_ISVTX,
425 Size: 7,
426 ModTime: time.Unix(1360602540, 0),
427 Typeflag: TypeReg,
428 },
429 fm: 0600 | fs.ModeSticky,
430 }, {
431
432 h: &Header{
433 Name: "hard.txt",
434 Mode: 0644,
435 Size: 0,
436 Linkname: "file.txt",
437 ModTime: time.Unix(1360600916, 0),
438 Typeflag: TypeLink,
439 },
440 fm: 0644,
441 }, {
442
443 h: &Header{
444 Name: "info.txt",
445 Mode: 0600,
446 Size: 0,
447 Uid: 1000,
448 Gid: 1000,
449 ModTime: time.Unix(1360602540, 0),
450 Uname: "slartibartfast",
451 Gname: "users",
452 Typeflag: TypeReg,
453 },
454 fm: 0600,
455 }}
456
457 for i, v := range vectors {
458 fi := v.h.FileInfo()
459 h2, err := FileInfoHeader(fi, "")
460 if err != nil {
461 t.Error(err)
462 continue
463 }
464 if strings.Contains(fi.Name(), "/") {
465 t.Errorf("FileInfo of %q contains slash: %q", v.h.Name, fi.Name())
466 }
467 name := path.Base(v.h.Name)
468 if fi.IsDir() {
469 name += "/"
470 }
471 if got, want := h2.Name, name; got != want {
472 t.Errorf("i=%d: Name: got %v, want %v", i, got, want)
473 }
474 if got, want := h2.Size, v.h.Size; got != want {
475 t.Errorf("i=%d: Size: got %v, want %v", i, got, want)
476 }
477 if got, want := h2.Uid, v.h.Uid; got != want {
478 t.Errorf("i=%d: Uid: got %d, want %d", i, got, want)
479 }
480 if got, want := h2.Gid, v.h.Gid; got != want {
481 t.Errorf("i=%d: Gid: got %d, want %d", i, got, want)
482 }
483 if got, want := h2.Uname, v.h.Uname; got != want {
484 t.Errorf("i=%d: Uname: got %q, want %q", i, got, want)
485 }
486 if got, want := h2.Gname, v.h.Gname; got != want {
487 t.Errorf("i=%d: Gname: got %q, want %q", i, got, want)
488 }
489 if got, want := h2.Linkname, v.h.Linkname; got != want {
490 t.Errorf("i=%d: Linkname: got %v, want %v", i, got, want)
491 }
492 if got, want := h2.Typeflag, v.h.Typeflag; got != want {
493 t.Logf("%#v %#v", v.h, fi.Sys())
494 t.Errorf("i=%d: Typeflag: got %q, want %q", i, got, want)
495 }
496 if got, want := h2.Mode, v.h.Mode; got != want {
497 t.Errorf("i=%d: Mode: got %o, want %o", i, got, want)
498 }
499 if got, want := fi.Mode(), v.fm; got != want {
500 t.Errorf("i=%d: fi.Mode: got %o, want %o", i, got, want)
501 }
502 if got, want := h2.AccessTime, v.h.AccessTime; got != want {
503 t.Errorf("i=%d: AccessTime: got %v, want %v", i, got, want)
504 }
505 if got, want := h2.ChangeTime, v.h.ChangeTime; got != want {
506 t.Errorf("i=%d: ChangeTime: got %v, want %v", i, got, want)
507 }
508 if got, want := h2.ModTime, v.h.ModTime; got != want {
509 t.Errorf("i=%d: ModTime: got %v, want %v", i, got, want)
510 }
511 if sysh, ok := fi.Sys().(*Header); !ok || sysh != v.h {
512 t.Errorf("i=%d: Sys didn't return original *Header", i)
513 }
514 }
515 }
516
517 func TestHeaderAllowedFormats(t *testing.T) {
518 vectors := []struct {
519 header *Header
520 paxHdrs map[string]string
521 formats Format
522 }{{
523 header: &Header{},
524 formats: FormatUSTAR | FormatPAX | FormatGNU,
525 }, {
526 header: &Header{Size: 077777777777},
527 formats: FormatUSTAR | FormatPAX | FormatGNU,
528 }, {
529 header: &Header{Size: 077777777777, Format: FormatUSTAR},
530 formats: FormatUSTAR,
531 }, {
532 header: &Header{Size: 077777777777, Format: FormatPAX},
533 formats: FormatUSTAR | FormatPAX,
534 }, {
535 header: &Header{Size: 077777777777, Format: FormatGNU},
536 formats: FormatGNU,
537 }, {
538 header: &Header{Size: 077777777777 + 1},
539 paxHdrs: map[string]string{paxSize: "8589934592"},
540 formats: FormatPAX | FormatGNU,
541 }, {
542 header: &Header{Size: 077777777777 + 1, Format: FormatPAX},
543 paxHdrs: map[string]string{paxSize: "8589934592"},
544 formats: FormatPAX,
545 }, {
546 header: &Header{Size: 077777777777 + 1, Format: FormatGNU},
547 paxHdrs: map[string]string{paxSize: "8589934592"},
548 formats: FormatGNU,
549 }, {
550 header: &Header{Mode: 07777777},
551 formats: FormatUSTAR | FormatPAX | FormatGNU,
552 }, {
553 header: &Header{Mode: 07777777 + 1},
554 formats: FormatGNU,
555 }, {
556 header: &Header{Devmajor: -123},
557 formats: FormatGNU,
558 }, {
559 header: &Header{Devmajor: 1<<56 - 1},
560 formats: FormatGNU,
561 }, {
562 header: &Header{Devmajor: 1 << 56},
563 formats: FormatUnknown,
564 }, {
565 header: &Header{Devmajor: -1 << 56},
566 formats: FormatGNU,
567 }, {
568 header: &Header{Devmajor: -1<<56 - 1},
569 formats: FormatUnknown,
570 }, {
571 header: &Header{Name: "用戶名", Devmajor: -1 << 56},
572 formats: FormatGNU,
573 }, {
574 header: &Header{Size: math.MaxInt64},
575 paxHdrs: map[string]string{paxSize: "9223372036854775807"},
576 formats: FormatPAX | FormatGNU,
577 }, {
578 header: &Header{Size: math.MinInt64},
579 paxHdrs: map[string]string{paxSize: "-9223372036854775808"},
580 formats: FormatUnknown,
581 }, {
582 header: &Header{Uname: "0123456789abcdef0123456789abcdef"},
583 formats: FormatUSTAR | FormatPAX | FormatGNU,
584 }, {
585 header: &Header{Uname: "0123456789abcdef0123456789abcdefx"},
586 paxHdrs: map[string]string{paxUname: "0123456789abcdef0123456789abcdefx"},
587 formats: FormatPAX,
588 }, {
589 header: &Header{Name: "foobar"},
590 formats: FormatUSTAR | FormatPAX | FormatGNU,
591 }, {
592 header: &Header{Name: strings.Repeat("a", nameSize)},
593 formats: FormatUSTAR | FormatPAX | FormatGNU,
594 }, {
595 header: &Header{Name: strings.Repeat("a", nameSize+1)},
596 paxHdrs: map[string]string{paxPath: strings.Repeat("a", nameSize+1)},
597 formats: FormatPAX | FormatGNU,
598 }, {
599 header: &Header{Linkname: "用戶名"},
600 paxHdrs: map[string]string{paxLinkpath: "用戶名"},
601 formats: FormatPAX | FormatGNU,
602 }, {
603 header: &Header{Linkname: strings.Repeat("用戶名\x00", nameSize)},
604 paxHdrs: map[string]string{paxLinkpath: strings.Repeat("用戶名\x00", nameSize)},
605 formats: FormatUnknown,
606 }, {
607 header: &Header{Linkname: "\x00hello"},
608 paxHdrs: map[string]string{paxLinkpath: "\x00hello"},
609 formats: FormatUnknown,
610 }, {
611 header: &Header{Uid: 07777777},
612 formats: FormatUSTAR | FormatPAX | FormatGNU,
613 }, {
614 header: &Header{Uid: 07777777 + 1},
615 paxHdrs: map[string]string{paxUid: "2097152"},
616 formats: FormatPAX | FormatGNU,
617 }, {
618 header: &Header{Xattrs: nil},
619 formats: FormatUSTAR | FormatPAX | FormatGNU,
620 }, {
621 header: &Header{Xattrs: map[string]string{"foo": "bar"}},
622 paxHdrs: map[string]string{paxSchilyXattr + "foo": "bar"},
623 formats: FormatPAX,
624 }, {
625 header: &Header{Xattrs: map[string]string{"foo": "bar"}, Format: FormatGNU},
626 paxHdrs: map[string]string{paxSchilyXattr + "foo": "bar"},
627 formats: FormatUnknown,
628 }, {
629 header: &Header{Xattrs: map[string]string{"用戶名": "\x00hello"}},
630 paxHdrs: map[string]string{paxSchilyXattr + "用戶名": "\x00hello"},
631 formats: FormatPAX,
632 }, {
633 header: &Header{Xattrs: map[string]string{"foo=bar": "baz"}},
634 formats: FormatUnknown,
635 }, {
636 header: &Header{Xattrs: map[string]string{"foo": ""}},
637 paxHdrs: map[string]string{paxSchilyXattr + "foo": ""},
638 formats: FormatPAX,
639 }, {
640 header: &Header{ModTime: time.Unix(0, 0)},
641 formats: FormatUSTAR | FormatPAX | FormatGNU,
642 }, {
643 header: &Header{ModTime: time.Unix(077777777777, 0)},
644 formats: FormatUSTAR | FormatPAX | FormatGNU,
645 }, {
646 header: &Header{ModTime: time.Unix(077777777777+1, 0)},
647 paxHdrs: map[string]string{paxMtime: "8589934592"},
648 formats: FormatPAX | FormatGNU,
649 }, {
650 header: &Header{ModTime: time.Unix(math.MaxInt64, 0)},
651 paxHdrs: map[string]string{paxMtime: "9223372036854775807"},
652 formats: FormatPAX | FormatGNU,
653 }, {
654 header: &Header{ModTime: time.Unix(math.MaxInt64, 0), Format: FormatUSTAR},
655 paxHdrs: map[string]string{paxMtime: "9223372036854775807"},
656 formats: FormatUnknown,
657 }, {
658 header: &Header{ModTime: time.Unix(-1, 0)},
659 paxHdrs: map[string]string{paxMtime: "-1"},
660 formats: FormatPAX | FormatGNU,
661 }, {
662 header: &Header{ModTime: time.Unix(1, 500)},
663 paxHdrs: map[string]string{paxMtime: "1.0000005"},
664 formats: FormatUSTAR | FormatPAX | FormatGNU,
665 }, {
666 header: &Header{ModTime: time.Unix(1, 0)},
667 formats: FormatUSTAR | FormatPAX | FormatGNU,
668 }, {
669 header: &Header{ModTime: time.Unix(1, 0), Format: FormatPAX},
670 formats: FormatUSTAR | FormatPAX,
671 }, {
672 header: &Header{ModTime: time.Unix(1, 500), Format: FormatUSTAR},
673 paxHdrs: map[string]string{paxMtime: "1.0000005"},
674 formats: FormatUSTAR,
675 }, {
676 header: &Header{ModTime: time.Unix(1, 500), Format: FormatPAX},
677 paxHdrs: map[string]string{paxMtime: "1.0000005"},
678 formats: FormatPAX,
679 }, {
680 header: &Header{ModTime: time.Unix(1, 500), Format: FormatGNU},
681 paxHdrs: map[string]string{paxMtime: "1.0000005"},
682 formats: FormatGNU,
683 }, {
684 header: &Header{ModTime: time.Unix(-1, 500)},
685 paxHdrs: map[string]string{paxMtime: "-0.9999995"},
686 formats: FormatPAX | FormatGNU,
687 }, {
688 header: &Header{ModTime: time.Unix(-1, 500), Format: FormatGNU},
689 paxHdrs: map[string]string{paxMtime: "-0.9999995"},
690 formats: FormatGNU,
691 }, {
692 header: &Header{AccessTime: time.Unix(0, 0)},
693 paxHdrs: map[string]string{paxAtime: "0"},
694 formats: FormatPAX | FormatGNU,
695 }, {
696 header: &Header{AccessTime: time.Unix(0, 0), Format: FormatUSTAR},
697 paxHdrs: map[string]string{paxAtime: "0"},
698 formats: FormatUnknown,
699 }, {
700 header: &Header{AccessTime: time.Unix(0, 0), Format: FormatPAX},
701 paxHdrs: map[string]string{paxAtime: "0"},
702 formats: FormatPAX,
703 }, {
704 header: &Header{AccessTime: time.Unix(0, 0), Format: FormatGNU},
705 paxHdrs: map[string]string{paxAtime: "0"},
706 formats: FormatGNU,
707 }, {
708 header: &Header{AccessTime: time.Unix(-123, 0)},
709 paxHdrs: map[string]string{paxAtime: "-123"},
710 formats: FormatPAX | FormatGNU,
711 }, {
712 header: &Header{AccessTime: time.Unix(-123, 0), Format: FormatPAX},
713 paxHdrs: map[string]string{paxAtime: "-123"},
714 formats: FormatPAX,
715 }, {
716 header: &Header{ChangeTime: time.Unix(123, 456)},
717 paxHdrs: map[string]string{paxCtime: "123.000000456"},
718 formats: FormatPAX | FormatGNU,
719 }, {
720 header: &Header{ChangeTime: time.Unix(123, 456), Format: FormatUSTAR},
721 paxHdrs: map[string]string{paxCtime: "123.000000456"},
722 formats: FormatUnknown,
723 }, {
724 header: &Header{ChangeTime: time.Unix(123, 456), Format: FormatGNU},
725 paxHdrs: map[string]string{paxCtime: "123.000000456"},
726 formats: FormatGNU,
727 }, {
728 header: &Header{ChangeTime: time.Unix(123, 456), Format: FormatPAX},
729 paxHdrs: map[string]string{paxCtime: "123.000000456"},
730 formats: FormatPAX,
731 }, {
732 header: &Header{Name: "foo/", Typeflag: TypeDir},
733 formats: FormatUSTAR | FormatPAX | FormatGNU,
734 }, {
735 header: &Header{Name: "foo/", Typeflag: TypeReg},
736 formats: FormatUnknown,
737 }, {
738 header: &Header{Name: "foo/", Typeflag: TypeSymlink},
739 formats: FormatUSTAR | FormatPAX | FormatGNU,
740 }}
741
742 for i, v := range vectors {
743 formats, paxHdrs, err := v.header.allowedFormats()
744 if formats != v.formats {
745 t.Errorf("test %d, allowedFormats(): got %v, want %v", i, formats, v.formats)
746 }
747 if formats&FormatPAX > 0 && !reflect.DeepEqual(paxHdrs, v.paxHdrs) && !(len(paxHdrs) == 0 && len(v.paxHdrs) == 0) {
748 t.Errorf("test %d, allowedFormats():\ngot %v\nwant %s", i, paxHdrs, v.paxHdrs)
749 }
750 if (formats != FormatUnknown) && (err != nil) {
751 t.Errorf("test %d, unexpected error: %v", i, err)
752 }
753 if (formats == FormatUnknown) && (err == nil) {
754 t.Errorf("test %d, got nil-error, want non-nil error", i)
755 }
756 }
757 }
758
759 func Benchmark(b *testing.B) {
760 type file struct {
761 hdr *Header
762 body []byte
763 }
764
765 vectors := []struct {
766 label string
767 files []file
768 }{{
769 "USTAR",
770 []file{{
771 &Header{Name: "bar", Mode: 0640, Size: int64(3)},
772 []byte("foo"),
773 }, {
774 &Header{Name: "world", Mode: 0640, Size: int64(5)},
775 []byte("hello"),
776 }},
777 }, {
778 "GNU",
779 []file{{
780 &Header{Name: "bar", Mode: 0640, Size: int64(3), Devmajor: -1},
781 []byte("foo"),
782 }, {
783 &Header{Name: "world", Mode: 0640, Size: int64(5), Devmajor: -1},
784 []byte("hello"),
785 }},
786 }, {
787 "PAX",
788 []file{{
789 &Header{Name: "bar", Mode: 0640, Size: int64(3), Xattrs: map[string]string{"foo": "bar"}},
790 []byte("foo"),
791 }, {
792 &Header{Name: "world", Mode: 0640, Size: int64(5), Xattrs: map[string]string{"foo": "bar"}},
793 []byte("hello"),
794 }},
795 }}
796
797 b.Run("Writer", func(b *testing.B) {
798 for _, v := range vectors {
799 b.Run(v.label, func(b *testing.B) {
800 b.ReportAllocs()
801 for i := 0; i < b.N; i++ {
802
803
804 tw := NewWriter(io.Discard)
805 for _, file := range v.files {
806 if err := tw.WriteHeader(file.hdr); err != nil {
807 b.Errorf("unexpected WriteHeader error: %v", err)
808 }
809 if _, err := tw.Write(file.body); err != nil {
810 b.Errorf("unexpected Write error: %v", err)
811 }
812 }
813 if err := tw.Close(); err != nil {
814 b.Errorf("unexpected Close error: %v", err)
815 }
816 }
817 })
818 }
819 })
820
821 b.Run("Reader", func(b *testing.B) {
822 for _, v := range vectors {
823 var buf bytes.Buffer
824 var r bytes.Reader
825
826
827 tw := NewWriter(&buf)
828 for _, file := range v.files {
829 tw.WriteHeader(file.hdr)
830 tw.Write(file.body)
831 }
832 tw.Close()
833 b.Run(v.label, func(b *testing.B) {
834 b.ReportAllocs()
835
836 for i := 0; i < b.N; i++ {
837 r.Reset(buf.Bytes())
838 tr := NewReader(&r)
839 if _, err := tr.Next(); err != nil {
840 b.Errorf("unexpected Next error: %v", err)
841 }
842 if _, err := io.Copy(io.Discard, tr); err != nil {
843 b.Errorf("unexpected Copy error : %v", err)
844 }
845 }
846 })
847 }
848 })
849
850 }
851
852 var _ fileInfoNames = fileInfoNames{}
853
854 type fileInfoNames struct{}
855
856 func (f *fileInfoNames) Name() string {
857 return "tmp"
858 }
859
860 func (f *fileInfoNames) Size() int64 {
861 return 0
862 }
863
864 func (f *fileInfoNames) Mode() fs.FileMode {
865 return 0777
866 }
867
868 func (f *fileInfoNames) ModTime() time.Time {
869 return time.Time{}
870 }
871
872 func (f *fileInfoNames) IsDir() bool {
873 return false
874 }
875
876 func (f *fileInfoNames) Sys() any {
877 return nil
878 }
879
880 func (f *fileInfoNames) Uname() (string, error) {
881 return "Uname", nil
882 }
883
884 func (f *fileInfoNames) Gname() (string, error) {
885 return "Gname", nil
886 }
887
888 func TestFileInfoHeaderUseFileInfoNames(t *testing.T) {
889 info := &fileInfoNames{}
890 header, err := FileInfoHeader(info, "")
891 if err != nil {
892 t.Fatal(err)
893 }
894 if header.Uname != "Uname" {
895 t.Fatalf("header.Uname: got %s, want %s", header.Uname, "Uname")
896 }
897 if header.Gname != "Gname" {
898 t.Fatalf("header.Gname: got %s, want %s", header.Gname, "Gname")
899 }
900 }
901
View as plain text