docker_cli_cp_test.go 22 KB

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