github.com/demonoid81/moby@v0.0.0-20200517203328-62dd8e17c460/pkg/archive/archive_test.go (about)

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