github.com/damirazo/docker@v1.9.0/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{IncludeFiles: []string{"1"}, RebaseNames: map[string]string{"1": "test"}}, 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, nil) 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 // We need this conversion on ARM64 877 return uint64(statT.Nlink), nil 878 } 879 880 func getInode(path string) (uint64, error) { 881 stat, err := os.Stat(path) 882 if err != nil { 883 return 0, err 884 } 885 statT, ok := stat.Sys().(*syscall.Stat_t) 886 if !ok { 887 return 0, fmt.Errorf("expected type *syscall.Stat_t, got %t", stat.Sys()) 888 } 889 return statT.Ino, nil 890 } 891 892 func prepareUntarSourceDirectory(numberOfFiles int, targetPath string, makeLinks bool) (int, error) { 893 fileData := []byte("fooo") 894 for n := 0; n < numberOfFiles; n++ { 895 fileName := fmt.Sprintf("file-%d", n) 896 if err := ioutil.WriteFile(path.Join(targetPath, fileName), fileData, 0700); err != nil { 897 return 0, err 898 } 899 if makeLinks { 900 if err := os.Link(path.Join(targetPath, fileName), path.Join(targetPath, fileName+"-link")); err != nil { 901 return 0, err 902 } 903 } 904 } 905 totalSize := numberOfFiles * len(fileData) 906 return totalSize, nil 907 } 908 909 func BenchmarkTarUntar(b *testing.B) { 910 origin, err := ioutil.TempDir("", "docker-test-untar-origin") 911 if err != nil { 912 b.Fatal(err) 913 } 914 tempDir, err := ioutil.TempDir("", "docker-test-untar-destination") 915 if err != nil { 916 b.Fatal(err) 917 } 918 target := path.Join(tempDir, "dest") 919 n, err := prepareUntarSourceDirectory(100, origin, false) 920 if err != nil { 921 b.Fatal(err) 922 } 923 defer os.RemoveAll(origin) 924 defer os.RemoveAll(tempDir) 925 926 b.ResetTimer() 927 b.SetBytes(int64(n)) 928 for n := 0; n < b.N; n++ { 929 err := TarUntar(origin, target) 930 if err != nil { 931 b.Fatal(err) 932 } 933 os.RemoveAll(target) 934 } 935 } 936 937 func BenchmarkTarUntarWithLinks(b *testing.B) { 938 origin, err := ioutil.TempDir("", "docker-test-untar-origin") 939 if err != nil { 940 b.Fatal(err) 941 } 942 tempDir, err := ioutil.TempDir("", "docker-test-untar-destination") 943 if err != nil { 944 b.Fatal(err) 945 } 946 target := path.Join(tempDir, "dest") 947 n, err := prepareUntarSourceDirectory(100, origin, true) 948 if err != nil { 949 b.Fatal(err) 950 } 951 defer os.RemoveAll(origin) 952 defer os.RemoveAll(tempDir) 953 954 b.ResetTimer() 955 b.SetBytes(int64(n)) 956 for n := 0; n < b.N; n++ { 957 err := TarUntar(origin, target) 958 if err != nil { 959 b.Fatal(err) 960 } 961 os.RemoveAll(target) 962 } 963 } 964 965 func TestUntarInvalidFilenames(t *testing.T) { 966 for i, headers := range [][]*tar.Header{ 967 { 968 { 969 Name: "../victim/dotdot", 970 Typeflag: tar.TypeReg, 971 Mode: 0644, 972 }, 973 }, 974 { 975 { 976 // Note the leading slash 977 Name: "/../victim/slash-dotdot", 978 Typeflag: tar.TypeReg, 979 Mode: 0644, 980 }, 981 }, 982 } { 983 if err := testBreakout("untar", "docker-TestUntarInvalidFilenames", headers); err != nil { 984 t.Fatalf("i=%d. %v", i, err) 985 } 986 } 987 } 988 989 func TestUntarHardlinkToSymlink(t *testing.T) { 990 for i, headers := range [][]*tar.Header{ 991 { 992 { 993 Name: "symlink1", 994 Typeflag: tar.TypeSymlink, 995 Linkname: "regfile", 996 Mode: 0644, 997 }, 998 { 999 Name: "symlink2", 1000 Typeflag: tar.TypeLink, 1001 Linkname: "symlink1", 1002 Mode: 0644, 1003 }, 1004 { 1005 Name: "regfile", 1006 Typeflag: tar.TypeReg, 1007 Mode: 0644, 1008 }, 1009 }, 1010 } { 1011 if err := testBreakout("untar", "docker-TestUntarHardlinkToSymlink", headers); err != nil { 1012 t.Fatalf("i=%d. %v", i, err) 1013 } 1014 } 1015 } 1016 1017 func TestUntarInvalidHardlink(t *testing.T) { 1018 for i, headers := range [][]*tar.Header{ 1019 { // try reading victim/hello (../) 1020 { 1021 Name: "dotdot", 1022 Typeflag: tar.TypeLink, 1023 Linkname: "../victim/hello", 1024 Mode: 0644, 1025 }, 1026 }, 1027 { // try reading victim/hello (/../) 1028 { 1029 Name: "slash-dotdot", 1030 Typeflag: tar.TypeLink, 1031 // Note the leading slash 1032 Linkname: "/../victim/hello", 1033 Mode: 0644, 1034 }, 1035 }, 1036 { // try writing victim/file 1037 { 1038 Name: "loophole-victim", 1039 Typeflag: tar.TypeLink, 1040 Linkname: "../victim", 1041 Mode: 0755, 1042 }, 1043 { 1044 Name: "loophole-victim/file", 1045 Typeflag: tar.TypeReg, 1046 Mode: 0644, 1047 }, 1048 }, 1049 { // try reading victim/hello (hardlink, symlink) 1050 { 1051 Name: "loophole-victim", 1052 Typeflag: tar.TypeLink, 1053 Linkname: "../victim", 1054 Mode: 0755, 1055 }, 1056 { 1057 Name: "symlink", 1058 Typeflag: tar.TypeSymlink, 1059 Linkname: "loophole-victim/hello", 1060 Mode: 0644, 1061 }, 1062 }, 1063 { // Try reading victim/hello (hardlink, hardlink) 1064 { 1065 Name: "loophole-victim", 1066 Typeflag: tar.TypeLink, 1067 Linkname: "../victim", 1068 Mode: 0755, 1069 }, 1070 { 1071 Name: "hardlink", 1072 Typeflag: tar.TypeLink, 1073 Linkname: "loophole-victim/hello", 1074 Mode: 0644, 1075 }, 1076 }, 1077 { // Try removing victim directory (hardlink) 1078 { 1079 Name: "loophole-victim", 1080 Typeflag: tar.TypeLink, 1081 Linkname: "../victim", 1082 Mode: 0755, 1083 }, 1084 { 1085 Name: "loophole-victim", 1086 Typeflag: tar.TypeReg, 1087 Mode: 0644, 1088 }, 1089 }, 1090 } { 1091 if err := testBreakout("untar", "docker-TestUntarInvalidHardlink", headers); err != nil { 1092 t.Fatalf("i=%d. %v", i, err) 1093 } 1094 } 1095 } 1096 1097 func TestUntarInvalidSymlink(t *testing.T) { 1098 for i, headers := range [][]*tar.Header{ 1099 { // try reading victim/hello (../) 1100 { 1101 Name: "dotdot", 1102 Typeflag: tar.TypeSymlink, 1103 Linkname: "../victim/hello", 1104 Mode: 0644, 1105 }, 1106 }, 1107 { // try reading victim/hello (/../) 1108 { 1109 Name: "slash-dotdot", 1110 Typeflag: tar.TypeSymlink, 1111 // Note the leading slash 1112 Linkname: "/../victim/hello", 1113 Mode: 0644, 1114 }, 1115 }, 1116 { // try writing victim/file 1117 { 1118 Name: "loophole-victim", 1119 Typeflag: tar.TypeSymlink, 1120 Linkname: "../victim", 1121 Mode: 0755, 1122 }, 1123 { 1124 Name: "loophole-victim/file", 1125 Typeflag: tar.TypeReg, 1126 Mode: 0644, 1127 }, 1128 }, 1129 { // try reading victim/hello (symlink, symlink) 1130 { 1131 Name: "loophole-victim", 1132 Typeflag: tar.TypeSymlink, 1133 Linkname: "../victim", 1134 Mode: 0755, 1135 }, 1136 { 1137 Name: "symlink", 1138 Typeflag: tar.TypeSymlink, 1139 Linkname: "loophole-victim/hello", 1140 Mode: 0644, 1141 }, 1142 }, 1143 { // try reading victim/hello (symlink, hardlink) 1144 { 1145 Name: "loophole-victim", 1146 Typeflag: tar.TypeSymlink, 1147 Linkname: "../victim", 1148 Mode: 0755, 1149 }, 1150 { 1151 Name: "hardlink", 1152 Typeflag: tar.TypeLink, 1153 Linkname: "loophole-victim/hello", 1154 Mode: 0644, 1155 }, 1156 }, 1157 { // try removing victim directory (symlink) 1158 { 1159 Name: "loophole-victim", 1160 Typeflag: tar.TypeSymlink, 1161 Linkname: "../victim", 1162 Mode: 0755, 1163 }, 1164 { 1165 Name: "loophole-victim", 1166 Typeflag: tar.TypeReg, 1167 Mode: 0644, 1168 }, 1169 }, 1170 { // try writing to victim/newdir/newfile with a symlink in the path 1171 { 1172 // this header needs to be before the next one, or else there is an error 1173 Name: "dir/loophole", 1174 Typeflag: tar.TypeSymlink, 1175 Linkname: "../../victim", 1176 Mode: 0755, 1177 }, 1178 { 1179 Name: "dir/loophole/newdir/newfile", 1180 Typeflag: tar.TypeReg, 1181 Mode: 0644, 1182 }, 1183 }, 1184 } { 1185 if err := testBreakout("untar", "docker-TestUntarInvalidSymlink", headers); err != nil { 1186 t.Fatalf("i=%d. %v", i, err) 1187 } 1188 } 1189 } 1190 1191 func TestTempArchiveCloseMultipleTimes(t *testing.T) { 1192 reader := ioutil.NopCloser(strings.NewReader("hello")) 1193 tempArchive, err := NewTempArchive(reader, "") 1194 buf := make([]byte, 10) 1195 n, err := tempArchive.Read(buf) 1196 if n != 5 { 1197 t.Fatalf("Expected to read 5 bytes. Read %d instead", n) 1198 } 1199 for i := 0; i < 3; i++ { 1200 if err = tempArchive.Close(); err != nil { 1201 t.Fatalf("i=%d. Unexpected error closing temp archive: %v", i, err) 1202 } 1203 } 1204 }