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