github.com/Prakhar-Agarwal-byte/moby@v0.0.0-20231027092010-a14e3e8ab87e/pkg/archive/archive_test.go (about) 1 package archive // import "github.com/Prakhar-Agarwal-byte/moby/pkg/archive" 2 3 import ( 4 "archive/tar" 5 "bytes" 6 "compress/gzip" 7 "errors" 8 "fmt" 9 "io" 10 "io/fs" 11 "os" 12 "os/exec" 13 "path/filepath" 14 "reflect" 15 "runtime" 16 "strings" 17 "testing" 18 "time" 19 20 "github.com/containerd/containerd/pkg/userns" 21 "github.com/Prakhar-Agarwal-byte/moby/pkg/idtools" 22 "github.com/Prakhar-Agarwal-byte/moby/pkg/ioutils" 23 "gotest.tools/v3/assert" 24 is "gotest.tools/v3/assert/cmp" 25 "gotest.tools/v3/skip" 26 ) 27 28 var tmp string 29 30 func init() { 31 tmp = "/tmp/" 32 if runtime.GOOS == "windows" { 33 tmp = os.Getenv("TEMP") + `\` 34 } 35 } 36 37 var defaultArchiver = NewDefaultArchiver() 38 39 func defaultTarUntar(src, dst string) error { 40 return defaultArchiver.TarUntar(src, dst) 41 } 42 43 func defaultUntarPath(src, dst string) error { 44 return defaultArchiver.UntarPath(src, dst) 45 } 46 47 func defaultCopyFileWithTar(src, dst string) (err error) { 48 return defaultArchiver.CopyFileWithTar(src, dst) 49 } 50 51 func defaultCopyWithTar(src, dst string) error { 52 return defaultArchiver.CopyWithTar(src, dst) 53 } 54 55 func TestIsArchivePathDir(t *testing.T) { 56 cmd := exec.Command("sh", "-c", "mkdir -p /tmp/archivedir") 57 output, err := cmd.CombinedOutput() 58 if err != nil { 59 t.Fatalf("Fail to create an archive file for test : %s.", output) 60 } 61 if IsArchivePath(tmp + "archivedir") { 62 t.Fatalf("Incorrectly recognised directory as an archive") 63 } 64 } 65 66 func TestIsArchivePathInvalidFile(t *testing.T) { 67 cmd := exec.Command("sh", "-c", "dd if=/dev/zero bs=1024 count=1 of=/tmp/archive && gzip --stdout /tmp/archive > /tmp/archive.gz") 68 output, err := cmd.CombinedOutput() 69 if err != nil { 70 t.Fatalf("Fail to create an archive file for test : %s.", output) 71 } 72 if IsArchivePath(tmp + "archive") { 73 t.Fatalf("Incorrectly recognised invalid tar path as archive") 74 } 75 if IsArchivePath(tmp + "archive.gz") { 76 t.Fatalf("Incorrectly recognised invalid compressed tar path as archive") 77 } 78 } 79 80 func TestIsArchivePathTar(t *testing.T) { 81 whichTar := "tar" 82 cmdStr := fmt.Sprintf("touch /tmp/archivedata && %s -cf /tmp/archive /tmp/archivedata && gzip --stdout /tmp/archive > /tmp/archive.gz", whichTar) 83 cmd := exec.Command("sh", "-c", cmdStr) 84 output, err := cmd.CombinedOutput() 85 if err != nil { 86 t.Fatalf("Fail to create an archive file for test : %s.", output) 87 } 88 if !IsArchivePath(tmp + "/archive") { 89 t.Fatalf("Did not recognise valid tar path as archive") 90 } 91 if !IsArchivePath(tmp + "archive.gz") { 92 t.Fatalf("Did not recognise valid compressed tar path as archive") 93 } 94 } 95 96 func testDecompressStream(t *testing.T, ext, compressCommand string) io.Reader { 97 cmd := exec.Command("sh", "-c", 98 fmt.Sprintf("touch /tmp/archive && %s /tmp/archive", compressCommand)) 99 output, err := cmd.CombinedOutput() 100 if err != nil { 101 t.Fatalf("Failed to create an archive file for test : %s.", output) 102 } 103 filename := "archive." + ext 104 archive, err := os.Open(tmp + filename) 105 if err != nil { 106 t.Fatalf("Failed to open file %s: %v", filename, err) 107 } 108 defer archive.Close() 109 110 r, err := DecompressStream(archive) 111 if err != nil { 112 t.Fatalf("Failed to decompress %s: %v", filename, err) 113 } 114 if _, err = io.ReadAll(r); err != nil { 115 t.Fatalf("Failed to read the decompressed stream: %v ", err) 116 } 117 if err = r.Close(); err != nil { 118 t.Fatalf("Failed to close the decompressed stream: %v ", err) 119 } 120 121 return r 122 } 123 124 func TestDecompressStreamGzip(t *testing.T) { 125 testDecompressStream(t, "gz", "gzip -f") 126 } 127 128 func TestDecompressStreamBzip2(t *testing.T) { 129 testDecompressStream(t, "bz2", "bzip2 -f") 130 } 131 132 func TestDecompressStreamXz(t *testing.T) { 133 if runtime.GOOS == "windows" { 134 t.Skip("Xz not present in msys2") 135 } 136 testDecompressStream(t, "xz", "xz -f") 137 } 138 139 func TestDecompressStreamZstd(t *testing.T) { 140 if _, err := exec.LookPath("zstd"); err != nil { 141 t.Skip("zstd not installed") 142 } 143 testDecompressStream(t, "zst", "zstd -f") 144 } 145 146 func TestCompressStreamXzUnsupported(t *testing.T) { 147 dest, err := os.Create(tmp + "dest") 148 if err != nil { 149 t.Fatalf("Fail to create the destination file") 150 } 151 defer dest.Close() 152 153 _, err = CompressStream(dest, Xz) 154 if err == nil { 155 t.Fatalf("Should fail as xz is unsupported for compression format.") 156 } 157 } 158 159 func TestCompressStreamBzip2Unsupported(t *testing.T) { 160 dest, err := os.Create(tmp + "dest") 161 if err != nil { 162 t.Fatalf("Fail to create the destination file") 163 } 164 defer dest.Close() 165 166 _, err = CompressStream(dest, Bzip2) 167 if err == nil { 168 t.Fatalf("Should fail as bzip2 is unsupported for compression format.") 169 } 170 } 171 172 func TestCompressStreamInvalid(t *testing.T) { 173 dest, err := os.Create(tmp + "dest") 174 if err != nil { 175 t.Fatalf("Fail to create the destination file") 176 } 177 defer dest.Close() 178 179 _, err = CompressStream(dest, -1) 180 if err == nil { 181 t.Fatalf("Should fail as xz is unsupported for compression format.") 182 } 183 } 184 185 func TestExtensionInvalid(t *testing.T) { 186 compression := Compression(-1) 187 output := compression.Extension() 188 if output != "" { 189 t.Fatalf("The extension of an invalid compression should be an empty string.") 190 } 191 } 192 193 func TestExtensionUncompressed(t *testing.T) { 194 compression := Uncompressed 195 output := compression.Extension() 196 if output != "tar" { 197 t.Fatalf("The extension of an uncompressed archive should be 'tar'.") 198 } 199 } 200 201 func TestExtensionBzip2(t *testing.T) { 202 compression := Bzip2 203 output := compression.Extension() 204 if output != "tar.bz2" { 205 t.Fatalf("The extension of a bzip2 archive should be 'tar.bz2'") 206 } 207 } 208 209 func TestExtensionGzip(t *testing.T) { 210 compression := Gzip 211 output := compression.Extension() 212 if output != "tar.gz" { 213 t.Fatalf("The extension of a gzip archive should be 'tar.gz'") 214 } 215 } 216 217 func TestExtensionXz(t *testing.T) { 218 compression := Xz 219 output := compression.Extension() 220 if output != "tar.xz" { 221 t.Fatalf("The extension of a xz archive should be 'tar.xz'") 222 } 223 } 224 225 func TestExtensionZstd(t *testing.T) { 226 compression := Zstd 227 output := compression.Extension() 228 if output != "tar.zst" { 229 t.Fatalf("The extension of a zstd archive should be 'tar.zst'") 230 } 231 } 232 233 func TestCmdStreamLargeStderr(t *testing.T) { 234 cmd := exec.Command("sh", "-c", "dd if=/dev/zero bs=1k count=1000 of=/dev/stderr; echo hello") 235 out, err := cmdStream(cmd, nil) 236 if err != nil { 237 t.Fatalf("Failed to start command: %s", err) 238 } 239 errCh := make(chan error, 1) 240 go func() { 241 _, err := io.Copy(io.Discard, out) 242 errCh <- err 243 }() 244 select { 245 case err := <-errCh: 246 if err != nil { 247 t.Fatalf("Command should not have failed (err=%.100s...)", err) 248 } 249 case <-time.After(5 * time.Second): 250 t.Fatalf("Command did not complete in 5 seconds; probable deadlock") 251 } 252 } 253 254 func TestCmdStreamBad(t *testing.T) { 255 // TODO Windows: Figure out why this is failing in CI but not locally 256 if runtime.GOOS == "windows" { 257 t.Skip("Failing on Windows CI machines") 258 } 259 badCmd := exec.Command("sh", "-c", "echo hello; echo >&2 error couldn\\'t reverse the phase pulser; exit 1") 260 out, err := cmdStream(badCmd, nil) 261 if err != nil { 262 t.Fatalf("Failed to start command: %s", err) 263 } 264 if output, err := io.ReadAll(out); err == nil { 265 t.Fatalf("Command should have failed") 266 } else if err.Error() != "exit status 1: error couldn't reverse the phase pulser\n" { 267 t.Fatalf("Wrong error value (%s)", err) 268 } else if s := string(output); s != "hello\n" { 269 t.Fatalf("Command output should be '%s', not '%s'", "hello\\n", output) 270 } 271 } 272 273 func TestCmdStreamGood(t *testing.T) { 274 cmd := exec.Command("sh", "-c", "echo hello; exit 0") 275 out, err := cmdStream(cmd, nil) 276 if err != nil { 277 t.Fatal(err) 278 } 279 if output, err := io.ReadAll(out); err != nil { 280 t.Fatalf("Command should not have failed (err=%s)", err) 281 } else if s := string(output); s != "hello\n" { 282 t.Fatalf("Command output should be '%s', not '%s'", "hello\\n", output) 283 } 284 } 285 286 func TestUntarPathWithInvalidDest(t *testing.T) { 287 tempFolder, err := os.MkdirTemp("", "docker-archive-test") 288 assert.NilError(t, err) 289 defer os.RemoveAll(tempFolder) 290 invalidDestFolder := filepath.Join(tempFolder, "invalidDest") 291 // Create a src file 292 srcFile := filepath.Join(tempFolder, "src") 293 tarFile := filepath.Join(tempFolder, "src.tar") 294 os.Create(srcFile) 295 os.Create(invalidDestFolder) // being a file (not dir) should cause an error 296 297 // Translate back to Unix semantics as next exec.Command is run under sh 298 srcFileU := srcFile 299 tarFileU := tarFile 300 if runtime.GOOS == "windows" { 301 tarFileU = "/tmp/" + filepath.Base(filepath.Dir(tarFile)) + "/src.tar" 302 srcFileU = "/tmp/" + filepath.Base(filepath.Dir(srcFile)) + "/src" 303 } 304 305 cmd := exec.Command("sh", "-c", "tar cf "+tarFileU+" "+srcFileU) 306 _, err = cmd.CombinedOutput() 307 assert.NilError(t, err) 308 309 err = defaultUntarPath(tarFile, invalidDestFolder) 310 if err == nil { 311 t.Fatalf("UntarPath with invalid destination path should throw an error.") 312 } 313 } 314 315 func TestUntarPathWithInvalidSrc(t *testing.T) { 316 dest, err := os.MkdirTemp("", "docker-archive-test") 317 if err != nil { 318 t.Fatalf("Fail to create the destination file") 319 } 320 defer os.RemoveAll(dest) 321 err = defaultUntarPath("/invalid/path", dest) 322 if err == nil { 323 t.Fatalf("UntarPath with invalid src path should throw an error.") 324 } 325 } 326 327 func TestUntarPath(t *testing.T) { 328 skip.If(t, runtime.GOOS != "windows" && os.Getuid() != 0, "skipping test that requires root") 329 tmpFolder, err := os.MkdirTemp("", "docker-archive-test") 330 assert.NilError(t, err) 331 defer os.RemoveAll(tmpFolder) 332 srcFile := filepath.Join(tmpFolder, "src") 333 tarFile := filepath.Join(tmpFolder, "src.tar") 334 os.Create(filepath.Join(tmpFolder, "src")) 335 336 destFolder := filepath.Join(tmpFolder, "dest") 337 err = os.MkdirAll(destFolder, 0o740) 338 if err != nil { 339 t.Fatalf("Fail to create the destination file") 340 } 341 342 // Translate back to Unix semantics as next exec.Command is run under sh 343 srcFileU := srcFile 344 tarFileU := tarFile 345 if runtime.GOOS == "windows" { 346 tarFileU = "/tmp/" + filepath.Base(filepath.Dir(tarFile)) + "/src.tar" 347 srcFileU = "/tmp/" + filepath.Base(filepath.Dir(srcFile)) + "/src" 348 } 349 cmd := exec.Command("sh", "-c", "tar cf "+tarFileU+" "+srcFileU) 350 _, err = cmd.CombinedOutput() 351 assert.NilError(t, err) 352 353 err = defaultUntarPath(tarFile, destFolder) 354 if err != nil { 355 t.Fatalf("UntarPath shouldn't throw an error, %s.", err) 356 } 357 expectedFile := filepath.Join(destFolder, srcFileU) 358 _, err = os.Stat(expectedFile) 359 if err != nil { 360 t.Fatalf("Destination folder should contain the source file but did not.") 361 } 362 } 363 364 // Do the same test as above but with the destination as file, it should fail 365 func TestUntarPathWithDestinationFile(t *testing.T) { 366 tmpFolder, err := os.MkdirTemp("", "docker-archive-test") 367 if err != nil { 368 t.Fatal(err) 369 } 370 defer os.RemoveAll(tmpFolder) 371 srcFile := filepath.Join(tmpFolder, "src") 372 tarFile := filepath.Join(tmpFolder, "src.tar") 373 os.Create(filepath.Join(tmpFolder, "src")) 374 375 // Translate back to Unix semantics as next exec.Command is run under sh 376 srcFileU := srcFile 377 tarFileU := tarFile 378 if runtime.GOOS == "windows" { 379 tarFileU = "/tmp/" + filepath.Base(filepath.Dir(tarFile)) + "/src.tar" 380 srcFileU = "/tmp/" + filepath.Base(filepath.Dir(srcFile)) + "/src" 381 } 382 cmd := exec.Command("sh", "-c", "tar cf "+tarFileU+" "+srcFileU) 383 _, err = cmd.CombinedOutput() 384 if err != nil { 385 t.Fatal(err) 386 } 387 destFile := filepath.Join(tmpFolder, "dest") 388 _, err = os.Create(destFile) 389 if err != nil { 390 t.Fatalf("Fail to create the destination file") 391 } 392 err = defaultUntarPath(tarFile, destFile) 393 if err == nil { 394 t.Fatalf("UntarPath should throw an error if the destination if a file") 395 } 396 } 397 398 // Do the same test as above but with the destination folder already exists 399 // and the destination file is a directory 400 // It's working, see https://github.com/Prakhar-Agarwal-byte/moby/issues/10040 401 func TestUntarPathWithDestinationSrcFileAsFolder(t *testing.T) { 402 tmpFolder, err := os.MkdirTemp("", "docker-archive-test") 403 if err != nil { 404 t.Fatal(err) 405 } 406 defer os.RemoveAll(tmpFolder) 407 srcFile := filepath.Join(tmpFolder, "src") 408 tarFile := filepath.Join(tmpFolder, "src.tar") 409 os.Create(srcFile) 410 411 // Translate back to Unix semantics as next exec.Command is run under sh 412 srcFileU := srcFile 413 tarFileU := tarFile 414 if runtime.GOOS == "windows" { 415 tarFileU = "/tmp/" + filepath.Base(filepath.Dir(tarFile)) + "/src.tar" 416 srcFileU = "/tmp/" + filepath.Base(filepath.Dir(srcFile)) + "/src" 417 } 418 419 cmd := exec.Command("sh", "-c", "tar cf "+tarFileU+" "+srcFileU) 420 _, err = cmd.CombinedOutput() 421 if err != nil { 422 t.Fatal(err) 423 } 424 destFolder := filepath.Join(tmpFolder, "dest") 425 err = os.MkdirAll(destFolder, 0o740) 426 if err != nil { 427 t.Fatalf("Fail to create the destination folder") 428 } 429 // Let's create a folder that will has the same path as the extracted file (from tar) 430 destSrcFileAsFolder := filepath.Join(destFolder, srcFileU) 431 err = os.MkdirAll(destSrcFileAsFolder, 0o740) 432 if err != nil { 433 t.Fatal(err) 434 } 435 err = defaultUntarPath(tarFile, destFolder) 436 if err != nil { 437 t.Fatalf("UntarPath should throw not throw an error if the extracted file already exists and is a folder") 438 } 439 } 440 441 func TestCopyWithTarInvalidSrc(t *testing.T) { 442 tempFolder, err := os.MkdirTemp("", "docker-archive-test") 443 if err != nil { 444 t.Fatal(nil) 445 } 446 destFolder := filepath.Join(tempFolder, "dest") 447 invalidSrc := filepath.Join(tempFolder, "doesnotexists") 448 err = os.MkdirAll(destFolder, 0o740) 449 if err != nil { 450 t.Fatal(err) 451 } 452 err = defaultCopyWithTar(invalidSrc, destFolder) 453 if err == nil { 454 t.Fatalf("archiver.CopyWithTar with invalid src path should throw an error.") 455 } 456 } 457 458 func TestCopyWithTarInexistentDestWillCreateIt(t *testing.T) { 459 skip.If(t, runtime.GOOS != "windows" && os.Getuid() != 0, "skipping test that requires root") 460 tempFolder, err := os.MkdirTemp("", "docker-archive-test") 461 if err != nil { 462 t.Fatal(nil) 463 } 464 srcFolder := filepath.Join(tempFolder, "src") 465 inexistentDestFolder := filepath.Join(tempFolder, "doesnotexists") 466 err = os.MkdirAll(srcFolder, 0o740) 467 if err != nil { 468 t.Fatal(err) 469 } 470 err = defaultCopyWithTar(srcFolder, inexistentDestFolder) 471 if err != nil { 472 t.Fatalf("CopyWithTar with an inexistent folder shouldn't fail.") 473 } 474 _, err = os.Stat(inexistentDestFolder) 475 if err != nil { 476 t.Fatalf("CopyWithTar with an inexistent folder should create it.") 477 } 478 } 479 480 // Test CopyWithTar with a file as src 481 func TestCopyWithTarSrcFile(t *testing.T) { 482 folder, err := os.MkdirTemp("", "docker-archive-test") 483 if err != nil { 484 t.Fatal(err) 485 } 486 defer os.RemoveAll(folder) 487 dest := filepath.Join(folder, "dest") 488 srcFolder := filepath.Join(folder, "src") 489 src := filepath.Join(folder, filepath.Join("src", "src")) 490 err = os.MkdirAll(srcFolder, 0o740) 491 if err != nil { 492 t.Fatal(err) 493 } 494 err = os.MkdirAll(dest, 0o740) 495 if err != nil { 496 t.Fatal(err) 497 } 498 os.WriteFile(src, []byte("content"), 0o777) 499 err = defaultCopyWithTar(src, dest) 500 if err != nil { 501 t.Fatalf("archiver.CopyWithTar shouldn't throw an error, %s.", err) 502 } 503 _, err = os.Stat(dest) 504 // FIXME Check the content 505 if err != nil { 506 t.Fatalf("Destination file should be the same as the source.") 507 } 508 } 509 510 // Test CopyWithTar with a folder as src 511 func TestCopyWithTarSrcFolder(t *testing.T) { 512 folder, err := os.MkdirTemp("", "docker-archive-test") 513 if err != nil { 514 t.Fatal(err) 515 } 516 defer os.RemoveAll(folder) 517 dest := filepath.Join(folder, "dest") 518 src := filepath.Join(folder, filepath.Join("src", "folder")) 519 err = os.MkdirAll(src, 0o740) 520 if err != nil { 521 t.Fatal(err) 522 } 523 err = os.MkdirAll(dest, 0o740) 524 if err != nil { 525 t.Fatal(err) 526 } 527 os.WriteFile(filepath.Join(src, "file"), []byte("content"), 0o777) 528 err = defaultCopyWithTar(src, dest) 529 if err != nil { 530 t.Fatalf("archiver.CopyWithTar shouldn't throw an error, %s.", err) 531 } 532 _, err = os.Stat(dest) 533 // FIXME Check the content (the file inside) 534 if err != nil { 535 t.Fatalf("Destination folder should contain the source file but did not.") 536 } 537 } 538 539 func TestCopyFileWithTarInvalidSrc(t *testing.T) { 540 tempFolder, err := os.MkdirTemp("", "docker-archive-test") 541 if err != nil { 542 t.Fatal(err) 543 } 544 defer os.RemoveAll(tempFolder) 545 destFolder := filepath.Join(tempFolder, "dest") 546 err = os.MkdirAll(destFolder, 0o740) 547 if err != nil { 548 t.Fatal(err) 549 } 550 invalidFile := filepath.Join(tempFolder, "doesnotexists") 551 err = defaultCopyFileWithTar(invalidFile, destFolder) 552 if err == nil { 553 t.Fatalf("archiver.CopyWithTar with invalid src path should throw an error.") 554 } 555 } 556 557 func TestCopyFileWithTarInexistentDestWillCreateIt(t *testing.T) { 558 tempFolder, err := os.MkdirTemp("", "docker-archive-test") 559 if err != nil { 560 t.Fatal(nil) 561 } 562 defer os.RemoveAll(tempFolder) 563 srcFile := filepath.Join(tempFolder, "src") 564 inexistentDestFolder := filepath.Join(tempFolder, "doesnotexists") 565 _, err = os.Create(srcFile) 566 if err != nil { 567 t.Fatal(err) 568 } 569 err = defaultCopyFileWithTar(srcFile, inexistentDestFolder) 570 if err != nil { 571 t.Fatalf("CopyWithTar with an inexistent folder shouldn't fail.") 572 } 573 _, err = os.Stat(inexistentDestFolder) 574 if err != nil { 575 t.Fatalf("CopyWithTar with an inexistent folder should create it.") 576 } 577 // FIXME Test the src file and content 578 } 579 580 func TestCopyFileWithTarSrcFolder(t *testing.T) { 581 folder, err := os.MkdirTemp("", "docker-archive-copyfilewithtar-test") 582 if err != nil { 583 t.Fatal(err) 584 } 585 defer os.RemoveAll(folder) 586 dest := filepath.Join(folder, "dest") 587 src := filepath.Join(folder, "srcfolder") 588 err = os.MkdirAll(src, 0o740) 589 if err != nil { 590 t.Fatal(err) 591 } 592 err = os.MkdirAll(dest, 0o740) 593 if err != nil { 594 t.Fatal(err) 595 } 596 err = defaultCopyFileWithTar(src, dest) 597 if err == nil { 598 t.Fatalf("CopyFileWithTar should throw an error with a folder.") 599 } 600 } 601 602 func TestCopyFileWithTarSrcFile(t *testing.T) { 603 folder, err := os.MkdirTemp("", "docker-archive-test") 604 if err != nil { 605 t.Fatal(err) 606 } 607 defer os.RemoveAll(folder) 608 dest := filepath.Join(folder, "dest") 609 srcFolder := filepath.Join(folder, "src") 610 src := filepath.Join(folder, filepath.Join("src", "src")) 611 err = os.MkdirAll(srcFolder, 0o740) 612 if err != nil { 613 t.Fatal(err) 614 } 615 err = os.MkdirAll(dest, 0o740) 616 if err != nil { 617 t.Fatal(err) 618 } 619 os.WriteFile(src, []byte("content"), 0o777) 620 err = defaultCopyWithTar(src, dest+"/") 621 if err != nil { 622 t.Fatalf("archiver.CopyFileWithTar shouldn't throw an error, %s.", err) 623 } 624 _, err = os.Stat(dest) 625 if err != nil { 626 t.Fatalf("Destination folder should contain the source file but did not.") 627 } 628 } 629 630 func TestTarFiles(t *testing.T) { 631 // try without hardlinks 632 if err := checkNoChanges(1000, false); err != nil { 633 t.Fatal(err) 634 } 635 // try with hardlinks 636 if err := checkNoChanges(1000, true); err != nil { 637 t.Fatal(err) 638 } 639 } 640 641 func checkNoChanges(fileNum int, hardlinks bool) error { 642 srcDir, err := os.MkdirTemp("", "docker-test-srcDir") 643 if err != nil { 644 return err 645 } 646 defer os.RemoveAll(srcDir) 647 648 destDir, err := os.MkdirTemp("", "docker-test-destDir") 649 if err != nil { 650 return err 651 } 652 defer os.RemoveAll(destDir) 653 654 _, err = prepareUntarSourceDirectory(fileNum, srcDir, hardlinks) 655 if err != nil { 656 return err 657 } 658 659 err = defaultTarUntar(srcDir, destDir) 660 if err != nil { 661 return err 662 } 663 664 changes, err := ChangesDirs(destDir, srcDir) 665 if err != nil { 666 return err 667 } 668 if len(changes) > 0 { 669 return fmt.Errorf("with %d files and %v hardlinks: expected 0 changes, got %d", fileNum, hardlinks, len(changes)) 670 } 671 return nil 672 } 673 674 func tarUntar(t *testing.T, origin string, options *TarOptions) ([]Change, error) { 675 archive, err := TarWithOptions(origin, options) 676 if err != nil { 677 t.Fatal(err) 678 } 679 defer archive.Close() 680 681 buf := make([]byte, 10) 682 if _, err := archive.Read(buf); err != nil { 683 return nil, err 684 } 685 wrap := io.MultiReader(bytes.NewReader(buf), archive) 686 687 detectedCompression := DetectCompression(buf) 688 compression := options.Compression 689 if detectedCompression.Extension() != compression.Extension() { 690 return nil, fmt.Errorf("Wrong compression detected. Actual compression: %s, found %s", compression.Extension(), detectedCompression.Extension()) 691 } 692 693 tmp, err := os.MkdirTemp("", "docker-test-untar") 694 if err != nil { 695 return nil, err 696 } 697 defer os.RemoveAll(tmp) 698 if err := Untar(wrap, tmp, nil); err != nil { 699 return nil, err 700 } 701 if _, err := os.Stat(tmp); err != nil { 702 return nil, err 703 } 704 705 return ChangesDirs(origin, tmp) 706 } 707 708 func TestDetectCompressionZstd(t *testing.T) { 709 // test zstd compression without skippable frames. 710 compressedData := []byte{ 711 0x28, 0xb5, 0x2f, 0xfd, // magic number of Zstandard frame: 0xFD2FB528 712 0x04, 0x00, 0x31, 0x00, 0x00, // frame header 713 0x64, 0x6f, 0x63, 0x6b, 0x65, 0x72, // data block "docker" 714 0x16, 0x0e, 0x21, 0xc3, // content checksum 715 } 716 compression := DetectCompression(compressedData) 717 if compression != Zstd { 718 t.Fatal("Unexpected compression") 719 } 720 // test zstd compression with skippable frames. 721 hex := []byte{ 722 0x50, 0x2a, 0x4d, 0x18, // magic number of skippable frame: 0x184D2A50 to 0x184D2A5F 723 0x04, 0x00, 0x00, 0x00, // frame size 724 0x5d, 0x00, 0x00, 0x00, // user data 725 0x28, 0xb5, 0x2f, 0xfd, // magic number of Zstandard frame: 0xFD2FB528 726 0x04, 0x00, 0x31, 0x00, 0x00, // frame header 727 0x64, 0x6f, 0x63, 0x6b, 0x65, 0x72, // data block "docker" 728 0x16, 0x0e, 0x21, 0xc3, // content checksum 729 } 730 compression = DetectCompression(hex) 731 if compression != Zstd { 732 t.Fatal("Unexpected compression") 733 } 734 } 735 736 func TestTarUntar(t *testing.T) { 737 origin, err := os.MkdirTemp("", "docker-test-untar-origin") 738 if err != nil { 739 t.Fatal(err) 740 } 741 defer os.RemoveAll(origin) 742 if err := os.WriteFile(filepath.Join(origin, "1"), []byte("hello world"), 0o700); err != nil { 743 t.Fatal(err) 744 } 745 if err := os.WriteFile(filepath.Join(origin, "2"), []byte("welcome!"), 0o700); err != nil { 746 t.Fatal(err) 747 } 748 if err := os.WriteFile(filepath.Join(origin, "3"), []byte("will be ignored"), 0o700); err != nil { 749 t.Fatal(err) 750 } 751 752 for _, c := range []Compression{ 753 Uncompressed, 754 Gzip, 755 } { 756 changes, err := tarUntar(t, origin, &TarOptions{ 757 Compression: c, 758 ExcludePatterns: []string{"3"}, 759 }) 760 if err != nil { 761 t.Fatalf("Error tar/untar for compression %s: %s", c.Extension(), err) 762 } 763 764 if len(changes) != 1 || changes[0].Path != string(filepath.Separator)+"3" { 765 t.Fatalf("Unexpected differences after tarUntar: %v", changes) 766 } 767 } 768 } 769 770 func TestTarWithOptionsChownOptsAlwaysOverridesIdPair(t *testing.T) { 771 origin, err := os.MkdirTemp("", "docker-test-tar-chown-opt") 772 assert.NilError(t, err) 773 774 defer os.RemoveAll(origin) 775 filePath := filepath.Join(origin, "1") 776 err = os.WriteFile(filePath, []byte("hello world"), 0o700) 777 assert.NilError(t, err) 778 779 idMaps := []idtools.IDMap{ 780 0: { 781 ContainerID: 0, 782 HostID: 0, 783 Size: 65536, 784 }, 785 1: { 786 ContainerID: 0, 787 HostID: 100000, 788 Size: 65536, 789 }, 790 } 791 792 cases := []struct { 793 opts *TarOptions 794 expectedUID int 795 expectedGID int 796 }{ 797 {&TarOptions{ChownOpts: &idtools.Identity{UID: 1337, GID: 42}}, 1337, 42}, 798 {&TarOptions{ChownOpts: &idtools.Identity{UID: 100001, GID: 100001}, IDMap: idtools.IdentityMapping{UIDMaps: idMaps, GIDMaps: idMaps}}, 100001, 100001}, 799 {&TarOptions{ChownOpts: &idtools.Identity{UID: 0, GID: 0}, NoLchown: false}, 0, 0}, 800 {&TarOptions{ChownOpts: &idtools.Identity{UID: 1, GID: 1}, NoLchown: true}, 1, 1}, 801 {&TarOptions{ChownOpts: &idtools.Identity{UID: 1000, GID: 1000}, NoLchown: true}, 1000, 1000}, 802 } 803 for _, testCase := range cases { 804 reader, err := TarWithOptions(filePath, testCase.opts) 805 assert.NilError(t, err) 806 tr := tar.NewReader(reader) 807 defer reader.Close() 808 for { 809 hdr, err := tr.Next() 810 if err == io.EOF { 811 // end of tar archive 812 break 813 } 814 assert.NilError(t, err) 815 assert.Check(t, is.Equal(hdr.Uid, testCase.expectedUID), "Uid equals expected value") 816 assert.Check(t, is.Equal(hdr.Gid, testCase.expectedGID), "Gid equals expected value") 817 } 818 } 819 } 820 821 func TestTarWithOptions(t *testing.T) { 822 origin, err := os.MkdirTemp("", "docker-test-untar-origin") 823 if err != nil { 824 t.Fatal(err) 825 } 826 if _, err := os.MkdirTemp(origin, "folder"); err != nil { 827 t.Fatal(err) 828 } 829 defer os.RemoveAll(origin) 830 if err := os.WriteFile(filepath.Join(origin, "1"), []byte("hello world"), 0o700); err != nil { 831 t.Fatal(err) 832 } 833 if err := os.WriteFile(filepath.Join(origin, "2"), []byte("welcome!"), 0o700); err != nil { 834 t.Fatal(err) 835 } 836 837 cases := []struct { 838 opts *TarOptions 839 numChanges int 840 }{ 841 {&TarOptions{IncludeFiles: []string{"1"}}, 2}, 842 {&TarOptions{ExcludePatterns: []string{"2"}}, 1}, 843 {&TarOptions{ExcludePatterns: []string{"1", "folder*"}}, 2}, 844 {&TarOptions{IncludeFiles: []string{"1", "1"}}, 2}, 845 {&TarOptions{IncludeFiles: []string{"1"}, RebaseNames: map[string]string{"1": "test"}}, 4}, 846 } 847 for _, testCase := range cases { 848 changes, err := tarUntar(t, origin, testCase.opts) 849 if err != nil { 850 t.Fatalf("Error tar/untar when testing inclusion/exclusion: %s", err) 851 } 852 if len(changes) != testCase.numChanges { 853 t.Errorf("Expected %d changes, got %d for %+v:", 854 testCase.numChanges, len(changes), testCase.opts) 855 } 856 } 857 } 858 859 // Some tar archives such as http://haproxy.1wt.eu/download/1.5/src/devel/haproxy-1.5-dev21.tar.gz 860 // use PAX Global Extended Headers. 861 // Failing prevents the archives from being uncompressed during ADD 862 func TestTypeXGlobalHeaderDoesNotFail(t *testing.T) { 863 hdr := tar.Header{Typeflag: tar.TypeXGlobalHeader} 864 tmpDir, err := os.MkdirTemp("", "docker-test-archive-pax-test") 865 if err != nil { 866 t.Fatal(err) 867 } 868 defer os.RemoveAll(tmpDir) 869 err = createTarFile(filepath.Join(tmpDir, "pax_global_header"), tmpDir, &hdr, nil, nil) 870 if err != nil { 871 t.Fatal(err) 872 } 873 } 874 875 // Some tar have both GNU specific (huge uid) and Ustar specific (long name) things. 876 // Not supposed to happen (should use PAX instead of Ustar for long name) but it does and it should still work. 877 func TestUntarUstarGnuConflict(t *testing.T) { 878 f, err := os.Open("testdata/broken.tar") 879 if err != nil { 880 t.Fatal(err) 881 } 882 defer f.Close() 883 884 found := false 885 tr := tar.NewReader(f) 886 // Iterate through the files in the archive. 887 for { 888 hdr, err := tr.Next() 889 if err == io.EOF { 890 // end of tar archive 891 break 892 } 893 if err != nil { 894 t.Fatal(err) 895 } 896 if hdr.Name == "root/.cpanm/work/1395823785.24209/Plack-1.0030/blib/man3/Plack::Middleware::LighttpdScriptNameFix.3pm" { 897 found = true 898 break 899 } 900 } 901 if !found { 902 t.Fatalf("%s not found in the archive", "root/.cpanm/work/1395823785.24209/Plack-1.0030/blib/man3/Plack::Middleware::LighttpdScriptNameFix.3pm") 903 } 904 } 905 906 func prepareUntarSourceDirectory(numberOfFiles int, targetPath string, makeLinks bool) (int, error) { 907 fileData := []byte("fooo") 908 for n := 0; n < numberOfFiles; n++ { 909 fileName := fmt.Sprintf("file-%d", n) 910 if err := os.WriteFile(filepath.Join(targetPath, fileName), fileData, 0o700); err != nil { 911 return 0, err 912 } 913 if makeLinks { 914 if err := os.Link(filepath.Join(targetPath, fileName), filepath.Join(targetPath, fileName+"-link")); err != nil { 915 return 0, err 916 } 917 } 918 } 919 totalSize := numberOfFiles * len(fileData) 920 return totalSize, nil 921 } 922 923 func BenchmarkTarUntar(b *testing.B) { 924 origin, err := os.MkdirTemp("", "docker-test-untar-origin") 925 if err != nil { 926 b.Fatal(err) 927 } 928 tempDir, err := os.MkdirTemp("", "docker-test-untar-destination") 929 if err != nil { 930 b.Fatal(err) 931 } 932 target := filepath.Join(tempDir, "dest") 933 n, err := prepareUntarSourceDirectory(100, origin, false) 934 if err != nil { 935 b.Fatal(err) 936 } 937 defer os.RemoveAll(origin) 938 defer os.RemoveAll(tempDir) 939 940 b.ResetTimer() 941 b.SetBytes(int64(n)) 942 for n := 0; n < b.N; n++ { 943 err := defaultTarUntar(origin, target) 944 if err != nil { 945 b.Fatal(err) 946 } 947 os.RemoveAll(target) 948 } 949 } 950 951 func BenchmarkTarUntarWithLinks(b *testing.B) { 952 origin, err := os.MkdirTemp("", "docker-test-untar-origin") 953 if err != nil { 954 b.Fatal(err) 955 } 956 tempDir, err := os.MkdirTemp("", "docker-test-untar-destination") 957 if err != nil { 958 b.Fatal(err) 959 } 960 target := filepath.Join(tempDir, "dest") 961 n, err := prepareUntarSourceDirectory(100, origin, true) 962 if err != nil { 963 b.Fatal(err) 964 } 965 defer os.RemoveAll(origin) 966 defer os.RemoveAll(tempDir) 967 968 b.ResetTimer() 969 b.SetBytes(int64(n)) 970 for n := 0; n < b.N; n++ { 971 err := defaultTarUntar(origin, target) 972 if err != nil { 973 b.Fatal(err) 974 } 975 os.RemoveAll(target) 976 } 977 } 978 979 func TestUntarInvalidFilenames(t *testing.T) { 980 for i, headers := range [][]*tar.Header{ 981 { 982 { 983 Name: "../victim/dotdot", 984 Typeflag: tar.TypeReg, 985 Mode: 0o644, 986 }, 987 }, 988 { 989 { 990 // Note the leading slash 991 Name: "/../victim/slash-dotdot", 992 Typeflag: tar.TypeReg, 993 Mode: 0o644, 994 }, 995 }, 996 } { 997 if err := testBreakout("untar", "docker-TestUntarInvalidFilenames", headers); err != nil { 998 t.Fatalf("i=%d. %v", i, err) 999 } 1000 } 1001 } 1002 1003 func TestUntarHardlinkToSymlink(t *testing.T) { 1004 skip.If(t, runtime.GOOS != "windows" && os.Getuid() != 0, "skipping test that requires root") 1005 for i, headers := range [][]*tar.Header{ 1006 { 1007 { 1008 Name: "symlink1", 1009 Typeflag: tar.TypeSymlink, 1010 Linkname: "regfile", 1011 Mode: 0o644, 1012 }, 1013 { 1014 Name: "symlink2", 1015 Typeflag: tar.TypeLink, 1016 Linkname: "symlink1", 1017 Mode: 0o644, 1018 }, 1019 { 1020 Name: "regfile", 1021 Typeflag: tar.TypeReg, 1022 Mode: 0o644, 1023 }, 1024 }, 1025 } { 1026 if err := testBreakout("untar", "docker-TestUntarHardlinkToSymlink", headers); err != nil { 1027 t.Fatalf("i=%d. %v", i, err) 1028 } 1029 } 1030 } 1031 1032 func TestUntarInvalidHardlink(t *testing.T) { 1033 for i, headers := range [][]*tar.Header{ 1034 { // try reading victim/hello (../) 1035 { 1036 Name: "dotdot", 1037 Typeflag: tar.TypeLink, 1038 Linkname: "../victim/hello", 1039 Mode: 0o644, 1040 }, 1041 }, 1042 { // try reading victim/hello (/../) 1043 { 1044 Name: "slash-dotdot", 1045 Typeflag: tar.TypeLink, 1046 // Note the leading slash 1047 Linkname: "/../victim/hello", 1048 Mode: 0o644, 1049 }, 1050 }, 1051 { // try writing victim/file 1052 { 1053 Name: "loophole-victim", 1054 Typeflag: tar.TypeLink, 1055 Linkname: "../victim", 1056 Mode: 0o755, 1057 }, 1058 { 1059 Name: "loophole-victim/file", 1060 Typeflag: tar.TypeReg, 1061 Mode: 0o644, 1062 }, 1063 }, 1064 { // try reading victim/hello (hardlink, symlink) 1065 { 1066 Name: "loophole-victim", 1067 Typeflag: tar.TypeLink, 1068 Linkname: "../victim", 1069 Mode: 0o755, 1070 }, 1071 { 1072 Name: "symlink", 1073 Typeflag: tar.TypeSymlink, 1074 Linkname: "loophole-victim/hello", 1075 Mode: 0o644, 1076 }, 1077 }, 1078 { // Try reading victim/hello (hardlink, hardlink) 1079 { 1080 Name: "loophole-victim", 1081 Typeflag: tar.TypeLink, 1082 Linkname: "../victim", 1083 Mode: 0o755, 1084 }, 1085 { 1086 Name: "hardlink", 1087 Typeflag: tar.TypeLink, 1088 Linkname: "loophole-victim/hello", 1089 Mode: 0o644, 1090 }, 1091 }, 1092 { // Try removing victim directory (hardlink) 1093 { 1094 Name: "loophole-victim", 1095 Typeflag: tar.TypeLink, 1096 Linkname: "../victim", 1097 Mode: 0o755, 1098 }, 1099 { 1100 Name: "loophole-victim", 1101 Typeflag: tar.TypeReg, 1102 Mode: 0o644, 1103 }, 1104 }, 1105 } { 1106 if err := testBreakout("untar", "docker-TestUntarInvalidHardlink", headers); err != nil { 1107 t.Fatalf("i=%d. %v", i, err) 1108 } 1109 } 1110 } 1111 1112 func TestUntarInvalidSymlink(t *testing.T) { 1113 for i, headers := range [][]*tar.Header{ 1114 { // try reading victim/hello (../) 1115 { 1116 Name: "dotdot", 1117 Typeflag: tar.TypeSymlink, 1118 Linkname: "../victim/hello", 1119 Mode: 0o644, 1120 }, 1121 }, 1122 { // try reading victim/hello (/../) 1123 { 1124 Name: "slash-dotdot", 1125 Typeflag: tar.TypeSymlink, 1126 // Note the leading slash 1127 Linkname: "/../victim/hello", 1128 Mode: 0o644, 1129 }, 1130 }, 1131 { // try writing victim/file 1132 { 1133 Name: "loophole-victim", 1134 Typeflag: tar.TypeSymlink, 1135 Linkname: "../victim", 1136 Mode: 0o755, 1137 }, 1138 { 1139 Name: "loophole-victim/file", 1140 Typeflag: tar.TypeReg, 1141 Mode: 0o644, 1142 }, 1143 }, 1144 { // try reading victim/hello (symlink, symlink) 1145 { 1146 Name: "loophole-victim", 1147 Typeflag: tar.TypeSymlink, 1148 Linkname: "../victim", 1149 Mode: 0o755, 1150 }, 1151 { 1152 Name: "symlink", 1153 Typeflag: tar.TypeSymlink, 1154 Linkname: "loophole-victim/hello", 1155 Mode: 0o644, 1156 }, 1157 }, 1158 { // try reading victim/hello (symlink, hardlink) 1159 { 1160 Name: "loophole-victim", 1161 Typeflag: tar.TypeSymlink, 1162 Linkname: "../victim", 1163 Mode: 0o755, 1164 }, 1165 { 1166 Name: "hardlink", 1167 Typeflag: tar.TypeLink, 1168 Linkname: "loophole-victim/hello", 1169 Mode: 0o644, 1170 }, 1171 }, 1172 { // try removing victim directory (symlink) 1173 { 1174 Name: "loophole-victim", 1175 Typeflag: tar.TypeSymlink, 1176 Linkname: "../victim", 1177 Mode: 0o755, 1178 }, 1179 { 1180 Name: "loophole-victim", 1181 Typeflag: tar.TypeReg, 1182 Mode: 0o644, 1183 }, 1184 }, 1185 { // try writing to victim/newdir/newfile with a symlink in the path 1186 { 1187 // this header needs to be before the next one, or else there is an error 1188 Name: "dir/loophole", 1189 Typeflag: tar.TypeSymlink, 1190 Linkname: "../../victim", 1191 Mode: 0o755, 1192 }, 1193 { 1194 Name: "dir/loophole/newdir/newfile", 1195 Typeflag: tar.TypeReg, 1196 Mode: 0o644, 1197 }, 1198 }, 1199 } { 1200 if err := testBreakout("untar", "docker-TestUntarInvalidSymlink", headers); err != nil { 1201 t.Fatalf("i=%d. %v", i, err) 1202 } 1203 } 1204 } 1205 1206 func TestTempArchiveCloseMultipleTimes(t *testing.T) { 1207 reader := io.NopCloser(strings.NewReader("hello")) 1208 tempArchive, err := NewTempArchive(reader, "") 1209 assert.NilError(t, err) 1210 buf := make([]byte, 10) 1211 n, err := tempArchive.Read(buf) 1212 assert.NilError(t, err) 1213 if n != 5 { 1214 t.Fatalf("Expected to read 5 bytes. Read %d instead", n) 1215 } 1216 for i := 0; i < 3; i++ { 1217 if err = tempArchive.Close(); err != nil { 1218 t.Fatalf("i=%d. Unexpected error closing temp archive: %v", i, err) 1219 } 1220 } 1221 } 1222 1223 // TestXGlobalNoParent is a regression test to check parent directories are not created for PAX headers 1224 func TestXGlobalNoParent(t *testing.T) { 1225 buf := &bytes.Buffer{} 1226 w := tar.NewWriter(buf) 1227 err := w.WriteHeader(&tar.Header{ 1228 Name: "foo/bar", 1229 Typeflag: tar.TypeXGlobalHeader, 1230 }) 1231 assert.NilError(t, err) 1232 tmpDir, err := os.MkdirTemp("", "pax-test") 1233 assert.NilError(t, err) 1234 defer os.RemoveAll(tmpDir) 1235 err = Untar(buf, tmpDir, nil) 1236 assert.NilError(t, err) 1237 1238 _, err = os.Lstat(filepath.Join(tmpDir, "foo")) 1239 assert.Check(t, err != nil) 1240 assert.Check(t, errors.Is(err, os.ErrNotExist)) 1241 } 1242 1243 // TestImpliedDirectoryPermissions ensures that directories implied by paths in the tar file, but without their own 1244 // header entries are created recursively with the default mode (permissions) stored in ImpliedDirectoryMode. This test 1245 // also verifies that the permissions of explicit directories are respected. 1246 func TestImpliedDirectoryPermissions(t *testing.T) { 1247 skip.If(t, runtime.GOOS == "windows", "skipping test that requires Unix permissions") 1248 1249 buf := &bytes.Buffer{} 1250 headers := []tar.Header{{ 1251 Name: "deeply/nested/and/implied", 1252 }, { 1253 Name: "explicit/", 1254 Mode: 0o644, 1255 }, { 1256 Name: "explicit/permissions/", 1257 Mode: 0o600, 1258 }, { 1259 Name: "explicit/permissions/specified", 1260 Mode: 0o400, 1261 }} 1262 1263 w := tar.NewWriter(buf) 1264 for _, header := range headers { 1265 err := w.WriteHeader(&header) 1266 assert.NilError(t, err) 1267 } 1268 1269 tmpDir := t.TempDir() 1270 1271 err := Untar(buf, tmpDir, nil) 1272 assert.NilError(t, err) 1273 1274 assertMode := func(path string, expected uint32) { 1275 t.Helper() 1276 stat, err := os.Lstat(filepath.Join(tmpDir, path)) 1277 assert.Check(t, err) 1278 assert.Check(t, is.Equal(stat.Mode().Perm(), fs.FileMode(expected))) 1279 } 1280 1281 assertMode("deeply", ImpliedDirectoryMode) 1282 assertMode("deeply/nested", ImpliedDirectoryMode) 1283 assertMode("deeply/nested/and", ImpliedDirectoryMode) 1284 1285 assertMode("explicit", 0o644) 1286 assertMode("explicit/permissions", 0o600) 1287 assertMode("explicit/permissions/specified", 0o400) 1288 } 1289 1290 func TestReplaceFileTarWrapper(t *testing.T) { 1291 filesInArchive := 20 1292 testcases := []struct { 1293 doc string 1294 filename string 1295 modifier TarModifierFunc 1296 expected string 1297 fileCount int 1298 }{ 1299 { 1300 doc: "Modifier creates a new file", 1301 filename: "newfile", 1302 modifier: createModifier(t), 1303 expected: "the new content", 1304 fileCount: filesInArchive + 1, 1305 }, 1306 { 1307 doc: "Modifier replaces a file", 1308 filename: "file-2", 1309 modifier: createOrReplaceModifier, 1310 expected: "the new content", 1311 fileCount: filesInArchive, 1312 }, 1313 { 1314 doc: "Modifier replaces the last file", 1315 filename: fmt.Sprintf("file-%d", filesInArchive-1), 1316 modifier: createOrReplaceModifier, 1317 expected: "the new content", 1318 fileCount: filesInArchive, 1319 }, 1320 { 1321 doc: "Modifier appends to a file", 1322 filename: "file-3", 1323 modifier: appendModifier, 1324 expected: "fooo\nnext line", 1325 fileCount: filesInArchive, 1326 }, 1327 } 1328 1329 for _, testcase := range testcases { 1330 sourceArchive, cleanup := buildSourceArchive(t, filesInArchive) 1331 defer cleanup() 1332 1333 resultArchive := ReplaceFileTarWrapper( 1334 sourceArchive, 1335 map[string]TarModifierFunc{testcase.filename: testcase.modifier}) 1336 1337 actual := readFileFromArchive(t, resultArchive, testcase.filename, testcase.fileCount, testcase.doc) 1338 assert.Check(t, is.Equal(testcase.expected, actual), testcase.doc) 1339 } 1340 } 1341 1342 // TestPrefixHeaderReadable tests that files that could be created with the 1343 // version of this package that was built with <=go17 are still readable. 1344 func TestPrefixHeaderReadable(t *testing.T) { 1345 skip.If(t, runtime.GOOS != "windows" && os.Getuid() != 0, "skipping test that requires root") 1346 skip.If(t, userns.RunningInUserNS(), "skipping test that requires more than 010000000 UIDs, which is unlikely to be satisfied when running in userns") 1347 // https://gist.github.com/stevvooe/e2a790ad4e97425896206c0816e1a882#file-out-go 1348 testFile := []byte("\x1f\x8b\x08\x08\x44\x21\x68\x59\x00\x03\x74\x2e\x74\x61\x72\x00\x4b\xcb\xcf\x67\xa0\x35\x30\x80\x00\x86\x06\x10\x47\x01\xc1\x37\x40\x00\x54\xb6\xb1\xa1\xa9\x99\x09\x48\x25\x1d\x40\x69\x71\x49\x62\x91\x02\xe5\x76\xa1\x79\x84\x21\x91\xd6\x80\x72\xaf\x8f\x82\x51\x30\x0a\x46\x36\x00\x00\xf0\x1c\x1e\x95\x00\x06\x00\x00") 1349 1350 tmpDir, err := os.MkdirTemp("", "prefix-test") 1351 assert.NilError(t, err) 1352 defer os.RemoveAll(tmpDir) 1353 err = Untar(bytes.NewReader(testFile), tmpDir, nil) 1354 assert.NilError(t, err) 1355 1356 baseName := "foo" 1357 pth := strings.Repeat("a", 100-len(baseName)) + "/" + baseName 1358 1359 _, err = os.Lstat(filepath.Join(tmpDir, pth)) 1360 assert.NilError(t, err) 1361 } 1362 1363 func buildSourceArchive(t *testing.T, numberOfFiles int) (io.ReadCloser, func()) { 1364 srcDir, err := os.MkdirTemp("", "docker-test-srcDir") 1365 assert.NilError(t, err) 1366 1367 _, err = prepareUntarSourceDirectory(numberOfFiles, srcDir, false) 1368 assert.NilError(t, err) 1369 1370 sourceArchive, err := TarWithOptions(srcDir, &TarOptions{}) 1371 assert.NilError(t, err) 1372 return sourceArchive, func() { 1373 os.RemoveAll(srcDir) 1374 sourceArchive.Close() 1375 } 1376 } 1377 1378 func createOrReplaceModifier(path string, header *tar.Header, content io.Reader) (*tar.Header, []byte, error) { 1379 return &tar.Header{ 1380 Mode: 0o600, 1381 Typeflag: tar.TypeReg, 1382 }, []byte("the new content"), nil 1383 } 1384 1385 func createModifier(t *testing.T) TarModifierFunc { 1386 return func(path string, header *tar.Header, content io.Reader) (*tar.Header, []byte, error) { 1387 assert.Check(t, is.Nil(content)) 1388 return createOrReplaceModifier(path, header, content) 1389 } 1390 } 1391 1392 func appendModifier(path string, header *tar.Header, content io.Reader) (*tar.Header, []byte, error) { 1393 buffer := bytes.Buffer{} 1394 if content != nil { 1395 if _, err := buffer.ReadFrom(content); err != nil { 1396 return nil, nil, err 1397 } 1398 } 1399 buffer.WriteString("\nnext line") 1400 return &tar.Header{Mode: 0o600, Typeflag: tar.TypeReg}, buffer.Bytes(), nil 1401 } 1402 1403 func readFileFromArchive(t *testing.T, archive io.ReadCloser, name string, expectedCount int, doc string) string { 1404 skip.If(t, runtime.GOOS != "windows" && os.Getuid() != 0, "skipping test that requires root") 1405 destDir, err := os.MkdirTemp("", "docker-test-destDir") 1406 assert.NilError(t, err) 1407 defer os.RemoveAll(destDir) 1408 1409 err = Untar(archive, destDir, nil) 1410 assert.NilError(t, err) 1411 1412 files, _ := os.ReadDir(destDir) 1413 assert.Check(t, is.Len(files, expectedCount), doc) 1414 1415 content, err := os.ReadFile(filepath.Join(destDir, name)) 1416 assert.Check(t, err) 1417 return string(content) 1418 } 1419 1420 func TestDisablePigz(t *testing.T) { 1421 _, err := exec.LookPath("unpigz") 1422 if err != nil { 1423 t.Log("Test will not check full path when Pigz not installed") 1424 } 1425 1426 t.Setenv("MOBY_DISABLE_PIGZ", "true") 1427 1428 r := testDecompressStream(t, "gz", "gzip -f") 1429 // For the bufio pool 1430 outsideReaderCloserWrapper := r.(*ioutils.ReadCloserWrapper) 1431 // For the context canceller 1432 contextReaderCloserWrapper := outsideReaderCloserWrapper.Reader.(*ioutils.ReadCloserWrapper) 1433 1434 assert.Equal(t, reflect.TypeOf(contextReaderCloserWrapper.Reader), reflect.TypeOf(&gzip.Reader{})) 1435 } 1436 1437 func TestPigz(t *testing.T) { 1438 r := testDecompressStream(t, "gz", "gzip -f") 1439 // For the bufio pool 1440 outsideReaderCloserWrapper := r.(*ioutils.ReadCloserWrapper) 1441 // For the context canceller 1442 contextReaderCloserWrapper := outsideReaderCloserWrapper.Reader.(*ioutils.ReadCloserWrapper) 1443 1444 _, err := exec.LookPath("unpigz") 1445 if err == nil { 1446 t.Log("Tested whether Pigz is used, as it installed") 1447 // For the command wait wrapper 1448 cmdWaitCloserWrapper := contextReaderCloserWrapper.Reader.(*ioutils.ReadCloserWrapper) 1449 assert.Equal(t, reflect.TypeOf(cmdWaitCloserWrapper.Reader), reflect.TypeOf(&io.PipeReader{})) 1450 } else { 1451 t.Log("Tested whether Pigz is not used, as it not installed") 1452 assert.Equal(t, reflect.TypeOf(contextReaderCloserWrapper.Reader), reflect.TypeOf(&gzip.Reader{})) 1453 } 1454 } 1455 1456 func TestNosysFileInfo(t *testing.T) { 1457 st, err := os.Stat("archive_test.go") 1458 assert.NilError(t, err) 1459 h, err := tar.FileInfoHeader(nosysFileInfo{st}, "") 1460 assert.NilError(t, err) 1461 assert.Check(t, h.Uname == "") 1462 assert.Check(t, h.Gname == "") 1463 }