From 6fe03b25abded53b67a7707abbcd9708042d9151 Mon Sep 17 00:00:00 2001 From: Mehul Arora Date: Sat, 19 Jun 2021 11:27:24 +0530 Subject: support container to container copy Implement container to container copy. Previously data could only be copied from/to the host. Fixes: #7370 Co-authored-by: Mehul Arora Signed-off-by: Valentin Rothberg --- cmd/podman/containers/cp.go | 108 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 107 insertions(+), 1 deletion(-) (limited to 'cmd') diff --git a/cmd/podman/containers/cp.go b/cmd/podman/containers/cp.go index c1f1e27f5..67bb13aa1 100644 --- a/cmd/podman/containers/cp.go +++ b/cmd/podman/containers/cp.go @@ -82,7 +82,9 @@ func cp(cmd *cobra.Command, args []string) error { return err } - if len(sourceContainerStr) > 0 { + if len(sourceContainerStr) > 0 && len(destContainerStr) > 0 { + return copyContainerToContainer(sourceContainerStr, sourcePath, destContainerStr, destPath) + } else if len(sourceContainerStr) > 0 { return copyFromContainer(sourceContainerStr, sourcePath, destPath) } @@ -115,6 +117,110 @@ func doCopy(funcA func() error, funcB func() error) error { return errorhandling.JoinErrors(copyErrors) } +func copyContainerToContainer(sourceContainer string, sourcePath string, destContainer string, destPath string) error { + if err := containerMustExist(sourceContainer); err != nil { + return err + } + + if err := containerMustExist(destContainer); err != nil { + return err + } + + sourceContainerInfo, err := registry.ContainerEngine().ContainerStat(registry.GetContext(), sourceContainer, sourcePath) + if err != nil { + return errors.Wrapf(err, "%q could not be found on container %s", sourcePath, sourceContainer) + } + + var destContainerBaseName string + destContainerInfo, destContainerInfoErr := registry.ContainerEngine().ContainerStat(registry.GetContext(), destContainer, destPath) + if destContainerInfoErr != nil { + if strings.HasSuffix(destPath, "/") { + return errors.Wrapf(destContainerInfoErr, "%q could not be found on container %s", destPath, destContainer) + } + // NOTE: containerInfo may actually be set. That happens when + // the container path is a symlink into nirvana. In that case, + // we must use the symlinked path instead. + path := destPath + if destContainerInfo != nil { + destContainerBaseName = filepath.Base(destContainerInfo.LinkTarget) + path = destContainerInfo.LinkTarget + } else { + destContainerBaseName = filepath.Base(destPath) + } + + parentDir, err := containerParentDir(destContainer, path) + if err != nil { + return errors.Wrapf(err, "could not determine parent dir of %q on container %s", path, destContainer) + } + destContainerInfo, err = registry.ContainerEngine().ContainerStat(registry.GetContext(), destContainer, parentDir) + if err != nil { + return errors.Wrapf(err, "%q could not be found on container %s", destPath, destContainer) + } + } else { + // If the specified path exists on the container, we must use + // its base path as it may have changed due to symlink + // evaluations. + destContainerBaseName = filepath.Base(destContainerInfo.LinkTarget) + } + + if sourceContainerInfo.IsDir && !destContainerInfo.IsDir { + return errors.New("destination must be a directory when copying a directory") + } + + sourceContainerTarget, destContainerTarget := sourceContainerInfo.LinkTarget, destContainerInfo.LinkTarget + if !destContainerInfo.IsDir { + destContainerTarget = filepath.Dir(destPath) + } + + // If we copy a directory via the "." notation and the container path + // does not exist, we need to make sure that the destination on the + // container gets created; otherwise the contents of the source + // directory will be written to the destination's parent directory. + // + // Hence, whenever "." is the source and the destination does not + // exist, we copy the source's parent and let the copier package create + // the destination via the Rename option. + if destContainerInfoErr != nil && sourceContainerInfo.IsDir && strings.HasSuffix(sourcePath, ".") { + sourceContainerTarget = filepath.Dir(sourceContainerTarget) + } + + reader, writer := io.Pipe() + + sourceContainerCopy := func() error { + defer writer.Close() + copyFunc, err := registry.ContainerEngine().ContainerCopyToArchive(registry.GetContext(), sourceContainer, sourceContainerTarget, writer) + if err != nil { + return err + } + if err := copyFunc(); err != nil { + return errors.Wrap(err, "error copying from container") + } + return nil + } + + destContainerCopy := func() error { + defer reader.Close() + + copyOptions := entities.CopyOptions{Chown: chown} + if (!sourceContainerInfo.IsDir && !destContainerInfo.IsDir) || destContainerInfoErr != nil { + // If we're having a file-to-file copy, make sure to + // rename accordingly. + copyOptions.Rename = map[string]string{filepath.Base(sourceContainerTarget): destContainerBaseName} + } + + copyFunc, err := registry.ContainerEngine().ContainerCopyFromArchive(registry.GetContext(), destContainer, destContainerTarget, reader, copyOptions) + if err != nil { + return err + } + if err := copyFunc(); err != nil { + return errors.Wrap(err, "error copying to container") + } + return nil + } + + return doCopy(sourceContainerCopy, destContainerCopy) +} + // copyFromContainer copies from the containerPath on the container to hostPath. func copyFromContainer(container string, containerPath string, hostPath string) error { if err := containerMustExist(container); err != nil { -- cgit v1.2.3-54-g00ecf