github.com/olljanat/moby@v1.13.1/pkg/archive/archive_test.go (about)

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