github.com/hanks177/podman/v4@v4.1.3-0.20220613032544-16d90015bc83/libpod/container_stat_linux.go (about)

     1  //go:build linux
     2  // +build linux
     3  
     4  package libpod
     5  
     6  import (
     7  	"os"
     8  	"path/filepath"
     9  	"strings"
    10  
    11  	"github.com/containers/buildah/copier"
    12  	"github.com/hanks177/podman/v4/libpod/define"
    13  	"github.com/hanks177/podman/v4/pkg/copy"
    14  	"github.com/pkg/errors"
    15  )
    16  
    17  // statInsideMount stats the specified path *inside* the container's mount and PID
    18  // namespace.  It returns the file info along with the resolved root ("/") and
    19  // the resolved path (relative to the root).
    20  func (c *Container) statInsideMount(containerPath string) (*copier.StatForItem, string, string, error) {
    21  	resolvedRoot := "/"
    22  	resolvedPath := c.pathAbs(containerPath)
    23  	var statInfo *copier.StatForItem
    24  
    25  	err := c.joinMountAndExec(
    26  		func() error {
    27  			var statErr error
    28  			statInfo, statErr = secureStat(resolvedRoot, resolvedPath)
    29  			return statErr
    30  		},
    31  	)
    32  
    33  	return statInfo, resolvedRoot, resolvedPath, err
    34  }
    35  
    36  // statOnHost stats the specified path *on the host*.  It returns the file info
    37  // along with the resolved root and the resolved path.  Both paths are absolute
    38  // to the host's root.  Note that the paths may resolved outside the
    39  // container's mount point (e.g., to a volume or bind mount).
    40  func (c *Container) statOnHost(mountPoint string, containerPath string) (*copier.StatForItem, string, string, error) {
    41  	// Now resolve the container's path.  It may hit a volume, it may hit a
    42  	// bind mount, it may be relative.
    43  	resolvedRoot, resolvedPath, err := c.resolvePath(mountPoint, containerPath)
    44  	if err != nil {
    45  		return nil, "", "", err
    46  	}
    47  
    48  	statInfo, err := secureStat(resolvedRoot, resolvedPath)
    49  	return statInfo, resolvedRoot, resolvedPath, err
    50  }
    51  
    52  func (c *Container) stat(containerMountPoint string, containerPath string) (*define.FileInfo, string, string, error) {
    53  	var (
    54  		resolvedRoot     string
    55  		resolvedPath     string
    56  		absContainerPath string
    57  		statInfo         *copier.StatForItem
    58  		statErr          error
    59  	)
    60  
    61  	// Make sure that "/" copies the *contents* of the mount point and not
    62  	// the directory.
    63  	if containerPath == "/" {
    64  		containerPath = "/."
    65  	}
    66  
    67  	// Wildcards are not allowed.
    68  	// TODO: it's now technically possible wildcards.
    69  	// We may consider enabling support in the future.
    70  	if strings.Contains(containerPath, "*") {
    71  		return nil, "", "", copy.ErrENOENT
    72  	}
    73  
    74  	if c.state.State == define.ContainerStateRunning {
    75  		// If the container is running, we need to join it's mount namespace
    76  		// and stat there.
    77  		statInfo, resolvedRoot, resolvedPath, statErr = c.statInsideMount(containerPath)
    78  	} else {
    79  		// If the container is NOT running, we need to resolve the path
    80  		// on the host.
    81  		statInfo, resolvedRoot, resolvedPath, statErr = c.statOnHost(containerMountPoint, containerPath)
    82  	}
    83  
    84  	if statErr != nil {
    85  		if statInfo == nil {
    86  			return nil, "", "", statErr
    87  		}
    88  		// Not all errors from secureStat map to ErrNotExist, so we
    89  		// have to look into the error string.  Turning it into an
    90  		// ENOENT let's the API handlers return the correct status code
    91  		// which is crucial for the remote client.
    92  		if os.IsNotExist(statErr) || strings.Contains(statErr.Error(), "o such file or directory") {
    93  			statErr = copy.ErrENOENT
    94  		}
    95  	}
    96  
    97  	switch {
    98  	case statInfo.IsSymlink:
    99  		// Symlinks are already evaluated and always relative to the
   100  		// container's mount point.
   101  		absContainerPath = statInfo.ImmediateTarget
   102  	case strings.HasPrefix(resolvedPath, containerMountPoint):
   103  		// If the path is on the container's mount point, strip it off.
   104  		absContainerPath = strings.TrimPrefix(resolvedPath, containerMountPoint)
   105  		absContainerPath = filepath.Join("/", absContainerPath)
   106  	default:
   107  		// No symlink and not on the container's mount point, so let's
   108  		// move it back to the original input.  It must have evaluated
   109  		// to a volume or bind mount but we cannot return host paths.
   110  		absContainerPath = containerPath
   111  	}
   112  
   113  	// Preserve the base path as specified by the user.  The `filepath`
   114  	// packages likes to remove trailing slashes and dots that are crucial
   115  	// to the copy logic.
   116  	absContainerPath = copy.PreserveBasePath(containerPath, absContainerPath)
   117  	resolvedPath = copy.PreserveBasePath(containerPath, resolvedPath)
   118  
   119  	info := &define.FileInfo{
   120  		IsDir:      statInfo.IsDir,
   121  		Name:       filepath.Base(absContainerPath),
   122  		Size:       statInfo.Size,
   123  		Mode:       statInfo.Mode,
   124  		ModTime:    statInfo.ModTime,
   125  		LinkTarget: absContainerPath,
   126  	}
   127  
   128  	return info, resolvedRoot, resolvedPath, statErr
   129  }
   130  
   131  // secureStat extracts file info for path in a chroot'ed environment in root.
   132  func secureStat(root string, path string) (*copier.StatForItem, error) {
   133  	var glob string
   134  	var err error
   135  
   136  	// If root and path are equal, then dir must be empty and the glob must
   137  	// be ".".
   138  	if filepath.Clean(root) == filepath.Clean(path) {
   139  		glob = "."
   140  	} else {
   141  		glob, err = filepath.Rel(root, path)
   142  		if err != nil {
   143  			return nil, err
   144  		}
   145  	}
   146  
   147  	globStats, err := copier.Stat(root, "", copier.StatOptions{}, []string{glob})
   148  	if err != nil {
   149  		return nil, err
   150  	}
   151  
   152  	if len(globStats) != 1 {
   153  		return nil, errors.Errorf("internal error: secureStat: expected 1 item but got %d", len(globStats))
   154  	}
   155  	if len(globStats) != 1 {
   156  		return nil, errors.Errorf("internal error: secureStat: expected 1 result but got %d", len(globStats[0].Results))
   157  	}
   158  
   159  	// NOTE: the key in the map differ from `glob` when hitting symlink.
   160  	// Hence, we just take the first (and only) key/value pair.
   161  	for _, stat := range globStats[0].Results {
   162  		var statErr error
   163  		if stat.Error != "" {
   164  			statErr = errors.New(stat.Error)
   165  		}
   166  		// If necessary evaluate the symlink
   167  		if stat.IsSymlink {
   168  			target, err := copier.Eval(root, path, copier.EvalOptions{})
   169  			if err != nil {
   170  				return nil, errors.Wrap(err, "error evaluating symlink in container")
   171  			}
   172  			// Need to make sure the symlink is relative to the root!
   173  			target = strings.TrimPrefix(target, root)
   174  			target = filepath.Join("/", target)
   175  			stat.ImmediateTarget = target
   176  		}
   177  		return stat, statErr
   178  	}
   179  
   180  	// Nothing found!
   181  	return nil, copy.ErrENOENT
   182  }