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