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