github.com/containers/libpod@v1.9.4-0.20220419124438-4284fd425507/cmd/podman/cp.go (about)

     1  package main
     2  
     3  import (
     4  	"archive/tar"
     5  	"fmt"
     6  	"io"
     7  	"os"
     8  	"path/filepath"
     9  	"strings"
    10  
    11  	"github.com/containers/buildah/pkg/chrootuser"
    12  	"github.com/containers/buildah/util"
    13  	"github.com/containers/libpod/cmd/podman/cliconfig"
    14  	"github.com/containers/libpod/cmd/podman/libpodruntime"
    15  	"github.com/containers/libpod/libpod"
    16  	"github.com/containers/libpod/libpod/define"
    17  	"github.com/containers/libpod/pkg/cgroups"
    18  	"github.com/containers/libpod/pkg/rootless"
    19  	"github.com/containers/storage"
    20  	"github.com/containers/storage/pkg/archive"
    21  	"github.com/containers/storage/pkg/chrootarchive"
    22  	"github.com/containers/storage/pkg/idtools"
    23  	securejoin "github.com/cyphar/filepath-securejoin"
    24  	"github.com/opencontainers/go-digest"
    25  	"github.com/opencontainers/runtime-spec/specs-go"
    26  	"github.com/pkg/errors"
    27  	"github.com/sirupsen/logrus"
    28  	"github.com/spf13/cobra"
    29  )
    30  
    31  var (
    32  	cpCommand cliconfig.CpValues
    33  
    34  	cpDescription = `Command copies the contents of SRC_PATH to the DEST_PATH.
    35  
    36    You can copy from the container's file system to the local machine or the reverse, from the local filesystem to the container. If "-" is specified for either the SRC_PATH or DEST_PATH, you can also stream a tar archive from STDIN or to STDOUT. The CONTAINER can be a running or stopped container.  The SRC_PATH or DEST_PATH can be a file or directory.
    37  `
    38  	_cpCommand = &cobra.Command{
    39  		Use:   "cp [flags] SRC_PATH DEST_PATH",
    40  		Short: "Copy files/folders between a container and the local filesystem",
    41  		Long:  cpDescription,
    42  		RunE: func(cmd *cobra.Command, args []string) error {
    43  			cpCommand.InputArgs = args
    44  			cpCommand.GlobalFlags = MainGlobalOpts
    45  			cpCommand.Remote = remoteclient
    46  			return cpCmd(&cpCommand)
    47  		},
    48  		Example: "[CONTAINER:]SRC_PATH [CONTAINER:]DEST_PATH",
    49  	}
    50  )
    51  
    52  func init() {
    53  	cpCommand.Command = _cpCommand
    54  	flags := cpCommand.Flags()
    55  	flags.BoolVar(&cpCommand.Extract, "extract", false, "Extract the tar file into the destination directory.")
    56  	flags.BoolVar(&cpCommand.Pause, "pause", copyPause(), "Pause the container while copying")
    57  	cpCommand.SetHelpTemplate(HelpTemplate())
    58  	cpCommand.SetUsageTemplate(UsageTemplate())
    59  }
    60  
    61  func cpCmd(c *cliconfig.CpValues) error {
    62  	args := c.InputArgs
    63  	if len(args) != 2 {
    64  		return errors.Errorf("you must provide a source path and a destination path")
    65  	}
    66  
    67  	runtime, err := libpodruntime.GetRuntime(getContext(), &c.PodmanCommand)
    68  	if err != nil {
    69  		return errors.Wrapf(err, "could not get runtime")
    70  	}
    71  	defer runtime.DeferredShutdown(false)
    72  
    73  	return copyBetweenHostAndContainer(runtime, args[0], args[1], c.Extract, c.Pause)
    74  }
    75  
    76  func copyBetweenHostAndContainer(runtime *libpod.Runtime, src string, dest string, extract bool, pause bool) error {
    77  
    78  	srcCtr, srcPath := parsePath(runtime, src)
    79  	destCtr, destPath := parsePath(runtime, dest)
    80  
    81  	if (srcCtr == nil && destCtr == nil) || (srcCtr != nil && destCtr != nil) {
    82  		return errors.Errorf("invalid arguments %s, %s you must use just one container", src, dest)
    83  	}
    84  
    85  	if len(srcPath) == 0 || len(destPath) == 0 {
    86  		return errors.Errorf("invalid arguments %s, %s you must specify paths", src, dest)
    87  	}
    88  	ctr := srcCtr
    89  	isFromHostToCtr := ctr == nil
    90  	if isFromHostToCtr {
    91  		ctr = destCtr
    92  	}
    93  
    94  	mountPoint, err := ctr.Mount()
    95  	if err != nil {
    96  		return err
    97  	}
    98  	defer func() {
    99  		if err := ctr.Unmount(false); err != nil {
   100  			logrus.Errorf("unable to umount container '%s': %q", ctr.ID(), err)
   101  		}
   102  	}()
   103  
   104  	if pause {
   105  		if err := ctr.Pause(); err != nil {
   106  			// An invalid state error is fine.
   107  			// The container isn't running or is already paused.
   108  			// TODO: We can potentially start the container while
   109  			// the copy is running, which still allows a race where
   110  			// malicious code could mess with the symlink.
   111  			if errors.Cause(err) != define.ErrCtrStateInvalid {
   112  				return err
   113  			}
   114  		} else {
   115  			// Only add the defer if we actually paused
   116  			defer func() {
   117  				if err := ctr.Unpause(); err != nil {
   118  					logrus.Errorf("Error unpausing container after copying: %v", err)
   119  				}
   120  			}()
   121  		}
   122  	}
   123  
   124  	user, err := getUser(mountPoint, ctr.User())
   125  	if err != nil {
   126  		return err
   127  	}
   128  	idMappingOpts, err := ctr.IDMappings()
   129  	if err != nil {
   130  		return errors.Wrapf(err, "error getting IDMappingOptions")
   131  	}
   132  	destOwner := idtools.IDPair{UID: int(user.UID), GID: int(user.GID)}
   133  	hostUID, hostGID, err := util.GetHostIDs(convertIDMap(idMappingOpts.UIDMap), convertIDMap(idMappingOpts.GIDMap), user.UID, user.GID)
   134  	if err != nil {
   135  		return err
   136  	}
   137  
   138  	hostOwner := idtools.IDPair{UID: int(hostUID), GID: int(hostGID)}
   139  
   140  	if isFromHostToCtr {
   141  		if isVol, volDestName, volName := isVolumeDestName(destPath, ctr); isVol { //nolint(gocritic)
   142  			path, err := pathWithVolumeMount(ctr, runtime, volDestName, volName, destPath)
   143  			if err != nil {
   144  				return errors.Wrapf(err, "error getting destination path from volume %s", volDestName)
   145  			}
   146  			destPath = path
   147  		} else if isBindMount, mount := isBindMountDestName(destPath, ctr); isBindMount { //nolint(gocritic)
   148  			path, err := pathWithBindMountSource(mount, destPath)
   149  			if err != nil {
   150  				return errors.Wrapf(err, "error getting destination path from bind mount %s", mount.Destination)
   151  			}
   152  			destPath = path
   153  		} else if filepath.IsAbs(destPath) { //nolint(gocritic)
   154  			cleanedPath, err := securejoin.SecureJoin(mountPoint, destPath)
   155  			if err != nil {
   156  				return err
   157  			}
   158  			destPath = cleanedPath
   159  		} else { //nolint(gocritic)
   160  			ctrWorkDir, err := securejoin.SecureJoin(mountPoint, ctr.WorkingDir())
   161  			if err != nil {
   162  				return err
   163  			}
   164  			if err = idtools.MkdirAllAndChownNew(ctrWorkDir, 0755, hostOwner); err != nil {
   165  				return errors.Wrapf(err, "error creating directory %q", destPath)
   166  			}
   167  			cleanedPath, err := securejoin.SecureJoin(mountPoint, filepath.Join(ctr.WorkingDir(), destPath))
   168  			if err != nil {
   169  				return err
   170  			}
   171  			destPath = cleanedPath
   172  		}
   173  	} else {
   174  		destOwner = idtools.IDPair{UID: os.Getuid(), GID: os.Getgid()}
   175  		if isVol, volDestName, volName := isVolumeDestName(srcPath, ctr); isVol { //nolint(gocritic)
   176  			path, err := pathWithVolumeMount(ctr, runtime, volDestName, volName, srcPath)
   177  			if err != nil {
   178  				return errors.Wrapf(err, "error getting source path from volume %s", volDestName)
   179  			}
   180  			srcPath = path
   181  		} else if isBindMount, mount := isBindMountDestName(srcPath, ctr); isBindMount { //nolint(gocritic)
   182  			path, err := pathWithBindMountSource(mount, srcPath)
   183  			if err != nil {
   184  				return errors.Wrapf(err, "error getting source path from bind mount %s", mount.Destination)
   185  			}
   186  			srcPath = path
   187  		} else if filepath.IsAbs(srcPath) { //nolint(gocritic)
   188  			cleanedPath, err := securejoin.SecureJoin(mountPoint, srcPath)
   189  			if err != nil {
   190  				return err
   191  			}
   192  			srcPath = cleanedPath
   193  		} else { //nolint(gocritic)
   194  			cleanedPath, err := securejoin.SecureJoin(mountPoint, filepath.Join(ctr.WorkingDir(), srcPath))
   195  			if err != nil {
   196  				return err
   197  			}
   198  			srcPath = cleanedPath
   199  		}
   200  	}
   201  
   202  	if !filepath.IsAbs(destPath) {
   203  		dir, err := os.Getwd()
   204  		if err != nil {
   205  			return errors.Wrapf(err, "err getting current working directory")
   206  		}
   207  		destPath = filepath.Join(dir, destPath)
   208  	}
   209  
   210  	if src == "-" {
   211  		srcPath = os.Stdin.Name()
   212  		extract = true
   213  	}
   214  	return copy(srcPath, destPath, src, dest, idMappingOpts, &destOwner, extract, isFromHostToCtr)
   215  }
   216  
   217  func getUser(mountPoint string, userspec string) (specs.User, error) {
   218  	uid, gid, _, err := chrootuser.GetUser(mountPoint, userspec)
   219  	u := specs.User{
   220  		UID:      uid,
   221  		GID:      gid,
   222  		Username: userspec,
   223  	}
   224  	if !strings.Contains(userspec, ":") {
   225  		groups, err2 := chrootuser.GetAdditionalGroupsForUser(mountPoint, uint64(u.UID))
   226  		if err2 != nil {
   227  			if errors.Cause(err2) != chrootuser.ErrNoSuchUser && err == nil {
   228  				err = err2
   229  			}
   230  		} else {
   231  			u.AdditionalGids = groups
   232  		}
   233  
   234  	}
   235  	return u, err
   236  }
   237  
   238  func parsePath(runtime *libpod.Runtime, path string) (*libpod.Container, string) {
   239  	pathArr := strings.SplitN(path, ":", 2)
   240  	if len(pathArr) == 2 {
   241  		ctr, err := runtime.LookupContainer(pathArr[0])
   242  		if err == nil {
   243  			return ctr, pathArr[1]
   244  		}
   245  	}
   246  	return nil, path
   247  }
   248  
   249  func evalSymlinks(path string) (string, error) {
   250  	if path == os.Stdin.Name() {
   251  		return path, nil
   252  	}
   253  	return filepath.EvalSymlinks(path)
   254  }
   255  
   256  func getPathInfo(path string) (string, os.FileInfo, error) {
   257  	path, err := evalSymlinks(path)
   258  	if err != nil {
   259  		return "", nil, errors.Wrapf(err, "error evaluating symlinks %q", path)
   260  	}
   261  	srcfi, err := os.Stat(path)
   262  	if err != nil {
   263  		return "", nil, errors.Wrapf(err, "error reading path %q", path)
   264  	}
   265  	return path, srcfi, nil
   266  }
   267  
   268  func copy(srcPath, destPath, src, dest string, idMappingOpts storage.IDMappingOptions, chownOpts *idtools.IDPair, extract, isFromHostToCtr bool) error {
   269  	srcPath, err := evalSymlinks(srcPath)
   270  	if err != nil {
   271  		return errors.Wrapf(err, "error evaluating symlinks %q", srcPath)
   272  	}
   273  
   274  	srcPath, srcfi, err := getPathInfo(srcPath)
   275  	if err != nil {
   276  		return err
   277  	}
   278  
   279  	filename := filepath.Base(destPath)
   280  	if filename == "-" && !isFromHostToCtr {
   281  		err := streamFileToStdout(srcPath, srcfi)
   282  		if err != nil {
   283  			return errors.Wrapf(err, "error streaming source file %s to Stdout", srcPath)
   284  		}
   285  		return nil
   286  	}
   287  
   288  	destdir := destPath
   289  	if !srcfi.IsDir() {
   290  		destdir = filepath.Dir(destPath)
   291  	}
   292  	_, err = os.Stat(destdir)
   293  	if err != nil && !os.IsNotExist(err) {
   294  		return errors.Wrapf(err, "error checking directory %q", destdir)
   295  	}
   296  	destDirIsExist := err == nil
   297  	if err = os.MkdirAll(destdir, 0755); err != nil {
   298  		return errors.Wrapf(err, "error creating directory %q", destdir)
   299  	}
   300  
   301  	// return functions for copying items
   302  	copyFileWithTar := chrootarchive.CopyFileWithTarAndChown(chownOpts, digest.Canonical.Digester().Hash(), idMappingOpts.UIDMap, idMappingOpts.GIDMap)
   303  	copyWithTar := chrootarchive.CopyWithTarAndChown(chownOpts, digest.Canonical.Digester().Hash(), idMappingOpts.UIDMap, idMappingOpts.GIDMap)
   304  	untarPath := chrootarchive.UntarPathAndChown(chownOpts, digest.Canonical.Digester().Hash(), idMappingOpts.UIDMap, idMappingOpts.GIDMap)
   305  
   306  	if srcfi.IsDir() {
   307  		logrus.Debugf("copying %q to %q", srcPath+string(os.PathSeparator)+"*", dest+string(os.PathSeparator)+"*")
   308  		if destDirIsExist && !strings.HasSuffix(src, fmt.Sprintf("%s.", string(os.PathSeparator))) {
   309  			destPath = filepath.Join(destPath, filepath.Base(srcPath))
   310  		}
   311  		if err = copyWithTar(srcPath, destPath); err != nil {
   312  			return errors.Wrapf(err, "error copying %q to %q", srcPath, dest)
   313  		}
   314  		return nil
   315  	}
   316  
   317  	if extract {
   318  		// We're extracting an archive into the destination directory.
   319  		logrus.Debugf("extracting contents of %q into %q", srcPath, destPath)
   320  		if err = untarPath(srcPath, destPath); err != nil {
   321  			return errors.Wrapf(err, "error extracting %q into %q", srcPath, destPath)
   322  		}
   323  		return nil
   324  	}
   325  
   326  	destfi, err := os.Stat(destPath)
   327  	if err != nil {
   328  		if !os.IsNotExist(err) || strings.HasSuffix(dest, string(os.PathSeparator)) {
   329  			return errors.Wrapf(err, "failed to get stat of dest path %s", destPath)
   330  		}
   331  	}
   332  	if destfi != nil && destfi.IsDir() {
   333  		destPath = filepath.Join(destPath, filepath.Base(srcPath))
   334  	}
   335  
   336  	// Copy the file, preserving attributes.
   337  	logrus.Debugf("copying %q to %q", srcPath, destPath)
   338  	if err = copyFileWithTar(srcPath, destPath); err != nil {
   339  		return errors.Wrapf(err, "error copying %q to %q", srcPath, destPath)
   340  	}
   341  	return nil
   342  }
   343  
   344  func convertIDMap(idMaps []idtools.IDMap) (convertedIDMap []specs.LinuxIDMapping) {
   345  	for _, idmap := range idMaps {
   346  		tempIDMap := specs.LinuxIDMapping{
   347  			ContainerID: uint32(idmap.ContainerID),
   348  			HostID:      uint32(idmap.HostID),
   349  			Size:        uint32(idmap.Size),
   350  		}
   351  		convertedIDMap = append(convertedIDMap, tempIDMap)
   352  	}
   353  	return convertedIDMap
   354  }
   355  
   356  func streamFileToStdout(srcPath string, srcfi os.FileInfo) error {
   357  	if srcfi.IsDir() {
   358  		tw := tar.NewWriter(os.Stdout)
   359  		err := filepath.Walk(srcPath, func(path string, info os.FileInfo, err error) error {
   360  			if err != nil || !info.Mode().IsRegular() || path == srcPath {
   361  				return err
   362  			}
   363  			hdr, err := tar.FileInfoHeader(info, "")
   364  			if err != nil {
   365  				return err
   366  			}
   367  
   368  			if err = tw.WriteHeader(hdr); err != nil {
   369  				return err
   370  			}
   371  			fh, err := os.Open(path)
   372  			if err != nil {
   373  				return err
   374  			}
   375  			defer fh.Close()
   376  
   377  			_, err = io.Copy(tw, fh)
   378  			return err
   379  		})
   380  		if err != nil {
   381  			return errors.Wrapf(err, "error streaming directory %s to Stdout", srcPath)
   382  		}
   383  		return nil
   384  	}
   385  
   386  	file, err := os.Open(srcPath)
   387  	if err != nil {
   388  		return errors.Wrapf(err, "error opening file %s", srcPath)
   389  	}
   390  	defer file.Close()
   391  	if !archive.IsArchivePath(srcPath) {
   392  		tw := tar.NewWriter(os.Stdout)
   393  		hdr, err := tar.FileInfoHeader(srcfi, "")
   394  		if err != nil {
   395  			return err
   396  		}
   397  		err = tw.WriteHeader(hdr)
   398  		if err != nil {
   399  			return err
   400  		}
   401  		_, err = io.Copy(tw, file)
   402  		if err != nil {
   403  			return errors.Wrapf(err, "error streaming archive %s to Stdout", srcPath)
   404  		}
   405  		return nil
   406  	}
   407  
   408  	_, err = io.Copy(os.Stdout, file)
   409  	if err != nil {
   410  		return errors.Wrapf(err, "error streaming file to Stdout")
   411  	}
   412  	return nil
   413  }
   414  
   415  func isVolumeDestName(path string, ctr *libpod.Container) (bool, string, string) {
   416  	separator := string(os.PathSeparator)
   417  	if filepath.IsAbs(path) {
   418  		path = strings.TrimPrefix(path, separator)
   419  	}
   420  	if path == "" {
   421  		return false, "", ""
   422  	}
   423  	for _, vol := range ctr.Config().NamedVolumes {
   424  		volNamePath := strings.TrimPrefix(vol.Dest, separator)
   425  		if matchVolumePath(path, volNamePath) {
   426  			return true, vol.Dest, vol.Name
   427  		}
   428  	}
   429  	return false, "", ""
   430  }
   431  
   432  // if SRCPATH or DESTPATH is from volume mount's destination -v or --mount type=volume, generates the path with volume mount point
   433  func pathWithVolumeMount(ctr *libpod.Container, runtime *libpod.Runtime, volDestName, volName, path string) (string, error) {
   434  	destVolume, err := runtime.GetVolume(volName)
   435  	if err != nil {
   436  		return "", errors.Wrapf(err, "error getting volume destination %s", volName)
   437  	}
   438  	if !filepath.IsAbs(path) {
   439  		path = filepath.Join(string(os.PathSeparator), path)
   440  	}
   441  	path, err = securejoin.SecureJoin(destVolume.MountPoint(), strings.TrimPrefix(path, volDestName))
   442  	return path, err
   443  }
   444  
   445  func isBindMountDestName(path string, ctr *libpod.Container) (bool, specs.Mount) {
   446  	separator := string(os.PathSeparator)
   447  	if filepath.IsAbs(path) {
   448  		path = strings.TrimPrefix(path, string(os.PathSeparator))
   449  	}
   450  	if path == "" {
   451  		return false, specs.Mount{}
   452  	}
   453  	for _, m := range ctr.Config().Spec.Mounts {
   454  		if m.Type != "bind" {
   455  			continue
   456  		}
   457  		mDest := strings.TrimPrefix(m.Destination, separator)
   458  		if matchVolumePath(path, mDest) {
   459  			return true, m
   460  		}
   461  	}
   462  	return false, specs.Mount{}
   463  }
   464  
   465  func matchVolumePath(path, target string) bool {
   466  	pathStr := filepath.Clean(path)
   467  	target = filepath.Clean(target)
   468  	for len(pathStr) > len(target) && strings.Contains(pathStr, string(os.PathSeparator)) {
   469  		pathStr = pathStr[:strings.LastIndex(pathStr, string(os.PathSeparator))]
   470  	}
   471  	return pathStr == target
   472  }
   473  
   474  func pathWithBindMountSource(m specs.Mount, path string) (string, error) {
   475  	if !filepath.IsAbs(path) {
   476  		path = filepath.Join(string(os.PathSeparator), path)
   477  	}
   478  	return securejoin.SecureJoin(m.Source, strings.TrimPrefix(path, m.Destination))
   479  }
   480  
   481  func copyPause() bool {
   482  	if !remoteclient && rootless.IsRootless() {
   483  		cgroupv2, _ := cgroups.IsCgroup2UnifiedMode()
   484  		if !cgroupv2 {
   485  			logrus.Debugf("defaulting to pause==false on rootless cp in cgroupv1 systems")
   486  			return false
   487  		}
   488  	}
   489  	return true
   490  }