Переглянути джерело

Merge pull request #28923 from erikh/fix-copy

Fix copy API (`docker cp`, etc) uid/gid handling
Anusha Ragunathan 8 роки тому
батько
коміт
d40a17ffc2

+ 1 - 1
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)
 }
 

+ 3 - 1
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)
 }

+ 1 - 0
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.

+ 5 - 2
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)

+ 4 - 0
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)

+ 14 - 9
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
 	}

+ 16 - 0
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,
+	}
+}

+ 28 - 0
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
+}

+ 12 - 0
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
+}

+ 26 - 26
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)
 }

+ 1 - 1
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")

+ 28 - 28
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))

+ 24 - 1
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"))

+ 46 - 30
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"}
+
+	for _, param := range params {
+		args = append(args, param)
+	}
 
-	args := []string{"cp", src, dst}
+	args = append(args, src, dst)
 
 	out, _, err := runCommandWithOutput(exec.Command(dockerBinary, args...))
 	if err != nil {