github.com/cdoern/storage@v1.12.13/pkg/archive/archive_test.go (about)

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