github.com/rhatdan/docker@v0.7.7-0.20180119204836-47a0dcbcd20a/pkg/archive/archive_test.go (about)

     1  package archive
     2  
     3  import (
     4  	"archive/tar"
     5  	"bytes"
     6  	"compress/gzip"
     7  	"fmt"
     8  	"io"
     9  	"io/ioutil"
    10  	"os"
    11  	"os/exec"
    12  	"path/filepath"
    13  	"runtime"
    14  	"strings"
    15  	"testing"
    16  	"time"
    17  
    18  	"github.com/docker/docker/pkg/idtools"
    19  	"github.com/docker/docker/pkg/ioutils"
    20  	"github.com/stretchr/testify/assert"
    21  	"github.com/stretchr/testify/require"
    22  )
    23  
    24  var tmp string
    25  
    26  func init() {
    27  	tmp = "/tmp/"
    28  	if runtime.GOOS == "windows" {
    29  		tmp = os.Getenv("TEMP") + `\`
    30  	}
    31  }
    32  
    33  var defaultArchiver = NewDefaultArchiver()
    34  
    35  func defaultTarUntar(src, dst string) error {
    36  	return defaultArchiver.TarUntar(src, dst)
    37  }
    38  
    39  func defaultUntarPath(src, dst string) error {
    40  	return defaultArchiver.UntarPath(src, dst)
    41  }
    42  
    43  func defaultCopyFileWithTar(src, dst string) (err error) {
    44  	return defaultArchiver.CopyFileWithTar(src, dst)
    45  }
    46  
    47  func defaultCopyWithTar(src, dst string) error {
    48  	return defaultArchiver.CopyWithTar(src, dst)
    49  }
    50  
    51  func TestIsArchivePathDir(t *testing.T) {
    52  	cmd := exec.Command("sh", "-c", "mkdir -p /tmp/archivedir")
    53  	output, err := cmd.CombinedOutput()
    54  	if err != nil {
    55  		t.Fatalf("Fail to create an archive file for test : %s.", output)
    56  	}
    57  	if IsArchivePath(tmp + "archivedir") {
    58  		t.Fatalf("Incorrectly recognised directory as an archive")
    59  	}
    60  }
    61  
    62  func TestIsArchivePathInvalidFile(t *testing.T) {
    63  	cmd := exec.Command("sh", "-c", "dd if=/dev/zero bs=1024 count=1 of=/tmp/archive && gzip --stdout /tmp/archive > /tmp/archive.gz")
    64  	output, err := cmd.CombinedOutput()
    65  	if err != nil {
    66  		t.Fatalf("Fail to create an archive file for test : %s.", output)
    67  	}
    68  	if IsArchivePath(tmp + "archive") {
    69  		t.Fatalf("Incorrectly recognised invalid tar path as archive")
    70  	}
    71  	if IsArchivePath(tmp + "archive.gz") {
    72  		t.Fatalf("Incorrectly recognised invalid compressed tar path as archive")
    73  	}
    74  }
    75  
    76  func TestIsArchivePathTar(t *testing.T) {
    77  	whichTar := "tar"
    78  	cmdStr := fmt.Sprintf("touch /tmp/archivedata && %s -cf /tmp/archive /tmp/archivedata && gzip --stdout /tmp/archive > /tmp/archive.gz", whichTar)
    79  	cmd := exec.Command("sh", "-c", cmdStr)
    80  	output, err := cmd.CombinedOutput()
    81  	if err != nil {
    82  		t.Fatalf("Fail to create an archive file for test : %s.", output)
    83  	}
    84  	if !IsArchivePath(tmp + "/archive") {
    85  		t.Fatalf("Did not recognise valid tar path as archive")
    86  	}
    87  	if !IsArchivePath(tmp + "archive.gz") {
    88  		t.Fatalf("Did not recognise valid compressed tar path as archive")
    89  	}
    90  }
    91  
    92  func testDecompressStream(t *testing.T, ext, compressCommand string) io.Reader {
    93  	cmd := exec.Command("sh", "-c",
    94  		fmt.Sprintf("touch /tmp/archive && %s /tmp/archive", compressCommand))
    95  	output, err := cmd.CombinedOutput()
    96  	if err != nil {
    97  		t.Fatalf("Failed to create an archive file for test : %s.", output)
    98  	}
    99  	filename := "archive." + ext
   100  	archive, err := os.Open(tmp + filename)
   101  	if err != nil {
   102  		t.Fatalf("Failed to open file %s: %v", filename, err)
   103  	}
   104  	defer archive.Close()
   105  
   106  	r, err := DecompressStream(archive)
   107  	if err != nil {
   108  		t.Fatalf("Failed to decompress %s: %v", filename, err)
   109  	}
   110  	if _, err = ioutil.ReadAll(r); err != nil {
   111  		t.Fatalf("Failed to read the decompressed stream: %v ", err)
   112  	}
   113  	if err = r.Close(); err != nil {
   114  		t.Fatalf("Failed to close the decompressed stream: %v ", err)
   115  	}
   116  
   117  	return r
   118  }
   119  
   120  func TestDecompressStreamGzip(t *testing.T) {
   121  	testDecompressStream(t, "gz", "gzip -f")
   122  }
   123  
   124  func TestDecompressStreamBzip2(t *testing.T) {
   125  	testDecompressStream(t, "bz2", "bzip2 -f")
   126  }
   127  
   128  func TestDecompressStreamXz(t *testing.T) {
   129  	if runtime.GOOS == "windows" {
   130  		t.Skip("Xz not present in msys2")
   131  	}
   132  	testDecompressStream(t, "xz", "xz -f")
   133  }
   134  
   135  func TestCompressStreamXzUnsupported(t *testing.T) {
   136  	dest, err := os.Create(tmp + "dest")
   137  	if err != nil {
   138  		t.Fatalf("Fail to create the destination file")
   139  	}
   140  	defer dest.Close()
   141  
   142  	_, err = CompressStream(dest, Xz)
   143  	if err == nil {
   144  		t.Fatalf("Should fail as xz is unsupported for compression format.")
   145  	}
   146  }
   147  
   148  func TestCompressStreamBzip2Unsupported(t *testing.T) {
   149  	dest, err := os.Create(tmp + "dest")
   150  	if err != nil {
   151  		t.Fatalf("Fail to create the destination file")
   152  	}
   153  	defer dest.Close()
   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  	defer dest.Close()
   167  
   168  	_, err = CompressStream(dest, -1)
   169  	if err == nil {
   170  		t.Fatalf("Should fail as xz is unsupported for compression format.")
   171  	}
   172  }
   173  
   174  func TestExtensionInvalid(t *testing.T) {
   175  	compression := Compression(-1)
   176  	output := compression.Extension()
   177  	if output != "" {
   178  		t.Fatalf("The extension of an invalid compression should be an empty string.")
   179  	}
   180  }
   181  
   182  func TestExtensionUncompressed(t *testing.T) {
   183  	compression := Uncompressed
   184  	output := compression.Extension()
   185  	if output != "tar" {
   186  		t.Fatalf("The extension of an uncompressed archive should be 'tar'.")
   187  	}
   188  }
   189  func TestExtensionBzip2(t *testing.T) {
   190  	compression := Bzip2
   191  	output := compression.Extension()
   192  	if output != "tar.bz2" {
   193  		t.Fatalf("The extension of a bzip2 archive should be 'tar.bz2'")
   194  	}
   195  }
   196  func TestExtensionGzip(t *testing.T) {
   197  	compression := Gzip
   198  	output := compression.Extension()
   199  	if output != "tar.gz" {
   200  		t.Fatalf("The extension of a bzip2 archive should be 'tar.gz'")
   201  	}
   202  }
   203  func TestExtensionXz(t *testing.T) {
   204  	compression := Xz
   205  	output := compression.Extension()
   206  	if output != "tar.xz" {
   207  		t.Fatalf("The extension of a bzip2 archive should be 'tar.xz'")
   208  	}
   209  }
   210  
   211  func TestCmdStreamLargeStderr(t *testing.T) {
   212  	cmd := exec.Command("sh", "-c", "dd if=/dev/zero bs=1k count=1000 of=/dev/stderr; echo hello")
   213  	out, err := cmdStream(cmd, nil)
   214  	if err != nil {
   215  		t.Fatalf("Failed to start command: %s", err)
   216  	}
   217  	errCh := make(chan error)
   218  	go func() {
   219  		_, err := io.Copy(ioutil.Discard, out)
   220  		errCh <- err
   221  	}()
   222  	select {
   223  	case err := <-errCh:
   224  		if err != nil {
   225  			t.Fatalf("Command should not have failed (err=%.100s...)", err)
   226  		}
   227  	case <-time.After(5 * time.Second):
   228  		t.Fatalf("Command did not complete in 5 seconds; probable deadlock")
   229  	}
   230  }
   231  
   232  func TestCmdStreamBad(t *testing.T) {
   233  	// TODO Windows: Figure out why this is failing in CI but not locally
   234  	if runtime.GOOS == "windows" {
   235  		t.Skip("Failing on Windows CI machines")
   236  	}
   237  	badCmd := exec.Command("sh", "-c", "echo hello; echo >&2 error couldn\\'t reverse the phase pulser; exit 1")
   238  	out, err := cmdStream(badCmd, nil)
   239  	if err != nil {
   240  		t.Fatalf("Failed to start command: %s", err)
   241  	}
   242  	if output, err := ioutil.ReadAll(out); err == nil {
   243  		t.Fatalf("Command should have failed")
   244  	} else if err.Error() != "exit status 1: error couldn't reverse the phase pulser\n" {
   245  		t.Fatalf("Wrong error value (%s)", err)
   246  	} else if s := string(output); s != "hello\n" {
   247  		t.Fatalf("Command output should be '%s', not '%s'", "hello\\n", output)
   248  	}
   249  }
   250  
   251  func TestCmdStreamGood(t *testing.T) {
   252  	cmd := exec.Command("sh", "-c", "echo hello; exit 0")
   253  	out, err := cmdStream(cmd, nil)
   254  	if err != nil {
   255  		t.Fatal(err)
   256  	}
   257  	if output, err := ioutil.ReadAll(out); err != nil {
   258  		t.Fatalf("Command should not have failed (err=%s)", err)
   259  	} else if s := string(output); s != "hello\n" {
   260  		t.Fatalf("Command output should be '%s', not '%s'", "hello\\n", output)
   261  	}
   262  }
   263  
   264  func TestUntarPathWithInvalidDest(t *testing.T) {
   265  	tempFolder, err := ioutil.TempDir("", "docker-archive-test")
   266  	require.NoError(t, err)
   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  	require.NoError(t, err)
   286  
   287  	err = defaultUntarPath(tarFile, invalidDestFolder)
   288  	if err == nil {
   289  		t.Fatalf("UntarPath with invalid destination path should throw an error.")
   290  	}
   291  }
   292  
   293  func TestUntarPathWithInvalidSrc(t *testing.T) {
   294  	dest, err := ioutil.TempDir("", "docker-archive-test")
   295  	if err != nil {
   296  		t.Fatalf("Fail to create the destination file")
   297  	}
   298  	defer os.RemoveAll(dest)
   299  	err = defaultUntarPath("/invalid/path", dest)
   300  	if err == nil {
   301  		t.Fatalf("UntarPath with invalid src path should throw an error.")
   302  	}
   303  }
   304  
   305  func TestUntarPath(t *testing.T) {
   306  	tmpFolder, err := ioutil.TempDir("", "docker-archive-test")
   307  	require.NoError(t, err)
   308  	defer os.RemoveAll(tmpFolder)
   309  	srcFile := filepath.Join(tmpFolder, "src")
   310  	tarFile := filepath.Join(tmpFolder, "src.tar")
   311  	os.Create(filepath.Join(tmpFolder, "src"))
   312  
   313  	destFolder := filepath.Join(tmpFolder, "dest")
   314  	err = os.MkdirAll(destFolder, 0740)
   315  	if err != nil {
   316  		t.Fatalf("Fail to create the destination file")
   317  	}
   318  
   319  	// Translate back to Unix semantics as next exec.Command is run under sh
   320  	srcFileU := srcFile
   321  	tarFileU := tarFile
   322  	if runtime.GOOS == "windows" {
   323  		tarFileU = "/tmp/" + filepath.Base(filepath.Dir(tarFile)) + "/src.tar"
   324  		srcFileU = "/tmp/" + filepath.Base(filepath.Dir(srcFile)) + "/src"
   325  	}
   326  	cmd := exec.Command("sh", "-c", "tar cf "+tarFileU+" "+srcFileU)
   327  	_, err = cmd.CombinedOutput()
   328  	require.NoError(t, err)
   329  
   330  	err = defaultUntarPath(tarFile, destFolder)
   331  	if err != nil {
   332  		t.Fatalf("UntarPath shouldn't throw an error, %s.", err)
   333  	}
   334  	expectedFile := filepath.Join(destFolder, srcFileU)
   335  	_, err = os.Stat(expectedFile)
   336  	if err != nil {
   337  		t.Fatalf("Destination folder should contain the source file but did not.")
   338  	}
   339  }
   340  
   341  // Do the same test as above but with the destination as file, it should fail
   342  func TestUntarPathWithDestinationFile(t *testing.T) {
   343  	tmpFolder, err := ioutil.TempDir("", "docker-archive-test")
   344  	if err != nil {
   345  		t.Fatal(err)
   346  	}
   347  	defer os.RemoveAll(tmpFolder)
   348  	srcFile := filepath.Join(tmpFolder, "src")
   349  	tarFile := filepath.Join(tmpFolder, "src.tar")
   350  	os.Create(filepath.Join(tmpFolder, "src"))
   351  
   352  	// Translate back to Unix semantics as next exec.Command is run under sh
   353  	srcFileU := srcFile
   354  	tarFileU := tarFile
   355  	if runtime.GOOS == "windows" {
   356  		tarFileU = "/tmp/" + filepath.Base(filepath.Dir(tarFile)) + "/src.tar"
   357  		srcFileU = "/tmp/" + filepath.Base(filepath.Dir(srcFile)) + "/src"
   358  	}
   359  	cmd := exec.Command("sh", "-c", "tar cf "+tarFileU+" "+srcFileU)
   360  	_, err = cmd.CombinedOutput()
   361  	if err != nil {
   362  		t.Fatal(err)
   363  	}
   364  	destFile := filepath.Join(tmpFolder, "dest")
   365  	_, err = os.Create(destFile)
   366  	if err != nil {
   367  		t.Fatalf("Fail to create the destination file")
   368  	}
   369  	err = defaultUntarPath(tarFile, destFile)
   370  	if err == nil {
   371  		t.Fatalf("UntarPath should throw an error if the destination if a file")
   372  	}
   373  }
   374  
   375  // Do the same test as above but with the destination folder already exists
   376  // and the destination file is a directory
   377  // It's working, see https://github.com/docker/docker/issues/10040
   378  func TestUntarPathWithDestinationSrcFileAsFolder(t *testing.T) {
   379  	tmpFolder, err := ioutil.TempDir("", "docker-archive-test")
   380  	if err != nil {
   381  		t.Fatal(err)
   382  	}
   383  	defer os.RemoveAll(tmpFolder)
   384  	srcFile := filepath.Join(tmpFolder, "src")
   385  	tarFile := filepath.Join(tmpFolder, "src.tar")
   386  	os.Create(srcFile)
   387  
   388  	// Translate back to Unix semantics as next exec.Command is run under sh
   389  	srcFileU := srcFile
   390  	tarFileU := tarFile
   391  	if runtime.GOOS == "windows" {
   392  		tarFileU = "/tmp/" + filepath.Base(filepath.Dir(tarFile)) + "/src.tar"
   393  		srcFileU = "/tmp/" + filepath.Base(filepath.Dir(srcFile)) + "/src"
   394  	}
   395  
   396  	cmd := exec.Command("sh", "-c", "tar cf "+tarFileU+" "+srcFileU)
   397  	_, err = cmd.CombinedOutput()
   398  	if err != nil {
   399  		t.Fatal(err)
   400  	}
   401  	destFolder := filepath.Join(tmpFolder, "dest")
   402  	err = os.MkdirAll(destFolder, 0740)
   403  	if err != nil {
   404  		t.Fatalf("Fail to create the destination folder")
   405  	}
   406  	// Let's create a folder that will has the same path as the extracted file (from tar)
   407  	destSrcFileAsFolder := filepath.Join(destFolder, srcFileU)
   408  	err = os.MkdirAll(destSrcFileAsFolder, 0740)
   409  	if err != nil {
   410  		t.Fatal(err)
   411  	}
   412  	err = defaultUntarPath(tarFile, destFolder)
   413  	if err != nil {
   414  		t.Fatalf("UntarPath should throw not throw an error if the extracted file already exists and is a folder")
   415  	}
   416  }
   417  
   418  func TestCopyWithTarInvalidSrc(t *testing.T) {
   419  	tempFolder, err := ioutil.TempDir("", "docker-archive-test")
   420  	if err != nil {
   421  		t.Fatal(nil)
   422  	}
   423  	destFolder := filepath.Join(tempFolder, "dest")
   424  	invalidSrc := filepath.Join(tempFolder, "doesnotexists")
   425  	err = os.MkdirAll(destFolder, 0740)
   426  	if err != nil {
   427  		t.Fatal(err)
   428  	}
   429  	err = defaultCopyWithTar(invalidSrc, destFolder)
   430  	if err == nil {
   431  		t.Fatalf("archiver.CopyWithTar with invalid src path should throw an error.")
   432  	}
   433  }
   434  
   435  func TestCopyWithTarInexistentDestWillCreateIt(t *testing.T) {
   436  	tempFolder, err := ioutil.TempDir("", "docker-archive-test")
   437  	if err != nil {
   438  		t.Fatal(nil)
   439  	}
   440  	srcFolder := filepath.Join(tempFolder, "src")
   441  	inexistentDestFolder := filepath.Join(tempFolder, "doesnotexists")
   442  	err = os.MkdirAll(srcFolder, 0740)
   443  	if err != nil {
   444  		t.Fatal(err)
   445  	}
   446  	err = defaultCopyWithTar(srcFolder, inexistentDestFolder)
   447  	if err != nil {
   448  		t.Fatalf("CopyWithTar with an inexistent folder shouldn't fail.")
   449  	}
   450  	_, err = os.Stat(inexistentDestFolder)
   451  	if err != nil {
   452  		t.Fatalf("CopyWithTar with an inexistent folder should create it.")
   453  	}
   454  }
   455  
   456  // Test CopyWithTar with a file as src
   457  func TestCopyWithTarSrcFile(t *testing.T) {
   458  	folder, err := ioutil.TempDir("", "docker-archive-test")
   459  	if err != nil {
   460  		t.Fatal(err)
   461  	}
   462  	defer os.RemoveAll(folder)
   463  	dest := filepath.Join(folder, "dest")
   464  	srcFolder := filepath.Join(folder, "src")
   465  	src := filepath.Join(folder, filepath.Join("src", "src"))
   466  	err = os.MkdirAll(srcFolder, 0740)
   467  	if err != nil {
   468  		t.Fatal(err)
   469  	}
   470  	err = os.MkdirAll(dest, 0740)
   471  	if err != nil {
   472  		t.Fatal(err)
   473  	}
   474  	ioutil.WriteFile(src, []byte("content"), 0777)
   475  	err = defaultCopyWithTar(src, dest)
   476  	if err != nil {
   477  		t.Fatalf("archiver.CopyWithTar shouldn't throw an error, %s.", err)
   478  	}
   479  	_, err = os.Stat(dest)
   480  	// FIXME Check the content
   481  	if err != nil {
   482  		t.Fatalf("Destination file should be the same as the source.")
   483  	}
   484  }
   485  
   486  // Test CopyWithTar with a folder as src
   487  func TestCopyWithTarSrcFolder(t *testing.T) {
   488  	folder, err := ioutil.TempDir("", "docker-archive-test")
   489  	if err != nil {
   490  		t.Fatal(err)
   491  	}
   492  	defer os.RemoveAll(folder)
   493  	dest := filepath.Join(folder, "dest")
   494  	src := filepath.Join(folder, filepath.Join("src", "folder"))
   495  	err = os.MkdirAll(src, 0740)
   496  	if err != nil {
   497  		t.Fatal(err)
   498  	}
   499  	err = os.MkdirAll(dest, 0740)
   500  	if err != nil {
   501  		t.Fatal(err)
   502  	}
   503  	ioutil.WriteFile(filepath.Join(src, "file"), []byte("content"), 0777)
   504  	err = defaultCopyWithTar(src, dest)
   505  	if err != nil {
   506  		t.Fatalf("archiver.CopyWithTar shouldn't throw an error, %s.", err)
   507  	}
   508  	_, err = os.Stat(dest)
   509  	// FIXME Check the content (the file inside)
   510  	if err != nil {
   511  		t.Fatalf("Destination folder should contain the source file but did not.")
   512  	}
   513  }
   514  
   515  func TestCopyFileWithTarInvalidSrc(t *testing.T) {
   516  	tempFolder, err := ioutil.TempDir("", "docker-archive-test")
   517  	if err != nil {
   518  		t.Fatal(err)
   519  	}
   520  	defer os.RemoveAll(tempFolder)
   521  	destFolder := filepath.Join(tempFolder, "dest")
   522  	err = os.MkdirAll(destFolder, 0740)
   523  	if err != nil {
   524  		t.Fatal(err)
   525  	}
   526  	invalidFile := filepath.Join(tempFolder, "doesnotexists")
   527  	err = defaultCopyFileWithTar(invalidFile, destFolder)
   528  	if err == nil {
   529  		t.Fatalf("archiver.CopyWithTar with invalid src path should throw an error.")
   530  	}
   531  }
   532  
   533  func TestCopyFileWithTarInexistentDestWillCreateIt(t *testing.T) {
   534  	tempFolder, err := ioutil.TempDir("", "docker-archive-test")
   535  	if err != nil {
   536  		t.Fatal(nil)
   537  	}
   538  	defer os.RemoveAll(tempFolder)
   539  	srcFile := filepath.Join(tempFolder, "src")
   540  	inexistentDestFolder := filepath.Join(tempFolder, "doesnotexists")
   541  	_, err = os.Create(srcFile)
   542  	if err != nil {
   543  		t.Fatal(err)
   544  	}
   545  	err = defaultCopyFileWithTar(srcFile, inexistentDestFolder)
   546  	if err != nil {
   547  		t.Fatalf("CopyWithTar with an inexistent folder shouldn't fail.")
   548  	}
   549  	_, err = os.Stat(inexistentDestFolder)
   550  	if err != nil {
   551  		t.Fatalf("CopyWithTar with an inexistent folder should create it.")
   552  	}
   553  	// FIXME Test the src file and content
   554  }
   555  
   556  func TestCopyFileWithTarSrcFolder(t *testing.T) {
   557  	folder, err := ioutil.TempDir("", "docker-archive-copyfilewithtar-test")
   558  	if err != nil {
   559  		t.Fatal(err)
   560  	}
   561  	defer os.RemoveAll(folder)
   562  	dest := filepath.Join(folder, "dest")
   563  	src := filepath.Join(folder, "srcfolder")
   564  	err = os.MkdirAll(src, 0740)
   565  	if err != nil {
   566  		t.Fatal(err)
   567  	}
   568  	err = os.MkdirAll(dest, 0740)
   569  	if err != nil {
   570  		t.Fatal(err)
   571  	}
   572  	err = defaultCopyFileWithTar(src, dest)
   573  	if err == nil {
   574  		t.Fatalf("CopyFileWithTar should throw an error with a folder.")
   575  	}
   576  }
   577  
   578  func TestCopyFileWithTarSrcFile(t *testing.T) {
   579  	folder, err := ioutil.TempDir("", "docker-archive-test")
   580  	if err != nil {
   581  		t.Fatal(err)
   582  	}
   583  	defer os.RemoveAll(folder)
   584  	dest := filepath.Join(folder, "dest")
   585  	srcFolder := filepath.Join(folder, "src")
   586  	src := filepath.Join(folder, filepath.Join("src", "src"))
   587  	err = os.MkdirAll(srcFolder, 0740)
   588  	if err != nil {
   589  		t.Fatal(err)
   590  	}
   591  	err = os.MkdirAll(dest, 0740)
   592  	if err != nil {
   593  		t.Fatal(err)
   594  	}
   595  	ioutil.WriteFile(src, []byte("content"), 0777)
   596  	err = defaultCopyWithTar(src, dest+"/")
   597  	if err != nil {
   598  		t.Fatalf("archiver.CopyFileWithTar shouldn't throw an error, %s.", err)
   599  	}
   600  	_, err = os.Stat(dest)
   601  	if err != nil {
   602  		t.Fatalf("Destination folder should contain the source file but did not.")
   603  	}
   604  }
   605  
   606  func TestTarFiles(t *testing.T) {
   607  	// TODO Windows: Figure out how to port this test.
   608  	if runtime.GOOS == "windows" {
   609  		t.Skip("Failing on Windows")
   610  	}
   611  	// try without hardlinks
   612  	if err := checkNoChanges(1000, false); err != nil {
   613  		t.Fatal(err)
   614  	}
   615  	// try with hardlinks
   616  	if err := checkNoChanges(1000, true); err != nil {
   617  		t.Fatal(err)
   618  	}
   619  }
   620  
   621  func checkNoChanges(fileNum int, hardlinks bool) error {
   622  	srcDir, err := ioutil.TempDir("", "docker-test-srcDir")
   623  	if err != nil {
   624  		return err
   625  	}
   626  	defer os.RemoveAll(srcDir)
   627  
   628  	destDir, err := ioutil.TempDir("", "docker-test-destDir")
   629  	if err != nil {
   630  		return err
   631  	}
   632  	defer os.RemoveAll(destDir)
   633  
   634  	_, err = prepareUntarSourceDirectory(fileNum, srcDir, hardlinks)
   635  	if err != nil {
   636  		return err
   637  	}
   638  
   639  	err = defaultTarUntar(srcDir, destDir)
   640  	if err != nil {
   641  		return err
   642  	}
   643  
   644  	changes, err := ChangesDirs(destDir, srcDir)
   645  	if err != nil {
   646  		return err
   647  	}
   648  	if len(changes) > 0 {
   649  		return fmt.Errorf("with %d files and %v hardlinks: expected 0 changes, got %d", fileNum, hardlinks, len(changes))
   650  	}
   651  	return nil
   652  }
   653  
   654  func tarUntar(t *testing.T, origin string, options *TarOptions) ([]Change, error) {
   655  	archive, err := TarWithOptions(origin, options)
   656  	if err != nil {
   657  		t.Fatal(err)
   658  	}
   659  	defer archive.Close()
   660  
   661  	buf := make([]byte, 10)
   662  	if _, err := archive.Read(buf); err != nil {
   663  		return nil, err
   664  	}
   665  	wrap := io.MultiReader(bytes.NewReader(buf), archive)
   666  
   667  	detectedCompression := DetectCompression(buf)
   668  	compression := options.Compression
   669  	if detectedCompression.Extension() != compression.Extension() {
   670  		return nil, fmt.Errorf("Wrong compression detected. Actual compression: %s, found %s", compression.Extension(), detectedCompression.Extension())
   671  	}
   672  
   673  	tmp, err := ioutil.TempDir("", "docker-test-untar")
   674  	if err != nil {
   675  		return nil, err
   676  	}
   677  	defer os.RemoveAll(tmp)
   678  	if err := Untar(wrap, tmp, nil); err != nil {
   679  		return nil, err
   680  	}
   681  	if _, err := os.Stat(tmp); err != nil {
   682  		return nil, err
   683  	}
   684  
   685  	return ChangesDirs(origin, tmp)
   686  }
   687  
   688  func TestTarUntar(t *testing.T) {
   689  	// TODO Windows: Figure out how to fix this test.
   690  	if runtime.GOOS == "windows" {
   691  		t.Skip("Failing on Windows")
   692  	}
   693  	origin, err := ioutil.TempDir("", "docker-test-untar-origin")
   694  	if err != nil {
   695  		t.Fatal(err)
   696  	}
   697  	defer os.RemoveAll(origin)
   698  	if err := ioutil.WriteFile(filepath.Join(origin, "1"), []byte("hello world"), 0700); err != nil {
   699  		t.Fatal(err)
   700  	}
   701  	if err := ioutil.WriteFile(filepath.Join(origin, "2"), []byte("welcome!"), 0700); err != nil {
   702  		t.Fatal(err)
   703  	}
   704  	if err := ioutil.WriteFile(filepath.Join(origin, "3"), []byte("will be ignored"), 0700); err != nil {
   705  		t.Fatal(err)
   706  	}
   707  
   708  	for _, c := range []Compression{
   709  		Uncompressed,
   710  		Gzip,
   711  	} {
   712  		changes, err := tarUntar(t, origin, &TarOptions{
   713  			Compression:     c,
   714  			ExcludePatterns: []string{"3"},
   715  		})
   716  
   717  		if err != nil {
   718  			t.Fatalf("Error tar/untar for compression %s: %s", c.Extension(), err)
   719  		}
   720  
   721  		if len(changes) != 1 || changes[0].Path != "/3" {
   722  			t.Fatalf("Unexpected differences after tarUntar: %v", changes)
   723  		}
   724  	}
   725  }
   726  
   727  func TestTarWithOptionsChownOptsAlwaysOverridesIdPair(t *testing.T) {
   728  	origin, err := ioutil.TempDir("", "docker-test-tar-chown-opt")
   729  	require.NoError(t, err)
   730  
   731  	defer os.RemoveAll(origin)
   732  	filePath := filepath.Join(origin, "1")
   733  	err = ioutil.WriteFile(filePath, []byte("hello world"), 0700)
   734  	require.NoError(t, err)
   735  
   736  	idMaps := []idtools.IDMap{
   737  		0: {
   738  			ContainerID: 0,
   739  			HostID:      0,
   740  			Size:        65536,
   741  		},
   742  		1: {
   743  			ContainerID: 0,
   744  			HostID:      100000,
   745  			Size:        65536,
   746  		},
   747  	}
   748  
   749  	cases := []struct {
   750  		opts        *TarOptions
   751  		expectedUID int
   752  		expectedGID int
   753  	}{
   754  		{&TarOptions{ChownOpts: &idtools.IDPair{UID: 1337, GID: 42}}, 1337, 42},
   755  		{&TarOptions{ChownOpts: &idtools.IDPair{UID: 100001, GID: 100001}, UIDMaps: idMaps, GIDMaps: idMaps}, 100001, 100001},
   756  		{&TarOptions{ChownOpts: &idtools.IDPair{UID: 0, GID: 0}, NoLchown: false}, 0, 0},
   757  		{&TarOptions{ChownOpts: &idtools.IDPair{UID: 1, GID: 1}, NoLchown: true}, 1, 1},
   758  		{&TarOptions{ChownOpts: &idtools.IDPair{UID: 1000, GID: 1000}, NoLchown: true}, 1000, 1000},
   759  	}
   760  	for _, testCase := range cases {
   761  		reader, err := TarWithOptions(filePath, testCase.opts)
   762  		require.NoError(t, err)
   763  		tr := tar.NewReader(reader)
   764  		defer reader.Close()
   765  		for {
   766  			hdr, err := tr.Next()
   767  			if err == io.EOF {
   768  				// end of tar archive
   769  				break
   770  			}
   771  			require.NoError(t, err)
   772  			assert.Equal(t, hdr.Uid, testCase.expectedUID, "Uid equals expected value")
   773  			assert.Equal(t, hdr.Gid, testCase.expectedGID, "Gid equals expected value")
   774  		}
   775  	}
   776  }
   777  
   778  func TestTarWithOptions(t *testing.T) {
   779  	// TODO Windows: Figure out how to fix this test.
   780  	if runtime.GOOS == "windows" {
   781  		t.Skip("Failing on Windows")
   782  	}
   783  	origin, err := ioutil.TempDir("", "docker-test-untar-origin")
   784  	if err != nil {
   785  		t.Fatal(err)
   786  	}
   787  	if _, err := ioutil.TempDir(origin, "folder"); err != nil {
   788  		t.Fatal(err)
   789  	}
   790  	defer os.RemoveAll(origin)
   791  	if err := ioutil.WriteFile(filepath.Join(origin, "1"), []byte("hello world"), 0700); err != nil {
   792  		t.Fatal(err)
   793  	}
   794  	if err := ioutil.WriteFile(filepath.Join(origin, "2"), []byte("welcome!"), 0700); err != nil {
   795  		t.Fatal(err)
   796  	}
   797  
   798  	cases := []struct {
   799  		opts       *TarOptions
   800  		numChanges int
   801  	}{
   802  		{&TarOptions{IncludeFiles: []string{"1"}}, 2},
   803  		{&TarOptions{ExcludePatterns: []string{"2"}}, 1},
   804  		{&TarOptions{ExcludePatterns: []string{"1", "folder*"}}, 2},
   805  		{&TarOptions{IncludeFiles: []string{"1", "1"}}, 2},
   806  		{&TarOptions{IncludeFiles: []string{"1"}, RebaseNames: map[string]string{"1": "test"}}, 4},
   807  	}
   808  	for _, testCase := range cases {
   809  		changes, err := tarUntar(t, origin, testCase.opts)
   810  		if err != nil {
   811  			t.Fatalf("Error tar/untar when testing inclusion/exclusion: %s", err)
   812  		}
   813  		if len(changes) != testCase.numChanges {
   814  			t.Errorf("Expected %d changes, got %d for %+v:",
   815  				testCase.numChanges, len(changes), testCase.opts)
   816  		}
   817  	}
   818  }
   819  
   820  // Some tar archives such as http://haproxy.1wt.eu/download/1.5/src/devel/haproxy-1.5-dev21.tar.gz
   821  // use PAX Global Extended Headers.
   822  // Failing prevents the archives from being uncompressed during ADD
   823  func TestTypeXGlobalHeaderDoesNotFail(t *testing.T) {
   824  	hdr := tar.Header{Typeflag: tar.TypeXGlobalHeader}
   825  	tmpDir, err := ioutil.TempDir("", "docker-test-archive-pax-test")
   826  	if err != nil {
   827  		t.Fatal(err)
   828  	}
   829  	defer os.RemoveAll(tmpDir)
   830  	err = createTarFile(filepath.Join(tmpDir, "pax_global_header"), tmpDir, &hdr, nil, true, nil, false)
   831  	if err != nil {
   832  		t.Fatal(err)
   833  	}
   834  }
   835  
   836  // Some tar have both GNU specific (huge uid) and Ustar specific (long name) things.
   837  // Not supposed to happen (should use PAX instead of Ustar for long name) but it does and it should still work.
   838  func TestUntarUstarGnuConflict(t *testing.T) {
   839  	f, err := os.Open("testdata/broken.tar")
   840  	if err != nil {
   841  		t.Fatal(err)
   842  	}
   843  	defer f.Close()
   844  
   845  	found := false
   846  	tr := tar.NewReader(f)
   847  	// Iterate through the files in the archive.
   848  	for {
   849  		hdr, err := tr.Next()
   850  		if err == io.EOF {
   851  			// end of tar archive
   852  			break
   853  		}
   854  		if err != nil {
   855  			t.Fatal(err)
   856  		}
   857  		if hdr.Name == "root/.cpanm/work/1395823785.24209/Plack-1.0030/blib/man3/Plack::Middleware::LighttpdScriptNameFix.3pm" {
   858  			found = true
   859  			break
   860  		}
   861  	}
   862  	if !found {
   863  		t.Fatalf("%s not found in the archive", "root/.cpanm/work/1395823785.24209/Plack-1.0030/blib/man3/Plack::Middleware::LighttpdScriptNameFix.3pm")
   864  	}
   865  }
   866  
   867  func prepareUntarSourceDirectory(numberOfFiles int, targetPath string, makeLinks bool) (int, error) {
   868  	fileData := []byte("fooo")
   869  	for n := 0; n < numberOfFiles; n++ {
   870  		fileName := fmt.Sprintf("file-%d", n)
   871  		if err := ioutil.WriteFile(filepath.Join(targetPath, fileName), fileData, 0700); err != nil {
   872  			return 0, err
   873  		}
   874  		if makeLinks {
   875  			if err := os.Link(filepath.Join(targetPath, fileName), filepath.Join(targetPath, fileName+"-link")); err != nil {
   876  				return 0, err
   877  			}
   878  		}
   879  	}
   880  	totalSize := numberOfFiles * len(fileData)
   881  	return totalSize, nil
   882  }
   883  
   884  func BenchmarkTarUntar(b *testing.B) {
   885  	origin, err := ioutil.TempDir("", "docker-test-untar-origin")
   886  	if err != nil {
   887  		b.Fatal(err)
   888  	}
   889  	tempDir, err := ioutil.TempDir("", "docker-test-untar-destination")
   890  	if err != nil {
   891  		b.Fatal(err)
   892  	}
   893  	target := filepath.Join(tempDir, "dest")
   894  	n, err := prepareUntarSourceDirectory(100, origin, false)
   895  	if err != nil {
   896  		b.Fatal(err)
   897  	}
   898  	defer os.RemoveAll(origin)
   899  	defer os.RemoveAll(tempDir)
   900  
   901  	b.ResetTimer()
   902  	b.SetBytes(int64(n))
   903  	for n := 0; n < b.N; n++ {
   904  		err := defaultTarUntar(origin, target)
   905  		if err != nil {
   906  			b.Fatal(err)
   907  		}
   908  		os.RemoveAll(target)
   909  	}
   910  }
   911  
   912  func BenchmarkTarUntarWithLinks(b *testing.B) {
   913  	origin, err := ioutil.TempDir("", "docker-test-untar-origin")
   914  	if err != nil {
   915  		b.Fatal(err)
   916  	}
   917  	tempDir, err := ioutil.TempDir("", "docker-test-untar-destination")
   918  	if err != nil {
   919  		b.Fatal(err)
   920  	}
   921  	target := filepath.Join(tempDir, "dest")
   922  	n, err := prepareUntarSourceDirectory(100, origin, true)
   923  	if err != nil {
   924  		b.Fatal(err)
   925  	}
   926  	defer os.RemoveAll(origin)
   927  	defer os.RemoveAll(tempDir)
   928  
   929  	b.ResetTimer()
   930  	b.SetBytes(int64(n))
   931  	for n := 0; n < b.N; n++ {
   932  		err := defaultTarUntar(origin, target)
   933  		if err != nil {
   934  			b.Fatal(err)
   935  		}
   936  		os.RemoveAll(target)
   937  	}
   938  }
   939  
   940  func TestUntarInvalidFilenames(t *testing.T) {
   941  	// TODO Windows: Figure out how to fix this test.
   942  	if runtime.GOOS == "windows" {
   943  		t.Skip("Passes but hits breakoutError: platform and architecture is not supported")
   944  	}
   945  	for i, headers := range [][]*tar.Header{
   946  		{
   947  			{
   948  				Name:     "../victim/dotdot",
   949  				Typeflag: tar.TypeReg,
   950  				Mode:     0644,
   951  			},
   952  		},
   953  		{
   954  			{
   955  				// Note the leading slash
   956  				Name:     "/../victim/slash-dotdot",
   957  				Typeflag: tar.TypeReg,
   958  				Mode:     0644,
   959  			},
   960  		},
   961  	} {
   962  		if err := testBreakout("untar", "docker-TestUntarInvalidFilenames", headers); err != nil {
   963  			t.Fatalf("i=%d. %v", i, err)
   964  		}
   965  	}
   966  }
   967  
   968  func TestUntarHardlinkToSymlink(t *testing.T) {
   969  	// TODO Windows. There may be a way of running this, but turning off for now
   970  	if runtime.GOOS == "windows" {
   971  		t.Skip("hardlinks on Windows")
   972  	}
   973  	for i, headers := range [][]*tar.Header{
   974  		{
   975  			{
   976  				Name:     "symlink1",
   977  				Typeflag: tar.TypeSymlink,
   978  				Linkname: "regfile",
   979  				Mode:     0644,
   980  			},
   981  			{
   982  				Name:     "symlink2",
   983  				Typeflag: tar.TypeLink,
   984  				Linkname: "symlink1",
   985  				Mode:     0644,
   986  			},
   987  			{
   988  				Name:     "regfile",
   989  				Typeflag: tar.TypeReg,
   990  				Mode:     0644,
   991  			},
   992  		},
   993  	} {
   994  		if err := testBreakout("untar", "docker-TestUntarHardlinkToSymlink", headers); err != nil {
   995  			t.Fatalf("i=%d. %v", i, err)
   996  		}
   997  	}
   998  }
   999  
  1000  func TestUntarInvalidHardlink(t *testing.T) {
  1001  	// TODO Windows. There may be a way of running this, but turning off for now
  1002  	if runtime.GOOS == "windows" {
  1003  		t.Skip("hardlinks on Windows")
  1004  	}
  1005  	for i, headers := range [][]*tar.Header{
  1006  		{ // try reading victim/hello (../)
  1007  			{
  1008  				Name:     "dotdot",
  1009  				Typeflag: tar.TypeLink,
  1010  				Linkname: "../victim/hello",
  1011  				Mode:     0644,
  1012  			},
  1013  		},
  1014  		{ // try reading victim/hello (/../)
  1015  			{
  1016  				Name:     "slash-dotdot",
  1017  				Typeflag: tar.TypeLink,
  1018  				// Note the leading slash
  1019  				Linkname: "/../victim/hello",
  1020  				Mode:     0644,
  1021  			},
  1022  		},
  1023  		{ // try writing victim/file
  1024  			{
  1025  				Name:     "loophole-victim",
  1026  				Typeflag: tar.TypeLink,
  1027  				Linkname: "../victim",
  1028  				Mode:     0755,
  1029  			},
  1030  			{
  1031  				Name:     "loophole-victim/file",
  1032  				Typeflag: tar.TypeReg,
  1033  				Mode:     0644,
  1034  			},
  1035  		},
  1036  		{ // try reading victim/hello (hardlink, symlink)
  1037  			{
  1038  				Name:     "loophole-victim",
  1039  				Typeflag: tar.TypeLink,
  1040  				Linkname: "../victim",
  1041  				Mode:     0755,
  1042  			},
  1043  			{
  1044  				Name:     "symlink",
  1045  				Typeflag: tar.TypeSymlink,
  1046  				Linkname: "loophole-victim/hello",
  1047  				Mode:     0644,
  1048  			},
  1049  		},
  1050  		{ // Try reading victim/hello (hardlink, hardlink)
  1051  			{
  1052  				Name:     "loophole-victim",
  1053  				Typeflag: tar.TypeLink,
  1054  				Linkname: "../victim",
  1055  				Mode:     0755,
  1056  			},
  1057  			{
  1058  				Name:     "hardlink",
  1059  				Typeflag: tar.TypeLink,
  1060  				Linkname: "loophole-victim/hello",
  1061  				Mode:     0644,
  1062  			},
  1063  		},
  1064  		{ // Try removing victim directory (hardlink)
  1065  			{
  1066  				Name:     "loophole-victim",
  1067  				Typeflag: tar.TypeLink,
  1068  				Linkname: "../victim",
  1069  				Mode:     0755,
  1070  			},
  1071  			{
  1072  				Name:     "loophole-victim",
  1073  				Typeflag: tar.TypeReg,
  1074  				Mode:     0644,
  1075  			},
  1076  		},
  1077  	} {
  1078  		if err := testBreakout("untar", "docker-TestUntarInvalidHardlink", headers); err != nil {
  1079  			t.Fatalf("i=%d. %v", i, err)
  1080  		}
  1081  	}
  1082  }
  1083  
  1084  func TestUntarInvalidSymlink(t *testing.T) {
  1085  	// TODO Windows. There may be a way of running this, but turning off for now
  1086  	if runtime.GOOS == "windows" {
  1087  		t.Skip("hardlinks on Windows")
  1088  	}
  1089  	for i, headers := range [][]*tar.Header{
  1090  		{ // try reading victim/hello (../)
  1091  			{
  1092  				Name:     "dotdot",
  1093  				Typeflag: tar.TypeSymlink,
  1094  				Linkname: "../victim/hello",
  1095  				Mode:     0644,
  1096  			},
  1097  		},
  1098  		{ // try reading victim/hello (/../)
  1099  			{
  1100  				Name:     "slash-dotdot",
  1101  				Typeflag: tar.TypeSymlink,
  1102  				// Note the leading slash
  1103  				Linkname: "/../victim/hello",
  1104  				Mode:     0644,
  1105  			},
  1106  		},
  1107  		{ // try writing victim/file
  1108  			{
  1109  				Name:     "loophole-victim",
  1110  				Typeflag: tar.TypeSymlink,
  1111  				Linkname: "../victim",
  1112  				Mode:     0755,
  1113  			},
  1114  			{
  1115  				Name:     "loophole-victim/file",
  1116  				Typeflag: tar.TypeReg,
  1117  				Mode:     0644,
  1118  			},
  1119  		},
  1120  		{ // try reading victim/hello (symlink, symlink)
  1121  			{
  1122  				Name:     "loophole-victim",
  1123  				Typeflag: tar.TypeSymlink,
  1124  				Linkname: "../victim",
  1125  				Mode:     0755,
  1126  			},
  1127  			{
  1128  				Name:     "symlink",
  1129  				Typeflag: tar.TypeSymlink,
  1130  				Linkname: "loophole-victim/hello",
  1131  				Mode:     0644,
  1132  			},
  1133  		},
  1134  		{ // try reading victim/hello (symlink, hardlink)
  1135  			{
  1136  				Name:     "loophole-victim",
  1137  				Typeflag: tar.TypeSymlink,
  1138  				Linkname: "../victim",
  1139  				Mode:     0755,
  1140  			},
  1141  			{
  1142  				Name:     "hardlink",
  1143  				Typeflag: tar.TypeLink,
  1144  				Linkname: "loophole-victim/hello",
  1145  				Mode:     0644,
  1146  			},
  1147  		},
  1148  		{ // try removing victim directory (symlink)
  1149  			{
  1150  				Name:     "loophole-victim",
  1151  				Typeflag: tar.TypeSymlink,
  1152  				Linkname: "../victim",
  1153  				Mode:     0755,
  1154  			},
  1155  			{
  1156  				Name:     "loophole-victim",
  1157  				Typeflag: tar.TypeReg,
  1158  				Mode:     0644,
  1159  			},
  1160  		},
  1161  		{ // try writing to victim/newdir/newfile with a symlink in the path
  1162  			{
  1163  				// this header needs to be before the next one, or else there is an error
  1164  				Name:     "dir/loophole",
  1165  				Typeflag: tar.TypeSymlink,
  1166  				Linkname: "../../victim",
  1167  				Mode:     0755,
  1168  			},
  1169  			{
  1170  				Name:     "dir/loophole/newdir/newfile",
  1171  				Typeflag: tar.TypeReg,
  1172  				Mode:     0644,
  1173  			},
  1174  		},
  1175  	} {
  1176  		if err := testBreakout("untar", "docker-TestUntarInvalidSymlink", headers); err != nil {
  1177  			t.Fatalf("i=%d. %v", i, err)
  1178  		}
  1179  	}
  1180  }
  1181  
  1182  func TestTempArchiveCloseMultipleTimes(t *testing.T) {
  1183  	reader := ioutil.NopCloser(strings.NewReader("hello"))
  1184  	tempArchive, err := NewTempArchive(reader, "")
  1185  	require.NoError(t, err)
  1186  	buf := make([]byte, 10)
  1187  	n, err := tempArchive.Read(buf)
  1188  	require.NoError(t, err)
  1189  	if n != 5 {
  1190  		t.Fatalf("Expected to read 5 bytes. Read %d instead", n)
  1191  	}
  1192  	for i := 0; i < 3; i++ {
  1193  		if err = tempArchive.Close(); err != nil {
  1194  			t.Fatalf("i=%d. Unexpected error closing temp archive: %v", i, err)
  1195  		}
  1196  	}
  1197  }
  1198  
  1199  func TestReplaceFileTarWrapper(t *testing.T) {
  1200  	filesInArchive := 20
  1201  	testcases := []struct {
  1202  		doc       string
  1203  		filename  string
  1204  		modifier  TarModifierFunc
  1205  		expected  string
  1206  		fileCount int
  1207  	}{
  1208  		{
  1209  			doc:       "Modifier creates a new file",
  1210  			filename:  "newfile",
  1211  			modifier:  createModifier(t),
  1212  			expected:  "the new content",
  1213  			fileCount: filesInArchive + 1,
  1214  		},
  1215  		{
  1216  			doc:       "Modifier replaces a file",
  1217  			filename:  "file-2",
  1218  			modifier:  createOrReplaceModifier,
  1219  			expected:  "the new content",
  1220  			fileCount: filesInArchive,
  1221  		},
  1222  		{
  1223  			doc:       "Modifier replaces the last file",
  1224  			filename:  fmt.Sprintf("file-%d", filesInArchive-1),
  1225  			modifier:  createOrReplaceModifier,
  1226  			expected:  "the new content",
  1227  			fileCount: filesInArchive,
  1228  		},
  1229  		{
  1230  			doc:       "Modifier appends to a file",
  1231  			filename:  "file-3",
  1232  			modifier:  appendModifier,
  1233  			expected:  "fooo\nnext line",
  1234  			fileCount: filesInArchive,
  1235  		},
  1236  	}
  1237  
  1238  	for _, testcase := range testcases {
  1239  		sourceArchive, cleanup := buildSourceArchive(t, filesInArchive)
  1240  		defer cleanup()
  1241  
  1242  		resultArchive := ReplaceFileTarWrapper(
  1243  			sourceArchive,
  1244  			map[string]TarModifierFunc{testcase.filename: testcase.modifier})
  1245  
  1246  		actual := readFileFromArchive(t, resultArchive, testcase.filename, testcase.fileCount, testcase.doc)
  1247  		assert.Equal(t, testcase.expected, actual, testcase.doc)
  1248  	}
  1249  }
  1250  
  1251  // TestPrefixHeaderReadable tests that files that could be created with the
  1252  // version of this package that was built with <=go17 are still readable.
  1253  func TestPrefixHeaderReadable(t *testing.T) {
  1254  	// https://gist.github.com/stevvooe/e2a790ad4e97425896206c0816e1a882#file-out-go
  1255  	var testFile = []byte("\x1f\x8b\x08\x08\x44\x21\x68\x59\x00\x03\x74\x2e\x74\x61\x72\x00\x4b\xcb\xcf\x67\xa0\x35\x30\x80\x00\x86\x06\x10\x47\x01\xc1\x37\x40\x00\x54\xb6\xb1\xa1\xa9\x99\x09\x48\x25\x1d\x40\x69\x71\x49\x62\x91\x02\xe5\x76\xa1\x79\x84\x21\x91\xd6\x80\x72\xaf\x8f\x82\x51\x30\x0a\x46\x36\x00\x00\xf0\x1c\x1e\x95\x00\x06\x00\x00")
  1256  
  1257  	tmpDir, err := ioutil.TempDir("", "prefix-test")
  1258  	require.NoError(t, err)
  1259  	defer os.RemoveAll(tmpDir)
  1260  	err = Untar(bytes.NewReader(testFile), tmpDir, nil)
  1261  	require.NoError(t, err)
  1262  
  1263  	baseName := "foo"
  1264  	pth := strings.Repeat("a", 100-len(baseName)) + "/" + baseName
  1265  
  1266  	_, err = os.Lstat(filepath.Join(tmpDir, pth))
  1267  	require.NoError(t, err)
  1268  }
  1269  
  1270  func buildSourceArchive(t *testing.T, numberOfFiles int) (io.ReadCloser, func()) {
  1271  	srcDir, err := ioutil.TempDir("", "docker-test-srcDir")
  1272  	require.NoError(t, err)
  1273  
  1274  	_, err = prepareUntarSourceDirectory(numberOfFiles, srcDir, false)
  1275  	require.NoError(t, err)
  1276  
  1277  	sourceArchive, err := TarWithOptions(srcDir, &TarOptions{})
  1278  	require.NoError(t, err)
  1279  	return sourceArchive, func() {
  1280  		os.RemoveAll(srcDir)
  1281  		sourceArchive.Close()
  1282  	}
  1283  }
  1284  
  1285  func createOrReplaceModifier(path string, header *tar.Header, content io.Reader) (*tar.Header, []byte, error) {
  1286  	return &tar.Header{
  1287  		Mode:     0600,
  1288  		Typeflag: tar.TypeReg,
  1289  	}, []byte("the new content"), nil
  1290  }
  1291  
  1292  func createModifier(t *testing.T) TarModifierFunc {
  1293  	return func(path string, header *tar.Header, content io.Reader) (*tar.Header, []byte, error) {
  1294  		assert.Nil(t, content)
  1295  		return createOrReplaceModifier(path, header, content)
  1296  	}
  1297  }
  1298  
  1299  func appendModifier(path string, header *tar.Header, content io.Reader) (*tar.Header, []byte, error) {
  1300  	buffer := bytes.Buffer{}
  1301  	if content != nil {
  1302  		if _, err := buffer.ReadFrom(content); err != nil {
  1303  			return nil, nil, err
  1304  		}
  1305  	}
  1306  	buffer.WriteString("\nnext line")
  1307  	return &tar.Header{Mode: 0600, Typeflag: tar.TypeReg}, buffer.Bytes(), nil
  1308  }
  1309  
  1310  func readFileFromArchive(t *testing.T, archive io.ReadCloser, name string, expectedCount int, doc string) string {
  1311  	destDir, err := ioutil.TempDir("", "docker-test-destDir")
  1312  	require.NoError(t, err)
  1313  	defer os.RemoveAll(destDir)
  1314  
  1315  	err = Untar(archive, destDir, nil)
  1316  	require.NoError(t, err)
  1317  
  1318  	files, _ := ioutil.ReadDir(destDir)
  1319  	assert.Len(t, files, expectedCount, doc)
  1320  
  1321  	content, err := ioutil.ReadFile(filepath.Join(destDir, name))
  1322  	assert.NoError(t, err)
  1323  	return string(content)
  1324  }
  1325  
  1326  func TestDisablePigz(t *testing.T) {
  1327  	_, err := exec.LookPath("unpigz")
  1328  	if err != nil {
  1329  		t.Log("Test will not check full path when Pigz not installed")
  1330  	}
  1331  
  1332  	os.Setenv("MOBY_DISABLE_PIGZ", "true")
  1333  	defer os.Unsetenv("MOBY_DISABLE_PIGZ")
  1334  
  1335  	r := testDecompressStream(t, "gz", "gzip -f")
  1336  	// For the bufio pool
  1337  	outsideReaderCloserWrapper := r.(*ioutils.ReadCloserWrapper)
  1338  	// For the context canceller
  1339  	contextReaderCloserWrapper := outsideReaderCloserWrapper.Reader.(*ioutils.ReadCloserWrapper)
  1340  
  1341  	assert.IsType(t, &gzip.Reader{}, contextReaderCloserWrapper.Reader)
  1342  }
  1343  
  1344  func TestPigz(t *testing.T) {
  1345  	r := testDecompressStream(t, "gz", "gzip -f")
  1346  	// For the bufio pool
  1347  	outsideReaderCloserWrapper := r.(*ioutils.ReadCloserWrapper)
  1348  	// For the context canceller
  1349  	contextReaderCloserWrapper := outsideReaderCloserWrapper.Reader.(*ioutils.ReadCloserWrapper)
  1350  
  1351  	_, err := exec.LookPath("unpigz")
  1352  	if err == nil {
  1353  		t.Log("Tested whether Pigz is used, as it installed")
  1354  		assert.IsType(t, &io.PipeReader{}, contextReaderCloserWrapper.Reader)
  1355  	} else {
  1356  		t.Log("Tested whether Pigz is not used, as it not installed")
  1357  		assert.IsType(t, &gzip.Reader{}, contextReaderCloserWrapper.Reader)
  1358  	}
  1359  }