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 }