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