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