github.com/lazyboychen7/engine@v17.12.1-ce-rc2+incompatible/pkg/archive/archive_test.go (about)

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