diff --git a/api/server/router/container/backend.go b/api/server/router/container/backend.go index 6f729bea16..4a396b2148 100644 --- a/api/server/router/container/backend.go +++ b/api/server/router/container/backend.go @@ -27,7 +27,7 @@ type copyBackend interface { ContainerArchivePath(name string, path string) (content io.ReadCloser, stat *types.ContainerPathStat, err error) ContainerCopy(name string, res string) (io.ReadCloser, error) ContainerExport(name string, out io.Writer) error - ContainerExtractToDir(name, path string, noOverwriteDirNonDir bool, content io.Reader) error + ContainerExtractToDir(name, path string, copyUIDGID, noOverwriteDirNonDir bool, content io.Reader) error ContainerStatPath(name string, path string) (stat *types.ContainerPathStat, err error) } diff --git a/api/server/router/container/copy.go b/api/server/router/container/copy.go index 72d8c4a679..5cfe8d7ba1 100644 --- a/api/server/router/container/copy.go +++ b/api/server/router/container/copy.go @@ -112,5 +112,7 @@ func (s *containerRouter) putContainersArchive(ctx context.Context, w http.Respo } noOverwriteDirNonDir := httputils.BoolValue(r, "noOverwriteDirNonDir") - return s.backend.ContainerExtractToDir(v.Name, v.Path, noOverwriteDirNonDir, r.Body) + copyUIDGID := httputils.BoolValue(r, "copyUIDGID") + + return s.backend.ContainerExtractToDir(v.Name, v.Path, copyUIDGID, noOverwriteDirNonDir, r.Body) } diff --git a/api/types/client.go b/api/types/client.go index 56ec211293..ba277d573f 100644 --- a/api/types/client.go +++ b/api/types/client.go @@ -97,6 +97,7 @@ type ContainerStartOptions struct { // about files to copy into a container type CopyToContainerOptions struct { AllowOverwriteDirWithFile bool + CopyUIDGID bool } // EventsOptions holds parameters to filter events with. diff --git a/cli/command/container/cp.go b/cli/command/container/cp.go index a1d7110a61..a4165a18d2 100644 --- a/cli/command/container/cp.go +++ b/cli/command/container/cp.go @@ -20,6 +20,7 @@ type copyOptions struct { source string destination string followLink bool + copyUIDGID bool } type copyDirection int @@ -66,6 +67,7 @@ func NewCopyCommand(dockerCli *command.DockerCli) *cobra.Command { flags := cmd.Flags() flags.BoolVarP(&opts.followLink, "follow-link", "L", false, "Always follow symbol link in SRC_PATH") + flags.BoolVarP(&opts.copyUIDGID, "archive", "a", false, "Archive mode (copy all uid/gid information)") return cmd } @@ -92,7 +94,7 @@ func runCopy(dockerCli *command.DockerCli, opts copyOptions) error { case fromContainer: return copyFromContainer(ctx, dockerCli, srcContainer, srcPath, dstPath, cpParam) case toContainer: - return copyToContainer(ctx, dockerCli, srcPath, dstContainer, dstPath, cpParam) + return copyToContainer(ctx, dockerCli, srcPath, dstContainer, dstPath, cpParam, opts.copyUIDGID) case acrossContainers: // Copying between containers isn't supported. return errors.New("copying between containers is not supported") @@ -175,7 +177,7 @@ func copyFromContainer(ctx context.Context, dockerCli *command.DockerCli, srcCon return archive.CopyTo(preArchive, srcInfo, dstPath) } -func copyToContainer(ctx context.Context, dockerCli *command.DockerCli, srcPath, dstContainer, dstPath string, cpParam *cpConfig) (err error) { +func copyToContainer(ctx context.Context, dockerCli *command.DockerCli, srcPath, dstContainer, dstPath string, cpParam *cpConfig, copyUIDGID bool) (err error) { if srcPath != "-" { // Get an absolute source path. srcPath, err = resolveLocalPath(srcPath) @@ -265,6 +267,7 @@ func copyToContainer(ctx context.Context, dockerCli *command.DockerCli, srcPath, options := types.CopyToContainerOptions{ AllowOverwriteDirWithFile: false, + CopyUIDGID: copyUIDGID, } return dockerCli.Client().CopyToContainer(ctx, dstContainer, resolvedDstPath, content, options) diff --git a/client/container_copy.go b/client/container_copy.go index 8380eeabc9..545aa54383 100644 --- a/client/container_copy.go +++ b/client/container_copy.go @@ -38,6 +38,10 @@ func (cli *Client) CopyToContainer(ctx context.Context, container, path string, query.Set("noOverwriteDirNonDir", "true") } + if options.CopyUIDGID { + query.Set("copyUIDGID", "true") + } + apiPath := fmt.Sprintf("/containers/%s/archive", container) response, err := cli.putRaw(ctx, apiPath, query, content, nil) diff --git a/daemon/archive.go b/daemon/archive.go index b1401600cc..f1018c7368 100644 --- a/daemon/archive.go +++ b/daemon/archive.go @@ -83,7 +83,7 @@ func (daemon *Daemon) ContainerArchivePath(name string, path string) (content io // be ErrExtractPointNotDirectory. If noOverwriteDirNonDir is true then it will // be an error if unpacking the given content would cause an existing directory // to be replaced with a non-directory and vice versa. -func (daemon *Daemon) ContainerExtractToDir(name, path string, noOverwriteDirNonDir bool, content io.Reader) error { +func (daemon *Daemon) ContainerExtractToDir(name, path string, copyUIDGID, noOverwriteDirNonDir bool, content io.Reader) error { container, err := daemon.GetContainer(name) if err != nil { return err @@ -94,7 +94,7 @@ func (daemon *Daemon) ContainerExtractToDir(name, path string, noOverwriteDirNon return err } - return daemon.containerExtractToDir(container, path, noOverwriteDirNonDir, content) + return daemon.containerExtractToDir(container, path, copyUIDGID, noOverwriteDirNonDir, content) } // containerStatPath stats the filesystem resource at the specified path in this @@ -196,7 +196,7 @@ func (daemon *Daemon) containerArchivePath(container *container.Container, path // noOverwriteDirNonDir is true then it will be an error if unpacking the // given content would cause an existing directory to be replaced with a non- // directory and vice versa. -func (daemon *Daemon) containerExtractToDir(container *container.Container, path string, noOverwriteDirNonDir bool, content io.Reader) (err error) { +func (daemon *Daemon) containerExtractToDir(container *container.Container, path string, copyUIDGID, noOverwriteDirNonDir bool, content io.Reader) (err error) { container.Lock() defer container.Unlock() @@ -279,13 +279,18 @@ func (daemon *Daemon) containerExtractToDir(container *container.Container, path return ErrRootFSReadOnly } - uid, gid := daemon.GetRemappedUIDGID() - options := &archive.TarOptions{ - NoOverwriteDirNonDir: noOverwriteDirNonDir, - ChownOpts: &archive.TarChownOptions{ - UID: uid, GID: gid, // TODO: should all ownership be set to root (either real or remapped)? - }, + options := daemon.defaultTarCopyOptions(noOverwriteDirNonDir) + + if copyUIDGID { + var err error + // tarCopyOptions will appropriately pull in the right uid/gid for the + // user/group and will set the options. + options, err = daemon.tarCopyOptions(container, noOverwriteDirNonDir) + if err != nil { + return err + } } + if err := chrootarchive.Untar(content, resolvedPath, options); err != nil { return err } diff --git a/daemon/archive_tarcopyoptions.go b/daemon/archive_tarcopyoptions.go new file mode 100644 index 0000000000..cbd1fc2cee --- /dev/null +++ b/daemon/archive_tarcopyoptions.go @@ -0,0 +1,16 @@ +package daemon + +import ( + "github.com/docker/docker/pkg/archive" +) + +// defaultTarCopyOptions is the setting that is used when unpacking an archive +// for a copy API event. +func (daemon *Daemon) defaultTarCopyOptions(noOverwriteDirNonDir bool) *archive.TarOptions { + uidMaps, gidMaps := daemon.GetUIDGIDMaps() + return &archive.TarOptions{ + NoOverwriteDirNonDir: noOverwriteDirNonDir, + UIDMaps: uidMaps, + GIDMaps: gidMaps, + } +} diff --git a/daemon/archive_tarcopyoptions_unix.go b/daemon/archive_tarcopyoptions_unix.go new file mode 100644 index 0000000000..7b68bc463d --- /dev/null +++ b/daemon/archive_tarcopyoptions_unix.go @@ -0,0 +1,28 @@ +// +build !windows + +package daemon + +import ( + "github.com/docker/docker/container" + "github.com/docker/docker/pkg/archive" + "github.com/docker/docker/pkg/idtools" +) + +func (daemon *Daemon) tarCopyOptions(container *container.Container, noOverwriteDirNonDir bool) (*archive.TarOptions, error) { + if container.Config.User == "" { + return daemon.defaultTarCopyOptions(noOverwriteDirNonDir), nil + } + + user, err := idtools.LookupUser(container.Config.User) + if err != nil { + return nil, err + } + + return &archive.TarOptions{ + NoOverwriteDirNonDir: noOverwriteDirNonDir, + ChownOpts: &archive.TarChownOptions{ + UID: user.Uid, + GID: user.Gid, + }, + }, nil +} diff --git a/daemon/archive_tarcopyoptions_windows.go b/daemon/archive_tarcopyoptions_windows.go new file mode 100644 index 0000000000..535efd222e --- /dev/null +++ b/daemon/archive_tarcopyoptions_windows.go @@ -0,0 +1,12 @@ +// +build windows + +package daemon + +import ( + "github.com/docker/docker/container" + "github.com/docker/docker/pkg/archive" +) + +func (daemon *Daemon) tarCopyOptions(container *container.Container, noOverwriteDirNonDir bool) (*archive.TarOptions, error) { + return daemon.defaultTarCopyOptions(noOverwriteDirNonDir), nil +} diff --git a/integration-cli/docker_cli_cp_from_container_test.go b/integration-cli/docker_cli_cp_from_container_test.go index 30cacf42bb..116f24610e 100644 --- a/integration-cli/docker_cli_cp_from_container_test.go +++ b/integration-cli/docker_cli_cp_from_container_test.go @@ -29,7 +29,7 @@ func (s *DockerSuite) TestCpFromErrSrcNotExists(c *check.C) { tmpDir := getTestDir(c, "test-cp-from-err-src-not-exists") defer os.RemoveAll(tmpDir) - err := runDockerCp(c, containerCpPath(containerID, "file1"), tmpDir) + err := runDockerCp(c, containerCpPath(containerID, "file1"), tmpDir, nil) c.Assert(err, checker.NotNil) c.Assert(isCpNotExist(err), checker.True, check.Commentf("expected IsNotExist error, but got %T: %s", err, err)) @@ -44,7 +44,7 @@ func (s *DockerSuite) TestCpFromErrSrcNotDir(c *check.C) { tmpDir := getTestDir(c, "test-cp-from-err-src-not-dir") defer os.RemoveAll(tmpDir) - err := runDockerCp(c, containerCpPathTrailingSep(containerID, "file1"), tmpDir) + err := runDockerCp(c, containerCpPathTrailingSep(containerID, "file1"), tmpDir, nil) c.Assert(err, checker.NotNil) c.Assert(isCpNotDir(err), checker.True, check.Commentf("expected IsNotDir error, but got %T: %s", err, err)) @@ -65,7 +65,7 @@ func (s *DockerSuite) TestCpFromErrDstParentNotExists(c *check.C) { srcPath := containerCpPath(containerID, "/file1") dstPath := cpPath(tmpDir, "notExists", "file1") - err := runDockerCp(c, srcPath, dstPath) + err := runDockerCp(c, srcPath, dstPath, nil) c.Assert(err, checker.NotNil) c.Assert(isCpNotExist(err), checker.True, check.Commentf("expected IsNotExist error, but got %T: %s", err, err)) @@ -73,7 +73,7 @@ func (s *DockerSuite) TestCpFromErrDstParentNotExists(c *check.C) { // Try with a directory source. srcPath = containerCpPath(containerID, "/dir1") - err = runDockerCp(c, srcPath, dstPath) + err = runDockerCp(c, srcPath, dstPath, nil) c.Assert(err, checker.NotNil) c.Assert(isCpNotExist(err), checker.True, check.Commentf("expected IsNotExist error, but got %T: %s", err, err)) @@ -94,7 +94,7 @@ func (s *DockerSuite) TestCpFromErrDstNotDir(c *check.C) { srcPath := containerCpPath(containerID, "/file1") dstPath := cpPathTrailingSep(tmpDir, "file1") - err := runDockerCp(c, srcPath, dstPath) + err := runDockerCp(c, srcPath, dstPath, nil) c.Assert(err, checker.NotNil) c.Assert(isCpNotDir(err), checker.True, check.Commentf("expected IsNotDir error, but got %T: %s", err, err)) @@ -102,7 +102,7 @@ func (s *DockerSuite) TestCpFromErrDstNotDir(c *check.C) { // Try with a directory source. srcPath = containerCpPath(containerID, "/dir1") - err = runDockerCp(c, srcPath, dstPath) + err = runDockerCp(c, srcPath, dstPath, nil) c.Assert(err, checker.NotNil) c.Assert(isCpNotDir(err), checker.True, check.Commentf("expected IsNotDir error, but got %T: %s", err, err)) @@ -124,7 +124,7 @@ func (s *DockerSuite) TestCpFromSymlinkDestination(c *check.C) { srcPath := containerCpPath(containerID, "/file2") dstPath := cpPath(tmpDir, "symlinkToFile1") - c.Assert(runDockerCp(c, srcPath, dstPath), checker.IsNil) + c.Assert(runDockerCp(c, srcPath, dstPath, nil), checker.IsNil) // The symlink should not have been modified. c.Assert(symlinkTargetEquals(c, dstPath, "file1"), checker.IsNil) @@ -136,7 +136,7 @@ func (s *DockerSuite) TestCpFromSymlinkDestination(c *check.C) { // should copy the file into the symlink target directory. dstPath = cpPath(tmpDir, "symlinkToDir1") - c.Assert(runDockerCp(c, srcPath, dstPath), checker.IsNil) + c.Assert(runDockerCp(c, srcPath, dstPath, nil), checker.IsNil) // The symlink should not have been modified. c.Assert(symlinkTargetEquals(c, dstPath, "dir1"), checker.IsNil) @@ -149,7 +149,7 @@ func (s *DockerSuite) TestCpFromSymlinkDestination(c *check.C) { // the contents of the source file. dstPath = cpPath(tmpDir, "brokenSymlinkToFileX") - c.Assert(runDockerCp(c, srcPath, dstPath), checker.IsNil) + c.Assert(runDockerCp(c, srcPath, dstPath, nil), checker.IsNil) // The symlink should not have been modified. c.Assert(symlinkTargetEquals(c, dstPath, "fileX"), checker.IsNil) @@ -163,7 +163,7 @@ func (s *DockerSuite) TestCpFromSymlinkDestination(c *check.C) { srcPath = containerCpPath(containerID, "/dir2") dstPath = cpPath(tmpDir, "symlinkToDir1") - c.Assert(runDockerCp(c, srcPath, dstPath), checker.IsNil) + c.Assert(runDockerCp(c, srcPath, dstPath, nil), checker.IsNil) // The symlink should not have been modified. c.Assert(symlinkTargetEquals(c, dstPath, "dir1"), checker.IsNil) @@ -177,7 +177,7 @@ func (s *DockerSuite) TestCpFromSymlinkDestination(c *check.C) { // should not modify the symlink. dstPath = cpPath(tmpDir, "brokenSymlinkToDirX") - c.Assert(runDockerCp(c, srcPath, dstPath), checker.IsNil) + c.Assert(runDockerCp(c, srcPath, dstPath, nil), checker.IsNil) // The symlink should not have been modified. c.Assert(symlinkTargetEquals(c, dstPath, "dirX"), checker.IsNil) @@ -217,7 +217,7 @@ func (s *DockerSuite) TestCpFromCaseA(c *check.C) { srcPath := containerCpPath(containerID, "/root/file1") dstPath := cpPath(tmpDir, "itWorks.txt") - c.Assert(runDockerCp(c, srcPath, dstPath), checker.IsNil) + c.Assert(runDockerCp(c, srcPath, dstPath, nil), checker.IsNil) c.Assert(fileContentEquals(c, dstPath, "file1\n"), checker.IsNil) } @@ -235,7 +235,7 @@ func (s *DockerSuite) TestCpFromCaseB(c *check.C) { srcPath := containerCpPath(containerID, "/file1") dstDir := cpPathTrailingSep(tmpDir, "testDir") - err := runDockerCp(c, srcPath, dstDir) + err := runDockerCp(c, srcPath, dstDir, nil) c.Assert(err, checker.NotNil) c.Assert(isCpDirNotExist(err), checker.True, check.Commentf("expected DirNotExists error, but got %T: %s", err, err)) @@ -260,7 +260,7 @@ func (s *DockerSuite) TestCpFromCaseC(c *check.C) { // Ensure the local file starts with different content. c.Assert(fileContentEquals(c, dstPath, "file2\n"), checker.IsNil) - c.Assert(runDockerCp(c, srcPath, dstPath), checker.IsNil) + c.Assert(runDockerCp(c, srcPath, dstPath, nil), checker.IsNil) c.Assert(fileContentEquals(c, dstPath, "file1\n"), checker.IsNil) } @@ -285,7 +285,7 @@ func (s *DockerSuite) TestCpFromCaseD(c *check.C) { _, err := os.Stat(dstPath) c.Assert(os.IsNotExist(err), checker.True, check.Commentf("did not expect dstPath %q to exist", dstPath)) - c.Assert(runDockerCp(c, srcPath, dstDir), checker.IsNil) + c.Assert(runDockerCp(c, srcPath, dstDir, nil), checker.IsNil) c.Assert(fileContentEquals(c, dstPath, "file1\n"), checker.IsNil) @@ -299,7 +299,7 @@ func (s *DockerSuite) TestCpFromCaseD(c *check.C) { dstDir = cpPathTrailingSep(tmpDir, "dir1") - c.Assert(runDockerCp(c, srcPath, dstDir), checker.IsNil) + c.Assert(runDockerCp(c, srcPath, dstDir, nil), checker.IsNil) c.Assert(fileContentEquals(c, dstPath, "file1\n"), checker.IsNil) } @@ -319,7 +319,7 @@ func (s *DockerSuite) TestCpFromCaseE(c *check.C) { dstDir := cpPath(tmpDir, "testDir") dstPath := filepath.Join(dstDir, "file1-1") - c.Assert(runDockerCp(c, srcDir, dstDir), checker.IsNil) + c.Assert(runDockerCp(c, srcDir, dstDir, nil), checker.IsNil) c.Assert(fileContentEquals(c, dstPath, "file1-1\n"), checker.IsNil) @@ -330,7 +330,7 @@ func (s *DockerSuite) TestCpFromCaseE(c *check.C) { dstDir = cpPathTrailingSep(tmpDir, "testDir") - c.Assert(runDockerCp(c, srcDir, dstDir), checker.IsNil) + c.Assert(runDockerCp(c, srcDir, dstDir, nil), checker.IsNil) c.Assert(fileContentEquals(c, dstPath, "file1-1\n"), checker.IsNil) } @@ -351,7 +351,7 @@ func (s *DockerSuite) TestCpFromCaseF(c *check.C) { srcDir := containerCpPath(containerID, "/root/dir1") dstFile := cpPath(tmpDir, "file1") - err := runDockerCp(c, srcDir, dstFile) + err := runDockerCp(c, srcDir, dstFile, nil) c.Assert(err, checker.NotNil) c.Assert(isCpCannotCopyDir(err), checker.True, check.Commentf("expected ErrCannotCopyDir error, but got %T: %s", err, err)) @@ -376,7 +376,7 @@ func (s *DockerSuite) TestCpFromCaseG(c *check.C) { resultDir := filepath.Join(dstDir, "dir1") dstPath := filepath.Join(resultDir, "file1-1") - c.Assert(runDockerCp(c, srcDir, dstDir), checker.IsNil) + c.Assert(runDockerCp(c, srcDir, dstDir, nil), checker.IsNil) c.Assert(fileContentEquals(c, dstPath, "file1-1\n"), checker.IsNil) @@ -390,7 +390,7 @@ func (s *DockerSuite) TestCpFromCaseG(c *check.C) { dstDir = cpPathTrailingSep(tmpDir, "dir2") - c.Assert(runDockerCp(c, srcDir, dstDir), checker.IsNil) + c.Assert(runDockerCp(c, srcDir, dstDir, nil), checker.IsNil) c.Assert(fileContentEquals(c, dstPath, "file1-1\n"), checker.IsNil) } @@ -410,7 +410,7 @@ func (s *DockerSuite) TestCpFromCaseH(c *check.C) { dstDir := cpPath(tmpDir, "testDir") dstPath := filepath.Join(dstDir, "file1-1") - c.Assert(runDockerCp(c, srcDir, dstDir), checker.IsNil) + c.Assert(runDockerCp(c, srcDir, dstDir, nil), checker.IsNil) c.Assert(fileContentEquals(c, dstPath, "file1-1\n"), checker.IsNil) @@ -421,7 +421,7 @@ func (s *DockerSuite) TestCpFromCaseH(c *check.C) { dstDir = cpPathTrailingSep(tmpDir, "testDir") - c.Assert(runDockerCp(c, srcDir, dstDir), checker.IsNil) + c.Assert(runDockerCp(c, srcDir, dstDir, nil), checker.IsNil) c.Assert(fileContentEquals(c, dstPath, "file1-1\n"), checker.IsNil) } @@ -443,7 +443,7 @@ func (s *DockerSuite) TestCpFromCaseI(c *check.C) { srcDir := containerCpPathTrailingSep(containerID, "/root/dir1") + "." dstFile := cpPath(tmpDir, "file1") - err := runDockerCp(c, srcDir, dstFile) + err := runDockerCp(c, srcDir, dstFile, nil) c.Assert(err, checker.NotNil) c.Assert(isCpCannotCopyDir(err), checker.True, check.Commentf("expected ErrCannotCopyDir error, but got %T: %s", err, err)) @@ -468,7 +468,7 @@ func (s *DockerSuite) TestCpFromCaseJ(c *check.C) { dstDir := cpPath(tmpDir, "dir2") dstPath := filepath.Join(dstDir, "file1-1") - c.Assert(runDockerCp(c, srcDir, dstDir), checker.IsNil) + c.Assert(runDockerCp(c, srcDir, dstDir, nil), checker.IsNil) c.Assert(fileContentEquals(c, dstPath, "file1-1\n"), checker.IsNil) @@ -482,7 +482,7 @@ func (s *DockerSuite) TestCpFromCaseJ(c *check.C) { dstDir = cpPathTrailingSep(tmpDir, "dir2") - c.Assert(runDockerCp(c, srcDir, dstDir), checker.IsNil) + c.Assert(runDockerCp(c, srcDir, dstDir, nil), checker.IsNil) c.Assert(fileContentEquals(c, dstPath, "file1-1\n"), checker.IsNil) } diff --git a/integration-cli/docker_cli_cp_test.go b/integration-cli/docker_cli_cp_test.go index 80dc56289f..f7ed459194 100644 --- a/integration-cli/docker_cli_cp_test.go +++ b/integration-cli/docker_cli_cp_test.go @@ -28,7 +28,7 @@ const ( // Ensure that an all-local path case returns an error. func (s *DockerSuite) TestCpLocalOnly(c *check.C) { - err := runDockerCp(c, "foo", "bar") + err := runDockerCp(c, "foo", "bar", nil) c.Assert(err, checker.NotNil) c.Assert(err.Error(), checker.Contains, "must specify at least one container source") diff --git a/integration-cli/docker_cli_cp_to_container_test.go b/integration-cli/docker_cli_cp_to_container_test.go index 30e152dbe3..97e9aa1230 100644 --- a/integration-cli/docker_cli_cp_to_container_test.go +++ b/integration-cli/docker_cli_cp_to_container_test.go @@ -31,7 +31,7 @@ func (s *DockerSuite) TestCpToErrSrcNotExists(c *check.C) { srcPath := cpPath(tmpDir, "file1") dstPath := containerCpPath(containerID, "file1") - err := runDockerCp(c, srcPath, dstPath) + err := runDockerCp(c, srcPath, dstPath, nil) c.Assert(err, checker.NotNil) c.Assert(isCpNotExist(err), checker.True, check.Commentf("expected IsNotExist error, but got %T: %s", err, err)) @@ -50,7 +50,7 @@ func (s *DockerSuite) TestCpToErrSrcNotDir(c *check.C) { srcPath := cpPathTrailingSep(tmpDir, "file1") dstPath := containerCpPath(containerID, "testDir") - err := runDockerCp(c, srcPath, dstPath) + err := runDockerCp(c, srcPath, dstPath, nil) c.Assert(err, checker.NotNil) c.Assert(isCpNotDir(err), checker.True, check.Commentf("expected IsNotDir error, but got %T: %s", err, err)) @@ -71,7 +71,7 @@ func (s *DockerSuite) TestCpToErrDstParentNotExists(c *check.C) { srcPath := cpPath(tmpDir, "file1") dstPath := containerCpPath(containerID, "/notExists", "file1") - err := runDockerCp(c, srcPath, dstPath) + err := runDockerCp(c, srcPath, dstPath, nil) c.Assert(err, checker.NotNil) c.Assert(isCpNotExist(err), checker.True, check.Commentf("expected IsNotExist error, but got %T: %s", err, err)) @@ -79,7 +79,7 @@ func (s *DockerSuite) TestCpToErrDstParentNotExists(c *check.C) { // Try with a directory source. srcPath = cpPath(tmpDir, "dir1") - err = runDockerCp(c, srcPath, dstPath) + err = runDockerCp(c, srcPath, dstPath, nil) c.Assert(err, checker.NotNil) c.Assert(isCpNotExist(err), checker.True, check.Commentf("expected IsNotExist error, but got %T: %s", err, err)) @@ -104,7 +104,7 @@ func (s *DockerSuite) TestCpToErrDstNotDir(c *check.C) { // The client should encounter an error trying to stat the destination // and then be unable to copy since the destination is asserted to be a // directory but does not exist. - err := runDockerCp(c, srcPath, dstPath) + err := runDockerCp(c, srcPath, dstPath, nil) c.Assert(err, checker.NotNil) c.Assert(isCpDirNotExist(err), checker.True, check.Commentf("expected DirNotExist error, but got %T: %s", err, err)) @@ -116,7 +116,7 @@ func (s *DockerSuite) TestCpToErrDstNotDir(c *check.C) { // then decide to extract to the parent directory instead with a rebased // name in the source archive, but this directory would overwrite the // existing file with the same name. - err = runDockerCp(c, srcPath, dstPath) + err = runDockerCp(c, srcPath, dstPath, nil) c.Assert(err, checker.NotNil) c.Assert(isCannotOverwriteNonDirWithDir(err), checker.True, check.Commentf("expected CannotOverwriteNonDirWithDir error, but got %T: %s", err, err)) @@ -144,7 +144,7 @@ func (s *DockerSuite) TestCpToSymlinkDestination(c *check.C) { srcPath := cpPath(testVol, "file2") dstPath := containerCpPath(containerID, "/vol2/symlinkToFile1") - c.Assert(runDockerCp(c, srcPath, dstPath), checker.IsNil) + c.Assert(runDockerCp(c, srcPath, dstPath, nil), checker.IsNil) // The symlink should not have been modified. c.Assert(symlinkTargetEquals(c, cpPath(testVol, "symlinkToFile1"), "file1"), checker.IsNil) @@ -156,7 +156,7 @@ func (s *DockerSuite) TestCpToSymlinkDestination(c *check.C) { // This should copy the file into the symlink target directory. dstPath = containerCpPath(containerID, "/vol2/symlinkToDir1") - c.Assert(runDockerCp(c, srcPath, dstPath), checker.IsNil) + c.Assert(runDockerCp(c, srcPath, dstPath, nil), checker.IsNil) // The symlink should not have been modified. c.Assert(symlinkTargetEquals(c, cpPath(testVol, "symlinkToDir1"), "dir1"), checker.IsNil) @@ -169,7 +169,7 @@ func (s *DockerSuite) TestCpToSymlinkDestination(c *check.C) { // contents of the source file. dstPath = containerCpPath(containerID, "/vol2/brokenSymlinkToFileX") - c.Assert(runDockerCp(c, srcPath, dstPath), checker.IsNil) + c.Assert(runDockerCp(c, srcPath, dstPath, nil), checker.IsNil) // The symlink should not have been modified. c.Assert(symlinkTargetEquals(c, cpPath(testVol, "brokenSymlinkToFileX"), "fileX"), checker.IsNil) @@ -183,7 +183,7 @@ func (s *DockerSuite) TestCpToSymlinkDestination(c *check.C) { srcPath = cpPath(testVol, "/dir2") dstPath = containerCpPath(containerID, "/vol2/symlinkToDir1") - c.Assert(runDockerCp(c, srcPath, dstPath), checker.IsNil) + c.Assert(runDockerCp(c, srcPath, dstPath, nil), checker.IsNil) // The symlink should not have been modified. c.Assert(symlinkTargetEquals(c, cpPath(testVol, "symlinkToDir1"), "dir1"), checker.IsNil) @@ -197,7 +197,7 @@ func (s *DockerSuite) TestCpToSymlinkDestination(c *check.C) { // should not modify the symlink. dstPath = containerCpPath(containerID, "/vol2/brokenSymlinkToDirX") - c.Assert(runDockerCp(c, srcPath, dstPath), checker.IsNil) + c.Assert(runDockerCp(c, srcPath, dstPath, nil), checker.IsNil) // The symlink should not have been modified. c.Assert(symlinkTargetEquals(c, cpPath(testVol, "brokenSymlinkToDirX"), "dirX"), checker.IsNil) @@ -238,7 +238,7 @@ func (s *DockerSuite) TestCpToCaseA(c *check.C) { srcPath := cpPath(tmpDir, "file1") dstPath := containerCpPath(containerID, "/root/itWorks.txt") - c.Assert(runDockerCp(c, srcPath, dstPath), checker.IsNil) + c.Assert(runDockerCp(c, srcPath, dstPath, nil), checker.IsNil) c.Assert(containerStartOutputEquals(c, containerID, "file1\n"), checker.IsNil) } @@ -259,7 +259,7 @@ func (s *DockerSuite) TestCpToCaseB(c *check.C) { srcPath := cpPath(tmpDir, "file1") dstDir := containerCpPathTrailingSep(containerID, "testDir") - err := runDockerCp(c, srcPath, dstDir) + err := runDockerCp(c, srcPath, dstDir, nil) c.Assert(err, checker.NotNil) c.Assert(isCpDirNotExist(err), checker.True, check.Commentf("expected DirNotExists error, but got %T: %s", err, err)) @@ -285,7 +285,7 @@ func (s *DockerSuite) TestCpToCaseC(c *check.C) { // Ensure the container's file starts with the original content. c.Assert(containerStartOutputEquals(c, containerID, "file2\n"), checker.IsNil) - c.Assert(runDockerCp(c, srcPath, dstPath), checker.IsNil) + c.Assert(runDockerCp(c, srcPath, dstPath, nil), checker.IsNil) // Should now contain file1's contents. c.Assert(containerStartOutputEquals(c, containerID, "file1\n"), checker.IsNil) @@ -312,7 +312,7 @@ func (s *DockerSuite) TestCpToCaseD(c *check.C) { // Ensure that dstPath doesn't exist. c.Assert(containerStartOutputEquals(c, containerID, ""), checker.IsNil) - c.Assert(runDockerCp(c, srcPath, dstDir), checker.IsNil) + c.Assert(runDockerCp(c, srcPath, dstDir, nil), checker.IsNil) // Should now contain file1's contents. c.Assert(containerStartOutputEquals(c, containerID, "file1\n"), checker.IsNil) @@ -330,7 +330,7 @@ func (s *DockerSuite) TestCpToCaseD(c *check.C) { // Ensure that dstPath doesn't exist. c.Assert(containerStartOutputEquals(c, containerID, ""), checker.IsNil) - c.Assert(runDockerCp(c, srcPath, dstDir), checker.IsNil) + c.Assert(runDockerCp(c, srcPath, dstDir, nil), checker.IsNil) // Should now contain file1's contents. c.Assert(containerStartOutputEquals(c, containerID, "file1\n"), checker.IsNil) @@ -353,7 +353,7 @@ func (s *DockerSuite) TestCpToCaseE(c *check.C) { srcDir := cpPath(tmpDir, "dir1") dstDir := containerCpPath(containerID, "testDir") - c.Assert(runDockerCp(c, srcDir, dstDir), checker.IsNil) + c.Assert(runDockerCp(c, srcDir, dstDir, nil), checker.IsNil) // Should now contain file1-1's contents. c.Assert(containerStartOutputEquals(c, containerID, "file1-1\n"), checker.IsNil) @@ -367,7 +367,7 @@ func (s *DockerSuite) TestCpToCaseE(c *check.C) { dstDir = containerCpPathTrailingSep(containerID, "testDir") - c.Assert(runDockerCp(c, srcDir, dstDir), checker.IsNil) + c.Assert(runDockerCp(c, srcDir, dstDir, nil), checker.IsNil) // Should now contain file1-1's contents. c.Assert(containerStartOutputEquals(c, containerID, "file1-1\n"), checker.IsNil) @@ -389,7 +389,7 @@ func (s *DockerSuite) TestCpToCaseF(c *check.C) { srcDir := cpPath(tmpDir, "dir1") dstFile := containerCpPath(containerID, "/root/file1") - err := runDockerCp(c, srcDir, dstFile) + err := runDockerCp(c, srcDir, dstFile, nil) c.Assert(err, checker.NotNil) c.Assert(isCpCannotCopyDir(err), checker.True, check.Commentf("expected ErrCannotCopyDir error, but got %T: %s", err, err)) @@ -416,7 +416,7 @@ func (s *DockerSuite) TestCpToCaseG(c *check.C) { // Ensure that dstPath doesn't exist. c.Assert(containerStartOutputEquals(c, containerID, ""), checker.IsNil) - c.Assert(runDockerCp(c, srcDir, dstDir), checker.IsNil) + c.Assert(runDockerCp(c, srcDir, dstDir, nil), checker.IsNil) // Should now contain file1-1's contents. c.Assert(containerStartOutputEquals(c, containerID, "file1-1\n"), checker.IsNil) @@ -434,7 +434,7 @@ func (s *DockerSuite) TestCpToCaseG(c *check.C) { // Ensure that dstPath doesn't exist. c.Assert(containerStartOutputEquals(c, containerID, ""), checker.IsNil) - c.Assert(runDockerCp(c, srcDir, dstDir), checker.IsNil) + c.Assert(runDockerCp(c, srcDir, dstDir, nil), checker.IsNil) // Should now contain file1-1's contents. c.Assert(containerStartOutputEquals(c, containerID, "file1-1\n"), checker.IsNil) @@ -457,7 +457,7 @@ func (s *DockerSuite) TestCpToCaseH(c *check.C) { srcDir := cpPathTrailingSep(tmpDir, "dir1") + "." dstDir := containerCpPath(containerID, "testDir") - c.Assert(runDockerCp(c, srcDir, dstDir), checker.IsNil) + c.Assert(runDockerCp(c, srcDir, dstDir, nil), checker.IsNil) // Should now contain file1-1's contents. c.Assert(containerStartOutputEquals(c, containerID, "file1-1\n"), checker.IsNil) @@ -471,7 +471,7 @@ func (s *DockerSuite) TestCpToCaseH(c *check.C) { dstDir = containerCpPathTrailingSep(containerID, "testDir") - c.Assert(runDockerCp(c, srcDir, dstDir), checker.IsNil) + c.Assert(runDockerCp(c, srcDir, dstDir, nil), checker.IsNil) // Should now contain file1-1's contents. c.Assert(containerStartOutputEquals(c, containerID, "file1-1\n"), checker.IsNil) @@ -494,7 +494,7 @@ func (s *DockerSuite) TestCpToCaseI(c *check.C) { srcDir := cpPathTrailingSep(tmpDir, "dir1") + "." dstFile := containerCpPath(containerID, "/root/file1") - err := runDockerCp(c, srcDir, dstFile) + err := runDockerCp(c, srcDir, dstFile, nil) c.Assert(err, checker.NotNil) c.Assert(isCpCannotCopyDir(err), checker.True, check.Commentf("expected ErrCannotCopyDir error, but got %T: %s", err, err)) @@ -522,7 +522,7 @@ func (s *DockerSuite) TestCpToCaseJ(c *check.C) { // Ensure that dstPath doesn't exist. c.Assert(containerStartOutputEquals(c, containerID, ""), checker.IsNil) - c.Assert(runDockerCp(c, srcDir, dstDir), checker.IsNil) + c.Assert(runDockerCp(c, srcDir, dstDir, nil), checker.IsNil) // Should now contain file1-1's contents. c.Assert(containerStartOutputEquals(c, containerID, "file1-1\n"), checker.IsNil) @@ -539,7 +539,7 @@ func (s *DockerSuite) TestCpToCaseJ(c *check.C) { // Ensure that dstPath doesn't exist. c.Assert(containerStartOutputEquals(c, containerID, ""), checker.IsNil) - c.Assert(runDockerCp(c, srcDir, dstDir), checker.IsNil) + c.Assert(runDockerCp(c, srcDir, dstDir, nil), checker.IsNil) // Should now contain file1-1's contents. c.Assert(containerStartOutputEquals(c, containerID, "file1-1\n"), checker.IsNil) @@ -563,7 +563,7 @@ func (s *DockerSuite) TestCpToErrReadOnlyRootfs(c *check.C) { srcPath := cpPath(tmpDir, "file1") dstPath := containerCpPath(containerID, "/root/shouldNotExist") - err := runDockerCp(c, srcPath, dstPath) + err := runDockerCp(c, srcPath, dstPath, nil) c.Assert(err, checker.NotNil) c.Assert(isCpCannotCopyReadOnly(err), checker.True, check.Commentf("expected ErrContainerRootfsReadonly error, but got %T: %s", err, err)) @@ -590,7 +590,7 @@ func (s *DockerSuite) TestCpToErrReadOnlyVolume(c *check.C) { srcPath := cpPath(tmpDir, "file1") dstPath := containerCpPath(containerID, "/vol_ro/shouldNotExist") - err := runDockerCp(c, srcPath, dstPath) + err := runDockerCp(c, srcPath, dstPath, nil) c.Assert(err, checker.NotNil) c.Assert(isCpCannotCopyReadOnly(err), checker.True, check.Commentf("expected ErrVolumeReadonly error, but got %T: %s", err, err)) diff --git a/integration-cli/docker_cli_cp_to_container_unix_test.go b/integration-cli/docker_cli_cp_to_container_unix_test.go index e369d80e18..fa55b6ee24 100644 --- a/integration-cli/docker_cli_cp_to_container_unix_test.go +++ b/integration-cli/docker_cli_cp_to_container_unix_test.go @@ -14,6 +14,29 @@ import ( "github.com/go-check/check" ) +func (s *DockerSuite) TestCpToContainerWithPermissions(c *check.C) { + testRequires(c, SameHostDaemon, DaemonIsLinux) + + tmpDir := getTestDir(c, "test-cp-to-host-with-permissions") + defer os.RemoveAll(tmpDir) + + makeTestContentInDir(c, tmpDir) + + containerName := "permtest" + + _, exc := dockerCmd(c, "create", "--name", containerName, "debian:jessie", "/bin/bash", "-c", "stat -c '%u %g %a' /permdirtest /permdirtest/permtest") + c.Assert(exc, checker.Equals, 0) + defer dockerCmd(c, "rm", "-f", containerName) + + srcPath := cpPath(tmpDir, "permdirtest") + dstPath := containerCpPath(containerName, "/") + c.Assert(runDockerCp(c, srcPath, dstPath, []string{"-a"}), checker.IsNil) + + out, err := startContainerGetOutput(c, containerName) + c.Assert(err, checker.IsNil, check.Commentf("output: %v", out)) + c.Assert(strings.TrimSpace(out), checker.Equals, "2 2 700\n65534 65534 400", check.Commentf("output: %v", out)) +} + // Check ownership is root, both in non-userns and userns enabled modes func (s *DockerSuite) TestCpCheckDestOwnership(c *check.C) { testRequires(c, DaemonIsLinux, SameHostDaemon) @@ -29,7 +52,7 @@ func (s *DockerSuite) TestCpCheckDestOwnership(c *check.C) { srcPath := cpPath(tmpDir, "file1") dstPath := containerCpPath(containerID, "/tmpvol", "file1") - err := runDockerCp(c, srcPath, dstPath) + err := runDockerCp(c, srcPath, dstPath, nil) c.Assert(err, checker.IsNil) stat, err := system.Stat(filepath.Join(tmpVolDir, "file1")) diff --git a/integration-cli/docker_cli_cp_utils.go b/integration-cli/docker_cli_cp_utils.go index 3c51ece1e1..48aff90617 100644 --- a/integration-cli/docker_cli_cp_utils.go +++ b/integration-cli/docker_cli_cp_utils.go @@ -7,6 +7,7 @@ import ( "os" "os/exec" "path/filepath" + "runtime" "strings" "github.com/docker/docker/integration-cli/checker" @@ -26,6 +27,9 @@ type fileData struct { filetype fileType path string contents string + uid int + gid int + mode int } func (fd fileData) creationCommand() string { @@ -55,31 +59,33 @@ func mkFilesCommand(fds []fileData) string { } var defaultFileData = []fileData{ - {ftRegular, "file1", "file1"}, - {ftRegular, "file2", "file2"}, - {ftRegular, "file3", "file3"}, - {ftRegular, "file4", "file4"}, - {ftRegular, "file5", "file5"}, - {ftRegular, "file6", "file6"}, - {ftRegular, "file7", "file7"}, - {ftDir, "dir1", ""}, - {ftRegular, "dir1/file1-1", "file1-1"}, - {ftRegular, "dir1/file1-2", "file1-2"}, - {ftDir, "dir2", ""}, - {ftRegular, "dir2/file2-1", "file2-1"}, - {ftRegular, "dir2/file2-2", "file2-2"}, - {ftDir, "dir3", ""}, - {ftRegular, "dir3/file3-1", "file3-1"}, - {ftRegular, "dir3/file3-2", "file3-2"}, - {ftDir, "dir4", ""}, - {ftRegular, "dir4/file3-1", "file4-1"}, - {ftRegular, "dir4/file3-2", "file4-2"}, - {ftDir, "dir5", ""}, - {ftSymlink, "symlinkToFile1", "file1"}, - {ftSymlink, "symlinkToDir1", "dir1"}, - {ftSymlink, "brokenSymlinkToFileX", "fileX"}, - {ftSymlink, "brokenSymlinkToDirX", "dirX"}, - {ftSymlink, "symlinkToAbsDir", "/root"}, + {ftRegular, "file1", "file1", 0, 0, 0666}, + {ftRegular, "file2", "file2", 0, 0, 0666}, + {ftRegular, "file3", "file3", 0, 0, 0666}, + {ftRegular, "file4", "file4", 0, 0, 0666}, + {ftRegular, "file5", "file5", 0, 0, 0666}, + {ftRegular, "file6", "file6", 0, 0, 0666}, + {ftRegular, "file7", "file7", 0, 0, 0666}, + {ftDir, "dir1", "", 0, 0, 0777}, + {ftRegular, "dir1/file1-1", "file1-1", 0, 0, 0666}, + {ftRegular, "dir1/file1-2", "file1-2", 0, 0, 0666}, + {ftDir, "dir2", "", 0, 0, 0666}, + {ftRegular, "dir2/file2-1", "file2-1", 0, 0, 0666}, + {ftRegular, "dir2/file2-2", "file2-2", 0, 0, 0666}, + {ftDir, "dir3", "", 0, 0, 0666}, + {ftRegular, "dir3/file3-1", "file3-1", 0, 0, 0666}, + {ftRegular, "dir3/file3-2", "file3-2", 0, 0, 0666}, + {ftDir, "dir4", "", 0, 0, 0666}, + {ftRegular, "dir4/file3-1", "file4-1", 0, 0, 0666}, + {ftRegular, "dir4/file3-2", "file4-2", 0, 0, 0666}, + {ftDir, "dir5", "", 0, 0, 0666}, + {ftSymlink, "symlinkToFile1", "file1", 0, 0, 0666}, + {ftSymlink, "symlinkToDir1", "dir1", 0, 0, 0666}, + {ftSymlink, "brokenSymlinkToFileX", "fileX", 0, 0, 0666}, + {ftSymlink, "brokenSymlinkToDirX", "dirX", 0, 0, 0666}, + {ftSymlink, "symlinkToAbsDir", "/root", 0, 0, 0666}, + {ftDir, "permdirtest", "", 2, 2, 0700}, + {ftRegular, "permdirtest/permtest", "perm_test", 65534, 65534, 0400}, } func defaultMkContentCommand() string { @@ -91,12 +97,16 @@ func makeTestContentInDir(c *check.C, dir string) { path := filepath.Join(dir, filepath.FromSlash(fd.path)) switch fd.filetype { case ftRegular: - c.Assert(ioutil.WriteFile(path, []byte(fd.contents+"\n"), os.FileMode(0666)), checker.IsNil) + c.Assert(ioutil.WriteFile(path, []byte(fd.contents+"\n"), os.FileMode(fd.mode)), checker.IsNil) case ftDir: - c.Assert(os.Mkdir(path, os.FileMode(0777)), checker.IsNil) + c.Assert(os.Mkdir(path, os.FileMode(fd.mode)), checker.IsNil) case ftSymlink: c.Assert(os.Symlink(fd.contents, path), checker.IsNil) } + + if fd.filetype != ftSymlink && runtime.GOOS != "windows" { + c.Assert(os.Chown(path, fd.uid, fd.gid), checker.IsNil) + } } } @@ -178,10 +188,16 @@ func containerCpPathTrailingSep(containerID string, pathElements ...string) stri return fmt.Sprintf("%s/", containerCpPath(containerID, pathElements...)) } -func runDockerCp(c *check.C, src, dst string) (err error) { - c.Logf("running `docker cp %s %s`", src, dst) +func runDockerCp(c *check.C, src, dst string, params []string) (err error) { + c.Logf("running `docker cp %s %s %s`", strings.Join(params, " "), src, dst) - args := []string{"cp", src, dst} + args := []string{"cp"} + + for _, param := range params { + args = append(args, param) + } + + args = append(args, src, dst) out, _, err := runCommandWithOutput(exec.Command(dockerBinary, args...)) if err != nil {