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 }