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