17 "github.com/stretchr/testify/assert"
18 "github.com/stretchr/testify/require"
25 if runtime.GOOS == "windows" {
26 tmp = os.Getenv("TEMP") + `\`
30 var defaultArchiver = NewDefaultArchiver()
32 func defaultTarUntar(src, dst string) error {
33 return defaultArchiver.TarUntar(src, dst)
36 func defaultUntarPath(src, dst string) error {
37 return defaultArchiver.UntarPath(src, dst)
40 func defaultCopyFileWithTar(src, dst string) (err error) {
41 return defaultArchiver.CopyFileWithTar(src, dst)
44 func defaultCopyWithTar(src, dst string) error {
45 return defaultArchiver.CopyWithTar(src, dst)
48 func TestIsArchivePathDir(t *testing.T) {
49 cmd := exec.Command("sh", "-c", "mkdir -p /tmp/archivedir")
50 output, err := cmd.CombinedOutput()
52 t.Fatalf("Fail to create an archive file for test : %s.", output)
54 if IsArchivePath(tmp + "archivedir") {
55 t.Fatalf("Incorrectly recognised directory as an archive")
59 func TestIsArchivePathInvalidFile(t *testing.T) {
60 cmd := exec.Command("sh", "-c", "dd if=/dev/zero bs=1024 count=1 of=/tmp/archive && gzip --stdout /tmp/archive > /tmp/archive.gz")
61 output, err := cmd.CombinedOutput()
63 t.Fatalf("Fail to create an archive file for test : %s.", output)
65 if IsArchivePath(tmp + "archive") {
66 t.Fatalf("Incorrectly recognised invalid tar path as archive")
68 if IsArchivePath(tmp + "archive.gz") {
69 t.Fatalf("Incorrectly recognised invalid compressed tar path as archive")
73 func TestIsArchivePathTar(t *testing.T) {
75 if runtime.GOOS == "solaris" {
80 cmdStr := fmt.Sprintf("touch /tmp/archivedata && %s -cf /tmp/archive /tmp/archivedata && gzip --stdout /tmp/archive > /tmp/archive.gz", whichTar)
81 cmd := exec.Command("sh", "-c", cmdStr)
82 output, err := cmd.CombinedOutput()
84 t.Fatalf("Fail to create an archive file for test : %s.", output)
86 if !IsArchivePath(tmp + "/archive") {
87 t.Fatalf("Did not recognise valid tar path as archive")
89 if !IsArchivePath(tmp + "archive.gz") {
90 t.Fatalf("Did not recognise valid compressed tar path as archive")
94 func testDecompressStream(t *testing.T, ext, compressCommand string) {
95 cmd := exec.Command("sh", "-c",
96 fmt.Sprintf("touch /tmp/archive && %s /tmp/archive", compressCommand))
97 output, err := cmd.CombinedOutput()
99 t.Fatalf("Failed to create an archive file for test : %s.", output)
101 filename := "archive." + ext
102 archive, err := os.Open(tmp + filename)
104 t.Fatalf("Failed to open file %s: %v", filename, err)
106 defer archive.Close()
108 r, err := DecompressStream(archive)
110 t.Fatalf("Failed to decompress %s: %v", filename, err)
112 if _, err = ioutil.ReadAll(r); err != nil {
113 t.Fatalf("Failed to read the decompressed stream: %v ", err)
115 if err = r.Close(); err != nil {
116 t.Fatalf("Failed to close the decompressed stream: %v ", err)
120 func TestDecompressStreamGzip(t *testing.T) {
121 testDecompressStream(t, "gz", "gzip -f")
124 func TestDecompressStreamBzip2(t *testing.T) {
125 testDecompressStream(t, "bz2", "bzip2 -f")
128 func TestDecompressStreamXz(t *testing.T) {
129 if runtime.GOOS == "windows" {
130 t.Skip("Xz not present in msys2")
132 testDecompressStream(t, "xz", "xz -f")
135 func TestCompressStreamXzUnsupported(t *testing.T) {
136 dest, err := os.Create(tmp + "dest")
138 t.Fatalf("Fail to create the destination file")
142 _, err = CompressStream(dest, Xz)
144 t.Fatalf("Should fail as xz is unsupported for compression format.")
148 func TestCompressStreamBzip2Unsupported(t *testing.T) {
149 dest, err := os.Create(tmp + "dest")
151 t.Fatalf("Fail to create the destination file")
155 _, err = CompressStream(dest, Xz)
157 t.Fatalf("Should fail as xz is unsupported for compression format.")
161 func TestCompressStreamInvalid(t *testing.T) {
162 dest, err := os.Create(tmp + "dest")
164 t.Fatalf("Fail to create the destination file")
168 _, err = CompressStream(dest, -1)
170 t.Fatalf("Should fail as xz is unsupported for compression format.")
174 func TestExtensionInvalid(t *testing.T) {
175 compression := Compression(-1)
176 output := compression.Extension()
178 t.Fatalf("The extension of an invalid compression should be an empty string.")
182 func TestExtensionUncompressed(t *testing.T) {
183 compression := Uncompressed
184 output := compression.Extension()
186 t.Fatalf("The extension of an uncompressed archive should be 'tar'.")
189 func TestExtensionBzip2(t *testing.T) {
191 output := compression.Extension()
192 if output != "tar.bz2" {
193 t.Fatalf("The extension of a bzip2 archive should be 'tar.bz2'")
196 func TestExtensionGzip(t *testing.T) {
198 output := compression.Extension()
199 if output != "tar.gz" {
200 t.Fatalf("The extension of a bzip2 archive should be 'tar.gz'")
203 func TestExtensionXz(t *testing.T) {
205 output := compression.Extension()
206 if output != "tar.xz" {
207 t.Fatalf("The extension of a bzip2 archive should be 'tar.xz'")
211 func TestCmdStreamLargeStderr(t *testing.T) {
212 cmd := exec.Command("sh", "-c", "dd if=/dev/zero bs=1k count=1000 of=/dev/stderr; echo hello")
213 out, _, err := cmdStream(cmd, nil)
215 t.Fatalf("Failed to start command: %s", err)
217 errCh := make(chan error)
219 _, err := io.Copy(ioutil.Discard, out)
225 t.Fatalf("Command should not have failed (err=%.100s...)", err)
227 case <-time.After(5 * time.Second):
228 t.Fatalf("Command did not complete in 5 seconds; probable deadlock")
232 func TestCmdStreamBad(t *testing.T) {
233 // TODO Windows: Figure out why this is failing in CI but not locally
234 if runtime.GOOS == "windows" {
235 t.Skip("Failing on Windows CI machines")
237 badCmd := exec.Command("sh", "-c", "echo hello; echo >&2 error couldn\\'t reverse the phase pulser; exit 1")
238 out, _, err := cmdStream(badCmd, nil)
240 t.Fatalf("Failed to start command: %s", err)
242 if output, err := ioutil.ReadAll(out); err == nil {
243 t.Fatalf("Command should have failed")
244 } else if err.Error() != "exit status 1: error couldn't reverse the phase pulser\n" {
245 t.Fatalf("Wrong error value (%s)", err)
246 } else if s := string(output); s != "hello\n" {
247 t.Fatalf("Command output should be '%s', not '%s'", "hello\\n", output)
251 func TestCmdStreamGood(t *testing.T) {
252 cmd := exec.Command("sh", "-c", "echo hello; exit 0")
253 out, _, err := cmdStream(cmd, nil)
257 if output, err := ioutil.ReadAll(out); err != nil {
258 t.Fatalf("Command should not have failed (err=%s)", err)
259 } else if s := string(output); s != "hello\n" {
260 t.Fatalf("Command output should be '%s', not '%s'", "hello\\n", output)
264 func TestUntarPathWithInvalidDest(t *testing.T) {
265 tempFolder, err := ioutil.TempDir("", "docker-archive-test")
269 defer os.RemoveAll(tempFolder)
270 invalidDestFolder := filepath.Join(tempFolder, "invalidDest")
272 srcFile := filepath.Join(tempFolder, "src")
273 tarFile := filepath.Join(tempFolder, "src.tar")
275 os.Create(invalidDestFolder) // being a file (not dir) should cause an error
277 // Translate back to Unix semantics as next exec.Command is run under sh
280 if runtime.GOOS == "windows" {
281 tarFileU = "/tmp/" + filepath.Base(filepath.Dir(tarFile)) + "/src.tar"
282 srcFileU = "/tmp/" + filepath.Base(filepath.Dir(srcFile)) + "/src"
285 cmd := exec.Command("sh", "-c", "tar cf "+tarFileU+" "+srcFileU)
286 _, err = cmd.CombinedOutput()
291 err = defaultUntarPath(tarFile, invalidDestFolder)
293 t.Fatalf("UntarPath with invalid destination path should throw an error.")
297 func TestUntarPathWithInvalidSrc(t *testing.T) {
298 dest, err := ioutil.TempDir("", "docker-archive-test")
300 t.Fatalf("Fail to create the destination file")
302 defer os.RemoveAll(dest)
303 err = defaultUntarPath("/invalid/path", dest)
305 t.Fatalf("UntarPath with invalid src path should throw an error.")
309 func TestUntarPath(t *testing.T) {
310 tmpFolder, err := ioutil.TempDir("", "docker-archive-test")
314 defer os.RemoveAll(tmpFolder)
315 srcFile := filepath.Join(tmpFolder, "src")
316 tarFile := filepath.Join(tmpFolder, "src.tar")
317 os.Create(filepath.Join(tmpFolder, "src"))
319 destFolder := filepath.Join(tmpFolder, "dest")
320 err = os.MkdirAll(destFolder, 0740)
322 t.Fatalf("Fail to create the destination file")
325 // Translate back to Unix semantics as next exec.Command is run under sh
328 if runtime.GOOS == "windows" {
329 tarFileU = "/tmp/" + filepath.Base(filepath.Dir(tarFile)) + "/src.tar"
330 srcFileU = "/tmp/" + filepath.Base(filepath.Dir(srcFile)) + "/src"
332 cmd := exec.Command("sh", "-c", "tar cf "+tarFileU+" "+srcFileU)
333 _, err = cmd.CombinedOutput()
338 err = defaultUntarPath(tarFile, destFolder)
340 t.Fatalf("UntarPath shouldn't throw an error, %s.", err)
342 expectedFile := filepath.Join(destFolder, srcFileU)
343 _, err = os.Stat(expectedFile)
345 t.Fatalf("Destination folder should contain the source file but did not.")
349 // Do the same test as above but with the destination as file, it should fail
350 func TestUntarPathWithDestinationFile(t *testing.T) {
351 tmpFolder, err := ioutil.TempDir("", "docker-archive-test")
355 defer os.RemoveAll(tmpFolder)
356 srcFile := filepath.Join(tmpFolder, "src")
357 tarFile := filepath.Join(tmpFolder, "src.tar")
358 os.Create(filepath.Join(tmpFolder, "src"))
360 // Translate back to Unix semantics as next exec.Command is run under sh
363 if runtime.GOOS == "windows" {
364 tarFileU = "/tmp/" + filepath.Base(filepath.Dir(tarFile)) + "/src.tar"
365 srcFileU = "/tmp/" + filepath.Base(filepath.Dir(srcFile)) + "/src"
367 cmd := exec.Command("sh", "-c", "tar cf "+tarFileU+" "+srcFileU)
368 _, err = cmd.CombinedOutput()
372 destFile := filepath.Join(tmpFolder, "dest")
373 _, err = os.Create(destFile)
375 t.Fatalf("Fail to create the destination file")
377 err = defaultUntarPath(tarFile, destFile)
379 t.Fatalf("UntarPath should throw an error if the destination if a file")
383 // Do the same test as above but with the destination folder already exists
384 // and the destination file is a directory
385 // It's working, see https://github.com/docker/docker/issues/10040
386 func TestUntarPathWithDestinationSrcFileAsFolder(t *testing.T) {
387 tmpFolder, err := ioutil.TempDir("", "docker-archive-test")
391 defer os.RemoveAll(tmpFolder)
392 srcFile := filepath.Join(tmpFolder, "src")
393 tarFile := filepath.Join(tmpFolder, "src.tar")
396 // Translate back to Unix semantics as next exec.Command is run under sh
399 if runtime.GOOS == "windows" {
400 tarFileU = "/tmp/" + filepath.Base(filepath.Dir(tarFile)) + "/src.tar"
401 srcFileU = "/tmp/" + filepath.Base(filepath.Dir(srcFile)) + "/src"
404 cmd := exec.Command("sh", "-c", "tar cf "+tarFileU+" "+srcFileU)
405 _, err = cmd.CombinedOutput()
409 destFolder := filepath.Join(tmpFolder, "dest")
410 err = os.MkdirAll(destFolder, 0740)
412 t.Fatalf("Fail to create the destination folder")
414 // Let's create a folder that will has the same path as the extracted file (from tar)
415 destSrcFileAsFolder := filepath.Join(destFolder, srcFileU)
416 err = os.MkdirAll(destSrcFileAsFolder, 0740)
420 err = defaultUntarPath(tarFile, destFolder)
422 t.Fatalf("UntarPath should throw not throw an error if the extracted file already exists and is a folder")
426 func TestCopyWithTarInvalidSrc(t *testing.T) {
427 tempFolder, err := ioutil.TempDir("", "docker-archive-test")
431 destFolder := filepath.Join(tempFolder, "dest")
432 invalidSrc := filepath.Join(tempFolder, "doesnotexists")
433 err = os.MkdirAll(destFolder, 0740)
437 err = defaultCopyWithTar(invalidSrc, destFolder)
439 t.Fatalf("archiver.CopyWithTar with invalid src path should throw an error.")
443 func TestCopyWithTarInexistentDestWillCreateIt(t *testing.T) {
444 tempFolder, err := ioutil.TempDir("", "docker-archive-test")
448 srcFolder := filepath.Join(tempFolder, "src")
449 inexistentDestFolder := filepath.Join(tempFolder, "doesnotexists")
450 err = os.MkdirAll(srcFolder, 0740)
454 err = defaultCopyWithTar(srcFolder, inexistentDestFolder)
456 t.Fatalf("CopyWithTar with an inexistent folder shouldn't fail.")
458 _, err = os.Stat(inexistentDestFolder)
460 t.Fatalf("CopyWithTar with an inexistent folder should create it.")
464 // Test CopyWithTar with a file as src
465 func TestCopyWithTarSrcFile(t *testing.T) {
466 folder, err := ioutil.TempDir("", "docker-archive-test")
470 defer os.RemoveAll(folder)
471 dest := filepath.Join(folder, "dest")
472 srcFolder := filepath.Join(folder, "src")
473 src := filepath.Join(folder, filepath.Join("src", "src"))
474 err = os.MkdirAll(srcFolder, 0740)
478 err = os.MkdirAll(dest, 0740)
482 ioutil.WriteFile(src, []byte("content"), 0777)
483 err = defaultCopyWithTar(src, dest)
485 t.Fatalf("archiver.CopyWithTar shouldn't throw an error, %s.", err)
487 _, err = os.Stat(dest)
488 // FIXME Check the content
490 t.Fatalf("Destination file should be the same as the source.")
494 // Test CopyWithTar with a folder as src
495 func TestCopyWithTarSrcFolder(t *testing.T) {
496 folder, err := ioutil.TempDir("", "docker-archive-test")
500 defer os.RemoveAll(folder)
501 dest := filepath.Join(folder, "dest")
502 src := filepath.Join(folder, filepath.Join("src", "folder"))
503 err = os.MkdirAll(src, 0740)
507 err = os.MkdirAll(dest, 0740)
511 ioutil.WriteFile(filepath.Join(src, "file"), []byte("content"), 0777)
512 err = defaultCopyWithTar(src, dest)
514 t.Fatalf("archiver.CopyWithTar shouldn't throw an error, %s.", err)
516 _, err = os.Stat(dest)
517 // FIXME Check the content (the file inside)
519 t.Fatalf("Destination folder should contain the source file but did not.")
523 func TestCopyFileWithTarInvalidSrc(t *testing.T) {
524 tempFolder, err := ioutil.TempDir("", "docker-archive-test")
528 defer os.RemoveAll(tempFolder)
529 destFolder := filepath.Join(tempFolder, "dest")
530 err = os.MkdirAll(destFolder, 0740)
534 invalidFile := filepath.Join(tempFolder, "doesnotexists")
535 err = defaultCopyFileWithTar(invalidFile, destFolder)
537 t.Fatalf("archiver.CopyWithTar with invalid src path should throw an error.")
541 func TestCopyFileWithTarInexistentDestWillCreateIt(t *testing.T) {
542 tempFolder, err := ioutil.TempDir("", "docker-archive-test")
546 defer os.RemoveAll(tempFolder)
547 srcFile := filepath.Join(tempFolder, "src")
548 inexistentDestFolder := filepath.Join(tempFolder, "doesnotexists")
549 _, err = os.Create(srcFile)
553 err = defaultCopyFileWithTar(srcFile, inexistentDestFolder)
555 t.Fatalf("CopyWithTar with an inexistent folder shouldn't fail.")
557 _, err = os.Stat(inexistentDestFolder)
559 t.Fatalf("CopyWithTar with an inexistent folder should create it.")
561 // FIXME Test the src file and content
564 func TestCopyFileWithTarSrcFolder(t *testing.T) {
565 folder, err := ioutil.TempDir("", "docker-archive-copyfilewithtar-test")
569 defer os.RemoveAll(folder)
570 dest := filepath.Join(folder, "dest")
571 src := filepath.Join(folder, "srcfolder")
572 err = os.MkdirAll(src, 0740)
576 err = os.MkdirAll(dest, 0740)
580 err = defaultCopyFileWithTar(src, dest)
582 t.Fatalf("CopyFileWithTar should throw an error with a folder.")
586 func TestCopyFileWithTarSrcFile(t *testing.T) {
587 folder, err := ioutil.TempDir("", "docker-archive-test")
591 defer os.RemoveAll(folder)
592 dest := filepath.Join(folder, "dest")
593 srcFolder := filepath.Join(folder, "src")
594 src := filepath.Join(folder, filepath.Join("src", "src"))
595 err = os.MkdirAll(srcFolder, 0740)
599 err = os.MkdirAll(dest, 0740)
603 ioutil.WriteFile(src, []byte("content"), 0777)
604 err = defaultCopyWithTar(src, dest+"/")
606 t.Fatalf("archiver.CopyFileWithTar shouldn't throw an error, %s.", err)
608 _, err = os.Stat(dest)
610 t.Fatalf("Destination folder should contain the source file but did not.")
614 func TestTarFiles(t *testing.T) {
615 // TODO Windows: Figure out how to port this test.
616 if runtime.GOOS == "windows" {
617 t.Skip("Failing on Windows")
619 // try without hardlinks
620 if err := checkNoChanges(1000, false); err != nil {
623 // try with hardlinks
624 if err := checkNoChanges(1000, true); err != nil {
629 func checkNoChanges(fileNum int, hardlinks bool) error {
630 srcDir, err := ioutil.TempDir("", "docker-test-srcDir")
634 defer os.RemoveAll(srcDir)
636 destDir, err := ioutil.TempDir("", "docker-test-destDir")
640 defer os.RemoveAll(destDir)
642 _, err = prepareUntarSourceDirectory(fileNum, srcDir, hardlinks)
647 err = defaultTarUntar(srcDir, destDir)
652 changes, err := ChangesDirs(destDir, srcDir)
656 if len(changes) > 0 {
657 return fmt.Errorf("with %d files and %v hardlinks: expected 0 changes, got %d", fileNum, hardlinks, len(changes))
662 func tarUntar(t *testing.T, origin string, options *TarOptions) ([]Change, error) {
663 archive, err := TarWithOptions(origin, options)
667 defer archive.Close()
669 buf := make([]byte, 10)
670 if _, err := archive.Read(buf); err != nil {
673 wrap := io.MultiReader(bytes.NewReader(buf), archive)
675 detectedCompression := DetectCompression(buf)
676 compression := options.Compression
677 if detectedCompression.Extension() != compression.Extension() {
678 return nil, fmt.Errorf("Wrong compression detected. Actual compression: %s, found %s", compression.Extension(), detectedCompression.Extension())
681 tmp, err := ioutil.TempDir("", "docker-test-untar")
685 defer os.RemoveAll(tmp)
686 if err := Untar(wrap, tmp, nil); err != nil {
689 if _, err := os.Stat(tmp); err != nil {
693 return ChangesDirs(origin, tmp)
696 func TestTarUntar(t *testing.T) {
697 // TODO Windows: Figure out how to fix this test.
698 if runtime.GOOS == "windows" {
699 t.Skip("Failing on Windows")
701 origin, err := ioutil.TempDir("", "docker-test-untar-origin")
705 defer os.RemoveAll(origin)
706 if err := ioutil.WriteFile(filepath.Join(origin, "1"), []byte("hello world"), 0700); err != nil {
709 if err := ioutil.WriteFile(filepath.Join(origin, "2"), []byte("welcome!"), 0700); err != nil {
712 if err := ioutil.WriteFile(filepath.Join(origin, "3"), []byte("will be ignored"), 0700); err != nil {
716 for _, c := range []Compression{
720 changes, err := tarUntar(t, origin, &TarOptions{
722 ExcludePatterns: []string{"3"},
726 t.Fatalf("Error tar/untar for compression %s: %s", c.Extension(), err)
729 if len(changes) != 1 || changes[0].Path != "/3" {
730 t.Fatalf("Unexpected differences after tarUntar: %v", changes)
735 func TestTarWithOptions(t *testing.T) {
736 // TODO Windows: Figure out how to fix this test.
737 if runtime.GOOS == "windows" {
738 t.Skip("Failing on Windows")
740 origin, err := ioutil.TempDir("", "docker-test-untar-origin")
744 if _, err := ioutil.TempDir(origin, "folder"); err != nil {
747 defer os.RemoveAll(origin)
748 if err := ioutil.WriteFile(filepath.Join(origin, "1"), []byte("hello world"), 0700); err != nil {
751 if err := ioutil.WriteFile(filepath.Join(origin, "2"), []byte("welcome!"), 0700); err != nil {
759 {&TarOptions{IncludeFiles: []string{"1"}}, 2},
760 {&TarOptions{ExcludePatterns: []string{"2"}}, 1},
761 {&TarOptions{ExcludePatterns: []string{"1", "folder*"}}, 2},
762 {&TarOptions{IncludeFiles: []string{"1", "1"}}, 2},
763 {&TarOptions{IncludeFiles: []string{"1"}, RebaseNames: map[string]string{"1": "test"}}, 4},
765 for _, testCase := range cases {
766 changes, err := tarUntar(t, origin, testCase.opts)
768 t.Fatalf("Error tar/untar when testing inclusion/exclusion: %s", err)
770 if len(changes) != testCase.numChanges {
771 t.Errorf("Expected %d changes, got %d for %+v:",
772 testCase.numChanges, len(changes), testCase.opts)
777 // Some tar archives such as http://haproxy.1wt.eu/download/1.5/src/devel/haproxy-1.5-dev21.tar.gz
778 // use PAX Global Extended Headers.
779 // Failing prevents the archives from being uncompressed during ADD
780 func TestTypeXGlobalHeaderDoesNotFail(t *testing.T) {
781 hdr := tar.Header{Typeflag: tar.TypeXGlobalHeader}
782 tmpDir, err := ioutil.TempDir("", "docker-test-archive-pax-test")
786 defer os.RemoveAll(tmpDir)
787 err = createTarFile(filepath.Join(tmpDir, "pax_global_header"), tmpDir, &hdr, nil, true, nil, false)
793 // Some tar have both GNU specific (huge uid) and Ustar specific (long name) things.
794 // Not supposed to happen (should use PAX instead of Ustar for long name) but it does and it should still work.
795 func TestUntarUstarGnuConflict(t *testing.T) {
796 f, err := os.Open("testdata/broken.tar")
803 tr := tar.NewReader(f)
804 // Iterate through the files in the archive.
806 hdr, err := tr.Next()
808 // end of tar archive
814 if hdr.Name == "root/.cpanm/work/1395823785.24209/Plack-1.0030/blib/man3/Plack::Middleware::LighttpdScriptNameFix.3pm" {
820 t.Fatalf("%s not found in the archive", "root/.cpanm/work/1395823785.24209/Plack-1.0030/blib/man3/Plack::Middleware::LighttpdScriptNameFix.3pm")
824 func prepareUntarSourceDirectory(numberOfFiles int, targetPath string, makeLinks bool) (int, error) {
825 fileData := []byte("fooo")
826 for n := 0; n < numberOfFiles; n++ {
827 fileName := fmt.Sprintf("file-%d", n)
828 if err := ioutil.WriteFile(filepath.Join(targetPath, fileName), fileData, 0700); err != nil {
832 if err := os.Link(filepath.Join(targetPath, fileName), filepath.Join(targetPath, fileName+"-link")); err != nil {
837 totalSize := numberOfFiles * len(fileData)
838 return totalSize, nil
841 func BenchmarkTarUntar(b *testing.B) {
842 origin, err := ioutil.TempDir("", "docker-test-untar-origin")
846 tempDir, err := ioutil.TempDir("", "docker-test-untar-destination")
850 target := filepath.Join(tempDir, "dest")
851 n, err := prepareUntarSourceDirectory(100, origin, false)
855 defer os.RemoveAll(origin)
856 defer os.RemoveAll(tempDir)
860 for n := 0; n < b.N; n++ {
861 err := defaultTarUntar(origin, target)
869 func BenchmarkTarUntarWithLinks(b *testing.B) {
870 origin, err := ioutil.TempDir("", "docker-test-untar-origin")
874 tempDir, err := ioutil.TempDir("", "docker-test-untar-destination")
878 target := filepath.Join(tempDir, "dest")
879 n, err := prepareUntarSourceDirectory(100, origin, true)
883 defer os.RemoveAll(origin)
884 defer os.RemoveAll(tempDir)
888 for n := 0; n < b.N; n++ {
889 err := defaultTarUntar(origin, target)
897 func TestUntarInvalidFilenames(t *testing.T) {
898 // TODO Windows: Figure out how to fix this test.
899 if runtime.GOOS == "windows" {
900 t.Skip("Passes but hits breakoutError: platform and architecture is not supported")
902 for i, headers := range [][]*tar.Header{
905 Name: "../victim/dotdot",
906 Typeflag: tar.TypeReg,
912 // Note the leading slash
913 Name: "/../victim/slash-dotdot",
914 Typeflag: tar.TypeReg,
919 if err := testBreakout("untar", "docker-TestUntarInvalidFilenames", headers); err != nil {
920 t.Fatalf("i=%d. %v", i, err)
925 func TestUntarHardlinkToSymlink(t *testing.T) {
926 // TODO Windows. There may be a way of running this, but turning off for now
927 if runtime.GOOS == "windows" {
928 t.Skip("hardlinks on Windows")
930 for i, headers := range [][]*tar.Header{
934 Typeflag: tar.TypeSymlink,
940 Typeflag: tar.TypeLink,
941 Linkname: "symlink1",
946 Typeflag: tar.TypeReg,
951 if err := testBreakout("untar", "docker-TestUntarHardlinkToSymlink", headers); err != nil {
952 t.Fatalf("i=%d. %v", i, err)
957 func TestUntarInvalidHardlink(t *testing.T) {
958 // TODO Windows. There may be a way of running this, but turning off for now
959 if runtime.GOOS == "windows" {
960 t.Skip("hardlinks on Windows")
962 for i, headers := range [][]*tar.Header{
963 { // try reading victim/hello (../)
966 Typeflag: tar.TypeLink,
967 Linkname: "../victim/hello",
971 { // try reading victim/hello (/../)
973 Name: "slash-dotdot",
974 Typeflag: tar.TypeLink,
975 // Note the leading slash
976 Linkname: "/../victim/hello",
980 { // try writing victim/file
982 Name: "loophole-victim",
983 Typeflag: tar.TypeLink,
984 Linkname: "../victim",
988 Name: "loophole-victim/file",
989 Typeflag: tar.TypeReg,
993 { // try reading victim/hello (hardlink, symlink)
995 Name: "loophole-victim",
996 Typeflag: tar.TypeLink,
997 Linkname: "../victim",
1002 Typeflag: tar.TypeSymlink,
1003 Linkname: "loophole-victim/hello",
1007 { // Try reading victim/hello (hardlink, hardlink)
1009 Name: "loophole-victim",
1010 Typeflag: tar.TypeLink,
1011 Linkname: "../victim",
1016 Typeflag: tar.TypeLink,
1017 Linkname: "loophole-victim/hello",
1021 { // Try removing victim directory (hardlink)
1023 Name: "loophole-victim",
1024 Typeflag: tar.TypeLink,
1025 Linkname: "../victim",
1029 Name: "loophole-victim",
1030 Typeflag: tar.TypeReg,
1035 if err := testBreakout("untar", "docker-TestUntarInvalidHardlink", headers); err != nil {
1036 t.Fatalf("i=%d. %v", i, err)
1041 func TestUntarInvalidSymlink(t *testing.T) {
1042 // TODO Windows. There may be a way of running this, but turning off for now
1043 if runtime.GOOS == "windows" {
1044 t.Skip("hardlinks on Windows")
1046 for i, headers := range [][]*tar.Header{
1047 { // try reading victim/hello (../)
1050 Typeflag: tar.TypeSymlink,
1051 Linkname: "../victim/hello",
1055 { // try reading victim/hello (/../)
1057 Name: "slash-dotdot",
1058 Typeflag: tar.TypeSymlink,
1059 // Note the leading slash
1060 Linkname: "/../victim/hello",
1064 { // try writing victim/file
1066 Name: "loophole-victim",
1067 Typeflag: tar.TypeSymlink,
1068 Linkname: "../victim",
1072 Name: "loophole-victim/file",
1073 Typeflag: tar.TypeReg,
1077 { // try reading victim/hello (symlink, symlink)
1079 Name: "loophole-victim",
1080 Typeflag: tar.TypeSymlink,
1081 Linkname: "../victim",
1086 Typeflag: tar.TypeSymlink,
1087 Linkname: "loophole-victim/hello",
1091 { // try reading victim/hello (symlink, hardlink)
1093 Name: "loophole-victim",
1094 Typeflag: tar.TypeSymlink,
1095 Linkname: "../victim",
1100 Typeflag: tar.TypeLink,
1101 Linkname: "loophole-victim/hello",
1105 { // try removing victim directory (symlink)
1107 Name: "loophole-victim",
1108 Typeflag: tar.TypeSymlink,
1109 Linkname: "../victim",
1113 Name: "loophole-victim",
1114 Typeflag: tar.TypeReg,
1118 { // try writing to victim/newdir/newfile with a symlink in the path
1120 // this header needs to be before the next one, or else there is an error
1121 Name: "dir/loophole",
1122 Typeflag: tar.TypeSymlink,
1123 Linkname: "../../victim",
1127 Name: "dir/loophole/newdir/newfile",
1128 Typeflag: tar.TypeReg,
1133 if err := testBreakout("untar", "docker-TestUntarInvalidSymlink", headers); err != nil {
1134 t.Fatalf("i=%d. %v", i, err)
1139 func TestTempArchiveCloseMultipleTimes(t *testing.T) {
1140 reader := ioutil.NopCloser(strings.NewReader("hello"))
1141 tempArchive, err := NewTempArchive(reader, "")
1142 buf := make([]byte, 10)
1143 n, err := tempArchive.Read(buf)
1145 t.Fatalf("Expected to read 5 bytes. Read %d instead", n)
1147 for i := 0; i < 3; i++ {
1148 if err = tempArchive.Close(); err != nil {
1149 t.Fatalf("i=%d. Unexpected error closing temp archive: %v", i, err)
1154 func TestReplaceFileTarWrapper(t *testing.T) {
1155 filesInArchive := 20
1156 testcases := []struct {
1159 modifier TarModifierFunc
1164 doc: "Modifier creates a new file",
1165 filename: "newfile",
1166 modifier: createModifier(t),
1167 expected: "the new content",
1168 fileCount: filesInArchive + 1,
1171 doc: "Modifier replaces a file",
1173 modifier: createOrReplaceModifier,
1174 expected: "the new content",
1175 fileCount: filesInArchive,
1178 doc: "Modifier replaces the last file",
1179 filename: fmt.Sprintf("file-%d", filesInArchive-1),
1180 modifier: createOrReplaceModifier,
1181 expected: "the new content",
1182 fileCount: filesInArchive,
1185 doc: "Modifier appends to a file",
1187 modifier: appendModifier,
1188 expected: "fooo\nnext line",
1189 fileCount: filesInArchive,
1193 for _, testcase := range testcases {
1194 sourceArchive, cleanup := buildSourceArchive(t, filesInArchive)
1197 resultArchive := ReplaceFileTarWrapper(
1199 map[string]TarModifierFunc{testcase.filename: testcase.modifier})
1201 actual := readFileFromArchive(t, resultArchive, testcase.filename, testcase.fileCount, testcase.doc)
1202 assert.Equal(t, testcase.expected, actual, testcase.doc)
1206 func buildSourceArchive(t *testing.T, numberOfFiles int) (io.ReadCloser, func()) {
1207 srcDir, err := ioutil.TempDir("", "docker-test-srcDir")
1208 require.NoError(t, err)
1210 _, err = prepareUntarSourceDirectory(numberOfFiles, srcDir, false)
1211 require.NoError(t, err)
1213 sourceArchive, err := TarWithOptions(srcDir, &TarOptions{})
1214 require.NoError(t, err)
1215 return sourceArchive, func() {
1216 os.RemoveAll(srcDir)
1217 sourceArchive.Close()
1221 func createOrReplaceModifier(path string, header *tar.Header, content io.Reader) (*tar.Header, []byte, error) {
1224 Typeflag: tar.TypeReg,
1225 }, []byte("the new content"), nil
1228 func createModifier(t *testing.T) TarModifierFunc {
1229 return func(path string, header *tar.Header, content io.Reader) (*tar.Header, []byte, error) {
1230 assert.Nil(t, content)
1231 return createOrReplaceModifier(path, header, content)
1235 func appendModifier(path string, header *tar.Header, content io.Reader) (*tar.Header, []byte, error) {
1236 buffer := bytes.Buffer{}
1238 if _, err := buffer.ReadFrom(content); err != nil {
1239 return nil, nil, err
1242 buffer.WriteString("\nnext line")
1243 return &tar.Header{Mode: 0600, Typeflag: tar.TypeReg}, buffer.Bytes(), nil
1246 func readFileFromArchive(t *testing.T, archive io.ReadCloser, name string, expectedCount int, doc string) string {
1247 destDir, err := ioutil.TempDir("", "docker-test-destDir")
1248 require.NoError(t, err)
1249 defer os.RemoveAll(destDir)
1251 err = Untar(archive, destDir, nil)
1252 require.NoError(t, err)
1254 files, _ := ioutil.ReadDir(destDir)
1255 assert.Len(t, files, expectedCount, doc)
1257 content, err := ioutil.ReadFile(filepath.Join(destDir, name))
1258 assert.NoError(t, err)
1259 return string(content)