github.com/wozhu6104/docker@v20.10.10+incompatible/pkg/archive/archive_unix_test.go (about)

     1  // +build !windows
     2  
     3  package archive // import "github.com/docker/docker/pkg/archive"
     4  
     5  import (
     6  	"archive/tar"
     7  	"bytes"
     8  	"fmt"
     9  	"io/ioutil"
    10  	"os"
    11  	"os/exec"
    12  	"path/filepath"
    13  	"strings"
    14  	"syscall"
    15  	"testing"
    16  
    17  	"github.com/containerd/containerd/sys"
    18  	"github.com/docker/docker/pkg/system"
    19  	"golang.org/x/sys/unix"
    20  	"gotest.tools/v3/assert"
    21  	is "gotest.tools/v3/assert/cmp"
    22  	"gotest.tools/v3/skip"
    23  )
    24  
    25  func TestCanonicalTarNameForPath(t *testing.T) {
    26  	cases := []struct{ in, expected string }{
    27  		{"foo", "foo"},
    28  		{"foo/bar", "foo/bar"},
    29  		{"foo/dir/", "foo/dir/"},
    30  	}
    31  	for _, v := range cases {
    32  		if CanonicalTarNameForPath(v.in) != v.expected {
    33  			t.Fatalf("wrong canonical tar name. expected:%s got:%s", v.expected, CanonicalTarNameForPath(v.in))
    34  		}
    35  	}
    36  }
    37  
    38  func TestCanonicalTarName(t *testing.T) {
    39  	cases := []struct {
    40  		in       string
    41  		isDir    bool
    42  		expected string
    43  	}{
    44  		{"foo", false, "foo"},
    45  		{"foo", true, "foo/"},
    46  		{"foo/bar", false, "foo/bar"},
    47  		{"foo/bar", true, "foo/bar/"},
    48  	}
    49  	for _, v := range cases {
    50  		if canonicalTarName(v.in, v.isDir) != v.expected {
    51  			t.Fatalf("wrong canonical tar name. expected:%s got:%s", v.expected, canonicalTarName(v.in, v.isDir))
    52  		}
    53  	}
    54  }
    55  
    56  func TestChmodTarEntry(t *testing.T) {
    57  	cases := []struct {
    58  		in, expected os.FileMode
    59  	}{
    60  		{0000, 0000},
    61  		{0777, 0777},
    62  		{0644, 0644},
    63  		{0755, 0755},
    64  		{0444, 0444},
    65  	}
    66  	for _, v := range cases {
    67  		if out := chmodTarEntry(v.in); out != v.expected {
    68  			t.Fatalf("wrong chmod. expected:%v got:%v", v.expected, out)
    69  		}
    70  	}
    71  }
    72  
    73  func TestTarWithHardLink(t *testing.T) {
    74  	origin, err := ioutil.TempDir("", "docker-test-tar-hardlink")
    75  	assert.NilError(t, err)
    76  	defer os.RemoveAll(origin)
    77  
    78  	err = ioutil.WriteFile(filepath.Join(origin, "1"), []byte("hello world"), 0700)
    79  	assert.NilError(t, err)
    80  
    81  	err = os.Link(filepath.Join(origin, "1"), filepath.Join(origin, "2"))
    82  	assert.NilError(t, err)
    83  
    84  	var i1, i2 uint64
    85  	i1, err = getNlink(filepath.Join(origin, "1"))
    86  	assert.NilError(t, err)
    87  
    88  	// sanity check that we can hardlink
    89  	if i1 != 2 {
    90  		t.Skipf("skipping since hardlinks don't work here; expected 2 links, got %d", i1)
    91  	}
    92  
    93  	dest, err := ioutil.TempDir("", "docker-test-tar-hardlink-dest")
    94  	assert.NilError(t, err)
    95  	defer os.RemoveAll(dest)
    96  
    97  	// we'll do this in two steps to separate failure
    98  	fh, err := Tar(origin, Uncompressed)
    99  	assert.NilError(t, err)
   100  
   101  	// ensure we can read the whole thing with no error, before writing back out
   102  	buf, err := ioutil.ReadAll(fh)
   103  	assert.NilError(t, err)
   104  
   105  	bRdr := bytes.NewReader(buf)
   106  	err = Untar(bRdr, dest, &TarOptions{Compression: Uncompressed})
   107  	assert.NilError(t, err)
   108  
   109  	i1, err = getInode(filepath.Join(dest, "1"))
   110  	assert.NilError(t, err)
   111  
   112  	i2, err = getInode(filepath.Join(dest, "2"))
   113  	assert.NilError(t, err)
   114  
   115  	assert.Check(t, is.Equal(i1, i2))
   116  }
   117  
   118  func TestTarWithHardLinkAndRebase(t *testing.T) {
   119  	tmpDir, err := ioutil.TempDir("", "docker-test-tar-hardlink-rebase")
   120  	assert.NilError(t, err)
   121  	defer os.RemoveAll(tmpDir)
   122  
   123  	origin := filepath.Join(tmpDir, "origin")
   124  	err = os.Mkdir(origin, 0700)
   125  	assert.NilError(t, err)
   126  
   127  	err = ioutil.WriteFile(filepath.Join(origin, "1"), []byte("hello world"), 0700)
   128  	assert.NilError(t, err)
   129  
   130  	err = os.Link(filepath.Join(origin, "1"), filepath.Join(origin, "2"))
   131  	assert.NilError(t, err)
   132  
   133  	var i1, i2 uint64
   134  	i1, err = getNlink(filepath.Join(origin, "1"))
   135  	assert.NilError(t, err)
   136  
   137  	// sanity check that we can hardlink
   138  	if i1 != 2 {
   139  		t.Skipf("skipping since hardlinks don't work here; expected 2 links, got %d", i1)
   140  	}
   141  
   142  	dest := filepath.Join(tmpDir, "dest")
   143  	bRdr, err := TarResourceRebase(origin, "origin")
   144  	assert.NilError(t, err)
   145  
   146  	dstDir, srcBase := SplitPathDirEntry(origin)
   147  	_, dstBase := SplitPathDirEntry(dest)
   148  	content := RebaseArchiveEntries(bRdr, srcBase, dstBase)
   149  	err = Untar(content, dstDir, &TarOptions{Compression: Uncompressed, NoLchown: true, NoOverwriteDirNonDir: true})
   150  	assert.NilError(t, err)
   151  
   152  	i1, err = getInode(filepath.Join(dest, "1"))
   153  	assert.NilError(t, err)
   154  	i2, err = getInode(filepath.Join(dest, "2"))
   155  	assert.NilError(t, err)
   156  
   157  	assert.Check(t, is.Equal(i1, i2))
   158  }
   159  
   160  // TestUntarParentPathPermissions is a regression test to check that missing
   161  // parent directories are created with the expected permissions
   162  func TestUntarParentPathPermissions(t *testing.T) {
   163  	skip.If(t, os.Getuid() != 0, "skipping test that requires root")
   164  	buf := &bytes.Buffer{}
   165  	w := tar.NewWriter(buf)
   166  	err := w.WriteHeader(&tar.Header{Name: "foo/bar"})
   167  	assert.NilError(t, err)
   168  	tmpDir, err := ioutil.TempDir("", t.Name())
   169  	assert.NilError(t, err)
   170  	defer os.RemoveAll(tmpDir)
   171  	err = Untar(buf, tmpDir, nil)
   172  	assert.NilError(t, err)
   173  
   174  	fi, err := os.Lstat(filepath.Join(tmpDir, "foo"))
   175  	assert.NilError(t, err)
   176  	assert.Equal(t, fi.Mode(), 0755|os.ModeDir)
   177  }
   178  
   179  func getNlink(path string) (uint64, error) {
   180  	stat, err := os.Stat(path)
   181  	if err != nil {
   182  		return 0, err
   183  	}
   184  	statT, ok := stat.Sys().(*syscall.Stat_t)
   185  	if !ok {
   186  		return 0, fmt.Errorf("expected type *syscall.Stat_t, got %t", stat.Sys())
   187  	}
   188  	// We need this conversion on ARM64
   189  	// nolint: unconvert
   190  	return uint64(statT.Nlink), nil
   191  }
   192  
   193  func getInode(path string) (uint64, error) {
   194  	stat, err := os.Stat(path)
   195  	if err != nil {
   196  		return 0, err
   197  	}
   198  	statT, ok := stat.Sys().(*syscall.Stat_t)
   199  	if !ok {
   200  		return 0, fmt.Errorf("expected type *syscall.Stat_t, got %t", stat.Sys())
   201  	}
   202  	return statT.Ino, nil
   203  }
   204  
   205  func TestTarWithBlockCharFifo(t *testing.T) {
   206  	skip.If(t, os.Getuid() != 0, "skipping test that requires root")
   207  	skip.If(t, sys.RunningInUserNS(), "skipping test that requires initial userns")
   208  	origin, err := ioutil.TempDir("", "docker-test-tar-hardlink")
   209  	assert.NilError(t, err)
   210  
   211  	defer os.RemoveAll(origin)
   212  	err = ioutil.WriteFile(filepath.Join(origin, "1"), []byte("hello world"), 0700)
   213  	assert.NilError(t, err)
   214  
   215  	err = system.Mknod(filepath.Join(origin, "2"), unix.S_IFBLK, int(system.Mkdev(int64(12), int64(5))))
   216  	assert.NilError(t, err)
   217  	err = system.Mknod(filepath.Join(origin, "3"), unix.S_IFCHR, int(system.Mkdev(int64(12), int64(5))))
   218  	assert.NilError(t, err)
   219  	err = system.Mknod(filepath.Join(origin, "4"), unix.S_IFIFO, int(system.Mkdev(int64(12), int64(5))))
   220  	assert.NilError(t, err)
   221  
   222  	dest, err := ioutil.TempDir("", "docker-test-tar-hardlink-dest")
   223  	assert.NilError(t, err)
   224  	defer os.RemoveAll(dest)
   225  
   226  	// we'll do this in two steps to separate failure
   227  	fh, err := Tar(origin, Uncompressed)
   228  	assert.NilError(t, err)
   229  
   230  	// ensure we can read the whole thing with no error, before writing back out
   231  	buf, err := ioutil.ReadAll(fh)
   232  	assert.NilError(t, err)
   233  
   234  	bRdr := bytes.NewReader(buf)
   235  	err = Untar(bRdr, dest, &TarOptions{Compression: Uncompressed})
   236  	assert.NilError(t, err)
   237  
   238  	changes, err := ChangesDirs(origin, dest)
   239  	assert.NilError(t, err)
   240  
   241  	if len(changes) > 0 {
   242  		t.Fatalf("Tar with special device (block, char, fifo) should keep them (recreate them when untar) : %v", changes)
   243  	}
   244  }
   245  
   246  // TestTarUntarWithXattr is Unix as Lsetxattr is not supported on Windows
   247  func TestTarUntarWithXattr(t *testing.T) {
   248  	skip.If(t, os.Getuid() != 0, "skipping test that requires root")
   249  	if _, err := exec.LookPath("setcap"); err != nil {
   250  		t.Skip("setcap not installed")
   251  	}
   252  	if _, err := exec.LookPath("getcap"); err != nil {
   253  		t.Skip("getcap not installed")
   254  	}
   255  
   256  	origin, err := ioutil.TempDir("", "docker-test-untar-origin")
   257  	assert.NilError(t, err)
   258  	defer os.RemoveAll(origin)
   259  	err = ioutil.WriteFile(filepath.Join(origin, "1"), []byte("hello world"), 0700)
   260  	assert.NilError(t, err)
   261  
   262  	err = ioutil.WriteFile(filepath.Join(origin, "2"), []byte("welcome!"), 0700)
   263  	assert.NilError(t, err)
   264  	err = ioutil.WriteFile(filepath.Join(origin, "3"), []byte("will be ignored"), 0700)
   265  	assert.NilError(t, err)
   266  	// there is no known Go implementation of setcap/getcap with support for v3 file capability
   267  	out, err := exec.Command("setcap", "cap_block_suspend+ep", filepath.Join(origin, "2")).CombinedOutput()
   268  	assert.NilError(t, err, string(out))
   269  
   270  	for _, c := range []Compression{
   271  		Uncompressed,
   272  		Gzip,
   273  	} {
   274  		changes, err := tarUntar(t, origin, &TarOptions{
   275  			Compression:     c,
   276  			ExcludePatterns: []string{"3"},
   277  		})
   278  
   279  		if err != nil {
   280  			t.Fatalf("Error tar/untar for compression %s: %s", c.Extension(), err)
   281  		}
   282  
   283  		if len(changes) != 1 || changes[0].Path != "/3" {
   284  			t.Fatalf("Unexpected differences after tarUntar: %v", changes)
   285  		}
   286  		out, err := exec.Command("getcap", filepath.Join(origin, "2")).CombinedOutput()
   287  		assert.NilError(t, err, string(out))
   288  		assert.Check(t, is.Contains(string(out), "= cap_block_suspend+ep"), "untar should have kept the 'security.capability' xattr")
   289  	}
   290  }
   291  
   292  func TestCopyInfoDestinationPathSymlink(t *testing.T) {
   293  	tmpDir, _ := getTestTempDirs(t)
   294  	defer removeAllPaths(tmpDir)
   295  
   296  	root := strings.TrimRight(tmpDir, "/") + "/"
   297  
   298  	type FileTestData struct {
   299  		resource FileData
   300  		file     string
   301  		expected CopyInfo
   302  	}
   303  
   304  	testData := []FileTestData{
   305  		// Create a directory: /tmp/archive-copy-test*/dir1
   306  		// Test will "copy" file1 to dir1
   307  		{resource: FileData{filetype: Dir, path: "dir1", permissions: 0740}, file: "file1", expected: CopyInfo{Path: root + "dir1/file1", Exists: false, IsDir: false}},
   308  
   309  		// Create a symlink directory to dir1: /tmp/archive-copy-test*/dirSymlink -> dir1
   310  		// Test will "copy" file2 to dirSymlink
   311  		{resource: FileData{filetype: Symlink, path: "dirSymlink", contents: root + "dir1", permissions: 0600}, file: "file2", expected: CopyInfo{Path: root + "dirSymlink/file2", Exists: false, IsDir: false}},
   312  
   313  		// Create a file in tmp directory: /tmp/archive-copy-test*/file1
   314  		// Test to cover when the full file path already exists.
   315  		{resource: FileData{filetype: Regular, path: "file1", permissions: 0600}, file: "", expected: CopyInfo{Path: root + "file1", Exists: true}},
   316  
   317  		// Create a directory: /tmp/archive-copy*/dir2
   318  		// Test to cover when the full directory path already exists
   319  		{resource: FileData{filetype: Dir, path: "dir2", permissions: 0740}, file: "", expected: CopyInfo{Path: root + "dir2", Exists: true, IsDir: true}},
   320  
   321  		// Create a symlink to a non-existent target: /tmp/archive-copy*/symlink1 -> noSuchTarget
   322  		// Negative test to cover symlinking to a target that does not exit
   323  		{resource: FileData{filetype: Symlink, path: "symlink1", contents: "noSuchTarget", permissions: 0600}, file: "", expected: CopyInfo{Path: root + "noSuchTarget", Exists: false}},
   324  
   325  		// Create a file in tmp directory for next test: /tmp/existingfile
   326  		{resource: FileData{filetype: Regular, path: "existingfile", permissions: 0600}, file: "", expected: CopyInfo{Path: root + "existingfile", Exists: true}},
   327  
   328  		// Create a symlink to an existing file: /tmp/archive-copy*/symlink2 -> /tmp/existingfile
   329  		// Test to cover when the parent directory of a new file is a symlink
   330  		{resource: FileData{filetype: Symlink, path: "symlink2", contents: "existingfile", permissions: 0600}, file: "", expected: CopyInfo{Path: root + "existingfile", Exists: true}},
   331  	}
   332  
   333  	var dirs []FileData
   334  	for _, data := range testData {
   335  		dirs = append(dirs, data.resource)
   336  	}
   337  	provisionSampleDir(t, tmpDir, dirs)
   338  
   339  	for _, info := range testData {
   340  		p := filepath.Join(tmpDir, info.resource.path, info.file)
   341  		ci, err := CopyInfoDestinationPath(p)
   342  		assert.Check(t, err)
   343  		assert.Check(t, is.DeepEqual(info.expected, ci))
   344  	}
   345  }