github.com/ice-blockchain/go/src@v0.0.0-20240403114104-1564d284e521/archive/tar/tar_test.go (about) 1 // Copyright 2012 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package tar 6 7 import ( 8 "bytes" 9 "errors" 10 "fmt" 11 "internal/testenv" 12 "io" 13 "io/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 // []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 := 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, // Too big for 8 octal digits 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 // Read it back. 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 // regular file. 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 // symbolic link. 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 // character device node. 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 // block device node. 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 // directory. 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 // fifo node. 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 // setuid. 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 // setguid. 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 // sticky. 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 // hard link. 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 // More information. 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 // Input header 520 paxHdrs map[string]string // Expected PAX headers that may be needed 521 formats Format // Expected formats that can encode the header 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 // Writing to io.Discard because we want to 803 // test purely the writer code and not bring in disk performance into this. 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 // Write the archive to a byte buffer. 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 // Read from the byte buffer. 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 }