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