github.com/toplink-cn/moby@v0.0.0-20240305205811-460b4aebdf81/integration-cli/docker_cli_cp_test.go (about)

     1  package main
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"fmt"
     7  	"io"
     8  	"os"
     9  	"os/exec"
    10  	"path"
    11  	"path/filepath"
    12  	"strings"
    13  	"testing"
    14  
    15  	"github.com/docker/docker/integration-cli/cli"
    16  	"gotest.tools/v3/assert"
    17  	is "gotest.tools/v3/assert/cmp"
    18  	"gotest.tools/v3/icmd"
    19  )
    20  
    21  const (
    22  	cpTestPathParent = "/some"
    23  	cpTestPath       = "/some/path"
    24  	cpTestName       = "test"
    25  	cpFullPath       = "/some/path/test"
    26  
    27  	cpContainerContents = "holla, i am the container"
    28  	cpHostContents      = "hello, i am the host"
    29  )
    30  
    31  type DockerCLICpSuite struct {
    32  	ds *DockerSuite
    33  }
    34  
    35  func (s *DockerCLICpSuite) TearDownTest(ctx context.Context, c *testing.T) {
    36  	s.ds.TearDownTest(ctx, c)
    37  }
    38  
    39  func (s *DockerCLICpSuite) OnTimeout(c *testing.T) {
    40  	s.ds.OnTimeout(c)
    41  }
    42  
    43  // Ensure that an all-local path case returns an error.
    44  func (s *DockerCLICpSuite) TestCpLocalOnly(c *testing.T) {
    45  	err := runDockerCp(c, "foo", "bar")
    46  	assert.ErrorContains(c, err, "must specify at least one container source")
    47  }
    48  
    49  // Test for #5656
    50  // Check that garbage paths don't escape the container's rootfs
    51  func (s *DockerCLICpSuite) TestCpGarbagePath(c *testing.T) {
    52  	containerID := cli.DockerCmd(c, "run", "-d", "busybox", "/bin/sh", "-c", "mkdir -p '"+cpTestPath+"' && echo -n '"+cpContainerContents+"' > "+cpFullPath).Stdout()
    53  	containerID = strings.TrimSpace(containerID)
    54  
    55  	out := cli.DockerCmd(c, "wait", containerID).Combined()
    56  	assert.Equal(c, strings.TrimSpace(out), "0", "failed to set up container")
    57  	assert.NilError(c, os.MkdirAll(cpTestPath, os.ModeDir))
    58  
    59  	hostFile, err := os.Create(cpFullPath)
    60  	assert.NilError(c, err)
    61  	defer hostFile.Close()
    62  	defer os.RemoveAll(cpTestPathParent)
    63  
    64  	fmt.Fprintf(hostFile, "%s", cpHostContents)
    65  
    66  	tmpdir, err := os.MkdirTemp("", "docker-integration")
    67  	assert.NilError(c, err)
    68  
    69  	tmpname := filepath.Join(tmpdir, cpTestName)
    70  	defer os.RemoveAll(tmpdir)
    71  
    72  	containerPath := path.Join("../../../../../../../../../../../../", cpFullPath)
    73  	cli.DockerCmd(c, "cp", containerID+":"+containerPath, tmpdir)
    74  
    75  	file, _ := os.Open(tmpname)
    76  	defer file.Close()
    77  
    78  	test, err := io.ReadAll(file)
    79  	assert.NilError(c, err)
    80  	assert.Assert(c, string(test) != cpHostContents, "output matched host file -- garbage path can escape container rootfs")
    81  	assert.Assert(c, string(test) == cpContainerContents, "output doesn't match the input for garbage path")
    82  }
    83  
    84  // Check that relative paths are relative to the container's rootfs
    85  func (s *DockerCLICpSuite) TestCpRelativePath(c *testing.T) {
    86  	containerID := cli.DockerCmd(c, "run", "-d", "busybox", "/bin/sh", "-c", "mkdir -p '"+cpTestPath+"' && echo -n '"+cpContainerContents+"' > "+cpFullPath).Stdout()
    87  	containerID = strings.TrimSpace(containerID)
    88  
    89  	out := cli.DockerCmd(c, "wait", containerID).Combined()
    90  	assert.Equal(c, strings.TrimSpace(out), "0", "failed to set up container")
    91  	assert.NilError(c, os.MkdirAll(cpTestPath, os.ModeDir))
    92  
    93  	hostFile, err := os.Create(cpFullPath)
    94  	assert.NilError(c, err)
    95  	defer hostFile.Close()
    96  	defer os.RemoveAll(cpTestPathParent)
    97  
    98  	fmt.Fprintf(hostFile, "%s", cpHostContents)
    99  
   100  	tmpdir, err := os.MkdirTemp("", "docker-integration")
   101  	assert.NilError(c, err)
   102  
   103  	tmpname := filepath.Join(tmpdir, cpTestName)
   104  	defer os.RemoveAll(tmpdir)
   105  
   106  	var relPath string
   107  	if path.IsAbs(cpFullPath) {
   108  		// normally this is `filepath.Rel("/", cpFullPath)` but we cannot
   109  		// get this unix-path manipulation on windows with filepath.
   110  		relPath = cpFullPath[1:]
   111  	}
   112  	assert.Assert(c, path.IsAbs(cpFullPath), "path %s was assumed to be an absolute path", cpFullPath)
   113  
   114  	cli.DockerCmd(c, "cp", containerID+":"+relPath, tmpdir)
   115  
   116  	file, _ := os.Open(tmpname)
   117  	defer file.Close()
   118  
   119  	test, err := io.ReadAll(file)
   120  	assert.NilError(c, err)
   121  	assert.Assert(c, string(test) != cpHostContents, "output matched host file -- relative path can escape container rootfs")
   122  	assert.Assert(c, string(test) == cpContainerContents, "output doesn't match the input for relative path")
   123  }
   124  
   125  // Check that absolute paths are relative to the container's rootfs
   126  func (s *DockerCLICpSuite) TestCpAbsolutePath(c *testing.T) {
   127  	containerID := cli.DockerCmd(c, "run", "-d", "busybox", "/bin/sh", "-c", "mkdir -p '"+cpTestPath+"' && echo -n '"+cpContainerContents+"' > "+cpFullPath).Stdout()
   128  	containerID = strings.TrimSpace(containerID)
   129  
   130  	out := cli.DockerCmd(c, "wait", containerID).Combined()
   131  	assert.Equal(c, strings.TrimSpace(out), "0", "failed to set up container")
   132  	assert.NilError(c, os.MkdirAll(cpTestPath, os.ModeDir))
   133  
   134  	hostFile, err := os.Create(cpFullPath)
   135  	assert.NilError(c, err)
   136  	defer hostFile.Close()
   137  	defer os.RemoveAll(cpTestPathParent)
   138  
   139  	fmt.Fprintf(hostFile, "%s", cpHostContents)
   140  
   141  	tmpdir, err := os.MkdirTemp("", "docker-integration")
   142  	assert.NilError(c, err)
   143  
   144  	tmpname := filepath.Join(tmpdir, cpTestName)
   145  	defer os.RemoveAll(tmpdir)
   146  
   147  	cli.DockerCmd(c, "cp", containerID+":"+cpFullPath, tmpdir)
   148  
   149  	file, _ := os.Open(tmpname)
   150  	defer file.Close()
   151  
   152  	test, err := io.ReadAll(file)
   153  	assert.NilError(c, err)
   154  	assert.Assert(c, string(test) != cpHostContents, "output matched host file -- absolute path can escape container rootfs")
   155  	assert.Assert(c, string(test) == cpContainerContents, "output doesn't match the input for absolute path")
   156  }
   157  
   158  // Test for #5619
   159  // Check that absolute symlinks are still relative to the container's rootfs
   160  func (s *DockerCLICpSuite) TestCpAbsoluteSymlink(c *testing.T) {
   161  	testRequires(c, DaemonIsLinux)
   162  	containerID := cli.DockerCmd(c, "run", "-d", "busybox", "/bin/sh", "-c", "mkdir -p '"+cpTestPath+"' && echo -n '"+cpContainerContents+"' > "+cpFullPath+" && ln -s "+cpFullPath+" container_path").Stdout()
   163  	containerID = strings.TrimSpace(containerID)
   164  
   165  	out := cli.DockerCmd(c, "wait", containerID).Combined()
   166  	assert.Equal(c, strings.TrimSpace(out), "0", "failed to set up container")
   167  
   168  	assert.NilError(c, os.MkdirAll(cpTestPath, os.ModeDir))
   169  
   170  	hostFile, err := os.Create(cpFullPath)
   171  	assert.NilError(c, err)
   172  	defer hostFile.Close()
   173  	defer os.RemoveAll(cpTestPathParent)
   174  
   175  	fmt.Fprintf(hostFile, "%s", cpHostContents)
   176  
   177  	tmpdir, err := os.MkdirTemp("", "docker-integration")
   178  	assert.NilError(c, err)
   179  
   180  	tmpname := filepath.Join(tmpdir, "container_path")
   181  	defer os.RemoveAll(tmpdir)
   182  
   183  	containerPath := path.Join("/", "container_path")
   184  	cli.DockerCmd(c, "cp", containerID+":"+containerPath, tmpdir)
   185  
   186  	// We should have copied a symlink *NOT* the file itself!
   187  	linkTarget, err := os.Readlink(tmpname)
   188  	assert.NilError(c, err)
   189  	assert.Equal(c, linkTarget, filepath.FromSlash(cpFullPath))
   190  }
   191  
   192  // Check that symlinks to a directory behave as expected when copying one from
   193  // a container.
   194  func (s *DockerCLICpSuite) TestCpFromSymlinkToDirectory(c *testing.T) {
   195  	testRequires(c, DaemonIsLinux)
   196  	containerID := cli.DockerCmd(c, "run", "-d", "busybox", "/bin/sh", "-c", "mkdir -p '"+cpTestPath+"' && echo -n '"+cpContainerContents+"' > "+cpFullPath+" && ln -s "+cpTestPathParent+" /dir_link").Stdout()
   197  	containerID = strings.TrimSpace(containerID)
   198  
   199  	out := cli.DockerCmd(c, "wait", containerID).Combined()
   200  	assert.Equal(c, strings.TrimSpace(out), "0", "failed to set up container")
   201  
   202  	testDir, err := os.MkdirTemp("", "test-cp-from-symlink-to-dir-")
   203  	assert.NilError(c, err)
   204  	defer os.RemoveAll(testDir)
   205  
   206  	// This copy command should copy the symlink, not the target, into the
   207  	// temporary directory.
   208  	cli.DockerCmd(c, "cp", containerID+":"+"/dir_link", testDir)
   209  
   210  	expectedPath := filepath.Join(testDir, "dir_link")
   211  	linkTarget, err := os.Readlink(expectedPath)
   212  	assert.NilError(c, err)
   213  
   214  	assert.Equal(c, linkTarget, filepath.FromSlash(cpTestPathParent))
   215  
   216  	os.Remove(expectedPath)
   217  
   218  	// This copy command should resolve the symlink (note the trailing
   219  	// separator), copying the target into the temporary directory.
   220  	cli.DockerCmd(c, "cp", containerID+":"+"/dir_link/", testDir)
   221  
   222  	// It *should not* have copied the directory using the target's name, but
   223  	// used the given name instead.
   224  	unexpectedPath := filepath.Join(testDir, cpTestPathParent)
   225  	stat, err := os.Lstat(unexpectedPath)
   226  	if err == nil {
   227  		out = fmt.Sprintf("target name was copied: %q - %q", stat.Mode(), stat.Name())
   228  	}
   229  	assert.ErrorContains(c, err, "", out)
   230  
   231  	// It *should* have copied the directory using the asked name "dir_link".
   232  	stat, err = os.Lstat(expectedPath)
   233  	assert.NilError(c, err, "unable to stat resource at %q", expectedPath)
   234  	assert.Assert(c, stat.IsDir(), "should have copied a directory but got %q instead", stat.Mode())
   235  }
   236  
   237  // Check that symlinks to a directory behave as expected when copying one to a
   238  // container.
   239  func (s *DockerCLICpSuite) TestCpToSymlinkToDirectory(c *testing.T) {
   240  	testRequires(c, DaemonIsLinux)
   241  	testRequires(c, testEnv.IsLocalDaemon) // Requires local volume mount bind.
   242  
   243  	testVol, err := os.MkdirTemp("", "test-cp-to-symlink-to-dir-")
   244  	assert.NilError(c, err)
   245  	defer os.RemoveAll(testVol)
   246  
   247  	// Create a test container with a local volume. We will test by copying
   248  	// to the volume path in the container which we can then verify locally.
   249  	containerID := cli.DockerCmd(c, "create", "-v", testVol+":/testVol", "busybox").Stdout()
   250  	containerID = strings.TrimSpace(containerID)
   251  
   252  	// Create a temp directory to hold a test file nested in a directory.
   253  	testDir, err := os.MkdirTemp("", "test-cp-to-symlink-to-dir-")
   254  	assert.NilError(c, err)
   255  	defer os.RemoveAll(testDir)
   256  
   257  	// This file will be at "/testDir/some/path/test" and will be copied into
   258  	// the test volume later.
   259  	hostTestFilename := filepath.Join(testDir, cpFullPath)
   260  	assert.NilError(c, os.MkdirAll(filepath.Dir(hostTestFilename), os.FileMode(0o700)))
   261  	assert.NilError(c, os.WriteFile(hostTestFilename, []byte(cpHostContents), os.FileMode(0o600)))
   262  
   263  	// Now create another temp directory to hold a symlink to the
   264  	// "/testDir/some" directory.
   265  	linkDir, err := os.MkdirTemp("", "test-cp-to-symlink-to-dir-")
   266  	assert.NilError(c, err)
   267  	defer os.RemoveAll(linkDir)
   268  
   269  	// Then symlink "/linkDir/dir_link" to "/testdir/some".
   270  	linkTarget := filepath.Join(testDir, cpTestPathParent)
   271  	localLink := filepath.Join(linkDir, "dir_link")
   272  	assert.NilError(c, os.Symlink(linkTarget, localLink))
   273  
   274  	// Now copy that symlink into the test volume in the container.
   275  	cli.DockerCmd(c, "cp", localLink, containerID+":/testVol")
   276  
   277  	// This copy command should have copied the symlink *not* the target.
   278  	expectedPath := filepath.Join(testVol, "dir_link")
   279  	actualLinkTarget, err := os.Readlink(expectedPath)
   280  	assert.NilError(c, err, "unable to read symlink at %q", expectedPath)
   281  	assert.Equal(c, actualLinkTarget, linkTarget)
   282  
   283  	// Good, now remove that copied link for the next test.
   284  	os.Remove(expectedPath)
   285  
   286  	// This copy command should resolve the symlink (note the trailing
   287  	// separator), copying the target into the test volume directory in the
   288  	// container.
   289  	cli.DockerCmd(c, "cp", localLink+"/", containerID+":/testVol")
   290  
   291  	// It *should not* have copied the directory using the target's name, but
   292  	// used the given name instead.
   293  	unexpectedPath := filepath.Join(testVol, cpTestPathParent)
   294  	stat, err := os.Lstat(unexpectedPath)
   295  	if err == nil {
   296  		c.Errorf("target name was unexpectedly preserved: %q - %q", stat.Mode(), stat.Name())
   297  	}
   298  
   299  	// It *should* have copied the directory using the asked name "dir_link".
   300  	stat, err = os.Lstat(expectedPath)
   301  	assert.NilError(c, err, "unable to stat resource at %q", expectedPath)
   302  	assert.Assert(c, stat.IsDir(), "should have copied a directory but got %q instead", stat.Mode())
   303  
   304  	// And this directory should contain the file copied from the host at the
   305  	// expected location: "/testVol/dir_link/path/test"
   306  	expectedFilepath := filepath.Join(testVol, "dir_link/path/test")
   307  	fileContents, err := os.ReadFile(expectedFilepath)
   308  	assert.NilError(c, err)
   309  	assert.Equal(c, string(fileContents), cpHostContents)
   310  }
   311  
   312  // Test for #5619
   313  // Check that symlinks which are part of the resource path are still relative to the container's rootfs
   314  func (s *DockerCLICpSuite) TestCpSymlinkComponent(c *testing.T) {
   315  	testRequires(c, DaemonIsLinux)
   316  	containerID := cli.DockerCmd(c, "run", "-d", "busybox", "/bin/sh", "-c", "mkdir -p '"+cpTestPath+"' && echo -n '"+cpContainerContents+"' > "+cpFullPath+" && ln -s "+cpTestPath+" container_path").Stdout()
   317  	containerID = strings.TrimSpace(containerID)
   318  
   319  	out := cli.DockerCmd(c, "wait", containerID).Combined()
   320  	assert.Equal(c, strings.TrimSpace(out), "0", "failed to set up container")
   321  
   322  	assert.NilError(c, os.MkdirAll(cpTestPath, os.ModeDir))
   323  
   324  	hostFile, err := os.Create(cpFullPath)
   325  	assert.NilError(c, err)
   326  	defer hostFile.Close()
   327  	defer os.RemoveAll(cpTestPathParent)
   328  
   329  	fmt.Fprintf(hostFile, "%s", cpHostContents)
   330  
   331  	tmpdir, err := os.MkdirTemp("", "docker-integration")
   332  
   333  	assert.NilError(c, err)
   334  
   335  	tmpname := filepath.Join(tmpdir, cpTestName)
   336  	defer os.RemoveAll(tmpdir)
   337  
   338  	containerPath := path.Join("/", "container_path", cpTestName)
   339  	cli.DockerCmd(c, "cp", containerID+":"+containerPath, tmpdir)
   340  
   341  	file, _ := os.Open(tmpname)
   342  	defer file.Close()
   343  
   344  	test, err := io.ReadAll(file)
   345  	assert.NilError(c, err)
   346  	assert.Assert(c, string(test) != cpHostContents, "output matched host file -- symlink path component can escape container rootfs")
   347  	assert.Equal(c, string(test), cpContainerContents, "output doesn't match the input for symlink path component")
   348  }
   349  
   350  // Check that cp with unprivileged user doesn't return any error
   351  func (s *DockerCLICpSuite) TestCpUnprivilegedUser(c *testing.T) {
   352  	testRequires(c, DaemonIsLinux, testEnv.IsLocalDaemon)
   353  	testRequires(c, UnixCli) // uses chmod/su: not available on windows
   354  
   355  	containerID := cli.DockerCmd(c, "run", "-d", "busybox", "/bin/sh", "-c", "touch "+cpTestName).Stdout()
   356  	containerID = strings.TrimSpace(containerID)
   357  
   358  	out := cli.DockerCmd(c, "wait", containerID).Combined()
   359  	assert.Equal(c, strings.TrimSpace(out), "0", "failed to set up container")
   360  
   361  	tmpdir, err := os.MkdirTemp("", "docker-integration")
   362  	assert.NilError(c, err)
   363  
   364  	defer os.RemoveAll(tmpdir)
   365  
   366  	err = os.Chmod(tmpdir, 0o777)
   367  	assert.NilError(c, err)
   368  
   369  	result := icmd.RunCommand("su", "unprivilegeduser", "-c", fmt.Sprintf("%s cp %s:%s %s", dockerBinary, containerID, cpTestName, tmpdir))
   370  	result.Assert(c, icmd.Expected{})
   371  }
   372  
   373  func (s *DockerCLICpSuite) TestCpSpecialFiles(c *testing.T) {
   374  	testRequires(c, DaemonIsLinux)
   375  	testRequires(c, testEnv.IsLocalDaemon)
   376  
   377  	outDir, err := os.MkdirTemp("", "cp-test-special-files")
   378  	assert.NilError(c, err)
   379  	defer os.RemoveAll(outDir)
   380  
   381  	containerID := cli.DockerCmd(c, "run", "-d", "busybox", "/bin/sh", "-c", "touch /foo").Stdout()
   382  	containerID = strings.TrimSpace(containerID)
   383  
   384  	out := cli.DockerCmd(c, "wait", containerID).Combined()
   385  	assert.Equal(c, strings.TrimSpace(out), "0", "failed to set up container")
   386  
   387  	// Copy actual /etc/resolv.conf
   388  	cli.DockerCmd(c, "cp", containerID+":/etc/resolv.conf", outDir)
   389  
   390  	expected := readContainerFile(c, containerID, "resolv.conf")
   391  	actual, err := os.ReadFile(outDir + "/resolv.conf")
   392  	assert.NilError(c, err)
   393  	assert.Assert(c, bytes.Equal(actual, expected), "Expected copied file to be duplicate of the container resolvconf")
   394  
   395  	// Copy actual /etc/hosts
   396  	cli.DockerCmd(c, "cp", containerID+":/etc/hosts", outDir)
   397  
   398  	expected = readContainerFile(c, containerID, "hosts")
   399  	actual, err = os.ReadFile(outDir + "/hosts")
   400  	assert.NilError(c, err)
   401  	assert.Assert(c, bytes.Equal(actual, expected), "Expected copied file to be duplicate of the container hosts")
   402  
   403  	// Copy actual /etc/resolv.conf
   404  	cli.DockerCmd(c, "cp", containerID+":/etc/hostname", outDir)
   405  
   406  	expected = readContainerFile(c, containerID, "hostname")
   407  	actual, err = os.ReadFile(outDir + "/hostname")
   408  	assert.NilError(c, err)
   409  	assert.Assert(c, bytes.Equal(actual, expected), "Expected copied file to be duplicate of the container hostname")
   410  }
   411  
   412  func (s *DockerCLICpSuite) TestCpVolumePath(c *testing.T) {
   413  	//  stat /tmp/cp-test-volumepath851508420/test gets permission denied for the user
   414  	testRequires(c, NotUserNamespace)
   415  	testRequires(c, DaemonIsLinux)
   416  	testRequires(c, testEnv.IsLocalDaemon)
   417  
   418  	tmpDir, err := os.MkdirTemp("", "cp-test-volumepath")
   419  	assert.NilError(c, err)
   420  	defer os.RemoveAll(tmpDir)
   421  	outDir, err := os.MkdirTemp("", "cp-test-volumepath-out")
   422  	assert.NilError(c, err)
   423  	defer os.RemoveAll(outDir)
   424  	_, err = os.Create(tmpDir + "/test")
   425  	assert.NilError(c, err)
   426  
   427  	containerID := cli.DockerCmd(c, "run", "-d", "-v", "/foo", "-v", tmpDir+"/test:/test", "-v", tmpDir+":/baz", "busybox", "/bin/sh", "-c", "touch /foo/bar").Stdout()
   428  	containerID = strings.TrimSpace(containerID)
   429  
   430  	out := cli.DockerCmd(c, "wait", containerID).Combined()
   431  	assert.Equal(c, strings.TrimSpace(out), "0", "failed to set up container")
   432  
   433  	// Copy actual volume path
   434  	cli.DockerCmd(c, "cp", containerID+":/foo", outDir)
   435  
   436  	stat, err := os.Stat(outDir + "/foo")
   437  	assert.NilError(c, err)
   438  	assert.Assert(c, stat.IsDir(), "Expected copied content to be dir")
   439  
   440  	stat, err = os.Stat(outDir + "/foo/bar")
   441  	assert.NilError(c, err)
   442  	assert.Assert(c, !stat.IsDir(), "Expected file `bar` to be a file")
   443  
   444  	// Copy file nested in volume
   445  	cli.DockerCmd(c, "cp", containerID+":/foo/bar", outDir)
   446  
   447  	stat, err = os.Stat(outDir + "/bar")
   448  	assert.NilError(c, err)
   449  	assert.Assert(c, !stat.IsDir(), "Expected file `bar` to be a file")
   450  
   451  	// Copy Bind-mounted dir
   452  	cli.DockerCmd(c, "cp", containerID+":/baz", outDir)
   453  	stat, err = os.Stat(outDir + "/baz")
   454  	assert.NilError(c, err)
   455  	assert.Assert(c, stat.IsDir(), "Expected `baz` to be a dir")
   456  
   457  	// Copy file nested in bind-mounted dir
   458  	cli.DockerCmd(c, "cp", containerID+":/baz/test", outDir)
   459  	fb, err := os.ReadFile(outDir + "/baz/test")
   460  	assert.NilError(c, err)
   461  	fb2, err := os.ReadFile(tmpDir + "/test")
   462  	assert.NilError(c, err)
   463  	assert.Assert(c, bytes.Equal(fb, fb2), "Expected copied file to be duplicate of bind-mounted file")
   464  
   465  	// Copy bind-mounted file
   466  	cli.DockerCmd(c, "cp", containerID+":/test", outDir)
   467  	fb, err = os.ReadFile(outDir + "/test")
   468  	assert.NilError(c, err)
   469  	fb2, err = os.ReadFile(tmpDir + "/test")
   470  	assert.NilError(c, err)
   471  	assert.Assert(c, bytes.Equal(fb, fb2), "Expected copied file to be duplicate of bind-mounted file")
   472  }
   473  
   474  func (s *DockerCLICpSuite) TestCpToDot(c *testing.T) {
   475  	containerID := cli.DockerCmd(c, "run", "-d", "busybox", "/bin/sh", "-c", "echo lololol > /test").Stdout()
   476  	containerID = strings.TrimSpace(containerID)
   477  
   478  	out := cli.DockerCmd(c, "wait", containerID).Combined()
   479  	assert.Equal(c, strings.TrimSpace(out), "0", "failed to set up container")
   480  
   481  	tmpdir, err := os.MkdirTemp("", "docker-integration")
   482  	assert.NilError(c, err)
   483  	defer os.RemoveAll(tmpdir)
   484  	cwd, err := os.Getwd()
   485  	assert.NilError(c, err)
   486  	defer os.Chdir(cwd)
   487  	err = os.Chdir(tmpdir)
   488  	assert.NilError(c, err)
   489  
   490  	cli.DockerCmd(c, "cp", containerID+":/test", ".")
   491  	content, err := os.ReadFile("./test")
   492  	assert.NilError(c, err)
   493  	assert.Equal(c, string(content), "lololol\n")
   494  }
   495  
   496  func (s *DockerCLICpSuite) TestCpToStdout(c *testing.T) {
   497  	containerID := cli.DockerCmd(c, "run", "-d", "busybox", "/bin/sh", "-c", "echo lololol > /test").Stdout()
   498  	containerID = strings.TrimSpace(containerID)
   499  
   500  	out := cli.DockerCmd(c, "wait", containerID).Combined()
   501  	assert.Equal(c, strings.TrimSpace(out), "0", "failed to set up container")
   502  
   503  	out, err := RunCommandPipelineWithOutput(
   504  		exec.Command(dockerBinary, "cp", containerID+":/test", "-"),
   505  		exec.Command("tar", "-vtf", "-"))
   506  
   507  	assert.NilError(c, err)
   508  	assert.Check(c, is.Contains(out, "test"))
   509  	assert.Check(c, is.Contains(out, "-rw"))
   510  }
   511  
   512  func (s *DockerCLICpSuite) TestCpNameHasColon(c *testing.T) {
   513  	testRequires(c, testEnv.IsLocalDaemon, DaemonIsLinux)
   514  
   515  	containerID := cli.DockerCmd(c, "run", "-d", "busybox", "/bin/sh", "-c", "echo lololol > /te:s:t").Stdout()
   516  	containerID = strings.TrimSpace(containerID)
   517  
   518  	out := cli.DockerCmd(c, "wait", containerID).Combined()
   519  	assert.Equal(c, strings.TrimSpace(out), "0", "failed to set up container")
   520  
   521  	tmpdir, err := os.MkdirTemp("", "docker-integration")
   522  	assert.NilError(c, err)
   523  	defer os.RemoveAll(tmpdir)
   524  	cli.DockerCmd(c, "cp", containerID+":/te:s:t", tmpdir)
   525  	content, err := os.ReadFile(tmpdir + "/te:s:t")
   526  	assert.NilError(c, err)
   527  	assert.Equal(c, string(content), "lololol\n")
   528  }
   529  
   530  func (s *DockerCLICpSuite) TestCopyAndRestart(c *testing.T) {
   531  	testRequires(c, DaemonIsLinux)
   532  	const expectedMsg = "hello"
   533  	containerID := cli.DockerCmd(c, "run", "-d", "busybox", "echo", expectedMsg).Stdout()
   534  	containerID = strings.TrimSpace(containerID)
   535  
   536  	out := cli.DockerCmd(c, "wait", containerID).Combined()
   537  	assert.Equal(c, strings.TrimSpace(out), "0", "failed to set up container")
   538  
   539  	tmpDir, err := os.MkdirTemp("", "test-docker-restart-after-copy-")
   540  	assert.NilError(c, err)
   541  	defer os.RemoveAll(tmpDir)
   542  
   543  	cli.DockerCmd(c, "cp", fmt.Sprintf("%s:/etc/group", containerID), tmpDir)
   544  
   545  	out = cli.DockerCmd(c, "start", "-a", containerID).Combined()
   546  	assert.Equal(c, strings.TrimSpace(out), expectedMsg)
   547  }
   548  
   549  func (s *DockerCLICpSuite) TestCopyCreatedContainer(c *testing.T) {
   550  	testRequires(c, DaemonIsLinux)
   551  	cli.DockerCmd(c, "create", "--name", "test_cp", "-v", "/test", "busybox")
   552  
   553  	tmpDir, err := os.MkdirTemp("", "test")
   554  	assert.NilError(c, err)
   555  	defer os.RemoveAll(tmpDir)
   556  	cli.DockerCmd(c, "cp", "test_cp:/bin/sh", tmpDir)
   557  }
   558  
   559  // test copy with option `-L`: following symbol link
   560  // Check that symlinks to a file behave as expected when copying one from
   561  // a container to host following symbol link
   562  func (s *DockerCLICpSuite) TestCpSymlinkFromConToHostFollowSymlink(c *testing.T) {
   563  	testRequires(c, DaemonIsLinux)
   564  	result := cli.DockerCmd(c, "run", "-d", "busybox", "/bin/sh", "-c", "mkdir -p '"+cpTestPath+"' && echo -n '"+cpContainerContents+"' > "+cpFullPath+" && ln -s "+cpFullPath+" /dir_link")
   565  	assert.Equal(c, result.ExitCode, 0, "failed to set up container: %s", result.Combined())
   566  	containerID := strings.TrimSpace(result.Stdout())
   567  
   568  	out := cli.DockerCmd(c, "wait", containerID).Combined()
   569  	assert.Equal(c, strings.TrimSpace(out), "0", "failed to set up container")
   570  
   571  	testDir, err := os.MkdirTemp("", "test-cp-symlink-container-to-host-follow-symlink")
   572  	assert.NilError(c, err)
   573  	defer os.RemoveAll(testDir)
   574  
   575  	// This copy command should copy the symlink, not the target, into the
   576  	// temporary directory.
   577  	cli.DockerCmd(c, "cp", "-L", containerID+":"+"/dir_link", testDir)
   578  
   579  	expectedPath := filepath.Join(testDir, "dir_link")
   580  
   581  	expected := []byte(cpContainerContents)
   582  	actual, err := os.ReadFile(expectedPath)
   583  	assert.NilError(c, err)
   584  	os.Remove(expectedPath)
   585  	assert.Assert(c, bytes.Equal(actual, expected), "Expected copied file to be duplicate of the container symbol link target")
   586  
   587  	// now test copy symbol link to a non-existing file in host
   588  	expectedPath = filepath.Join(testDir, "somefile_host")
   589  	// expectedPath shouldn't exist, if exists, remove it
   590  	if _, err := os.Lstat(expectedPath); err == nil {
   591  		os.Remove(expectedPath)
   592  	}
   593  
   594  	cli.DockerCmd(c, "cp", "-L", containerID+":"+"/dir_link", expectedPath)
   595  
   596  	actual, err = os.ReadFile(expectedPath)
   597  	assert.NilError(c, err)
   598  	defer os.Remove(expectedPath)
   599  	assert.Assert(c, bytes.Equal(actual, expected), "Expected copied file to be duplicate of the container symbol link target")
   600  }