github.com/kunnos/engine@v1.13.1/pkg/archive/archive_test.go (about) 1 package archive 2 3 import ( 4 "archive/tar" 5 "bytes" 6 "fmt" 7 "io" 8 "io/ioutil" 9 "os" 10 "os/exec" 11 "path/filepath" 12 "runtime" 13 "strings" 14 "testing" 15 "time" 16 ) 17 18 var tmp string 19 20 func init() { 21 tmp = "/tmp/" 22 if runtime.GOOS == "windows" { 23 tmp = os.Getenv("TEMP") + `\` 24 } 25 } 26 27 func TestIsArchiveNilHeader(t *testing.T) { 28 out := IsArchive(nil) 29 if out { 30 t.Fatalf("isArchive should return false as nil is not a valid archive header") 31 } 32 } 33 34 func TestIsArchiveInvalidHeader(t *testing.T) { 35 header := []byte{0x00, 0x01, 0x02} 36 out := IsArchive(header) 37 if out { 38 t.Fatalf("isArchive should return false as %s is not a valid archive header", header) 39 } 40 } 41 42 func TestIsArchiveBzip2(t *testing.T) { 43 header := []byte{0x42, 0x5A, 0x68} 44 out := IsArchive(header) 45 if !out { 46 t.Fatalf("isArchive should return true as %s is a bz2 header", header) 47 } 48 } 49 50 func TestIsArchive7zip(t *testing.T) { 51 header := []byte{0x50, 0x4b, 0x03, 0x04} 52 out := IsArchive(header) 53 if out { 54 t.Fatalf("isArchive should return false as %s is a 7z header and it is not supported", header) 55 } 56 } 57 58 func TestIsArchivePathDir(t *testing.T) { 59 cmd := exec.Command("sh", "-c", "mkdir -p /tmp/archivedir") 60 output, err := cmd.CombinedOutput() 61 if err != nil { 62 t.Fatalf("Fail to create an archive file for test : %s.", output) 63 } 64 if IsArchivePath(tmp + "archivedir") { 65 t.Fatalf("Incorrectly recognised directory as an archive") 66 } 67 } 68 69 func TestIsArchivePathInvalidFile(t *testing.T) { 70 cmd := exec.Command("sh", "-c", "dd if=/dev/zero bs=1024 count=1 of=/tmp/archive && gzip --stdout /tmp/archive > /tmp/archive.gz") 71 output, err := cmd.CombinedOutput() 72 if err != nil { 73 t.Fatalf("Fail to create an archive file for test : %s.", output) 74 } 75 if IsArchivePath(tmp + "archive") { 76 t.Fatalf("Incorrectly recognised invalid tar path as archive") 77 } 78 if IsArchivePath(tmp + "archive.gz") { 79 t.Fatalf("Incorrectly recognised invalid compressed tar path as archive") 80 } 81 } 82 83 func TestIsArchivePathTar(t *testing.T) { 84 var whichTar string 85 if runtime.GOOS == "solaris" { 86 whichTar = "gtar" 87 } else { 88 whichTar = "tar" 89 } 90 cmdStr := fmt.Sprintf("touch /tmp/archivedata && %s -cf /tmp/archive /tmp/archivedata && gzip --stdout /tmp/archive > /tmp/archive.gz", whichTar) 91 cmd := exec.Command("sh", "-c", cmdStr) 92 output, err := cmd.CombinedOutput() 93 if err != nil { 94 t.Fatalf("Fail to create an archive file for test : %s.", output) 95 } 96 if !IsArchivePath(tmp + "/archive") { 97 t.Fatalf("Did not recognise valid tar path as archive") 98 } 99 if !IsArchivePath(tmp + "archive.gz") { 100 t.Fatalf("Did not recognise valid compressed tar path as archive") 101 } 102 } 103 104 func testDecompressStream(t *testing.T, ext, compressCommand string) { 105 cmd := exec.Command("sh", "-c", 106 fmt.Sprintf("touch /tmp/archive && %s /tmp/archive", compressCommand)) 107 output, err := cmd.CombinedOutput() 108 if err != nil { 109 t.Fatalf("Failed to create an archive file for test : %s.", output) 110 } 111 filename := "archive." + ext 112 archive, err := os.Open(tmp + filename) 113 if err != nil { 114 t.Fatalf("Failed to open file %s: %v", filename, err) 115 } 116 defer archive.Close() 117 118 r, err := DecompressStream(archive) 119 if err != nil { 120 t.Fatalf("Failed to decompress %s: %v", filename, err) 121 } 122 if _, err = ioutil.ReadAll(r); err != nil { 123 t.Fatalf("Failed to read the decompressed stream: %v ", err) 124 } 125 if err = r.Close(); err != nil { 126 t.Fatalf("Failed to close the decompressed stream: %v ", err) 127 } 128 } 129 130 func TestDecompressStreamGzip(t *testing.T) { 131 testDecompressStream(t, "gz", "gzip -f") 132 } 133 134 func TestDecompressStreamBzip2(t *testing.T) { 135 testDecompressStream(t, "bz2", "bzip2 -f") 136 } 137 138 func TestDecompressStreamXz(t *testing.T) { 139 if runtime.GOOS == "windows" { 140 t.Skip("Xz not present in msys2") 141 } 142 testDecompressStream(t, "xz", "xz -f") 143 } 144 145 func TestCompressStreamXzUnsuported(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, Xz) 166 if err == nil { 167 t.Fatalf("Should fail as xz 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 bzip2 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 bzip2 archive should be 'tar.xz'") 218 } 219 } 220 221 func TestCmdStreamLargeStderr(t *testing.T) { 222 cmd := exec.Command("sh", "-c", "dd if=/dev/zero bs=1k count=1000 of=/dev/stderr; echo hello") 223 out, _, err := cmdStream(cmd, nil) 224 if err != nil { 225 t.Fatalf("Failed to start command: %s", err) 226 } 227 errCh := make(chan error) 228 go func() { 229 _, err := io.Copy(ioutil.Discard, out) 230 errCh <- err 231 }() 232 select { 233 case err := <-errCh: 234 if err != nil { 235 t.Fatalf("Command should not have failed (err=%.100s...)", err) 236 } 237 case <-time.After(5 * time.Second): 238 t.Fatalf("Command did not complete in 5 seconds; probable deadlock") 239 } 240 } 241 242 func TestCmdStreamBad(t *testing.T) { 243 // TODO Windows: Figure out why this is failing in CI but not locally 244 if runtime.GOOS == "windows" { 245 t.Skip("Failing on Windows CI machines") 246 } 247 badCmd := exec.Command("sh", "-c", "echo hello; echo >&2 error couldn\\'t reverse the phase pulser; exit 1") 248 out, _, err := cmdStream(badCmd, nil) 249 if err != nil { 250 t.Fatalf("Failed to start command: %s", err) 251 } 252 if output, err := ioutil.ReadAll(out); err == nil { 253 t.Fatalf("Command should have failed") 254 } else if err.Error() != "exit status 1: error couldn't reverse the phase pulser\n" { 255 t.Fatalf("Wrong error value (%s)", err) 256 } else if s := string(output); s != "hello\n" { 257 t.Fatalf("Command output should be '%s', not '%s'", "hello\\n", output) 258 } 259 } 260 261 func TestCmdStreamGood(t *testing.T) { 262 cmd := exec.Command("sh", "-c", "echo hello; exit 0") 263 out, _, err := cmdStream(cmd, nil) 264 if err != nil { 265 t.Fatal(err) 266 } 267 if output, err := ioutil.ReadAll(out); err != nil { 268 t.Fatalf("Command should not have failed (err=%s)", err) 269 } else if s := string(output); s != "hello\n" { 270 t.Fatalf("Command output should be '%s', not '%s'", "hello\\n", output) 271 } 272 } 273 274 func TestUntarPathWithInvalidDest(t *testing.T) { 275 tempFolder, err := ioutil.TempDir("", "docker-archive-test") 276 if err != nil { 277 t.Fatal(err) 278 } 279 defer os.RemoveAll(tempFolder) 280 invalidDestFolder := filepath.Join(tempFolder, "invalidDest") 281 // Create a src file 282 srcFile := filepath.Join(tempFolder, "src") 283 tarFile := filepath.Join(tempFolder, "src.tar") 284 os.Create(srcFile) 285 os.Create(invalidDestFolder) // being a file (not dir) should cause an error 286 287 // Translate back to Unix semantics as next exec.Command is run under sh 288 srcFileU := srcFile 289 tarFileU := tarFile 290 if runtime.GOOS == "windows" { 291 tarFileU = "/tmp/" + filepath.Base(filepath.Dir(tarFile)) + "/src.tar" 292 srcFileU = "/tmp/" + filepath.Base(filepath.Dir(srcFile)) + "/src" 293 } 294 295 cmd := exec.Command("sh", "-c", "tar cf "+tarFileU+" "+srcFileU) 296 _, err = cmd.CombinedOutput() 297 if err != nil { 298 t.Fatal(err) 299 } 300 301 err = UntarPath(tarFile, invalidDestFolder) 302 if err == nil { 303 t.Fatalf("UntarPath with invalid destination path should throw an error.") 304 } 305 } 306 307 func TestUntarPathWithInvalidSrc(t *testing.T) { 308 dest, err := ioutil.TempDir("", "docker-archive-test") 309 if err != nil { 310 t.Fatalf("Fail to create the destination file") 311 } 312 defer os.RemoveAll(dest) 313 err = UntarPath("/invalid/path", dest) 314 if err == nil { 315 t.Fatalf("UntarPath with invalid src path should throw an error.") 316 } 317 } 318 319 func TestUntarPath(t *testing.T) { 320 tmpFolder, err := ioutil.TempDir("", "docker-archive-test") 321 if err != nil { 322 t.Fatal(err) 323 } 324 defer os.RemoveAll(tmpFolder) 325 srcFile := filepath.Join(tmpFolder, "src") 326 tarFile := filepath.Join(tmpFolder, "src.tar") 327 os.Create(filepath.Join(tmpFolder, "src")) 328 329 destFolder := filepath.Join(tmpFolder, "dest") 330 err = os.MkdirAll(destFolder, 0740) 331 if err != nil { 332 t.Fatalf("Fail to create the destination file") 333 } 334 335 // Translate back to Unix semantics as next exec.Command is run under sh 336 srcFileU := srcFile 337 tarFileU := tarFile 338 if runtime.GOOS == "windows" { 339 tarFileU = "/tmp/" + filepath.Base(filepath.Dir(tarFile)) + "/src.tar" 340 srcFileU = "/tmp/" + filepath.Base(filepath.Dir(srcFile)) + "/src" 341 } 342 cmd := exec.Command("sh", "-c", "tar cf "+tarFileU+" "+srcFileU) 343 _, err = cmd.CombinedOutput() 344 if err != nil { 345 t.Fatal(err) 346 } 347 348 err = UntarPath(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 := ioutil.TempDir("", "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 = UntarPath(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 := ioutil.TempDir("", "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 = UntarPath(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 := ioutil.TempDir("", "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 = CopyWithTar(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 tempFolder, err := ioutil.TempDir("", "docker-archive-test") 455 if err != nil { 456 t.Fatal(nil) 457 } 458 srcFolder := filepath.Join(tempFolder, "src") 459 inexistentDestFolder := filepath.Join(tempFolder, "doesnotexists") 460 err = os.MkdirAll(srcFolder, 0740) 461 if err != nil { 462 t.Fatal(err) 463 } 464 err = CopyWithTar(srcFolder, inexistentDestFolder) 465 if err != nil { 466 t.Fatalf("CopyWithTar with an inexistent folder shouldn't fail.") 467 } 468 _, err = os.Stat(inexistentDestFolder) 469 if err != nil { 470 t.Fatalf("CopyWithTar with an inexistent folder should create it.") 471 } 472 } 473 474 // Test CopyWithTar with a file as src 475 func TestCopyWithTarSrcFile(t *testing.T) { 476 folder, err := ioutil.TempDir("", "docker-archive-test") 477 if err != nil { 478 t.Fatal(err) 479 } 480 defer os.RemoveAll(folder) 481 dest := filepath.Join(folder, "dest") 482 srcFolder := filepath.Join(folder, "src") 483 src := filepath.Join(folder, filepath.Join("src", "src")) 484 err = os.MkdirAll(srcFolder, 0740) 485 if err != nil { 486 t.Fatal(err) 487 } 488 err = os.MkdirAll(dest, 0740) 489 if err != nil { 490 t.Fatal(err) 491 } 492 ioutil.WriteFile(src, []byte("content"), 0777) 493 err = CopyWithTar(src, dest) 494 if err != nil { 495 t.Fatalf("archiver.CopyWithTar shouldn't throw an error, %s.", err) 496 } 497 _, err = os.Stat(dest) 498 // FIXME Check the content 499 if err != nil { 500 t.Fatalf("Destination file should be the same as the source.") 501 } 502 } 503 504 // Test CopyWithTar with a folder as src 505 func TestCopyWithTarSrcFolder(t *testing.T) { 506 folder, err := ioutil.TempDir("", "docker-archive-test") 507 if err != nil { 508 t.Fatal(err) 509 } 510 defer os.RemoveAll(folder) 511 dest := filepath.Join(folder, "dest") 512 src := filepath.Join(folder, filepath.Join("src", "folder")) 513 err = os.MkdirAll(src, 0740) 514 if err != nil { 515 t.Fatal(err) 516 } 517 err = os.MkdirAll(dest, 0740) 518 if err != nil { 519 t.Fatal(err) 520 } 521 ioutil.WriteFile(filepath.Join(src, "file"), []byte("content"), 0777) 522 err = CopyWithTar(src, dest) 523 if err != nil { 524 t.Fatalf("archiver.CopyWithTar shouldn't throw an error, %s.", err) 525 } 526 _, err = os.Stat(dest) 527 // FIXME Check the content (the file inside) 528 if err != nil { 529 t.Fatalf("Destination folder should contain the source file but did not.") 530 } 531 } 532 533 func TestCopyFileWithTarInvalidSrc(t *testing.T) { 534 tempFolder, err := ioutil.TempDir("", "docker-archive-test") 535 if err != nil { 536 t.Fatal(err) 537 } 538 defer os.RemoveAll(tempFolder) 539 destFolder := filepath.Join(tempFolder, "dest") 540 err = os.MkdirAll(destFolder, 0740) 541 if err != nil { 542 t.Fatal(err) 543 } 544 invalidFile := filepath.Join(tempFolder, "doesnotexists") 545 err = CopyFileWithTar(invalidFile, destFolder) 546 if err == nil { 547 t.Fatalf("archiver.CopyWithTar with invalid src path should throw an error.") 548 } 549 } 550 551 func TestCopyFileWithTarInexistentDestWillCreateIt(t *testing.T) { 552 tempFolder, err := ioutil.TempDir("", "docker-archive-test") 553 if err != nil { 554 t.Fatal(nil) 555 } 556 defer os.RemoveAll(tempFolder) 557 srcFile := filepath.Join(tempFolder, "src") 558 inexistentDestFolder := filepath.Join(tempFolder, "doesnotexists") 559 _, err = os.Create(srcFile) 560 if err != nil { 561 t.Fatal(err) 562 } 563 err = CopyFileWithTar(srcFile, inexistentDestFolder) 564 if err != nil { 565 t.Fatalf("CopyWithTar with an inexistent folder shouldn't fail.") 566 } 567 _, err = os.Stat(inexistentDestFolder) 568 if err != nil { 569 t.Fatalf("CopyWithTar with an inexistent folder should create it.") 570 } 571 // FIXME Test the src file and content 572 } 573 574 func TestCopyFileWithTarSrcFolder(t *testing.T) { 575 folder, err := ioutil.TempDir("", "docker-archive-copyfilewithtar-test") 576 if err != nil { 577 t.Fatal(err) 578 } 579 defer os.RemoveAll(folder) 580 dest := filepath.Join(folder, "dest") 581 src := filepath.Join(folder, "srcfolder") 582 err = os.MkdirAll(src, 0740) 583 if err != nil { 584 t.Fatal(err) 585 } 586 err = os.MkdirAll(dest, 0740) 587 if err != nil { 588 t.Fatal(err) 589 } 590 err = CopyFileWithTar(src, dest) 591 if err == nil { 592 t.Fatalf("CopyFileWithTar should throw an error with a folder.") 593 } 594 } 595 596 func TestCopyFileWithTarSrcFile(t *testing.T) { 597 folder, err := ioutil.TempDir("", "docker-archive-test") 598 if err != nil { 599 t.Fatal(err) 600 } 601 defer os.RemoveAll(folder) 602 dest := filepath.Join(folder, "dest") 603 srcFolder := filepath.Join(folder, "src") 604 src := filepath.Join(folder, filepath.Join("src", "src")) 605 err = os.MkdirAll(srcFolder, 0740) 606 if err != nil { 607 t.Fatal(err) 608 } 609 err = os.MkdirAll(dest, 0740) 610 if err != nil { 611 t.Fatal(err) 612 } 613 ioutil.WriteFile(src, []byte("content"), 0777) 614 err = CopyWithTar(src, dest+"/") 615 if err != nil { 616 t.Fatalf("archiver.CopyFileWithTar shouldn't throw an error, %s.", err) 617 } 618 _, err = os.Stat(dest) 619 if err != nil { 620 t.Fatalf("Destination folder should contain the source file but did not.") 621 } 622 } 623 624 func TestTarFiles(t *testing.T) { 625 // TODO Windows: Figure out how to port this test. 626 if runtime.GOOS == "windows" { 627 t.Skip("Failing on Windows") 628 } 629 // try without hardlinks 630 if err := checkNoChanges(1000, false); err != nil { 631 t.Fatal(err) 632 } 633 // try with hardlinks 634 if err := checkNoChanges(1000, true); err != nil { 635 t.Fatal(err) 636 } 637 } 638 639 func checkNoChanges(fileNum int, hardlinks bool) error { 640 srcDir, err := ioutil.TempDir("", "docker-test-srcDir") 641 if err != nil { 642 return err 643 } 644 defer os.RemoveAll(srcDir) 645 646 destDir, err := ioutil.TempDir("", "docker-test-destDir") 647 if err != nil { 648 return err 649 } 650 defer os.RemoveAll(destDir) 651 652 _, err = prepareUntarSourceDirectory(fileNum, srcDir, hardlinks) 653 if err != nil { 654 return err 655 } 656 657 err = TarUntar(srcDir, destDir) 658 if err != nil { 659 return err 660 } 661 662 changes, err := ChangesDirs(destDir, srcDir) 663 if err != nil { 664 return err 665 } 666 if len(changes) > 0 { 667 return fmt.Errorf("with %d files and %v hardlinks: expected 0 changes, got %d", fileNum, hardlinks, len(changes)) 668 } 669 return nil 670 } 671 672 func tarUntar(t *testing.T, origin string, options *TarOptions) ([]Change, error) { 673 archive, err := TarWithOptions(origin, options) 674 if err != nil { 675 t.Fatal(err) 676 } 677 defer archive.Close() 678 679 buf := make([]byte, 10) 680 if _, err := archive.Read(buf); err != nil { 681 return nil, err 682 } 683 wrap := io.MultiReader(bytes.NewReader(buf), archive) 684 685 detectedCompression := DetectCompression(buf) 686 compression := options.Compression 687 if detectedCompression.Extension() != compression.Extension() { 688 return nil, fmt.Errorf("Wrong compression detected. Actual compression: %s, found %s", compression.Extension(), detectedCompression.Extension()) 689 } 690 691 tmp, err := ioutil.TempDir("", "docker-test-untar") 692 if err != nil { 693 return nil, err 694 } 695 defer os.RemoveAll(tmp) 696 if err := Untar(wrap, tmp, nil); err != nil { 697 return nil, err 698 } 699 if _, err := os.Stat(tmp); err != nil { 700 return nil, err 701 } 702 703 return ChangesDirs(origin, tmp) 704 } 705 706 func TestTarUntar(t *testing.T) { 707 // TODO Windows: Figure out how to fix this test. 708 if runtime.GOOS == "windows" { 709 t.Skip("Failing on Windows") 710 } 711 origin, err := ioutil.TempDir("", "docker-test-untar-origin") 712 if err != nil { 713 t.Fatal(err) 714 } 715 defer os.RemoveAll(origin) 716 if err := ioutil.WriteFile(filepath.Join(origin, "1"), []byte("hello world"), 0700); err != nil { 717 t.Fatal(err) 718 } 719 if err := ioutil.WriteFile(filepath.Join(origin, "2"), []byte("welcome!"), 0700); err != nil { 720 t.Fatal(err) 721 } 722 if err := ioutil.WriteFile(filepath.Join(origin, "3"), []byte("will be ignored"), 0700); err != nil { 723 t.Fatal(err) 724 } 725 726 for _, c := range []Compression{ 727 Uncompressed, 728 Gzip, 729 } { 730 changes, err := tarUntar(t, origin, &TarOptions{ 731 Compression: c, 732 ExcludePatterns: []string{"3"}, 733 }) 734 735 if err != nil { 736 t.Fatalf("Error tar/untar for compression %s: %s", c.Extension(), err) 737 } 738 739 if len(changes) != 1 || changes[0].Path != "/3" { 740 t.Fatalf("Unexpected differences after tarUntar: %v", changes) 741 } 742 } 743 } 744 745 func TestTarWithOptions(t *testing.T) { 746 // TODO Windows: Figure out how to fix this test. 747 if runtime.GOOS == "windows" { 748 t.Skip("Failing on Windows") 749 } 750 origin, err := ioutil.TempDir("", "docker-test-untar-origin") 751 if err != nil { 752 t.Fatal(err) 753 } 754 if _, err := ioutil.TempDir(origin, "folder"); err != nil { 755 t.Fatal(err) 756 } 757 defer os.RemoveAll(origin) 758 if err := ioutil.WriteFile(filepath.Join(origin, "1"), []byte("hello world"), 0700); err != nil { 759 t.Fatal(err) 760 } 761 if err := ioutil.WriteFile(filepath.Join(origin, "2"), []byte("welcome!"), 0700); err != nil { 762 t.Fatal(err) 763 } 764 765 cases := []struct { 766 opts *TarOptions 767 numChanges int 768 }{ 769 {&TarOptions{IncludeFiles: []string{"1"}}, 2}, 770 {&TarOptions{ExcludePatterns: []string{"2"}}, 1}, 771 {&TarOptions{ExcludePatterns: []string{"1", "folder*"}}, 2}, 772 {&TarOptions{IncludeFiles: []string{"1", "1"}}, 2}, 773 {&TarOptions{IncludeFiles: []string{"1"}, RebaseNames: map[string]string{"1": "test"}}, 4}, 774 } 775 for _, testCase := range cases { 776 changes, err := tarUntar(t, origin, testCase.opts) 777 if err != nil { 778 t.Fatalf("Error tar/untar when testing inclusion/exclusion: %s", err) 779 } 780 if len(changes) != testCase.numChanges { 781 t.Errorf("Expected %d changes, got %d for %+v:", 782 testCase.numChanges, len(changes), testCase.opts) 783 } 784 } 785 } 786 787 // Some tar archives such as http://haproxy.1wt.eu/download/1.5/src/devel/haproxy-1.5-dev21.tar.gz 788 // use PAX Global Extended Headers. 789 // Failing prevents the archives from being uncompressed during ADD 790 func TestTypeXGlobalHeaderDoesNotFail(t *testing.T) { 791 hdr := tar.Header{Typeflag: tar.TypeXGlobalHeader} 792 tmpDir, err := ioutil.TempDir("", "docker-test-archive-pax-test") 793 if err != nil { 794 t.Fatal(err) 795 } 796 defer os.RemoveAll(tmpDir) 797 err = createTarFile(filepath.Join(tmpDir, "pax_global_header"), tmpDir, &hdr, nil, true, nil, false) 798 if err != nil { 799 t.Fatal(err) 800 } 801 } 802 803 // Some tar have both GNU specific (huge uid) and Ustar specific (long name) things. 804 // Not supposed to happen (should use PAX instead of Ustar for long name) but it does and it should still work. 805 func TestUntarUstarGnuConflict(t *testing.T) { 806 f, err := os.Open("testdata/broken.tar") 807 if err != nil { 808 t.Fatal(err) 809 } 810 defer f.Close() 811 812 found := false 813 tr := tar.NewReader(f) 814 // Iterate through the files in the archive. 815 for { 816 hdr, err := tr.Next() 817 if err == io.EOF { 818 // end of tar archive 819 break 820 } 821 if err != nil { 822 t.Fatal(err) 823 } 824 if hdr.Name == "root/.cpanm/work/1395823785.24209/Plack-1.0030/blib/man3/Plack::Middleware::LighttpdScriptNameFix.3pm" { 825 found = true 826 break 827 } 828 } 829 if !found { 830 t.Fatalf("%s not found in the archive", "root/.cpanm/work/1395823785.24209/Plack-1.0030/blib/man3/Plack::Middleware::LighttpdScriptNameFix.3pm") 831 } 832 } 833 834 func prepareUntarSourceDirectory(numberOfFiles int, targetPath string, makeLinks bool) (int, error) { 835 fileData := []byte("fooo") 836 for n := 0; n < numberOfFiles; n++ { 837 fileName := fmt.Sprintf("file-%d", n) 838 if err := ioutil.WriteFile(filepath.Join(targetPath, fileName), fileData, 0700); err != nil { 839 return 0, err 840 } 841 if makeLinks { 842 if err := os.Link(filepath.Join(targetPath, fileName), filepath.Join(targetPath, fileName+"-link")); err != nil { 843 return 0, err 844 } 845 } 846 } 847 totalSize := numberOfFiles * len(fileData) 848 return totalSize, nil 849 } 850 851 func BenchmarkTarUntar(b *testing.B) { 852 origin, err := ioutil.TempDir("", "docker-test-untar-origin") 853 if err != nil { 854 b.Fatal(err) 855 } 856 tempDir, err := ioutil.TempDir("", "docker-test-untar-destination") 857 if err != nil { 858 b.Fatal(err) 859 } 860 target := filepath.Join(tempDir, "dest") 861 n, err := prepareUntarSourceDirectory(100, origin, false) 862 if err != nil { 863 b.Fatal(err) 864 } 865 defer os.RemoveAll(origin) 866 defer os.RemoveAll(tempDir) 867 868 b.ResetTimer() 869 b.SetBytes(int64(n)) 870 for n := 0; n < b.N; n++ { 871 err := TarUntar(origin, target) 872 if err != nil { 873 b.Fatal(err) 874 } 875 os.RemoveAll(target) 876 } 877 } 878 879 func BenchmarkTarUntarWithLinks(b *testing.B) { 880 origin, err := ioutil.TempDir("", "docker-test-untar-origin") 881 if err != nil { 882 b.Fatal(err) 883 } 884 tempDir, err := ioutil.TempDir("", "docker-test-untar-destination") 885 if err != nil { 886 b.Fatal(err) 887 } 888 target := filepath.Join(tempDir, "dest") 889 n, err := prepareUntarSourceDirectory(100, origin, true) 890 if err != nil { 891 b.Fatal(err) 892 } 893 defer os.RemoveAll(origin) 894 defer os.RemoveAll(tempDir) 895 896 b.ResetTimer() 897 b.SetBytes(int64(n)) 898 for n := 0; n < b.N; n++ { 899 err := TarUntar(origin, target) 900 if err != nil { 901 b.Fatal(err) 902 } 903 os.RemoveAll(target) 904 } 905 } 906 907 func TestUntarInvalidFilenames(t *testing.T) { 908 // TODO Windows: Figure out how to fix this test. 909 if runtime.GOOS == "windows" { 910 t.Skip("Passes but hits breakoutError: platform and architecture is not supported") 911 } 912 for i, headers := range [][]*tar.Header{ 913 { 914 { 915 Name: "../victim/dotdot", 916 Typeflag: tar.TypeReg, 917 Mode: 0644, 918 }, 919 }, 920 { 921 { 922 // Note the leading slash 923 Name: "/../victim/slash-dotdot", 924 Typeflag: tar.TypeReg, 925 Mode: 0644, 926 }, 927 }, 928 } { 929 if err := testBreakout("untar", "docker-TestUntarInvalidFilenames", headers); err != nil { 930 t.Fatalf("i=%d. %v", i, err) 931 } 932 } 933 } 934 935 func TestUntarHardlinkToSymlink(t *testing.T) { 936 // TODO Windows. There may be a way of running this, but turning off for now 937 if runtime.GOOS == "windows" { 938 t.Skip("hardlinks on Windows") 939 } 940 for i, headers := range [][]*tar.Header{ 941 { 942 { 943 Name: "symlink1", 944 Typeflag: tar.TypeSymlink, 945 Linkname: "regfile", 946 Mode: 0644, 947 }, 948 { 949 Name: "symlink2", 950 Typeflag: tar.TypeLink, 951 Linkname: "symlink1", 952 Mode: 0644, 953 }, 954 { 955 Name: "regfile", 956 Typeflag: tar.TypeReg, 957 Mode: 0644, 958 }, 959 }, 960 } { 961 if err := testBreakout("untar", "docker-TestUntarHardlinkToSymlink", headers); err != nil { 962 t.Fatalf("i=%d. %v", i, err) 963 } 964 } 965 } 966 967 func TestUntarInvalidHardlink(t *testing.T) { 968 // TODO Windows. There may be a way of running this, but turning off for now 969 if runtime.GOOS == "windows" { 970 t.Skip("hardlinks on Windows") 971 } 972 for i, headers := range [][]*tar.Header{ 973 { // try reading victim/hello (../) 974 { 975 Name: "dotdot", 976 Typeflag: tar.TypeLink, 977 Linkname: "../victim/hello", 978 Mode: 0644, 979 }, 980 }, 981 { // try reading victim/hello (/../) 982 { 983 Name: "slash-dotdot", 984 Typeflag: tar.TypeLink, 985 // Note the leading slash 986 Linkname: "/../victim/hello", 987 Mode: 0644, 988 }, 989 }, 990 { // try writing victim/file 991 { 992 Name: "loophole-victim", 993 Typeflag: tar.TypeLink, 994 Linkname: "../victim", 995 Mode: 0755, 996 }, 997 { 998 Name: "loophole-victim/file", 999 Typeflag: tar.TypeReg, 1000 Mode: 0644, 1001 }, 1002 }, 1003 { // try reading victim/hello (hardlink, symlink) 1004 { 1005 Name: "loophole-victim", 1006 Typeflag: tar.TypeLink, 1007 Linkname: "../victim", 1008 Mode: 0755, 1009 }, 1010 { 1011 Name: "symlink", 1012 Typeflag: tar.TypeSymlink, 1013 Linkname: "loophole-victim/hello", 1014 Mode: 0644, 1015 }, 1016 }, 1017 { // Try reading victim/hello (hardlink, hardlink) 1018 { 1019 Name: "loophole-victim", 1020 Typeflag: tar.TypeLink, 1021 Linkname: "../victim", 1022 Mode: 0755, 1023 }, 1024 { 1025 Name: "hardlink", 1026 Typeflag: tar.TypeLink, 1027 Linkname: "loophole-victim/hello", 1028 Mode: 0644, 1029 }, 1030 }, 1031 { // Try removing victim directory (hardlink) 1032 { 1033 Name: "loophole-victim", 1034 Typeflag: tar.TypeLink, 1035 Linkname: "../victim", 1036 Mode: 0755, 1037 }, 1038 { 1039 Name: "loophole-victim", 1040 Typeflag: tar.TypeReg, 1041 Mode: 0644, 1042 }, 1043 }, 1044 } { 1045 if err := testBreakout("untar", "docker-TestUntarInvalidHardlink", headers); err != nil { 1046 t.Fatalf("i=%d. %v", i, err) 1047 } 1048 } 1049 } 1050 1051 func TestUntarInvalidSymlink(t *testing.T) { 1052 // TODO Windows. There may be a way of running this, but turning off for now 1053 if runtime.GOOS == "windows" { 1054 t.Skip("hardlinks on Windows") 1055 } 1056 for i, headers := range [][]*tar.Header{ 1057 { // try reading victim/hello (../) 1058 { 1059 Name: "dotdot", 1060 Typeflag: tar.TypeSymlink, 1061 Linkname: "../victim/hello", 1062 Mode: 0644, 1063 }, 1064 }, 1065 { // try reading victim/hello (/../) 1066 { 1067 Name: "slash-dotdot", 1068 Typeflag: tar.TypeSymlink, 1069 // Note the leading slash 1070 Linkname: "/../victim/hello", 1071 Mode: 0644, 1072 }, 1073 }, 1074 { // try writing victim/file 1075 { 1076 Name: "loophole-victim", 1077 Typeflag: tar.TypeSymlink, 1078 Linkname: "../victim", 1079 Mode: 0755, 1080 }, 1081 { 1082 Name: "loophole-victim/file", 1083 Typeflag: tar.TypeReg, 1084 Mode: 0644, 1085 }, 1086 }, 1087 { // try reading victim/hello (symlink, symlink) 1088 { 1089 Name: "loophole-victim", 1090 Typeflag: tar.TypeSymlink, 1091 Linkname: "../victim", 1092 Mode: 0755, 1093 }, 1094 { 1095 Name: "symlink", 1096 Typeflag: tar.TypeSymlink, 1097 Linkname: "loophole-victim/hello", 1098 Mode: 0644, 1099 }, 1100 }, 1101 { // try reading victim/hello (symlink, hardlink) 1102 { 1103 Name: "loophole-victim", 1104 Typeflag: tar.TypeSymlink, 1105 Linkname: "../victim", 1106 Mode: 0755, 1107 }, 1108 { 1109 Name: "hardlink", 1110 Typeflag: tar.TypeLink, 1111 Linkname: "loophole-victim/hello", 1112 Mode: 0644, 1113 }, 1114 }, 1115 { // try removing victim directory (symlink) 1116 { 1117 Name: "loophole-victim", 1118 Typeflag: tar.TypeSymlink, 1119 Linkname: "../victim", 1120 Mode: 0755, 1121 }, 1122 { 1123 Name: "loophole-victim", 1124 Typeflag: tar.TypeReg, 1125 Mode: 0644, 1126 }, 1127 }, 1128 { // try writing to victim/newdir/newfile with a symlink in the path 1129 { 1130 // this header needs to be before the next one, or else there is an error 1131 Name: "dir/loophole", 1132 Typeflag: tar.TypeSymlink, 1133 Linkname: "../../victim", 1134 Mode: 0755, 1135 }, 1136 { 1137 Name: "dir/loophole/newdir/newfile", 1138 Typeflag: tar.TypeReg, 1139 Mode: 0644, 1140 }, 1141 }, 1142 } { 1143 if err := testBreakout("untar", "docker-TestUntarInvalidSymlink", headers); err != nil { 1144 t.Fatalf("i=%d. %v", i, err) 1145 } 1146 } 1147 } 1148 1149 func TestTempArchiveCloseMultipleTimes(t *testing.T) { 1150 reader := ioutil.NopCloser(strings.NewReader("hello")) 1151 tempArchive, err := NewTempArchive(reader, "") 1152 buf := make([]byte, 10) 1153 n, err := tempArchive.Read(buf) 1154 if n != 5 { 1155 t.Fatalf("Expected to read 5 bytes. Read %d instead", n) 1156 } 1157 for i := 0; i < 3; i++ { 1158 if err = tempArchive.Close(); err != nil { 1159 t.Fatalf("i=%d. Unexpected error closing temp archive: %v", i, err) 1160 } 1161 } 1162 }