github.com/zhouyu0/docker-note@v0.0.0-20190722021225-b8d3825084db/pkg/archive/archive_test.go (about)

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