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 }