github.com/mattn/go@v0.0.0-20171011075504-07f7db3ea99f/src/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/ioutil" 14 "math" 15 "os" 16 "path" 17 "path/filepath" 18 "reflect" 19 "runtime" 20 "strings" 21 "testing" 22 "time" 23 ) 24 25 type testError struct{ error } 26 27 type fileOps []interface{} // []T where T is (string | int64) 28 29 // testFile is an io.ReadWriteSeeker where the IO operations performed 30 // on it must match the list of operations in ops. 31 type testFile struct { 32 ops fileOps 33 pos int64 34 } 35 36 func (f *testFile) Read(b []byte) (int, error) { 37 if len(b) == 0 { 38 return 0, nil 39 } 40 if len(f.ops) == 0 { 41 return 0, io.EOF 42 } 43 s, ok := f.ops[0].(string) 44 if !ok { 45 return 0, errors.New("unexpected Read operation") 46 } 47 48 n := copy(b, s) 49 if len(s) > n { 50 f.ops[0] = s[n:] 51 } else { 52 f.ops = f.ops[1:] 53 } 54 f.pos += int64(len(b)) 55 return n, nil 56 } 57 58 func (f *testFile) Write(b []byte) (int, error) { 59 if len(b) == 0 { 60 return 0, nil 61 } 62 if len(f.ops) == 0 { 63 return 0, errors.New("unexpected Write operation") 64 } 65 s, ok := f.ops[0].(string) 66 if !ok { 67 return 0, errors.New("unexpected Write operation") 68 } 69 70 if !strings.HasPrefix(s, string(b)) { 71 return 0, testError{fmt.Errorf("got Write(%q), want Write(%q)", b, s)} 72 } 73 if len(s) > len(b) { 74 f.ops[0] = s[len(b):] 75 } else { 76 f.ops = f.ops[1:] 77 } 78 f.pos += int64(len(b)) 79 return len(b), nil 80 } 81 82 func (f *testFile) Seek(pos int64, whence int) (int64, error) { 83 if pos == 0 && whence == io.SeekCurrent { 84 return f.pos, nil 85 } 86 if len(f.ops) == 0 { 87 return 0, errors.New("unexpected Seek operation") 88 } 89 s, ok := f.ops[0].(int64) 90 if !ok { 91 return 0, errors.New("unexpected Seek operation") 92 } 93 94 if s != pos || whence != io.SeekCurrent { 95 return 0, testError{fmt.Errorf("got Seek(%d, %d), want Seek(%d, %d)", pos, whence, s, io.SeekCurrent)} 96 } 97 f.pos += s 98 f.ops = f.ops[1:] 99 return f.pos, nil 100 } 101 102 func equalSparseEntries(x, y []SparseEntry) bool { 103 return (len(x) == 0 && len(y) == 0) || reflect.DeepEqual(x, y) 104 } 105 106 func TestSparseEntries(t *testing.T) { 107 vectors := []struct { 108 in []SparseEntry 109 size int64 110 111 wantValid bool // Result of validateSparseEntries 112 wantAligned []SparseEntry // Result of alignSparseEntries 113 wantInverted []SparseEntry // Result of invertSparseEntries 114 }{{ 115 in: []SparseEntry{}, size: 0, 116 wantValid: true, 117 wantInverted: []SparseEntry{{0, 0}}, 118 }, { 119 in: []SparseEntry{}, size: 5000, 120 wantValid: true, 121 wantInverted: []SparseEntry{{0, 5000}}, 122 }, { 123 in: []SparseEntry{{0, 5000}}, size: 5000, 124 wantValid: true, 125 wantAligned: []SparseEntry{{0, 5000}}, 126 wantInverted: []SparseEntry{{5000, 0}}, 127 }, { 128 in: []SparseEntry{{1000, 4000}}, size: 5000, 129 wantValid: true, 130 wantAligned: []SparseEntry{{1024, 3976}}, 131 wantInverted: []SparseEntry{{0, 1000}, {5000, 0}}, 132 }, { 133 in: []SparseEntry{{0, 3000}}, size: 5000, 134 wantValid: true, 135 wantAligned: []SparseEntry{{0, 2560}}, 136 wantInverted: []SparseEntry{{3000, 2000}}, 137 }, { 138 in: []SparseEntry{{3000, 2000}}, size: 5000, 139 wantValid: true, 140 wantAligned: []SparseEntry{{3072, 1928}}, 141 wantInverted: []SparseEntry{{0, 3000}, {5000, 0}}, 142 }, { 143 in: []SparseEntry{{2000, 2000}}, size: 5000, 144 wantValid: true, 145 wantAligned: []SparseEntry{{2048, 1536}}, 146 wantInverted: []SparseEntry{{0, 2000}, {4000, 1000}}, 147 }, { 148 in: []SparseEntry{{0, 2000}, {8000, 2000}}, size: 10000, 149 wantValid: true, 150 wantAligned: []SparseEntry{{0, 1536}, {8192, 1808}}, 151 wantInverted: []SparseEntry{{2000, 6000}, {10000, 0}}, 152 }, { 153 in: []SparseEntry{{0, 2000}, {2000, 2000}, {4000, 0}, {4000, 3000}, {7000, 1000}, {8000, 0}, {8000, 2000}}, size: 10000, 154 wantValid: true, 155 wantAligned: []SparseEntry{{0, 1536}, {2048, 1536}, {4096, 2560}, {7168, 512}, {8192, 1808}}, 156 wantInverted: []SparseEntry{{10000, 0}}, 157 }, { 158 in: []SparseEntry{{0, 0}, {1000, 0}, {2000, 0}, {3000, 0}, {4000, 0}, {5000, 0}}, size: 5000, 159 wantValid: true, 160 wantInverted: []SparseEntry{{0, 5000}}, 161 }, { 162 in: []SparseEntry{{1, 0}}, size: 0, 163 wantValid: false, 164 }, { 165 in: []SparseEntry{{-1, 0}}, size: 100, 166 wantValid: false, 167 }, { 168 in: []SparseEntry{{0, -1}}, size: 100, 169 wantValid: false, 170 }, { 171 in: []SparseEntry{{0, 0}}, size: -100, 172 wantValid: false, 173 }, { 174 in: []SparseEntry{{math.MaxInt64, 3}, {6, -5}}, size: 35, 175 wantValid: false, 176 }, { 177 in: []SparseEntry{{1, 3}, {6, -5}}, size: 35, 178 wantValid: false, 179 }, { 180 in: []SparseEntry{{math.MaxInt64, math.MaxInt64}}, size: math.MaxInt64, 181 wantValid: false, 182 }, { 183 in: []SparseEntry{{3, 3}}, size: 5, 184 wantValid: false, 185 }, { 186 in: []SparseEntry{{2, 0}, {1, 0}, {0, 0}}, size: 3, 187 wantValid: false, 188 }, { 189 in: []SparseEntry{{1, 3}, {2, 2}}, size: 10, 190 wantValid: false, 191 }} 192 193 for i, v := range vectors { 194 gotValid := validateSparseEntries(v.in, v.size) 195 if gotValid != v.wantValid { 196 t.Errorf("test %d, validateSparseEntries() = %v, want %v", i, gotValid, v.wantValid) 197 } 198 if !v.wantValid { 199 continue 200 } 201 gotAligned := alignSparseEntries(append([]SparseEntry{}, v.in...), v.size) 202 if !equalSparseEntries(gotAligned, v.wantAligned) { 203 t.Errorf("test %d, alignSparseEntries():\ngot %v\nwant %v", i, gotAligned, v.wantAligned) 204 } 205 gotInverted := invertSparseEntries(append([]SparseEntry{}, v.in...), v.size) 206 if !equalSparseEntries(gotInverted, v.wantInverted) { 207 t.Errorf("test %d, inverseSparseEntries():\ngot %v\nwant %v", i, gotInverted, v.wantInverted) 208 } 209 } 210 } 211 212 func TestFileInfoHeader(t *testing.T) { 213 fi, err := os.Stat("testdata/small.txt") 214 if err != nil { 215 t.Fatal(err) 216 } 217 h, err := FileInfoHeader(fi, "") 218 if err != nil { 219 t.Fatalf("FileInfoHeader: %v", err) 220 } 221 if g, e := h.Name, "small.txt"; g != e { 222 t.Errorf("Name = %q; want %q", g, e) 223 } 224 if g, e := h.Mode, int64(fi.Mode().Perm()); g != e { 225 t.Errorf("Mode = %#o; want %#o", g, e) 226 } 227 if g, e := h.Size, int64(5); g != e { 228 t.Errorf("Size = %v; want %v", g, e) 229 } 230 if g, e := h.ModTime, fi.ModTime(); !g.Equal(e) { 231 t.Errorf("ModTime = %v; want %v", g, e) 232 } 233 // FileInfoHeader should error when passing nil FileInfo 234 if _, err := FileInfoHeader(nil, ""); err == nil { 235 t.Fatalf("Expected error when passing nil to FileInfoHeader") 236 } 237 } 238 239 func TestFileInfoHeaderDir(t *testing.T) { 240 fi, err := os.Stat("testdata") 241 if err != nil { 242 t.Fatal(err) 243 } 244 h, err := FileInfoHeader(fi, "") 245 if err != nil { 246 t.Fatalf("FileInfoHeader: %v", err) 247 } 248 if g, e := h.Name, "testdata/"; g != e { 249 t.Errorf("Name = %q; want %q", g, e) 250 } 251 // Ignoring c_ISGID for golang.org/issue/4867 252 if g, e := h.Mode&^c_ISGID, int64(fi.Mode().Perm()); g != e { 253 t.Errorf("Mode = %#o; want %#o", g, e) 254 } 255 if g, e := h.Size, int64(0); g != e { 256 t.Errorf("Size = %v; want %v", g, e) 257 } 258 if g, e := h.ModTime, fi.ModTime(); !g.Equal(e) { 259 t.Errorf("ModTime = %v; want %v", g, e) 260 } 261 } 262 263 func TestFileInfoHeaderSymlink(t *testing.T) { 264 testenv.MustHaveSymlink(t) 265 266 tmpdir, err := ioutil.TempDir("", "TestFileInfoHeaderSymlink") 267 if err != nil { 268 t.Fatal(err) 269 } 270 defer os.RemoveAll(tmpdir) 271 272 link := filepath.Join(tmpdir, "link") 273 target := tmpdir 274 err = os.Symlink(target, link) 275 if err != nil { 276 t.Fatal(err) 277 } 278 fi, err := os.Lstat(link) 279 if err != nil { 280 t.Fatal(err) 281 } 282 283 h, err := FileInfoHeader(fi, target) 284 if err != nil { 285 t.Fatal(err) 286 } 287 if g, e := h.Name, fi.Name(); g != e { 288 t.Errorf("Name = %q; want %q", g, e) 289 } 290 if g, e := h.Linkname, target; g != e { 291 t.Errorf("Linkname = %q; want %q", g, e) 292 } 293 if g, e := h.Typeflag, byte(TypeSymlink); g != e { 294 t.Errorf("Typeflag = %v; want %v", g, e) 295 } 296 } 297 298 func TestRoundTrip(t *testing.T) { 299 data := []byte("some file contents") 300 301 var b bytes.Buffer 302 tw := NewWriter(&b) 303 hdr := &Header{ 304 Name: "file.txt", 305 Uid: 1 << 21, // Too big for 8 octal digits 306 Size: int64(len(data)), 307 ModTime: time.Now().Round(time.Second), 308 PAXRecords: map[string]string{"uid": "2097152"}, 309 Format: FormatPAX, 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 := ioutil.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 339 type headerRoundTripTest struct { 340 h *Header 341 fm os.FileMode 342 } 343 344 func 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 | os.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 | os.ModeDevice | os.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 | os.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 | os.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 | os.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 | os.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 | os.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 | os.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 522 func 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: "sparse.db", Size: 1000, SparseHoles: []SparseEntry{{0, 500}}}, 738 formats: FormatPAX, 739 }, { 740 header: &Header{Name: "sparse.db", Size: 1000, Typeflag: TypeGNUSparse, SparseHoles: []SparseEntry{{0, 500}}}, 741 formats: FormatGNU, 742 }, { 743 header: &Header{Name: "sparse.db", Size: 1000, SparseHoles: []SparseEntry{{0, 500}}, Format: FormatGNU}, 744 formats: FormatUnknown, 745 }, { 746 header: &Header{Name: "sparse.db", Size: 1000, Typeflag: TypeGNUSparse, SparseHoles: []SparseEntry{{0, 500}}, Format: FormatPAX}, 747 formats: FormatUnknown, 748 }, { 749 header: &Header{Name: "sparse.db", Size: 1000, SparseHoles: []SparseEntry{{0, 500}}, Format: FormatUSTAR}, 750 formats: FormatUnknown, 751 }, { 752 header: &Header{Name: "foo/", Typeflag: TypeDir}, 753 formats: FormatUSTAR | FormatPAX | FormatGNU, 754 }, { 755 header: &Header{Name: "foo/", Typeflag: TypeReg}, 756 formats: FormatUnknown, 757 }, { 758 header: &Header{Name: "foo/", Typeflag: TypeSymlink}, 759 formats: FormatUSTAR | FormatPAX | FormatGNU, 760 }} 761 762 for i, v := range vectors { 763 formats, paxHdrs, err := v.header.allowedFormats() 764 if formats != v.formats { 765 t.Errorf("test %d, allowedFormats(): got %v, want %v", i, formats, v.formats) 766 } 767 if formats&FormatPAX > 0 && !reflect.DeepEqual(paxHdrs, v.paxHdrs) && !(len(paxHdrs) == 0 && len(v.paxHdrs) == 0) { 768 t.Errorf("test %d, allowedFormats():\ngot %v\nwant %s", i, paxHdrs, v.paxHdrs) 769 } 770 if (formats != FormatUnknown) && (err != nil) { 771 t.Errorf("test %d, unexpected error: %v", i, err) 772 } 773 if (formats == FormatUnknown) && (err == nil) { 774 t.Errorf("test %d, got nil-error, want non-nil error", i) 775 } 776 } 777 } 778 779 func TestSparseFiles(t *testing.T) { 780 if runtime.GOOS == "plan9" { 781 t.Skip("skipping test on plan9; see https://golang.org/issue/21977") 782 } 783 // Only perform the tests for hole-detection on the builders, 784 // where we have greater control over the filesystem. 785 sparseSupport := testenv.Builder() != "" 786 switch runtime.GOOS + "-" + runtime.GOARCH { 787 case "linux-amd64", "linux-386", "windows-amd64", "windows-386": 788 default: 789 sparseSupport = false 790 } 791 792 vectors := []struct { 793 label string 794 sparseMap sparseHoles 795 }{ 796 {"EmptyFile", sparseHoles{{0, 0}}}, 797 {"BigData", sparseHoles{{1e6, 0}}}, 798 {"BigHole", sparseHoles{{0, 1e6}}}, 799 {"DataFront", sparseHoles{{1e3, 1e6 - 1e3}}}, 800 {"HoleFront", sparseHoles{{0, 1e6 - 1e3}, {1e6, 0}}}, 801 {"DataMiddle", sparseHoles{{0, 5e5 - 1e3}, {5e5, 5e5}}}, 802 {"HoleMiddle", sparseHoles{{1e3, 1e6 - 2e3}, {1e6, 0}}}, 803 {"Multiple", func() (sph []SparseEntry) { 804 const chunkSize = 1e6 805 for i := 0; i < 100; i++ { 806 sph = append(sph, SparseEntry{chunkSize * int64(i), chunkSize - 1e3}) 807 } 808 return append(sph, SparseEntry{int64(len(sph) * chunkSize), 0}) 809 }()}, 810 } 811 812 for _, v := range vectors { 813 sph := v.sparseMap 814 t.Run(v.label, func(t *testing.T) { 815 src, err := ioutil.TempFile("", "") 816 if err != nil { 817 t.Fatalf("unexpected TempFile error: %v", err) 818 } 819 defer os.Remove(src.Name()) 820 dst, err := ioutil.TempFile("", "") 821 if err != nil { 822 t.Fatalf("unexpected TempFile error: %v", err) 823 } 824 defer os.Remove(dst.Name()) 825 826 // Create the source sparse file. 827 hdr := Header{ 828 Typeflag: TypeReg, 829 Name: "sparse.db", 830 Size: sph[len(sph)-1].endOffset(), 831 SparseHoles: sph, 832 } 833 junk := bytes.Repeat([]byte{'Z'}, int(hdr.Size+1e3)) 834 if _, err := src.Write(junk); err != nil { 835 t.Fatalf("unexpected Write error: %v", err) 836 } 837 if err := hdr.PunchSparseHoles(src); err != nil { 838 t.Fatalf("unexpected PunchSparseHoles error: %v", err) 839 } 840 var pos int64 841 for _, s := range sph { 842 b := bytes.Repeat([]byte{'X'}, int(s.Offset-pos)) 843 if _, err := src.WriteAt(b, pos); err != nil { 844 t.Fatalf("unexpected WriteAt error: %v", err) 845 } 846 pos = s.endOffset() 847 } 848 849 // Round-trip the sparse file to/from a tar archive. 850 b := new(bytes.Buffer) 851 tw := NewWriter(b) 852 if err := tw.WriteHeader(&hdr); err != nil { 853 t.Fatalf("unexpected WriteHeader error: %v", err) 854 } 855 if _, err := tw.ReadFrom(src); err != nil { 856 t.Fatalf("unexpected ReadFrom error: %v", err) 857 } 858 if err := tw.Close(); err != nil { 859 t.Fatalf("unexpected Close error: %v", err) 860 } 861 tr := NewReader(b) 862 if _, err := tr.Next(); err != nil { 863 t.Fatalf("unexpected Next error: %v", err) 864 } 865 if err := hdr.PunchSparseHoles(dst); err != nil { 866 t.Fatalf("unexpected PunchSparseHoles error: %v", err) 867 } 868 if _, err := tr.WriteTo(dst); err != nil { 869 t.Fatalf("unexpected Copy error: %v", err) 870 } 871 872 // Verify the sparse file matches. 873 // Even if the OS and underlying FS do not support sparse files, 874 // the content should still match (i.e., holes read as zeros). 875 got, err := ioutil.ReadFile(dst.Name()) 876 if err != nil { 877 t.Fatalf("unexpected ReadFile error: %v", err) 878 } 879 want, err := ioutil.ReadFile(src.Name()) 880 if err != nil { 881 t.Fatalf("unexpected ReadFile error: %v", err) 882 } 883 if !bytes.Equal(got, want) { 884 t.Fatal("sparse files mismatch") 885 } 886 887 // Detect and compare the sparse holes. 888 if err := hdr.DetectSparseHoles(dst); err != nil { 889 t.Fatalf("unexpected DetectSparseHoles error: %v", err) 890 } 891 if sparseSupport && sysSparseDetect != nil { 892 if len(sph) > 0 && sph[len(sph)-1].Length == 0 { 893 sph = sph[:len(sph)-1] 894 } 895 if len(hdr.SparseHoles) != len(sph) { 896 t.Fatalf("len(SparseHoles) = %d, want %d", len(hdr.SparseHoles), len(sph)) 897 } 898 for j, got := range hdr.SparseHoles { 899 // Each FS has their own block size, so these may not match. 900 want := sph[j] 901 if got.Offset < want.Offset { 902 t.Errorf("index %d, StartOffset = %d, want <%d", j, got.Offset, want.Offset) 903 } 904 if got.endOffset() > want.endOffset() { 905 t.Errorf("index %d, EndOffset = %d, want >%d", j, got.endOffset(), want.endOffset()) 906 } 907 } 908 } 909 }) 910 } 911 } 912 913 func Benchmark(b *testing.B) { 914 type file struct { 915 hdr *Header 916 body []byte 917 } 918 919 vectors := []struct { 920 label string 921 files []file 922 }{{ 923 "USTAR", 924 []file{{ 925 &Header{Name: "bar", Mode: 0640, Size: int64(3)}, 926 []byte("foo"), 927 }, { 928 &Header{Name: "world", Mode: 0640, Size: int64(5)}, 929 []byte("hello"), 930 }}, 931 }, { 932 "GNU", 933 []file{{ 934 &Header{Name: "bar", Mode: 0640, Size: int64(3), Devmajor: -1}, 935 []byte("foo"), 936 }, { 937 &Header{Name: "world", Mode: 0640, Size: int64(5), Devmajor: -1}, 938 []byte("hello"), 939 }}, 940 }, { 941 "PAX", 942 []file{{ 943 &Header{Name: "bar", Mode: 0640, Size: int64(3), Xattrs: map[string]string{"foo": "bar"}}, 944 []byte("foo"), 945 }, { 946 &Header{Name: "world", Mode: 0640, Size: int64(5), Xattrs: map[string]string{"foo": "bar"}}, 947 []byte("hello"), 948 }}, 949 }} 950 951 b.Run("Writer", func(b *testing.B) { 952 for _, v := range vectors { 953 b.Run(v.label, func(b *testing.B) { 954 b.ReportAllocs() 955 for i := 0; i < b.N; i++ { 956 // Writing to ioutil.Discard because we want to 957 // test purely the writer code and not bring in disk performance into this. 958 tw := NewWriter(ioutil.Discard) 959 for _, file := range v.files { 960 if err := tw.WriteHeader(file.hdr); err != nil { 961 b.Errorf("unexpected WriteHeader error: %v", err) 962 } 963 if _, err := tw.Write(file.body); err != nil { 964 b.Errorf("unexpected Write error: %v", err) 965 } 966 } 967 if err := tw.Close(); err != nil { 968 b.Errorf("unexpected Close error: %v", err) 969 } 970 } 971 }) 972 } 973 }) 974 975 b.Run("Reader", func(b *testing.B) { 976 for _, v := range vectors { 977 var buf bytes.Buffer 978 var r bytes.Reader 979 980 // Write the archive to a byte buffer. 981 tw := NewWriter(&buf) 982 for _, file := range v.files { 983 tw.WriteHeader(file.hdr) 984 tw.Write(file.body) 985 } 986 tw.Close() 987 b.Run(v.label, func(b *testing.B) { 988 b.ReportAllocs() 989 // Read from the byte buffer. 990 for i := 0; i < b.N; i++ { 991 r.Reset(buf.Bytes()) 992 tr := NewReader(&r) 993 if _, err := tr.Next(); err != nil { 994 b.Errorf("unexpected Next error: %v", err) 995 } 996 if _, err := io.Copy(ioutil.Discard, tr); err != nil { 997 b.Errorf("unexpected Copy error : %v", err) 998 } 999 } 1000 }) 1001 } 1002 }) 1003 1004 }