github.com/adityamillind98/moby@v23.0.0-rc.4+incompatible/integration-cli/docker_cli_cp_test.go (about)

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