github.com/hustcat/docker@v1.3.3-0.20160314103604-901c67a8eeab/pkg/archive/archive_test.go (about)

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