github.com/ncdc/docker@v0.10.1-0.20160129113957-6c6729ef5b74/pkg/archive/archive_test.go (about)

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