mirror of https://github.com/containers/podman.git
				
				
				
			
		
			
				
	
	
		
			182 lines
		
	
	
		
			5.9 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			182 lines
		
	
	
		
			5.9 KiB
		
	
	
	
		
			Go
		
	
	
	
| // +build linux
 | |
| 
 | |
| package libpod
 | |
| 
 | |
| import (
 | |
| 	"context"
 | |
| 	"os"
 | |
| 	"path/filepath"
 | |
| 	"strings"
 | |
| 
 | |
| 	"github.com/containers/buildah/copier"
 | |
| 	"github.com/containers/podman/v4/libpod/define"
 | |
| 	"github.com/containers/podman/v4/pkg/copy"
 | |
| 	"github.com/pkg/errors"
 | |
| )
 | |
| 
 | |
| // statInsideMount stats the specified path *inside* the container's mount and PID
 | |
| // namespace.  It returns the file info along with the resolved root ("/") and
 | |
| // the resolved path (relative to the root).
 | |
| func (c *Container) statInsideMount(ctx context.Context, containerPath string) (*copier.StatForItem, string, string, error) {
 | |
| 	resolvedRoot := "/"
 | |
| 	resolvedPath := c.pathAbs(containerPath)
 | |
| 	var statInfo *copier.StatForItem
 | |
| 
 | |
| 	err := c.joinMountAndExec(ctx,
 | |
| 		func() error {
 | |
| 			var statErr error
 | |
| 			statInfo, statErr = secureStat(resolvedRoot, resolvedPath)
 | |
| 			return statErr
 | |
| 		},
 | |
| 	)
 | |
| 
 | |
| 	return statInfo, resolvedRoot, resolvedPath, err
 | |
| }
 | |
| 
 | |
| // statOnHost stats the specified path *on the host*.  It returns the file info
 | |
| // along with the resolved root and the resolved path.  Both paths are absolute
 | |
| // to the host's root.  Note that the paths may resolved outside the
 | |
| // container's mount point (e.g., to a volume or bind mount).
 | |
| func (c *Container) statOnHost(ctx context.Context, mountPoint string, containerPath string) (*copier.StatForItem, string, string, error) {
 | |
| 	// Now resolve the container's path.  It may hit a volume, it may hit a
 | |
| 	// bind mount, it may be relative.
 | |
| 	resolvedRoot, resolvedPath, err := c.resolvePath(mountPoint, containerPath)
 | |
| 	if err != nil {
 | |
| 		return nil, "", "", err
 | |
| 	}
 | |
| 
 | |
| 	statInfo, err := secureStat(resolvedRoot, resolvedPath)
 | |
| 	return statInfo, resolvedRoot, resolvedPath, err
 | |
| }
 | |
| 
 | |
| func (c *Container) stat(ctx context.Context, containerMountPoint string, containerPath string) (*define.FileInfo, string, string, error) {
 | |
| 	var (
 | |
| 		resolvedRoot     string
 | |
| 		resolvedPath     string
 | |
| 		absContainerPath string
 | |
| 		statInfo         *copier.StatForItem
 | |
| 		statErr          error
 | |
| 	)
 | |
| 
 | |
| 	// Make sure that "/" copies the *contents* of the mount point and not
 | |
| 	// the directory.
 | |
| 	if containerPath == "/" {
 | |
| 		containerPath = "/."
 | |
| 	}
 | |
| 
 | |
| 	// Wildcards are not allowed.
 | |
| 	// TODO: it's now technically possible wildcards.
 | |
| 	// We may consider enabling support in the future.
 | |
| 	if strings.Contains(containerPath, "*") {
 | |
| 		return nil, "", "", copy.ErrENOENT
 | |
| 	}
 | |
| 
 | |
| 	if c.state.State == define.ContainerStateRunning {
 | |
| 		// If the container is running, we need to join it's mount namespace
 | |
| 		// and stat there.
 | |
| 		statInfo, resolvedRoot, resolvedPath, statErr = c.statInsideMount(ctx, containerPath)
 | |
| 	} else {
 | |
| 		// If the container is NOT running, we need to resolve the path
 | |
| 		// on the host.
 | |
| 		statInfo, resolvedRoot, resolvedPath, statErr = c.statOnHost(ctx, containerMountPoint, containerPath)
 | |
| 	}
 | |
| 
 | |
| 	if statErr != nil {
 | |
| 		if statInfo == nil {
 | |
| 			return nil, "", "", statErr
 | |
| 		}
 | |
| 		// Not all errors from secureStat map to ErrNotExist, so we
 | |
| 		// have to look into the error string.  Turning it into an
 | |
| 		// ENOENT let's the API handlers return the correct status code
 | |
| 		// which is crucial for the remote client.
 | |
| 		if os.IsNotExist(statErr) || strings.Contains(statErr.Error(), "o such file or directory") {
 | |
| 			statErr = copy.ErrENOENT
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if statInfo.IsSymlink {
 | |
| 		// Symlinks are already evaluated and always relative to the
 | |
| 		// container's mount point.
 | |
| 		absContainerPath = statInfo.ImmediateTarget
 | |
| 	} else if strings.HasPrefix(resolvedPath, containerMountPoint) {
 | |
| 		// If the path is on the container's mount point, strip it off.
 | |
| 		absContainerPath = strings.TrimPrefix(resolvedPath, containerMountPoint)
 | |
| 		absContainerPath = filepath.Join("/", absContainerPath)
 | |
| 	} else {
 | |
| 		// No symlink and not on the container's mount point, so let's
 | |
| 		// move it back to the original input.  It must have evaluated
 | |
| 		// to a volume or bind mount but we cannot return host paths.
 | |
| 		absContainerPath = containerPath
 | |
| 	}
 | |
| 
 | |
| 	// Preserve the base path as specified by the user.  The `filepath`
 | |
| 	// packages likes to remove trailing slashes and dots that are crucial
 | |
| 	// to the copy logic.
 | |
| 	absContainerPath = copy.PreserveBasePath(containerPath, absContainerPath)
 | |
| 	resolvedPath = copy.PreserveBasePath(containerPath, resolvedPath)
 | |
| 
 | |
| 	info := &define.FileInfo{
 | |
| 		IsDir:      statInfo.IsDir,
 | |
| 		Name:       filepath.Base(absContainerPath),
 | |
| 		Size:       statInfo.Size,
 | |
| 		Mode:       statInfo.Mode,
 | |
| 		ModTime:    statInfo.ModTime,
 | |
| 		LinkTarget: absContainerPath,
 | |
| 	}
 | |
| 
 | |
| 	return info, resolvedRoot, resolvedPath, statErr
 | |
| }
 | |
| 
 | |
| // secureStat extracts file info for path in a chroot'ed environment in root.
 | |
| func secureStat(root string, path string) (*copier.StatForItem, error) {
 | |
| 	var glob string
 | |
| 	var err error
 | |
| 
 | |
| 	// If root and path are equal, then dir must be empty and the glob must
 | |
| 	// be ".".
 | |
| 	if filepath.Clean(root) == filepath.Clean(path) {
 | |
| 		glob = "."
 | |
| 	} else {
 | |
| 		glob, err = filepath.Rel(root, path)
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	globStats, err := copier.Stat(root, "", copier.StatOptions{}, []string{glob})
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	if len(globStats) != 1 {
 | |
| 		return nil, errors.Errorf("internal error: secureStat: expected 1 item but got %d", len(globStats))
 | |
| 	}
 | |
| 	if len(globStats) != 1 {
 | |
| 		return nil, errors.Errorf("internal error: secureStat: expected 1 result but got %d", len(globStats[0].Results))
 | |
| 	}
 | |
| 
 | |
| 	// NOTE: the key in the map differ from `glob` when hitting symlink.
 | |
| 	// Hence, we just take the first (and only) key/value pair.
 | |
| 	for _, stat := range globStats[0].Results {
 | |
| 		var statErr error
 | |
| 		if stat.Error != "" {
 | |
| 			statErr = errors.New(stat.Error)
 | |
| 		}
 | |
| 		// If necessary evaluate the symlink
 | |
| 		if stat.IsSymlink {
 | |
| 			target, err := copier.Eval(root, path, copier.EvalOptions{})
 | |
| 			if err != nil {
 | |
| 				return nil, errors.Wrap(err, "error evaluating symlink in container")
 | |
| 			}
 | |
| 			// Need to make sure the symlink is relative to the root!
 | |
| 			target = strings.TrimPrefix(target, root)
 | |
| 			target = filepath.Join("/", target)
 | |
| 			stat.ImmediateTarget = target
 | |
| 		}
 | |
| 		return stat, statErr
 | |
| 	}
 | |
| 
 | |
| 	// Nothing found!
 | |
| 	return nil, copy.ErrENOENT
 | |
| }
 |