github.com/endophage/docker@v1.4.2-0.20161027011718-242853499895/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=1K 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 cmd := exec.Command("sh", "-c", "touch /tmp/archivedata && tar -cf /tmp/archive /tmp/archivedata && gzip --stdout /tmp/archive > /tmp/archive.gz") 85 output, err := cmd.CombinedOutput() 86 if err != nil { 87 t.Fatalf("Fail to create an archive file for test : %s.", output) 88 } 89 if !IsArchivePath(tmp + "/archive") { 90 t.Fatalf("Did not recognise valid tar path as archive") 91 } 92 if !IsArchivePath(tmp + "archive.gz") { 93 t.Fatalf("Did not recognise valid compressed tar path as archive") 94 } 95 } 96 97 func testDecompressStream(t *testing.T, ext, compressCommand string) { 98 cmd := exec.Command("sh", "-c", 99 fmt.Sprintf("touch /tmp/archive && %s /tmp/archive", compressCommand)) 100 output, err := cmd.CombinedOutput() 101 if err != nil { 102 t.Fatalf("Failed to create an archive file for test : %s.", output) 103 } 104 filename := "archive." + ext 105 archive, err := os.Open(tmp + filename) 106 if err != nil { 107 t.Fatalf("Failed to open file %s: %v", filename, err) 108 } 109 defer archive.Close() 110 111 r, err := DecompressStream(archive) 112 if err != nil { 113 t.Fatalf("Failed to decompress %s: %v", filename, err) 114 } 115 if _, err = ioutil.ReadAll(r); err != nil { 116 t.Fatalf("Failed to read the decompressed stream: %v ", err) 117 } 118 if err = r.Close(); err != nil { 119 t.Fatalf("Failed to close the decompressed stream: %v ", err) 120 } 121 } 122 123 func TestDecompressStreamGzip(t *testing.T) { 124 testDecompressStream(t, "gz", "gzip -f") 125 } 126 127 func TestDecompressStreamBzip2(t *testing.T) { 128 testDecompressStream(t, "bz2", "bzip2 -f") 129 } 130 131 func TestDecompressStreamXz(t *testing.T) { 132 if runtime.GOOS == "windows" { 133 t.Skip("Xz not present in msys2") 134 } 135 testDecompressStream(t, "xz", "xz -f") 136 } 137 138 func TestCompressStreamXzUnsuported(t *testing.T) { 139 dest, err := os.Create(tmp + "dest") 140 if err != nil { 141 t.Fatalf("Fail to create the destination file") 142 } 143 defer dest.Close() 144 145 _, err = CompressStream(dest, Xz) 146 if err == nil { 147 t.Fatalf("Should fail as xz is unsupported for compression format.") 148 } 149 } 150 151 func TestCompressStreamBzip2Unsupported(t *testing.T) { 152 dest, err := os.Create(tmp + "dest") 153 if err != nil { 154 t.Fatalf("Fail to create the destination file") 155 } 156 defer dest.Close() 157 158 _, err = CompressStream(dest, Xz) 159 if err == nil { 160 t.Fatalf("Should fail as xz is unsupported for compression format.") 161 } 162 } 163 164 func TestCompressStreamInvalid(t *testing.T) { 165 dest, err := os.Create(tmp + "dest") 166 if err != nil { 167 t.Fatalf("Fail to create the destination file") 168 } 169 defer dest.Close() 170 171 _, err = CompressStream(dest, -1) 172 if err == nil { 173 t.Fatalf("Should fail as xz is unsupported for compression format.") 174 } 175 } 176 177 func TestExtensionInvalid(t *testing.T) { 178 compression := Compression(-1) 179 output := compression.Extension() 180 if output != "" { 181 t.Fatalf("The extension of an invalid compression should be an empty string.") 182 } 183 } 184 185 func TestExtensionUncompressed(t *testing.T) { 186 compression := Uncompressed 187 output := compression.Extension() 188 if output != "tar" { 189 t.Fatalf("The extension of an uncompressed archive should be 'tar'.") 190 } 191 } 192 func TestExtensionBzip2(t *testing.T) { 193 compression := Bzip2 194 output := compression.Extension() 195 if output != "tar.bz2" { 196 t.Fatalf("The extension of a bzip2 archive should be 'tar.bz2'") 197 } 198 } 199 func TestExtensionGzip(t *testing.T) { 200 compression := Gzip 201 output := compression.Extension() 202 if output != "tar.gz" { 203 t.Fatalf("The extension of a bzip2 archive should be 'tar.gz'") 204 } 205 } 206 func TestExtensionXz(t *testing.T) { 207 compression := Xz 208 output := compression.Extension() 209 if output != "tar.xz" { 210 t.Fatalf("The extension of a bzip2 archive should be 'tar.xz'") 211 } 212 } 213 214 func TestCmdStreamLargeStderr(t *testing.T) { 215 cmd := exec.Command("sh", "-c", "dd if=/dev/zero bs=1k count=1000 of=/dev/stderr; echo hello") 216 out, _, err := cmdStream(cmd, nil) 217 if err != nil { 218 t.Fatalf("Failed to start command: %s", err) 219 } 220 errCh := make(chan error) 221 go func() { 222 _, err := io.Copy(ioutil.Discard, out) 223 errCh <- err 224 }() 225 select { 226 case err := <-errCh: 227 if err != nil { 228 t.Fatalf("Command should not have failed (err=%.100s...)", err) 229 } 230 case <-time.After(5 * time.Second): 231 t.Fatalf("Command did not complete in 5 seconds; probable deadlock") 232 } 233 } 234 235 func TestCmdStreamBad(t *testing.T) { 236 // TODO Windows: Figure out why this is failing in CI but not locally 237 if runtime.GOOS == "windows" { 238 t.Skip("Failing on Windows CI machines") 239 } 240 badCmd := exec.Command("sh", "-c", "echo hello; echo >&2 error couldn\\'t reverse the phase pulser; exit 1") 241 out, _, err := cmdStream(badCmd, nil) 242 if err != nil { 243 t.Fatalf("Failed to start command: %s", err) 244 } 245 if output, err := ioutil.ReadAll(out); err == nil { 246 t.Fatalf("Command should have failed") 247 } else if err.Error() != "exit status 1: error couldn't reverse the phase pulser\n" { 248 t.Fatalf("Wrong error value (%s)", err) 249 } else if s := string(output); s != "hello\n" { 250 t.Fatalf("Command output should be '%s', not '%s'", "hello\\n", output) 251 } 252 } 253 254 func TestCmdStreamGood(t *testing.T) { 255 cmd := exec.Command("sh", "-c", "echo hello; exit 0") 256 out, _, err := cmdStream(cmd, nil) 257 if err != nil { 258 t.Fatal(err) 259 } 260 if output, err := ioutil.ReadAll(out); err != nil { 261 t.Fatalf("Command should not have failed (err=%s)", err) 262 } else if s := string(output); s != "hello\n" { 263 t.Fatalf("Command output should be '%s', not '%s'", "hello\\n", output) 264 } 265 } 266 267 func TestUntarPathWithInvalidDest(t *testing.T) { 268 tempFolder, err := ioutil.TempDir("", "docker-archive-test") 269 if err != nil { 270 t.Fatal(err) 271 } 272 defer os.RemoveAll(tempFolder) 273 invalidDestFolder := filepath.Join(tempFolder, "invalidDest") 274 // Create a src file 275 srcFile := filepath.Join(tempFolder, "src") 276 tarFile := filepath.Join(tempFolder, "src.tar") 277 os.Create(srcFile) 278 os.Create(invalidDestFolder) // being a file (not dir) should cause an error 279 280 // Translate back to Unix semantics as next exec.Command is run under sh 281 srcFileU := srcFile 282 tarFileU := tarFile 283 if runtime.GOOS == "windows" { 284 tarFileU = "/tmp/" + filepath.Base(filepath.Dir(tarFile)) + "/src.tar" 285 srcFileU = "/tmp/" + filepath.Base(filepath.Dir(srcFile)) + "/src" 286 } 287 288 cmd := exec.Command("sh", "-c", "tar cf "+tarFileU+" "+srcFileU) 289 _, err = cmd.CombinedOutput() 290 if err != nil { 291 t.Fatal(err) 292 } 293 294 err = UntarPath(tarFile, invalidDestFolder) 295 if err == nil { 296 t.Fatalf("UntarPath with invalid destination path should throw an error.") 297 } 298 } 299 300 func TestUntarPathWithInvalidSrc(t *testing.T) { 301 dest, err := ioutil.TempDir("", "docker-archive-test") 302 if err != nil { 303 t.Fatalf("Fail to create the destination file") 304 } 305 defer os.RemoveAll(dest) 306 err = UntarPath("/invalid/path", dest) 307 if err == nil { 308 t.Fatalf("UntarPath with invalid src path should throw an error.") 309 } 310 } 311 312 func TestUntarPath(t *testing.T) { 313 tmpFolder, err := ioutil.TempDir("", "docker-archive-test") 314 if err != nil { 315 t.Fatal(err) 316 } 317 defer os.RemoveAll(tmpFolder) 318 srcFile := filepath.Join(tmpFolder, "src") 319 tarFile := filepath.Join(tmpFolder, "src.tar") 320 os.Create(filepath.Join(tmpFolder, "src")) 321 322 destFolder := filepath.Join(tmpFolder, "dest") 323 err = os.MkdirAll(destFolder, 0740) 324 if err != nil { 325 t.Fatalf("Fail to create the destination file") 326 } 327 328 // Translate back to Unix semantics as next exec.Command is run under sh 329 srcFileU := srcFile 330 tarFileU := tarFile 331 if runtime.GOOS == "windows" { 332 tarFileU = "/tmp/" + filepath.Base(filepath.Dir(tarFile)) + "/src.tar" 333 srcFileU = "/tmp/" + filepath.Base(filepath.Dir(srcFile)) + "/src" 334 } 335 cmd := exec.Command("sh", "-c", "tar cf "+tarFileU+" "+srcFileU) 336 _, err = cmd.CombinedOutput() 337 if err != nil { 338 t.Fatal(err) 339 } 340 341 err = UntarPath(tarFile, destFolder) 342 if err != nil { 343 t.Fatalf("UntarPath shouldn't throw an error, %s.", err) 344 } 345 expectedFile := filepath.Join(destFolder, srcFileU) 346 _, err = os.Stat(expectedFile) 347 if err != nil { 348 t.Fatalf("Destination folder should contain the source file but did not.") 349 } 350 } 351 352 // Do the same test as above but with the destination as file, it should fail 353 func TestUntarPathWithDestinationFile(t *testing.T) { 354 tmpFolder, err := ioutil.TempDir("", "docker-archive-test") 355 if err != nil { 356 t.Fatal(err) 357 } 358 defer os.RemoveAll(tmpFolder) 359 srcFile := filepath.Join(tmpFolder, "src") 360 tarFile := filepath.Join(tmpFolder, "src.tar") 361 os.Create(filepath.Join(tmpFolder, "src")) 362 363 // Translate back to Unix semantics as next exec.Command is run under sh 364 srcFileU := srcFile 365 tarFileU := tarFile 366 if runtime.GOOS == "windows" { 367 tarFileU = "/tmp/" + filepath.Base(filepath.Dir(tarFile)) + "/src.tar" 368 srcFileU = "/tmp/" + filepath.Base(filepath.Dir(srcFile)) + "/src" 369 } 370 cmd := exec.Command("sh", "-c", "tar cf "+tarFileU+" "+srcFileU) 371 _, err = cmd.CombinedOutput() 372 if err != nil { 373 t.Fatal(err) 374 } 375 destFile := filepath.Join(tmpFolder, "dest") 376 _, err = os.Create(destFile) 377 if err != nil { 378 t.Fatalf("Fail to create the destination file") 379 } 380 err = UntarPath(tarFile, destFile) 381 if err == nil { 382 t.Fatalf("UntarPath should throw an error if the destination if a file") 383 } 384 } 385 386 // Do the same test as above but with the destination folder already exists 387 // and the destination file is a directory 388 // It's working, see https://github.com/docker/docker/issues/10040 389 func TestUntarPathWithDestinationSrcFileAsFolder(t *testing.T) { 390 tmpFolder, err := ioutil.TempDir("", "docker-archive-test") 391 if err != nil { 392 t.Fatal(err) 393 } 394 defer os.RemoveAll(tmpFolder) 395 srcFile := filepath.Join(tmpFolder, "src") 396 tarFile := filepath.Join(tmpFolder, "src.tar") 397 os.Create(srcFile) 398 399 // Translate back to Unix semantics as next exec.Command is run under sh 400 srcFileU := srcFile 401 tarFileU := tarFile 402 if runtime.GOOS == "windows" { 403 tarFileU = "/tmp/" + filepath.Base(filepath.Dir(tarFile)) + "/src.tar" 404 srcFileU = "/tmp/" + filepath.Base(filepath.Dir(srcFile)) + "/src" 405 } 406 407 cmd := exec.Command("sh", "-c", "tar cf "+tarFileU+" "+srcFileU) 408 _, err = cmd.CombinedOutput() 409 if err != nil { 410 t.Fatal(err) 411 } 412 destFolder := filepath.Join(tmpFolder, "dest") 413 err = os.MkdirAll(destFolder, 0740) 414 if err != nil { 415 t.Fatalf("Fail to create the destination folder") 416 } 417 // Let's create a folder that will has the same path as the extracted file (from tar) 418 destSrcFileAsFolder := filepath.Join(destFolder, srcFileU) 419 err = os.MkdirAll(destSrcFileAsFolder, 0740) 420 if err != nil { 421 t.Fatal(err) 422 } 423 err = UntarPath(tarFile, destFolder) 424 if err != nil { 425 t.Fatalf("UntarPath should throw not throw an error if the extracted file already exists and is a folder") 426 } 427 } 428 429 func TestCopyWithTarInvalidSrc(t *testing.T) { 430 tempFolder, err := ioutil.TempDir("", "docker-archive-test") 431 if err != nil { 432 t.Fatal(nil) 433 } 434 destFolder := filepath.Join(tempFolder, "dest") 435 invalidSrc := filepath.Join(tempFolder, "doesnotexists") 436 err = os.MkdirAll(destFolder, 0740) 437 if err != nil { 438 t.Fatal(err) 439 } 440 err = CopyWithTar(invalidSrc, destFolder) 441 if err == nil { 442 t.Fatalf("archiver.CopyWithTar with invalid src path should throw an error.") 443 } 444 } 445 446 func TestCopyWithTarInexistentDestWillCreateIt(t *testing.T) { 447 tempFolder, err := ioutil.TempDir("", "docker-archive-test") 448 if err != nil { 449 t.Fatal(nil) 450 } 451 srcFolder := filepath.Join(tempFolder, "src") 452 inexistentDestFolder := filepath.Join(tempFolder, "doesnotexists") 453 err = os.MkdirAll(srcFolder, 0740) 454 if err != nil { 455 t.Fatal(err) 456 } 457 err = CopyWithTar(srcFolder, inexistentDestFolder) 458 if err != nil { 459 t.Fatalf("CopyWithTar with an inexistent folder shouldn't fail.") 460 } 461 _, err = os.Stat(inexistentDestFolder) 462 if err != nil { 463 t.Fatalf("CopyWithTar with an inexistent folder should create it.") 464 } 465 } 466 467 // Test CopyWithTar with a file as src 468 func TestCopyWithTarSrcFile(t *testing.T) { 469 folder, err := ioutil.TempDir("", "docker-archive-test") 470 if err != nil { 471 t.Fatal(err) 472 } 473 defer os.RemoveAll(folder) 474 dest := filepath.Join(folder, "dest") 475 srcFolder := filepath.Join(folder, "src") 476 src := filepath.Join(folder, filepath.Join("src", "src")) 477 err = os.MkdirAll(srcFolder, 0740) 478 if err != nil { 479 t.Fatal(err) 480 } 481 err = os.MkdirAll(dest, 0740) 482 if err != nil { 483 t.Fatal(err) 484 } 485 ioutil.WriteFile(src, []byte("content"), 0777) 486 err = CopyWithTar(src, dest) 487 if err != nil { 488 t.Fatalf("archiver.CopyWithTar shouldn't throw an error, %s.", err) 489 } 490 _, err = os.Stat(dest) 491 // FIXME Check the content 492 if err != nil { 493 t.Fatalf("Destination file should be the same as the source.") 494 } 495 } 496 497 // Test CopyWithTar with a folder as src 498 func TestCopyWithTarSrcFolder(t *testing.T) { 499 folder, err := ioutil.TempDir("", "docker-archive-test") 500 if err != nil { 501 t.Fatal(err) 502 } 503 defer os.RemoveAll(folder) 504 dest := filepath.Join(folder, "dest") 505 src := filepath.Join(folder, filepath.Join("src", "folder")) 506 err = os.MkdirAll(src, 0740) 507 if err != nil { 508 t.Fatal(err) 509 } 510 err = os.MkdirAll(dest, 0740) 511 if err != nil { 512 t.Fatal(err) 513 } 514 ioutil.WriteFile(filepath.Join(src, "file"), []byte("content"), 0777) 515 err = CopyWithTar(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 (the file inside) 521 if err != nil { 522 t.Fatalf("Destination folder should contain the source file but did not.") 523 } 524 } 525 526 func TestCopyFileWithTarInvalidSrc(t *testing.T) { 527 tempFolder, err := ioutil.TempDir("", "docker-archive-test") 528 if err != nil { 529 t.Fatal(err) 530 } 531 defer os.RemoveAll(tempFolder) 532 destFolder := filepath.Join(tempFolder, "dest") 533 err = os.MkdirAll(destFolder, 0740) 534 if err != nil { 535 t.Fatal(err) 536 } 537 invalidFile := filepath.Join(tempFolder, "doesnotexists") 538 err = CopyFileWithTar(invalidFile, destFolder) 539 if err == nil { 540 t.Fatalf("archiver.CopyWithTar with invalid src path should throw an error.") 541 } 542 } 543 544 func TestCopyFileWithTarInexistentDestWillCreateIt(t *testing.T) { 545 tempFolder, err := ioutil.TempDir("", "docker-archive-test") 546 if err != nil { 547 t.Fatal(nil) 548 } 549 defer os.RemoveAll(tempFolder) 550 srcFile := filepath.Join(tempFolder, "src") 551 inexistentDestFolder := filepath.Join(tempFolder, "doesnotexists") 552 _, err = os.Create(srcFile) 553 if err != nil { 554 t.Fatal(err) 555 } 556 err = CopyFileWithTar(srcFile, inexistentDestFolder) 557 if err != nil { 558 t.Fatalf("CopyWithTar with an inexistent folder shouldn't fail.") 559 } 560 _, err = os.Stat(inexistentDestFolder) 561 if err != nil { 562 t.Fatalf("CopyWithTar with an inexistent folder should create it.") 563 } 564 // FIXME Test the src file and content 565 } 566 567 func TestCopyFileWithTarSrcFolder(t *testing.T) { 568 folder, err := ioutil.TempDir("", "docker-archive-copyfilewithtar-test") 569 if err != nil { 570 t.Fatal(err) 571 } 572 defer os.RemoveAll(folder) 573 dest := filepath.Join(folder, "dest") 574 src := filepath.Join(folder, "srcfolder") 575 err = os.MkdirAll(src, 0740) 576 if err != nil { 577 t.Fatal(err) 578 } 579 err = os.MkdirAll(dest, 0740) 580 if err != nil { 581 t.Fatal(err) 582 } 583 err = CopyFileWithTar(src, dest) 584 if err == nil { 585 t.Fatalf("CopyFileWithTar should throw an error with a folder.") 586 } 587 } 588 589 func TestCopyFileWithTarSrcFile(t *testing.T) { 590 folder, err := ioutil.TempDir("", "docker-archive-test") 591 if err != nil { 592 t.Fatal(err) 593 } 594 defer os.RemoveAll(folder) 595 dest := filepath.Join(folder, "dest") 596 srcFolder := filepath.Join(folder, "src") 597 src := filepath.Join(folder, filepath.Join("src", "src")) 598 err = os.MkdirAll(srcFolder, 0740) 599 if err != nil { 600 t.Fatal(err) 601 } 602 err = os.MkdirAll(dest, 0740) 603 if err != nil { 604 t.Fatal(err) 605 } 606 ioutil.WriteFile(src, []byte("content"), 0777) 607 err = CopyWithTar(src, dest+"/") 608 if err != nil { 609 t.Fatalf("archiver.CopyFileWithTar shouldn't throw an error, %s.", err) 610 } 611 _, err = os.Stat(dest) 612 if err != nil { 613 t.Fatalf("Destination folder should contain the source file but did not.") 614 } 615 } 616 617 func TestTarFiles(t *testing.T) { 618 // TODO Windows: Figure out how to port this test. 619 if runtime.GOOS == "windows" { 620 t.Skip("Failing on Windows") 621 } 622 // try without hardlinks 623 if err := checkNoChanges(1000, false); err != nil { 624 t.Fatal(err) 625 } 626 // try with hardlinks 627 if err := checkNoChanges(1000, true); err != nil { 628 t.Fatal(err) 629 } 630 } 631 632 func checkNoChanges(fileNum int, hardlinks bool) error { 633 srcDir, err := ioutil.TempDir("", "docker-test-srcDir") 634 if err != nil { 635 return err 636 } 637 defer os.RemoveAll(srcDir) 638 639 destDir, err := ioutil.TempDir("", "docker-test-destDir") 640 if err != nil { 641 return err 642 } 643 defer os.RemoveAll(destDir) 644 645 _, err = prepareUntarSourceDirectory(fileNum, srcDir, hardlinks) 646 if err != nil { 647 return err 648 } 649 650 err = TarUntar(srcDir, destDir) 651 if err != nil { 652 return err 653 } 654 655 changes, err := ChangesDirs(destDir, srcDir) 656 if err != nil { 657 return err 658 } 659 if len(changes) > 0 { 660 return fmt.Errorf("with %d files and %v hardlinks: expected 0 changes, got %d", fileNum, hardlinks, len(changes)) 661 } 662 return nil 663 } 664 665 func tarUntar(t *testing.T, origin string, options *TarOptions) ([]Change, error) { 666 archive, err := TarWithOptions(origin, options) 667 if err != nil { 668 t.Fatal(err) 669 } 670 defer archive.Close() 671 672 buf := make([]byte, 10) 673 if _, err := archive.Read(buf); err != nil { 674 return nil, err 675 } 676 wrap := io.MultiReader(bytes.NewReader(buf), archive) 677 678 detectedCompression := DetectCompression(buf) 679 compression := options.Compression 680 if detectedCompression.Extension() != compression.Extension() { 681 return nil, fmt.Errorf("Wrong compression detected. Actual compression: %s, found %s", compression.Extension(), detectedCompression.Extension()) 682 } 683 684 tmp, err := ioutil.TempDir("", "docker-test-untar") 685 if err != nil { 686 return nil, err 687 } 688 defer os.RemoveAll(tmp) 689 if err := Untar(wrap, tmp, nil); err != nil { 690 return nil, err 691 } 692 if _, err := os.Stat(tmp); err != nil { 693 return nil, err 694 } 695 696 return ChangesDirs(origin, tmp) 697 } 698 699 func TestTarUntar(t *testing.T) { 700 // TODO Windows: Figure out how to fix this test. 701 if runtime.GOOS == "windows" { 702 t.Skip("Failing on Windows") 703 } 704 origin, err := ioutil.TempDir("", "docker-test-untar-origin") 705 if err != nil { 706 t.Fatal(err) 707 } 708 defer os.RemoveAll(origin) 709 if err := ioutil.WriteFile(filepath.Join(origin, "1"), []byte("hello world"), 0700); err != nil { 710 t.Fatal(err) 711 } 712 if err := ioutil.WriteFile(filepath.Join(origin, "2"), []byte("welcome!"), 0700); err != nil { 713 t.Fatal(err) 714 } 715 if err := ioutil.WriteFile(filepath.Join(origin, "3"), []byte("will be ignored"), 0700); err != nil { 716 t.Fatal(err) 717 } 718 719 for _, c := range []Compression{ 720 Uncompressed, 721 Gzip, 722 } { 723 changes, err := tarUntar(t, origin, &TarOptions{ 724 Compression: c, 725 ExcludePatterns: []string{"3"}, 726 }) 727 728 if err != nil { 729 t.Fatalf("Error tar/untar for compression %s: %s", c.Extension(), err) 730 } 731 732 if len(changes) != 1 || changes[0].Path != "/3" { 733 t.Fatalf("Unexpected differences after tarUntar: %v", changes) 734 } 735 } 736 } 737 738 func TestTarWithOptions(t *testing.T) { 739 // TODO Windows: Figure out how to fix this test. 740 if runtime.GOOS == "windows" { 741 t.Skip("Failing on Windows") 742 } 743 origin, err := ioutil.TempDir("", "docker-test-untar-origin") 744 if err != nil { 745 t.Fatal(err) 746 } 747 if _, err := ioutil.TempDir(origin, "folder"); err != nil { 748 t.Fatal(err) 749 } 750 defer os.RemoveAll(origin) 751 if err := ioutil.WriteFile(filepath.Join(origin, "1"), []byte("hello world"), 0700); err != nil { 752 t.Fatal(err) 753 } 754 if err := ioutil.WriteFile(filepath.Join(origin, "2"), []byte("welcome!"), 0700); err != nil { 755 t.Fatal(err) 756 } 757 758 cases := []struct { 759 opts *TarOptions 760 numChanges int 761 }{ 762 {&TarOptions{IncludeFiles: []string{"1"}}, 2}, 763 {&TarOptions{ExcludePatterns: []string{"2"}}, 1}, 764 {&TarOptions{ExcludePatterns: []string{"1", "folder*"}}, 2}, 765 {&TarOptions{IncludeFiles: []string{"1", "1"}}, 2}, 766 {&TarOptions{IncludeFiles: []string{"1"}, RebaseNames: map[string]string{"1": "test"}}, 4}, 767 } 768 for _, testCase := range cases { 769 changes, err := tarUntar(t, origin, testCase.opts) 770 if err != nil { 771 t.Fatalf("Error tar/untar when testing inclusion/exclusion: %s", err) 772 } 773 if len(changes) != testCase.numChanges { 774 t.Errorf("Expected %d changes, got %d for %+v:", 775 testCase.numChanges, len(changes), testCase.opts) 776 } 777 } 778 } 779 780 // Some tar archives such as http://haproxy.1wt.eu/download/1.5/src/devel/haproxy-1.5-dev21.tar.gz 781 // use PAX Global Extended Headers. 782 // Failing prevents the archives from being uncompressed during ADD 783 func TestTypeXGlobalHeaderDoesNotFail(t *testing.T) { 784 hdr := tar.Header{Typeflag: tar.TypeXGlobalHeader} 785 tmpDir, err := ioutil.TempDir("", "docker-test-archive-pax-test") 786 if err != nil { 787 t.Fatal(err) 788 } 789 defer os.RemoveAll(tmpDir) 790 err = createTarFile(filepath.Join(tmpDir, "pax_global_header"), tmpDir, &hdr, nil, true, nil, false) 791 if err != nil { 792 t.Fatal(err) 793 } 794 } 795 796 // Some tar have both GNU specific (huge uid) and Ustar specific (long name) things. 797 // Not supposed to happen (should use PAX instead of Ustar for long name) but it does and it should still work. 798 func TestUntarUstarGnuConflict(t *testing.T) { 799 f, err := os.Open("testdata/broken.tar") 800 if err != nil { 801 t.Fatal(err) 802 } 803 defer f.Close() 804 805 found := false 806 tr := tar.NewReader(f) 807 // Iterate through the files in the archive. 808 for { 809 hdr, err := tr.Next() 810 if err == io.EOF { 811 // end of tar archive 812 break 813 } 814 if err != nil { 815 t.Fatal(err) 816 } 817 if hdr.Name == "root/.cpanm/work/1395823785.24209/Plack-1.0030/blib/man3/Plack::Middleware::LighttpdScriptNameFix.3pm" { 818 found = true 819 break 820 } 821 } 822 if !found { 823 t.Fatalf("%s not found in the archive", "root/.cpanm/work/1395823785.24209/Plack-1.0030/blib/man3/Plack::Middleware::LighttpdScriptNameFix.3pm") 824 } 825 } 826 827 func prepareUntarSourceDirectory(numberOfFiles int, targetPath string, makeLinks bool) (int, error) { 828 fileData := []byte("fooo") 829 for n := 0; n < numberOfFiles; n++ { 830 fileName := fmt.Sprintf("file-%d", n) 831 if err := ioutil.WriteFile(filepath.Join(targetPath, fileName), fileData, 0700); err != nil { 832 return 0, err 833 } 834 if makeLinks { 835 if err := os.Link(filepath.Join(targetPath, fileName), filepath.Join(targetPath, fileName+"-link")); err != nil { 836 return 0, err 837 } 838 } 839 } 840 totalSize := numberOfFiles * len(fileData) 841 return totalSize, nil 842 } 843 844 func BenchmarkTarUntar(b *testing.B) { 845 origin, err := ioutil.TempDir("", "docker-test-untar-origin") 846 if err != nil { 847 b.Fatal(err) 848 } 849 tempDir, err := ioutil.TempDir("", "docker-test-untar-destination") 850 if err != nil { 851 b.Fatal(err) 852 } 853 target := filepath.Join(tempDir, "dest") 854 n, err := prepareUntarSourceDirectory(100, origin, false) 855 if err != nil { 856 b.Fatal(err) 857 } 858 defer os.RemoveAll(origin) 859 defer os.RemoveAll(tempDir) 860 861 b.ResetTimer() 862 b.SetBytes(int64(n)) 863 for n := 0; n < b.N; n++ { 864 err := TarUntar(origin, target) 865 if err != nil { 866 b.Fatal(err) 867 } 868 os.RemoveAll(target) 869 } 870 } 871 872 func BenchmarkTarUntarWithLinks(b *testing.B) { 873 origin, err := ioutil.TempDir("", "docker-test-untar-origin") 874 if err != nil { 875 b.Fatal(err) 876 } 877 tempDir, err := ioutil.TempDir("", "docker-test-untar-destination") 878 if err != nil { 879 b.Fatal(err) 880 } 881 target := filepath.Join(tempDir, "dest") 882 n, err := prepareUntarSourceDirectory(100, origin, true) 883 if err != nil { 884 b.Fatal(err) 885 } 886 defer os.RemoveAll(origin) 887 defer os.RemoveAll(tempDir) 888 889 b.ResetTimer() 890 b.SetBytes(int64(n)) 891 for n := 0; n < b.N; n++ { 892 err := TarUntar(origin, target) 893 if err != nil { 894 b.Fatal(err) 895 } 896 os.RemoveAll(target) 897 } 898 } 899 900 func TestUntarInvalidFilenames(t *testing.T) { 901 // TODO Windows: Figure out how to fix this test. 902 if runtime.GOOS == "windows" { 903 t.Skip("Passes but hits breakoutError: platform and architecture is not supported") 904 } 905 for i, headers := range [][]*tar.Header{ 906 { 907 { 908 Name: "../victim/dotdot", 909 Typeflag: tar.TypeReg, 910 Mode: 0644, 911 }, 912 }, 913 { 914 { 915 // Note the leading slash 916 Name: "/../victim/slash-dotdot", 917 Typeflag: tar.TypeReg, 918 Mode: 0644, 919 }, 920 }, 921 } { 922 if err := testBreakout("untar", "docker-TestUntarInvalidFilenames", headers); err != nil { 923 t.Fatalf("i=%d. %v", i, err) 924 } 925 } 926 } 927 928 func TestUntarHardlinkToSymlink(t *testing.T) { 929 // TODO Windows. There may be a way of running this, but turning off for now 930 if runtime.GOOS == "windows" { 931 t.Skip("hardlinks on Windows") 932 } 933 for i, headers := range [][]*tar.Header{ 934 { 935 { 936 Name: "symlink1", 937 Typeflag: tar.TypeSymlink, 938 Linkname: "regfile", 939 Mode: 0644, 940 }, 941 { 942 Name: "symlink2", 943 Typeflag: tar.TypeLink, 944 Linkname: "symlink1", 945 Mode: 0644, 946 }, 947 { 948 Name: "regfile", 949 Typeflag: tar.TypeReg, 950 Mode: 0644, 951 }, 952 }, 953 } { 954 if err := testBreakout("untar", "docker-TestUntarHardlinkToSymlink", headers); err != nil { 955 t.Fatalf("i=%d. %v", i, err) 956 } 957 } 958 } 959 960 func TestUntarInvalidHardlink(t *testing.T) { 961 // TODO Windows. There may be a way of running this, but turning off for now 962 if runtime.GOOS == "windows" { 963 t.Skip("hardlinks on Windows") 964 } 965 for i, headers := range [][]*tar.Header{ 966 { // try reading victim/hello (../) 967 { 968 Name: "dotdot", 969 Typeflag: tar.TypeLink, 970 Linkname: "../victim/hello", 971 Mode: 0644, 972 }, 973 }, 974 { // try reading victim/hello (/../) 975 { 976 Name: "slash-dotdot", 977 Typeflag: tar.TypeLink, 978 // Note the leading slash 979 Linkname: "/../victim/hello", 980 Mode: 0644, 981 }, 982 }, 983 { // try writing victim/file 984 { 985 Name: "loophole-victim", 986 Typeflag: tar.TypeLink, 987 Linkname: "../victim", 988 Mode: 0755, 989 }, 990 { 991 Name: "loophole-victim/file", 992 Typeflag: tar.TypeReg, 993 Mode: 0644, 994 }, 995 }, 996 { // try reading victim/hello (hardlink, symlink) 997 { 998 Name: "loophole-victim", 999 Typeflag: tar.TypeLink, 1000 Linkname: "../victim", 1001 Mode: 0755, 1002 }, 1003 { 1004 Name: "symlink", 1005 Typeflag: tar.TypeSymlink, 1006 Linkname: "loophole-victim/hello", 1007 Mode: 0644, 1008 }, 1009 }, 1010 { // Try reading victim/hello (hardlink, hardlink) 1011 { 1012 Name: "loophole-victim", 1013 Typeflag: tar.TypeLink, 1014 Linkname: "../victim", 1015 Mode: 0755, 1016 }, 1017 { 1018 Name: "hardlink", 1019 Typeflag: tar.TypeLink, 1020 Linkname: "loophole-victim/hello", 1021 Mode: 0644, 1022 }, 1023 }, 1024 { // Try removing victim directory (hardlink) 1025 { 1026 Name: "loophole-victim", 1027 Typeflag: tar.TypeLink, 1028 Linkname: "../victim", 1029 Mode: 0755, 1030 }, 1031 { 1032 Name: "loophole-victim", 1033 Typeflag: tar.TypeReg, 1034 Mode: 0644, 1035 }, 1036 }, 1037 } { 1038 if err := testBreakout("untar", "docker-TestUntarInvalidHardlink", headers); err != nil { 1039 t.Fatalf("i=%d. %v", i, err) 1040 } 1041 } 1042 } 1043 1044 func TestUntarInvalidSymlink(t *testing.T) { 1045 // TODO Windows. There may be a way of running this, but turning off for now 1046 if runtime.GOOS == "windows" { 1047 t.Skip("hardlinks on Windows") 1048 } 1049 for i, headers := range [][]*tar.Header{ 1050 { // try reading victim/hello (../) 1051 { 1052 Name: "dotdot", 1053 Typeflag: tar.TypeSymlink, 1054 Linkname: "../victim/hello", 1055 Mode: 0644, 1056 }, 1057 }, 1058 { // try reading victim/hello (/../) 1059 { 1060 Name: "slash-dotdot", 1061 Typeflag: tar.TypeSymlink, 1062 // Note the leading slash 1063 Linkname: "/../victim/hello", 1064 Mode: 0644, 1065 }, 1066 }, 1067 { // try writing victim/file 1068 { 1069 Name: "loophole-victim", 1070 Typeflag: tar.TypeSymlink, 1071 Linkname: "../victim", 1072 Mode: 0755, 1073 }, 1074 { 1075 Name: "loophole-victim/file", 1076 Typeflag: tar.TypeReg, 1077 Mode: 0644, 1078 }, 1079 }, 1080 { // try reading victim/hello (symlink, symlink) 1081 { 1082 Name: "loophole-victim", 1083 Typeflag: tar.TypeSymlink, 1084 Linkname: "../victim", 1085 Mode: 0755, 1086 }, 1087 { 1088 Name: "symlink", 1089 Typeflag: tar.TypeSymlink, 1090 Linkname: "loophole-victim/hello", 1091 Mode: 0644, 1092 }, 1093 }, 1094 { // try reading victim/hello (symlink, hardlink) 1095 { 1096 Name: "loophole-victim", 1097 Typeflag: tar.TypeSymlink, 1098 Linkname: "../victim", 1099 Mode: 0755, 1100 }, 1101 { 1102 Name: "hardlink", 1103 Typeflag: tar.TypeLink, 1104 Linkname: "loophole-victim/hello", 1105 Mode: 0644, 1106 }, 1107 }, 1108 { // try removing victim directory (symlink) 1109 { 1110 Name: "loophole-victim", 1111 Typeflag: tar.TypeSymlink, 1112 Linkname: "../victim", 1113 Mode: 0755, 1114 }, 1115 { 1116 Name: "loophole-victim", 1117 Typeflag: tar.TypeReg, 1118 Mode: 0644, 1119 }, 1120 }, 1121 { // try writing to victim/newdir/newfile with a symlink in the path 1122 { 1123 // this header needs to be before the next one, or else there is an error 1124 Name: "dir/loophole", 1125 Typeflag: tar.TypeSymlink, 1126 Linkname: "../../victim", 1127 Mode: 0755, 1128 }, 1129 { 1130 Name: "dir/loophole/newdir/newfile", 1131 Typeflag: tar.TypeReg, 1132 Mode: 0644, 1133 }, 1134 }, 1135 } { 1136 if err := testBreakout("untar", "docker-TestUntarInvalidSymlink", headers); err != nil { 1137 t.Fatalf("i=%d. %v", i, err) 1138 } 1139 } 1140 } 1141 1142 func TestTempArchiveCloseMultipleTimes(t *testing.T) { 1143 reader := ioutil.NopCloser(strings.NewReader("hello")) 1144 tempArchive, err := NewTempArchive(reader, "") 1145 buf := make([]byte, 10) 1146 n, err := tempArchive.Read(buf) 1147 if n != 5 { 1148 t.Fatalf("Expected to read 5 bytes. Read %d instead", n) 1149 } 1150 for i := 0; i < 3; i++ { 1151 if err = tempArchive.Close(); err != nil { 1152 t.Fatalf("i=%d. Unexpected error closing temp archive: %v", i, err) 1153 } 1154 } 1155 }