github.com/ice-blockchain/go/src@v0.0.0-20240403114104-1564d284e521/archive/tar/writer_test.go (about) 1 // Copyright 2009 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 "encoding/hex" 10 "errors" 11 "io" 12 "io/fs" 13 "os" 14 "path" 15 "reflect" 16 "sort" 17 "strings" 18 "testing" 19 "testing/fstest" 20 "testing/iotest" 21 "time" 22 ) 23 24 func bytediff(a, b []byte) string { 25 const ( 26 uniqueA = "- " 27 uniqueB = "+ " 28 identity = " " 29 ) 30 var ss []string 31 sa := strings.Split(strings.TrimSpace(hex.Dump(a)), "\n") 32 sb := strings.Split(strings.TrimSpace(hex.Dump(b)), "\n") 33 for len(sa) > 0 && len(sb) > 0 { 34 if sa[0] == sb[0] { 35 ss = append(ss, identity+sa[0]) 36 } else { 37 ss = append(ss, uniqueA+sa[0]) 38 ss = append(ss, uniqueB+sb[0]) 39 } 40 sa, sb = sa[1:], sb[1:] 41 } 42 for len(sa) > 0 { 43 ss = append(ss, uniqueA+sa[0]) 44 sa = sa[1:] 45 } 46 for len(sb) > 0 { 47 ss = append(ss, uniqueB+sb[0]) 48 sb = sb[1:] 49 } 50 return strings.Join(ss, "\n") 51 } 52 53 func TestWriter(t *testing.T) { 54 type ( 55 testHeader struct { // WriteHeader(hdr) == wantErr 56 hdr Header 57 wantErr error 58 } 59 testWrite struct { // Write(str) == (wantCnt, wantErr) 60 str string 61 wantCnt int 62 wantErr error 63 } 64 testReadFrom struct { // ReadFrom(testFile{ops}) == (wantCnt, wantErr) 65 ops fileOps 66 wantCnt int64 67 wantErr error 68 } 69 testClose struct { // Close() == wantErr 70 wantErr error 71 } 72 testFnc any // testHeader | testWrite | testReadFrom | testClose 73 ) 74 75 vectors := []struct { 76 file string // Optional filename of expected output 77 tests []testFnc 78 }{{ 79 // The writer test file was produced with this command: 80 // tar (GNU tar) 1.26 81 // ln -s small.txt link.txt 82 // tar -b 1 --format=ustar -c -f writer.tar small.txt small2.txt link.txt 83 file: "testdata/writer.tar", 84 tests: []testFnc{ 85 testHeader{Header{ 86 Typeflag: TypeReg, 87 Name: "small.txt", 88 Size: 5, 89 Mode: 0640, 90 Uid: 73025, 91 Gid: 5000, 92 Uname: "dsymonds", 93 Gname: "eng", 94 ModTime: time.Unix(1246508266, 0), 95 }, nil}, 96 testWrite{"Kilts", 5, nil}, 97 98 testHeader{Header{ 99 Typeflag: TypeReg, 100 Name: "small2.txt", 101 Size: 11, 102 Mode: 0640, 103 Uid: 73025, 104 Uname: "dsymonds", 105 Gname: "eng", 106 Gid: 5000, 107 ModTime: time.Unix(1245217492, 0), 108 }, nil}, 109 testWrite{"Google.com\n", 11, nil}, 110 111 testHeader{Header{ 112 Typeflag: TypeSymlink, 113 Name: "link.txt", 114 Linkname: "small.txt", 115 Mode: 0777, 116 Uid: 1000, 117 Gid: 1000, 118 Uname: "strings", 119 Gname: "strings", 120 ModTime: time.Unix(1314603082, 0), 121 }, nil}, 122 testWrite{"", 0, nil}, 123 124 testClose{nil}, 125 }, 126 }, { 127 // The truncated test file was produced using these commands: 128 // dd if=/dev/zero bs=1048576 count=16384 > /tmp/16gig.txt 129 // tar -b 1 -c -f- /tmp/16gig.txt | dd bs=512 count=8 > writer-big.tar 130 file: "testdata/writer-big.tar", 131 tests: []testFnc{ 132 testHeader{Header{ 133 Typeflag: TypeReg, 134 Name: "tmp/16gig.txt", 135 Size: 16 << 30, 136 Mode: 0640, 137 Uid: 73025, 138 Gid: 5000, 139 Uname: "dsymonds", 140 Gname: "eng", 141 ModTime: time.Unix(1254699560, 0), 142 Format: FormatGNU, 143 }, nil}, 144 }, 145 }, { 146 // This truncated file was produced using this library. 147 // It was verified to work with GNU tar 1.27.1 and BSD tar 3.1.2. 148 // dd if=/dev/zero bs=1G count=16 >> writer-big-long.tar 149 // gnutar -xvf writer-big-long.tar 150 // bsdtar -xvf writer-big-long.tar 151 // 152 // This file is in PAX format. 153 file: "testdata/writer-big-long.tar", 154 tests: []testFnc{ 155 testHeader{Header{ 156 Typeflag: TypeReg, 157 Name: strings.Repeat("longname/", 15) + "16gig.txt", 158 Size: 16 << 30, 159 Mode: 0644, 160 Uid: 1000, 161 Gid: 1000, 162 Uname: "guillaume", 163 Gname: "guillaume", 164 ModTime: time.Unix(1399583047, 0), 165 }, nil}, 166 }, 167 }, { 168 // This file was produced using GNU tar v1.17. 169 // gnutar -b 4 --format=ustar (longname/)*15 + file.txt 170 file: "testdata/ustar.tar", 171 tests: []testFnc{ 172 testHeader{Header{ 173 Typeflag: TypeReg, 174 Name: strings.Repeat("longname/", 15) + "file.txt", 175 Size: 6, 176 Mode: 0644, 177 Uid: 501, 178 Gid: 20, 179 Uname: "shane", 180 Gname: "staff", 181 ModTime: time.Unix(1360135598, 0), 182 }, nil}, 183 testWrite{"hello\n", 6, nil}, 184 testClose{nil}, 185 }, 186 }, { 187 // This file was produced using GNU tar v1.26: 188 // echo "Slartibartfast" > file.txt 189 // ln file.txt hard.txt 190 // tar -b 1 --format=ustar -c -f hardlink.tar file.txt hard.txt 191 file: "testdata/hardlink.tar", 192 tests: []testFnc{ 193 testHeader{Header{ 194 Typeflag: TypeReg, 195 Name: "file.txt", 196 Size: 15, 197 Mode: 0644, 198 Uid: 1000, 199 Gid: 100, 200 Uname: "vbatts", 201 Gname: "users", 202 ModTime: time.Unix(1425484303, 0), 203 }, nil}, 204 testWrite{"Slartibartfast\n", 15, nil}, 205 206 testHeader{Header{ 207 Typeflag: TypeLink, 208 Name: "hard.txt", 209 Linkname: "file.txt", 210 Mode: 0644, 211 Uid: 1000, 212 Gid: 100, 213 Uname: "vbatts", 214 Gname: "users", 215 ModTime: time.Unix(1425484303, 0), 216 }, nil}, 217 testWrite{"", 0, nil}, 218 219 testClose{nil}, 220 }, 221 }, { 222 tests: []testFnc{ 223 testHeader{Header{ 224 Typeflag: TypeReg, 225 Name: "bad-null.txt", 226 Xattrs: map[string]string{"null\x00null\x00": "fizzbuzz"}, 227 }, headerError{}}, 228 }, 229 }, { 230 tests: []testFnc{ 231 testHeader{Header{ 232 Typeflag: TypeReg, 233 Name: "null\x00.txt", 234 }, headerError{}}, 235 }, 236 }, { 237 file: "testdata/pax-records.tar", 238 tests: []testFnc{ 239 testHeader{Header{ 240 Typeflag: TypeReg, 241 Name: "file", 242 Uname: strings.Repeat("long", 10), 243 PAXRecords: map[string]string{ 244 "path": "FILE", // Should be ignored 245 "GNU.sparse.map": "0,0", // Should be ignored 246 "comment": "Hello, 世界", 247 "GOLANG.pkg": "tar", 248 }, 249 }, nil}, 250 testClose{nil}, 251 }, 252 }, { 253 // Craft a theoretically valid PAX archive with global headers. 254 // The GNU and BSD tar tools do not parse these the same way. 255 // 256 // BSD tar v3.1.2 parses and ignores all global headers; 257 // the behavior is verified by researching the source code. 258 // 259 // $ bsdtar -tvf pax-global-records.tar 260 // ---------- 0 0 0 0 Dec 31 1969 file1 261 // ---------- 0 0 0 0 Dec 31 1969 file2 262 // ---------- 0 0 0 0 Dec 31 1969 file3 263 // ---------- 0 0 0 0 May 13 2014 file4 264 // 265 // GNU tar v1.27.1 applies global headers to subsequent records, 266 // but does not do the following properly: 267 // * It does not treat an empty record as deletion. 268 // * It does not use subsequent global headers to update previous ones. 269 // 270 // $ gnutar -tvf pax-global-records.tar 271 // ---------- 0/0 0 2017-07-13 19:40 global1 272 // ---------- 0/0 0 2017-07-13 19:40 file2 273 // gnutar: Substituting `.' for empty member name 274 // ---------- 0/0 0 1969-12-31 16:00 275 // gnutar: Substituting `.' for empty member name 276 // ---------- 0/0 0 2014-05-13 09:53 277 // 278 // According to the PAX specification, this should have been the result: 279 // ---------- 0/0 0 2017-07-13 19:40 global1 280 // ---------- 0/0 0 2017-07-13 19:40 file2 281 // ---------- 0/0 0 2017-07-13 19:40 file3 282 // ---------- 0/0 0 2014-05-13 09:53 file4 283 file: "testdata/pax-global-records.tar", 284 tests: []testFnc{ 285 testHeader{Header{ 286 Typeflag: TypeXGlobalHeader, 287 PAXRecords: map[string]string{"path": "global1", "mtime": "1500000000.0"}, 288 }, nil}, 289 testHeader{Header{ 290 Typeflag: TypeReg, Name: "file1", 291 }, nil}, 292 testHeader{Header{ 293 Typeflag: TypeReg, 294 Name: "file2", 295 PAXRecords: map[string]string{"path": "file2"}, 296 }, nil}, 297 testHeader{Header{ 298 Typeflag: TypeXGlobalHeader, 299 PAXRecords: map[string]string{"path": ""}, // Should delete "path", but keep "mtime" 300 }, nil}, 301 testHeader{Header{ 302 Typeflag: TypeReg, Name: "file3", 303 }, nil}, 304 testHeader{Header{ 305 Typeflag: TypeReg, 306 Name: "file4", 307 ModTime: time.Unix(1400000000, 0), 308 PAXRecords: map[string]string{"mtime": "1400000000"}, 309 }, nil}, 310 testClose{nil}, 311 }, 312 }, { 313 file: "testdata/gnu-utf8.tar", 314 tests: []testFnc{ 315 testHeader{Header{ 316 Typeflag: TypeReg, 317 Name: "☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹", 318 Mode: 0644, 319 Uid: 1000, Gid: 1000, 320 Uname: "☺", 321 Gname: "⚹", 322 ModTime: time.Unix(0, 0), 323 Format: FormatGNU, 324 }, nil}, 325 testClose{nil}, 326 }, 327 }, { 328 file: "testdata/gnu-not-utf8.tar", 329 tests: []testFnc{ 330 testHeader{Header{ 331 Typeflag: TypeReg, 332 Name: "hi\x80\x81\x82\x83bye", 333 Mode: 0644, 334 Uid: 1000, 335 Gid: 1000, 336 Uname: "rawr", 337 Gname: "dsnet", 338 ModTime: time.Unix(0, 0), 339 Format: FormatGNU, 340 }, nil}, 341 testClose{nil}, 342 }, 343 // TODO(dsnet): Re-enable this test when adding sparse support. 344 // See https://golang.org/issue/22735 345 /* 346 }, { 347 file: "testdata/gnu-nil-sparse-data.tar", 348 tests: []testFnc{ 349 testHeader{Header{ 350 Typeflag: TypeGNUSparse, 351 Name: "sparse.db", 352 Size: 1000, 353 SparseHoles: []sparseEntry{{Offset: 1000, Length: 0}}, 354 }, nil}, 355 testWrite{strings.Repeat("0123456789", 100), 1000, nil}, 356 testClose{}, 357 }, 358 }, { 359 file: "testdata/gnu-nil-sparse-hole.tar", 360 tests: []testFnc{ 361 testHeader{Header{ 362 Typeflag: TypeGNUSparse, 363 Name: "sparse.db", 364 Size: 1000, 365 SparseHoles: []sparseEntry{{Offset: 0, Length: 1000}}, 366 }, nil}, 367 testWrite{strings.Repeat("\x00", 1000), 1000, nil}, 368 testClose{}, 369 }, 370 }, { 371 file: "testdata/pax-nil-sparse-data.tar", 372 tests: []testFnc{ 373 testHeader{Header{ 374 Typeflag: TypeReg, 375 Name: "sparse.db", 376 Size: 1000, 377 SparseHoles: []sparseEntry{{Offset: 1000, Length: 0}}, 378 }, nil}, 379 testWrite{strings.Repeat("0123456789", 100), 1000, nil}, 380 testClose{}, 381 }, 382 }, { 383 file: "testdata/pax-nil-sparse-hole.tar", 384 tests: []testFnc{ 385 testHeader{Header{ 386 Typeflag: TypeReg, 387 Name: "sparse.db", 388 Size: 1000, 389 SparseHoles: []sparseEntry{{Offset: 0, Length: 1000}}, 390 }, nil}, 391 testWrite{strings.Repeat("\x00", 1000), 1000, nil}, 392 testClose{}, 393 }, 394 }, { 395 file: "testdata/gnu-sparse-big.tar", 396 tests: []testFnc{ 397 testHeader{Header{ 398 Typeflag: TypeGNUSparse, 399 Name: "gnu-sparse", 400 Size: 6e10, 401 SparseHoles: []sparseEntry{ 402 {Offset: 0e10, Length: 1e10 - 100}, 403 {Offset: 1e10, Length: 1e10 - 100}, 404 {Offset: 2e10, Length: 1e10 - 100}, 405 {Offset: 3e10, Length: 1e10 - 100}, 406 {Offset: 4e10, Length: 1e10 - 100}, 407 {Offset: 5e10, Length: 1e10 - 100}, 408 }, 409 }, nil}, 410 testReadFrom{fileOps{ 411 int64(1e10 - blockSize), 412 strings.Repeat("\x00", blockSize-100) + strings.Repeat("0123456789", 10), 413 int64(1e10 - blockSize), 414 strings.Repeat("\x00", blockSize-100) + strings.Repeat("0123456789", 10), 415 int64(1e10 - blockSize), 416 strings.Repeat("\x00", blockSize-100) + strings.Repeat("0123456789", 10), 417 int64(1e10 - blockSize), 418 strings.Repeat("\x00", blockSize-100) + strings.Repeat("0123456789", 10), 419 int64(1e10 - blockSize), 420 strings.Repeat("\x00", blockSize-100) + strings.Repeat("0123456789", 10), 421 int64(1e10 - blockSize), 422 strings.Repeat("\x00", blockSize-100) + strings.Repeat("0123456789", 10), 423 }, 6e10, nil}, 424 testClose{nil}, 425 }, 426 }, { 427 file: "testdata/pax-sparse-big.tar", 428 tests: []testFnc{ 429 testHeader{Header{ 430 Typeflag: TypeReg, 431 Name: "pax-sparse", 432 Size: 6e10, 433 SparseHoles: []sparseEntry{ 434 {Offset: 0e10, Length: 1e10 - 100}, 435 {Offset: 1e10, Length: 1e10 - 100}, 436 {Offset: 2e10, Length: 1e10 - 100}, 437 {Offset: 3e10, Length: 1e10 - 100}, 438 {Offset: 4e10, Length: 1e10 - 100}, 439 {Offset: 5e10, Length: 1e10 - 100}, 440 }, 441 }, nil}, 442 testReadFrom{fileOps{ 443 int64(1e10 - blockSize), 444 strings.Repeat("\x00", blockSize-100) + strings.Repeat("0123456789", 10), 445 int64(1e10 - blockSize), 446 strings.Repeat("\x00", blockSize-100) + strings.Repeat("0123456789", 10), 447 int64(1e10 - blockSize), 448 strings.Repeat("\x00", blockSize-100) + strings.Repeat("0123456789", 10), 449 int64(1e10 - blockSize), 450 strings.Repeat("\x00", blockSize-100) + strings.Repeat("0123456789", 10), 451 int64(1e10 - blockSize), 452 strings.Repeat("\x00", blockSize-100) + strings.Repeat("0123456789", 10), 453 int64(1e10 - blockSize), 454 strings.Repeat("\x00", blockSize-100) + strings.Repeat("0123456789", 10), 455 }, 6e10, nil}, 456 testClose{nil}, 457 }, 458 */ 459 }, { 460 file: "testdata/trailing-slash.tar", 461 tests: []testFnc{ 462 testHeader{Header{Name: strings.Repeat("123456789/", 30)}, nil}, 463 testClose{nil}, 464 }, 465 }, { 466 // Automatically promote zero value of Typeflag depending on the name. 467 file: "testdata/file-and-dir.tar", 468 tests: []testFnc{ 469 testHeader{Header{Name: "small.txt", Size: 5}, nil}, 470 testWrite{"Kilts", 5, nil}, 471 testHeader{Header{Name: "dir/"}, nil}, 472 testClose{nil}, 473 }, 474 }} 475 476 equalError := func(x, y error) bool { 477 _, ok1 := x.(headerError) 478 _, ok2 := y.(headerError) 479 if ok1 || ok2 { 480 return ok1 && ok2 481 } 482 return x == y 483 } 484 for _, v := range vectors { 485 t.Run(path.Base(v.file), func(t *testing.T) { 486 const maxSize = 10 << 10 // 10KiB 487 buf := new(bytes.Buffer) 488 tw := NewWriter(iotest.TruncateWriter(buf, maxSize)) 489 490 for i, tf := range v.tests { 491 switch tf := tf.(type) { 492 case testHeader: 493 err := tw.WriteHeader(&tf.hdr) 494 if !equalError(err, tf.wantErr) { 495 t.Fatalf("test %d, WriteHeader() = %v, want %v", i, err, tf.wantErr) 496 } 497 case testWrite: 498 got, err := tw.Write([]byte(tf.str)) 499 if got != tf.wantCnt || !equalError(err, tf.wantErr) { 500 t.Fatalf("test %d, Write() = (%d, %v), want (%d, %v)", i, got, err, tf.wantCnt, tf.wantErr) 501 } 502 case testReadFrom: 503 f := &testFile{ops: tf.ops} 504 got, err := tw.readFrom(f) 505 if _, ok := err.(testError); ok { 506 t.Errorf("test %d, ReadFrom(): %v", i, err) 507 } else if got != tf.wantCnt || !equalError(err, tf.wantErr) { 508 t.Errorf("test %d, ReadFrom() = (%d, %v), want (%d, %v)", i, got, err, tf.wantCnt, tf.wantErr) 509 } 510 if len(f.ops) > 0 { 511 t.Errorf("test %d, expected %d more operations", i, len(f.ops)) 512 } 513 case testClose: 514 err := tw.Close() 515 if !equalError(err, tf.wantErr) { 516 t.Fatalf("test %d, Close() = %v, want %v", i, err, tf.wantErr) 517 } 518 default: 519 t.Fatalf("test %d, unknown test operation: %T", i, tf) 520 } 521 } 522 523 if v.file != "" { 524 want, err := os.ReadFile(v.file) 525 if err != nil { 526 t.Fatalf("ReadFile() = %v, want nil", err) 527 } 528 got := buf.Bytes() 529 if !bytes.Equal(want, got) { 530 t.Fatalf("incorrect result: (-got +want)\n%v", bytediff(got, want)) 531 } 532 } 533 }) 534 } 535 } 536 537 func TestPax(t *testing.T) { 538 // Create an archive with a large name 539 fileinfo, err := os.Stat("testdata/small.txt") 540 if err != nil { 541 t.Fatal(err) 542 } 543 hdr, err := FileInfoHeader(fileinfo, "") 544 if err != nil { 545 t.Fatalf("os.Stat: %v", err) 546 } 547 // Force a PAX long name to be written 548 longName := strings.Repeat("ab", 100) 549 contents := strings.Repeat(" ", int(hdr.Size)) 550 hdr.Name = longName 551 var buf bytes.Buffer 552 writer := NewWriter(&buf) 553 if err := writer.WriteHeader(hdr); err != nil { 554 t.Fatal(err) 555 } 556 if _, err = writer.Write([]byte(contents)); err != nil { 557 t.Fatal(err) 558 } 559 if err := writer.Close(); err != nil { 560 t.Fatal(err) 561 } 562 // Simple test to make sure PAX extensions are in effect 563 if !bytes.Contains(buf.Bytes(), []byte("PaxHeaders.0")) { 564 t.Fatal("Expected at least one PAX header to be written.") 565 } 566 // Test that we can get a long name back out of the archive. 567 reader := NewReader(&buf) 568 hdr, err = reader.Next() 569 if err != nil { 570 t.Fatal(err) 571 } 572 if hdr.Name != longName { 573 t.Fatal("Couldn't recover long file name") 574 } 575 } 576 577 func TestPaxSymlink(t *testing.T) { 578 // Create an archive with a large linkname 579 fileinfo, err := os.Stat("testdata/small.txt") 580 if err != nil { 581 t.Fatal(err) 582 } 583 hdr, err := FileInfoHeader(fileinfo, "") 584 if err != nil { 585 t.Fatalf("os.Stat:1 %v", err) 586 } 587 hdr.Typeflag = TypeSymlink 588 // Force a PAX long linkname to be written 589 longLinkname := strings.Repeat("1234567890/1234567890", 10) 590 hdr.Linkname = longLinkname 591 592 hdr.Size = 0 593 var buf bytes.Buffer 594 writer := NewWriter(&buf) 595 if err := writer.WriteHeader(hdr); err != nil { 596 t.Fatal(err) 597 } 598 if err := writer.Close(); err != nil { 599 t.Fatal(err) 600 } 601 // Simple test to make sure PAX extensions are in effect 602 if !bytes.Contains(buf.Bytes(), []byte("PaxHeaders.0")) { 603 t.Fatal("Expected at least one PAX header to be written.") 604 } 605 // Test that we can get a long name back out of the archive. 606 reader := NewReader(&buf) 607 hdr, err = reader.Next() 608 if err != nil { 609 t.Fatal(err) 610 } 611 if hdr.Linkname != longLinkname { 612 t.Fatal("Couldn't recover long link name") 613 } 614 } 615 616 func TestPaxNonAscii(t *testing.T) { 617 // Create an archive with non ascii. These should trigger a pax header 618 // because pax headers have a defined utf-8 encoding. 619 fileinfo, err := os.Stat("testdata/small.txt") 620 if err != nil { 621 t.Fatal(err) 622 } 623 624 hdr, err := FileInfoHeader(fileinfo, "") 625 if err != nil { 626 t.Fatalf("os.Stat:1 %v", err) 627 } 628 629 // some sample data 630 chineseFilename := "文件名" 631 chineseGroupname := "組" 632 chineseUsername := "用戶名" 633 634 hdr.Name = chineseFilename 635 hdr.Gname = chineseGroupname 636 hdr.Uname = chineseUsername 637 638 contents := strings.Repeat(" ", int(hdr.Size)) 639 640 var buf bytes.Buffer 641 writer := NewWriter(&buf) 642 if err := writer.WriteHeader(hdr); err != nil { 643 t.Fatal(err) 644 } 645 if _, err = writer.Write([]byte(contents)); err != nil { 646 t.Fatal(err) 647 } 648 if err := writer.Close(); err != nil { 649 t.Fatal(err) 650 } 651 // Simple test to make sure PAX extensions are in effect 652 if !bytes.Contains(buf.Bytes(), []byte("PaxHeaders.0")) { 653 t.Fatal("Expected at least one PAX header to be written.") 654 } 655 // Test that we can get a long name back out of the archive. 656 reader := NewReader(&buf) 657 hdr, err = reader.Next() 658 if err != nil { 659 t.Fatal(err) 660 } 661 if hdr.Name != chineseFilename { 662 t.Fatal("Couldn't recover unicode name") 663 } 664 if hdr.Gname != chineseGroupname { 665 t.Fatal("Couldn't recover unicode group") 666 } 667 if hdr.Uname != chineseUsername { 668 t.Fatal("Couldn't recover unicode user") 669 } 670 } 671 672 func TestPaxXattrs(t *testing.T) { 673 xattrs := map[string]string{ 674 "user.key": "value", 675 } 676 677 // Create an archive with an xattr 678 fileinfo, err := os.Stat("testdata/small.txt") 679 if err != nil { 680 t.Fatal(err) 681 } 682 hdr, err := FileInfoHeader(fileinfo, "") 683 if err != nil { 684 t.Fatalf("os.Stat: %v", err) 685 } 686 contents := "Kilts" 687 hdr.Xattrs = xattrs 688 var buf bytes.Buffer 689 writer := NewWriter(&buf) 690 if err := writer.WriteHeader(hdr); err != nil { 691 t.Fatal(err) 692 } 693 if _, err = writer.Write([]byte(contents)); err != nil { 694 t.Fatal(err) 695 } 696 if err := writer.Close(); err != nil { 697 t.Fatal(err) 698 } 699 // Test that we can get the xattrs back out of the archive. 700 reader := NewReader(&buf) 701 hdr, err = reader.Next() 702 if err != nil { 703 t.Fatal(err) 704 } 705 if !reflect.DeepEqual(hdr.Xattrs, xattrs) { 706 t.Fatalf("xattrs did not survive round trip: got %+v, want %+v", 707 hdr.Xattrs, xattrs) 708 } 709 } 710 711 func TestPaxHeadersSorted(t *testing.T) { 712 fileinfo, err := os.Stat("testdata/small.txt") 713 if err != nil { 714 t.Fatal(err) 715 } 716 hdr, err := FileInfoHeader(fileinfo, "") 717 if err != nil { 718 t.Fatalf("os.Stat: %v", err) 719 } 720 contents := strings.Repeat(" ", int(hdr.Size)) 721 722 hdr.Xattrs = map[string]string{ 723 "foo": "foo", 724 "bar": "bar", 725 "baz": "baz", 726 "qux": "qux", 727 } 728 729 var buf bytes.Buffer 730 writer := NewWriter(&buf) 731 if err := writer.WriteHeader(hdr); err != nil { 732 t.Fatal(err) 733 } 734 if _, err = writer.Write([]byte(contents)); err != nil { 735 t.Fatal(err) 736 } 737 if err := writer.Close(); err != nil { 738 t.Fatal(err) 739 } 740 // Simple test to make sure PAX extensions are in effect 741 if !bytes.Contains(buf.Bytes(), []byte("PaxHeaders.0")) { 742 t.Fatal("Expected at least one PAX header to be written.") 743 } 744 745 // xattr bar should always appear before others 746 indices := []int{ 747 bytes.Index(buf.Bytes(), []byte("bar=bar")), 748 bytes.Index(buf.Bytes(), []byte("baz=baz")), 749 bytes.Index(buf.Bytes(), []byte("foo=foo")), 750 bytes.Index(buf.Bytes(), []byte("qux=qux")), 751 } 752 if !sort.IntsAreSorted(indices) { 753 t.Fatal("PAX headers are not sorted") 754 } 755 } 756 757 func TestUSTARLongName(t *testing.T) { 758 // Create an archive with a path that failed to split with USTAR extension in previous versions. 759 fileinfo, err := os.Stat("testdata/small.txt") 760 if err != nil { 761 t.Fatal(err) 762 } 763 hdr, err := FileInfoHeader(fileinfo, "") 764 if err != nil { 765 t.Fatalf("os.Stat:1 %v", err) 766 } 767 hdr.Typeflag = TypeDir 768 // Force a PAX long name to be written. The name was taken from a practical example 769 // that fails and replaced ever char through numbers to anonymize the sample. 770 longName := "/0000_0000000/00000-000000000/0000_0000000/00000-0000000000000/0000_0000000/00000-0000000-00000000/0000_0000000/00000000/0000_0000000/000/0000_0000000/00000000v00/0000_0000000/000000/0000_0000000/0000000/0000_0000000/00000y-00/0000/0000/00000000/0x000000/" 771 hdr.Name = longName 772 773 hdr.Size = 0 774 var buf bytes.Buffer 775 writer := NewWriter(&buf) 776 if err := writer.WriteHeader(hdr); err != nil { 777 t.Fatal(err) 778 } 779 if err := writer.Close(); err != nil { 780 t.Fatal(err) 781 } 782 // Test that we can get a long name back out of the archive. 783 reader := NewReader(&buf) 784 hdr, err = reader.Next() 785 if err != nil && err != ErrInsecurePath { 786 t.Fatal(err) 787 } 788 if hdr.Name != longName { 789 t.Fatal("Couldn't recover long name") 790 } 791 } 792 793 func TestValidTypeflagWithPAXHeader(t *testing.T) { 794 var buffer bytes.Buffer 795 tw := NewWriter(&buffer) 796 797 fileName := strings.Repeat("ab", 100) 798 799 hdr := &Header{ 800 Name: fileName, 801 Size: 4, 802 Typeflag: 0, 803 } 804 if err := tw.WriteHeader(hdr); err != nil { 805 t.Fatalf("Failed to write header: %s", err) 806 } 807 if _, err := tw.Write([]byte("fooo")); err != nil { 808 t.Fatalf("Failed to write the file's data: %s", err) 809 } 810 tw.Close() 811 812 tr := NewReader(&buffer) 813 814 for { 815 header, err := tr.Next() 816 if err == io.EOF { 817 break 818 } 819 if err != nil { 820 t.Fatalf("Failed to read header: %s", err) 821 } 822 if header.Typeflag != TypeReg { 823 t.Fatalf("Typeflag should've been %d, found %d", TypeReg, header.Typeflag) 824 } 825 } 826 } 827 828 // failOnceWriter fails exactly once and then always reports success. 829 type failOnceWriter bool 830 831 func (w *failOnceWriter) Write(b []byte) (int, error) { 832 if !*w { 833 return 0, io.ErrShortWrite 834 } 835 *w = true 836 return len(b), nil 837 } 838 839 func TestWriterErrors(t *testing.T) { 840 t.Run("HeaderOnly", func(t *testing.T) { 841 tw := NewWriter(new(bytes.Buffer)) 842 hdr := &Header{Name: "dir/", Typeflag: TypeDir} 843 if err := tw.WriteHeader(hdr); err != nil { 844 t.Fatalf("WriteHeader() = %v, want nil", err) 845 } 846 if _, err := tw.Write([]byte{0x00}); err != ErrWriteTooLong { 847 t.Fatalf("Write() = %v, want %v", err, ErrWriteTooLong) 848 } 849 }) 850 851 t.Run("NegativeSize", func(t *testing.T) { 852 tw := NewWriter(new(bytes.Buffer)) 853 hdr := &Header{Name: "small.txt", Size: -1} 854 if err := tw.WriteHeader(hdr); err == nil { 855 t.Fatalf("WriteHeader() = nil, want non-nil error") 856 } 857 }) 858 859 t.Run("BeforeHeader", func(t *testing.T) { 860 tw := NewWriter(new(bytes.Buffer)) 861 if _, err := tw.Write([]byte("Kilts")); err != ErrWriteTooLong { 862 t.Fatalf("Write() = %v, want %v", err, ErrWriteTooLong) 863 } 864 }) 865 866 t.Run("AfterClose", func(t *testing.T) { 867 tw := NewWriter(new(bytes.Buffer)) 868 hdr := &Header{Name: "small.txt"} 869 if err := tw.WriteHeader(hdr); err != nil { 870 t.Fatalf("WriteHeader() = %v, want nil", err) 871 } 872 if err := tw.Close(); err != nil { 873 t.Fatalf("Close() = %v, want nil", err) 874 } 875 if _, err := tw.Write([]byte("Kilts")); err != ErrWriteAfterClose { 876 t.Fatalf("Write() = %v, want %v", err, ErrWriteAfterClose) 877 } 878 if err := tw.Flush(); err != ErrWriteAfterClose { 879 t.Fatalf("Flush() = %v, want %v", err, ErrWriteAfterClose) 880 } 881 if err := tw.Close(); err != nil { 882 t.Fatalf("Close() = %v, want nil", err) 883 } 884 }) 885 886 t.Run("PrematureFlush", func(t *testing.T) { 887 tw := NewWriter(new(bytes.Buffer)) 888 hdr := &Header{Name: "small.txt", Size: 5} 889 if err := tw.WriteHeader(hdr); err != nil { 890 t.Fatalf("WriteHeader() = %v, want nil", err) 891 } 892 if err := tw.Flush(); err == nil { 893 t.Fatalf("Flush() = %v, want non-nil error", err) 894 } 895 }) 896 897 t.Run("PrematureClose", func(t *testing.T) { 898 tw := NewWriter(new(bytes.Buffer)) 899 hdr := &Header{Name: "small.txt", Size: 5} 900 if err := tw.WriteHeader(hdr); err != nil { 901 t.Fatalf("WriteHeader() = %v, want nil", err) 902 } 903 if err := tw.Close(); err == nil { 904 t.Fatalf("Close() = %v, want non-nil error", err) 905 } 906 }) 907 908 t.Run("Persistence", func(t *testing.T) { 909 tw := NewWriter(new(failOnceWriter)) 910 if err := tw.WriteHeader(&Header{}); err != io.ErrShortWrite { 911 t.Fatalf("WriteHeader() = %v, want %v", err, io.ErrShortWrite) 912 } 913 if err := tw.WriteHeader(&Header{Name: "small.txt"}); err == nil { 914 t.Errorf("WriteHeader() = got %v, want non-nil error", err) 915 } 916 if _, err := tw.Write(nil); err == nil { 917 t.Errorf("Write() = %v, want non-nil error", err) 918 } 919 if err := tw.Flush(); err == nil { 920 t.Errorf("Flush() = %v, want non-nil error", err) 921 } 922 if err := tw.Close(); err == nil { 923 t.Errorf("Close() = %v, want non-nil error", err) 924 } 925 }) 926 } 927 928 func TestSplitUSTARPath(t *testing.T) { 929 sr := strings.Repeat 930 931 vectors := []struct { 932 input string // Input path 933 prefix string // Expected output prefix 934 suffix string // Expected output suffix 935 ok bool // Split success? 936 }{ 937 {"", "", "", false}, 938 {"abc", "", "", false}, 939 {"用戶名", "", "", false}, 940 {sr("a", nameSize), "", "", false}, 941 {sr("a", nameSize) + "/", "", "", false}, 942 {sr("a", nameSize) + "/a", sr("a", nameSize), "a", true}, 943 {sr("a", prefixSize) + "/", "", "", false}, 944 {sr("a", prefixSize) + "/a", sr("a", prefixSize), "a", true}, 945 {sr("a", nameSize+1), "", "", false}, 946 {sr("/", nameSize+1), sr("/", nameSize-1), "/", true}, 947 {sr("a", prefixSize) + "/" + sr("b", nameSize), 948 sr("a", prefixSize), sr("b", nameSize), true}, 949 {sr("a", prefixSize) + "//" + sr("b", nameSize), "", "", false}, 950 {sr("a/", nameSize), sr("a/", 77) + "a", sr("a/", 22), true}, 951 } 952 953 for _, v := range vectors { 954 prefix, suffix, ok := splitUSTARPath(v.input) 955 if prefix != v.prefix || suffix != v.suffix || ok != v.ok { 956 t.Errorf("splitUSTARPath(%q):\ngot (%q, %q, %v)\nwant (%q, %q, %v)", 957 v.input, prefix, suffix, ok, v.prefix, v.suffix, v.ok) 958 } 959 } 960 } 961 962 // TestIssue12594 tests that the Writer does not attempt to populate the prefix 963 // field when encoding a header in the GNU format. The prefix field is valid 964 // in USTAR and PAX, but not GNU. 965 func TestIssue12594(t *testing.T) { 966 names := []string{ 967 "0/1/2/3/4/5/6/7/8/9/10/11/12/13/14/15/16/17/18/19/20/21/22/23/24/25/26/27/28/29/30/file.txt", 968 "0/1/2/3/4/5/6/7/8/9/10/11/12/13/14/15/16/17/18/19/20/21/22/23/24/25/26/27/28/29/30/31/32/33/file.txt", 969 "0/1/2/3/4/5/6/7/8/9/10/11/12/13/14/15/16/17/18/19/20/21/22/23/24/25/26/27/28/29/30/31/32/333/file.txt", 970 "0/1/2/3/4/5/6/7/8/9/10/11/12/13/14/15/16/17/18/19/20/21/22/23/24/25/26/27/28/29/30/31/32/33/34/35/36/37/38/39/40/file.txt", 971 "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000/file.txt", 972 "/home/support/.openoffice.org/3/user/uno_packages/cache/registry/com.sun.star.comp.deployment.executable.PackageRegistryBackend", 973 } 974 975 for i, name := range names { 976 var b bytes.Buffer 977 978 tw := NewWriter(&b) 979 if err := tw.WriteHeader(&Header{ 980 Name: name, 981 Uid: 1 << 25, // Prevent USTAR format 982 }); err != nil { 983 t.Errorf("test %d, unexpected WriteHeader error: %v", i, err) 984 } 985 if err := tw.Close(); err != nil { 986 t.Errorf("test %d, unexpected Close error: %v", i, err) 987 } 988 989 // The prefix field should never appear in the GNU format. 990 var blk block 991 copy(blk[:], b.Bytes()) 992 prefix := string(blk.toUSTAR().prefix()) 993 prefix, _, _ = strings.Cut(prefix, "\x00") // Truncate at the NUL terminator 994 if blk.getFormat() == FormatGNU && len(prefix) > 0 && strings.HasPrefix(name, prefix) { 995 t.Errorf("test %d, found prefix in GNU format: %s", i, prefix) 996 } 997 998 tr := NewReader(&b) 999 hdr, err := tr.Next() 1000 if err != nil && err != ErrInsecurePath { 1001 t.Errorf("test %d, unexpected Next error: %v", i, err) 1002 } 1003 if hdr.Name != name { 1004 t.Errorf("test %d, hdr.Name = %s, want %s", i, hdr.Name, name) 1005 } 1006 } 1007 } 1008 1009 func TestWriteLongHeader(t *testing.T) { 1010 for _, test := range []struct { 1011 name string 1012 h *Header 1013 }{{ 1014 name: "name too long", 1015 h: &Header{Name: strings.Repeat("a", maxSpecialFileSize)}, 1016 }, { 1017 name: "linkname too long", 1018 h: &Header{Linkname: strings.Repeat("a", maxSpecialFileSize)}, 1019 }, { 1020 name: "uname too long", 1021 h: &Header{Uname: strings.Repeat("a", maxSpecialFileSize)}, 1022 }, { 1023 name: "gname too long", 1024 h: &Header{Gname: strings.Repeat("a", maxSpecialFileSize)}, 1025 }, { 1026 name: "PAX header too long", 1027 h: &Header{PAXRecords: map[string]string{"GOLANG.x": strings.Repeat("a", maxSpecialFileSize)}}, 1028 }} { 1029 w := NewWriter(io.Discard) 1030 if err := w.WriteHeader(test.h); err != ErrFieldTooLong { 1031 t.Errorf("%v: w.WriteHeader() = %v, want ErrFieldTooLong", test.name, err) 1032 } 1033 } 1034 } 1035 1036 // testNonEmptyWriter wraps an io.Writer and ensures that 1037 // Write is never called with an empty buffer. 1038 type testNonEmptyWriter struct{ io.Writer } 1039 1040 func (w testNonEmptyWriter) Write(b []byte) (int, error) { 1041 if len(b) == 0 { 1042 return 0, errors.New("unexpected empty Write call") 1043 } 1044 return w.Writer.Write(b) 1045 } 1046 1047 func TestFileWriter(t *testing.T) { 1048 type ( 1049 testWrite struct { // Write(str) == (wantCnt, wantErr) 1050 str string 1051 wantCnt int 1052 wantErr error 1053 } 1054 testReadFrom struct { // ReadFrom(testFile{ops}) == (wantCnt, wantErr) 1055 ops fileOps 1056 wantCnt int64 1057 wantErr error 1058 } 1059 testRemaining struct { // logicalRemaining() == wantLCnt, physicalRemaining() == wantPCnt 1060 wantLCnt int64 1061 wantPCnt int64 1062 } 1063 testFnc any // testWrite | testReadFrom | testRemaining 1064 ) 1065 1066 type ( 1067 makeReg struct { 1068 size int64 1069 wantStr string 1070 } 1071 makeSparse struct { 1072 makeReg makeReg 1073 sph sparseHoles 1074 size int64 1075 } 1076 fileMaker any // makeReg | makeSparse 1077 ) 1078 1079 vectors := []struct { 1080 maker fileMaker 1081 tests []testFnc 1082 }{{ 1083 maker: makeReg{0, ""}, 1084 tests: []testFnc{ 1085 testRemaining{0, 0}, 1086 testWrite{"", 0, nil}, 1087 testWrite{"a", 0, ErrWriteTooLong}, 1088 testReadFrom{fileOps{""}, 0, nil}, 1089 testReadFrom{fileOps{"a"}, 0, ErrWriteTooLong}, 1090 testRemaining{0, 0}, 1091 }, 1092 }, { 1093 maker: makeReg{1, "a"}, 1094 tests: []testFnc{ 1095 testRemaining{1, 1}, 1096 testWrite{"", 0, nil}, 1097 testWrite{"a", 1, nil}, 1098 testWrite{"bcde", 0, ErrWriteTooLong}, 1099 testWrite{"", 0, nil}, 1100 testReadFrom{fileOps{""}, 0, nil}, 1101 testReadFrom{fileOps{"a"}, 0, ErrWriteTooLong}, 1102 testRemaining{0, 0}, 1103 }, 1104 }, { 1105 maker: makeReg{5, "hello"}, 1106 tests: []testFnc{ 1107 testRemaining{5, 5}, 1108 testWrite{"hello", 5, nil}, 1109 testRemaining{0, 0}, 1110 }, 1111 }, { 1112 maker: makeReg{5, "\x00\x00\x00\x00\x00"}, 1113 tests: []testFnc{ 1114 testRemaining{5, 5}, 1115 testReadFrom{fileOps{"\x00\x00\x00\x00\x00"}, 5, nil}, 1116 testRemaining{0, 0}, 1117 }, 1118 }, { 1119 maker: makeReg{5, "\x00\x00\x00\x00\x00"}, 1120 tests: []testFnc{ 1121 testRemaining{5, 5}, 1122 testReadFrom{fileOps{"\x00\x00\x00\x00\x00extra"}, 5, ErrWriteTooLong}, 1123 testRemaining{0, 0}, 1124 }, 1125 }, { 1126 maker: makeReg{5, "abc\x00\x00"}, 1127 tests: []testFnc{ 1128 testRemaining{5, 5}, 1129 testWrite{"abc", 3, nil}, 1130 testRemaining{2, 2}, 1131 testReadFrom{fileOps{"\x00\x00"}, 2, nil}, 1132 testRemaining{0, 0}, 1133 }, 1134 }, { 1135 maker: makeReg{5, "\x00\x00abc"}, 1136 tests: []testFnc{ 1137 testRemaining{5, 5}, 1138 testWrite{"\x00\x00", 2, nil}, 1139 testRemaining{3, 3}, 1140 testWrite{"abc", 3, nil}, 1141 testReadFrom{fileOps{"z"}, 0, ErrWriteTooLong}, 1142 testWrite{"z", 0, ErrWriteTooLong}, 1143 testRemaining{0, 0}, 1144 }, 1145 }, { 1146 maker: makeSparse{makeReg{5, "abcde"}, sparseHoles{{2, 3}}, 8}, 1147 tests: []testFnc{ 1148 testRemaining{8, 5}, 1149 testWrite{"ab\x00\x00\x00cde", 8, nil}, 1150 testWrite{"a", 0, ErrWriteTooLong}, 1151 testRemaining{0, 0}, 1152 }, 1153 }, { 1154 maker: makeSparse{makeReg{5, "abcde"}, sparseHoles{{2, 3}}, 8}, 1155 tests: []testFnc{ 1156 testWrite{"ab\x00\x00\x00cdez", 8, ErrWriteTooLong}, 1157 testRemaining{0, 0}, 1158 }, 1159 }, { 1160 maker: makeSparse{makeReg{5, "abcde"}, sparseHoles{{2, 3}}, 8}, 1161 tests: []testFnc{ 1162 testWrite{"ab\x00", 3, nil}, 1163 testRemaining{5, 3}, 1164 testWrite{"\x00\x00cde", 5, nil}, 1165 testWrite{"a", 0, ErrWriteTooLong}, 1166 testRemaining{0, 0}, 1167 }, 1168 }, { 1169 maker: makeSparse{makeReg{5, "abcde"}, sparseHoles{{2, 3}}, 8}, 1170 tests: []testFnc{ 1171 testWrite{"ab", 2, nil}, 1172 testRemaining{6, 3}, 1173 testReadFrom{fileOps{int64(3), "cde"}, 6, nil}, 1174 testRemaining{0, 0}, 1175 }, 1176 }, { 1177 maker: makeSparse{makeReg{5, "abcde"}, sparseHoles{{2, 3}}, 8}, 1178 tests: []testFnc{ 1179 testReadFrom{fileOps{"ab", int64(3), "cde"}, 8, nil}, 1180 testRemaining{0, 0}, 1181 }, 1182 }, { 1183 maker: makeSparse{makeReg{5, "abcde"}, sparseHoles{{2, 3}}, 8}, 1184 tests: []testFnc{ 1185 testReadFrom{fileOps{"ab", int64(3), "cdeX"}, 8, ErrWriteTooLong}, 1186 testRemaining{0, 0}, 1187 }, 1188 }, { 1189 maker: makeSparse{makeReg{4, "abcd"}, sparseHoles{{2, 3}}, 8}, 1190 tests: []testFnc{ 1191 testReadFrom{fileOps{"ab", int64(3), "cd"}, 7, io.ErrUnexpectedEOF}, 1192 testRemaining{1, 0}, 1193 }, 1194 }, { 1195 maker: makeSparse{makeReg{4, "abcd"}, sparseHoles{{2, 3}}, 8}, 1196 tests: []testFnc{ 1197 testReadFrom{fileOps{"ab", int64(3), "cde"}, 7, errMissData}, 1198 testRemaining{1, 0}, 1199 }, 1200 }, { 1201 maker: makeSparse{makeReg{6, "abcde"}, sparseHoles{{2, 3}}, 8}, 1202 tests: []testFnc{ 1203 testReadFrom{fileOps{"ab", int64(3), "cde"}, 8, errUnrefData}, 1204 testRemaining{0, 1}, 1205 }, 1206 }, { 1207 maker: makeSparse{makeReg{4, "abcd"}, sparseHoles{{2, 3}}, 8}, 1208 tests: []testFnc{ 1209 testWrite{"ab", 2, nil}, 1210 testRemaining{6, 2}, 1211 testWrite{"\x00\x00\x00", 3, nil}, 1212 testRemaining{3, 2}, 1213 testWrite{"cde", 2, errMissData}, 1214 testRemaining{1, 0}, 1215 }, 1216 }, { 1217 maker: makeSparse{makeReg{6, "abcde"}, sparseHoles{{2, 3}}, 8}, 1218 tests: []testFnc{ 1219 testWrite{"ab", 2, nil}, 1220 testRemaining{6, 4}, 1221 testWrite{"\x00\x00\x00", 3, nil}, 1222 testRemaining{3, 4}, 1223 testWrite{"cde", 3, errUnrefData}, 1224 testRemaining{0, 1}, 1225 }, 1226 }, { 1227 maker: makeSparse{makeReg{3, "abc"}, sparseHoles{{0, 2}, {5, 2}}, 7}, 1228 tests: []testFnc{ 1229 testRemaining{7, 3}, 1230 testWrite{"\x00\x00abc\x00\x00", 7, nil}, 1231 testRemaining{0, 0}, 1232 }, 1233 }, { 1234 maker: makeSparse{makeReg{3, "abc"}, sparseHoles{{0, 2}, {5, 2}}, 7}, 1235 tests: []testFnc{ 1236 testRemaining{7, 3}, 1237 testReadFrom{fileOps{int64(2), "abc", int64(1), "\x00"}, 7, nil}, 1238 testRemaining{0, 0}, 1239 }, 1240 }, { 1241 maker: makeSparse{makeReg{3, ""}, sparseHoles{{0, 2}, {5, 2}}, 7}, 1242 tests: []testFnc{ 1243 testWrite{"abcdefg", 0, errWriteHole}, 1244 }, 1245 }, { 1246 maker: makeSparse{makeReg{3, "abc"}, sparseHoles{{0, 2}, {5, 2}}, 7}, 1247 tests: []testFnc{ 1248 testWrite{"\x00\x00abcde", 5, errWriteHole}, 1249 }, 1250 }, { 1251 maker: makeSparse{makeReg{3, "abc"}, sparseHoles{{0, 2}, {5, 2}}, 7}, 1252 tests: []testFnc{ 1253 testWrite{"\x00\x00abc\x00\x00z", 7, ErrWriteTooLong}, 1254 testRemaining{0, 0}, 1255 }, 1256 }, { 1257 maker: makeSparse{makeReg{3, "abc"}, sparseHoles{{0, 2}, {5, 2}}, 7}, 1258 tests: []testFnc{ 1259 testWrite{"\x00\x00", 2, nil}, 1260 testRemaining{5, 3}, 1261 testWrite{"abc", 3, nil}, 1262 testRemaining{2, 0}, 1263 testWrite{"\x00\x00", 2, nil}, 1264 testRemaining{0, 0}, 1265 }, 1266 }, { 1267 maker: makeSparse{makeReg{2, "ab"}, sparseHoles{{0, 2}, {5, 2}}, 7}, 1268 tests: []testFnc{ 1269 testWrite{"\x00\x00", 2, nil}, 1270 testWrite{"abc", 2, errMissData}, 1271 testWrite{"\x00\x00", 0, errMissData}, 1272 }, 1273 }, { 1274 maker: makeSparse{makeReg{4, "abc"}, sparseHoles{{0, 2}, {5, 2}}, 7}, 1275 tests: []testFnc{ 1276 testWrite{"\x00\x00", 2, nil}, 1277 testWrite{"abc", 3, nil}, 1278 testWrite{"\x00\x00", 2, errUnrefData}, 1279 }, 1280 }} 1281 1282 for i, v := range vectors { 1283 var wantStr string 1284 bb := new(strings.Builder) 1285 w := testNonEmptyWriter{bb} 1286 var fw fileWriter 1287 switch maker := v.maker.(type) { 1288 case makeReg: 1289 fw = ®FileWriter{w, maker.size} 1290 wantStr = maker.wantStr 1291 case makeSparse: 1292 if !validateSparseEntries(maker.sph, maker.size) { 1293 t.Fatalf("invalid sparse map: %v", maker.sph) 1294 } 1295 spd := invertSparseEntries(maker.sph, maker.size) 1296 fw = ®FileWriter{w, maker.makeReg.size} 1297 fw = &sparseFileWriter{fw, spd, 0} 1298 wantStr = maker.makeReg.wantStr 1299 default: 1300 t.Fatalf("test %d, unknown make operation: %T", i, maker) 1301 } 1302 1303 for j, tf := range v.tests { 1304 switch tf := tf.(type) { 1305 case testWrite: 1306 got, err := fw.Write([]byte(tf.str)) 1307 if got != tf.wantCnt || err != tf.wantErr { 1308 t.Errorf("test %d.%d, Write(%s):\ngot (%d, %v)\nwant (%d, %v)", i, j, tf.str, got, err, tf.wantCnt, tf.wantErr) 1309 } 1310 case testReadFrom: 1311 f := &testFile{ops: tf.ops} 1312 got, err := fw.ReadFrom(f) 1313 if _, ok := err.(testError); ok { 1314 t.Errorf("test %d.%d, ReadFrom(): %v", i, j, err) 1315 } else if got != tf.wantCnt || err != tf.wantErr { 1316 t.Errorf("test %d.%d, ReadFrom() = (%d, %v), want (%d, %v)", i, j, got, err, tf.wantCnt, tf.wantErr) 1317 } 1318 if len(f.ops) > 0 { 1319 t.Errorf("test %d.%d, expected %d more operations", i, j, len(f.ops)) 1320 } 1321 case testRemaining: 1322 if got := fw.logicalRemaining(); got != tf.wantLCnt { 1323 t.Errorf("test %d.%d, logicalRemaining() = %d, want %d", i, j, got, tf.wantLCnt) 1324 } 1325 if got := fw.physicalRemaining(); got != tf.wantPCnt { 1326 t.Errorf("test %d.%d, physicalRemaining() = %d, want %d", i, j, got, tf.wantPCnt) 1327 } 1328 default: 1329 t.Fatalf("test %d.%d, unknown test operation: %T", i, j, tf) 1330 } 1331 } 1332 1333 if got := bb.String(); got != wantStr { 1334 t.Fatalf("test %d, String() = %q, want %q", i, got, wantStr) 1335 } 1336 } 1337 } 1338 1339 func TestWriterAddFS(t *testing.T) { 1340 fsys := fstest.MapFS{ 1341 "file.go": {Data: []byte("hello")}, 1342 "subfolder/another.go": {Data: []byte("world")}, 1343 } 1344 var buf bytes.Buffer 1345 tw := NewWriter(&buf) 1346 if err := tw.AddFS(fsys); err != nil { 1347 t.Fatal(err) 1348 } 1349 1350 // Test that we can get the files back from the archive 1351 tr := NewReader(&buf) 1352 1353 entries, err := fsys.ReadDir(".") 1354 if err != nil { 1355 t.Fatal(err) 1356 } 1357 1358 var curfname string 1359 for _, entry := range entries { 1360 curfname = entry.Name() 1361 if entry.IsDir() { 1362 curfname += "/" 1363 continue 1364 } 1365 hdr, err := tr.Next() 1366 if err == io.EOF { 1367 break // End of archive 1368 } 1369 if err != nil { 1370 t.Fatal(err) 1371 } 1372 1373 data, err := io.ReadAll(tr) 1374 if err != nil { 1375 t.Fatal(err) 1376 } 1377 1378 if hdr.Name != curfname { 1379 t.Fatalf("got filename %v, want %v", 1380 curfname, hdr.Name) 1381 } 1382 1383 origdata := fsys[curfname].Data 1384 if string(data) != string(origdata) { 1385 t.Fatalf("got file content %v, want %v", 1386 data, origdata) 1387 } 1388 } 1389 } 1390 1391 func TestWriterAddFSNonRegularFiles(t *testing.T) { 1392 fsys := fstest.MapFS{ 1393 "device": {Data: []byte("hello"), Mode: 0755 | fs.ModeDevice}, 1394 "symlink": {Data: []byte("world"), Mode: 0755 | fs.ModeSymlink}, 1395 } 1396 var buf bytes.Buffer 1397 tw := NewWriter(&buf) 1398 if err := tw.AddFS(fsys); err == nil { 1399 t.Fatal("expected error, got nil") 1400 } 1401 }