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