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