github.com/containers/podman/v2@v2.2.2-0.20210501105131-c1e07d070c4c/pkg/domain/infra/abi/cp.go (about)

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