1
//go:build !remote && (linux || freebsd)
12
"github.com/containers/buildah/copier"
13
"github.com/containers/podman/v5/libpod/define"
14
"github.com/containers/podman/v5/pkg/copy"
17
// statOnHost stats the specified path *on the host*. It returns the file info
18
// along with the resolved root and the resolved path. Both paths are absolute
19
// to the host's root. Note that the paths may resolved outside the
20
// container's mount point (e.g., to a volume or bind mount).
21
func (c *Container) statOnHost(mountPoint string, containerPath string) (*copier.StatForItem, string, string, error) {
22
// Now resolve the container's path. It may hit a volume, it may hit a
23
// bind mount, it may be relative.
24
resolvedRoot, resolvedPath, err := c.resolvePath(mountPoint, containerPath)
26
return nil, "", "", err
29
statInfo, err := secureStat(resolvedRoot, resolvedPath)
30
return statInfo, resolvedRoot, resolvedPath, err
33
func (c *Container) stat(containerMountPoint string, containerPath string) (*define.FileInfo, string, string, error) {
37
absContainerPath string
38
statInfo *copier.StatForItem
42
// Make sure that "/" copies the *contents* of the mount point and not
44
if containerPath == "/" {
48
// Wildcards are not allowed.
49
// TODO: it's now technically possible wildcards.
50
// We may consider enabling support in the future.
51
if strings.Contains(containerPath, "*") {
52
return nil, "", "", copy.ErrENOENT
55
statInfo, resolvedRoot, resolvedPath, statErr = c.statInContainer(containerMountPoint, containerPath)
58
return nil, "", "", statErr
60
// Not all errors from secureStat map to ErrNotExist, so we
61
// have to look into the error string. Turning it into an
62
// ENOENT lets the API handlers return the correct status code
63
// which is crucial for the remote client.
64
if os.IsNotExist(statErr) || strings.Contains(statErr.Error(), "o such file or directory") {
65
statErr = copy.ErrENOENT
70
case statInfo.IsSymlink:
71
// Symlinks are already evaluated and always relative to the
72
// container's mount point.
73
absContainerPath = statInfo.ImmediateTarget
74
case strings.HasPrefix(resolvedPath, containerMountPoint):
75
// If the path is on the container's mount point, strip it off.
76
absContainerPath = strings.TrimPrefix(resolvedPath, containerMountPoint)
77
absContainerPath = filepath.Join("/", absContainerPath)
79
// No symlink and not on the container's mount point, so let's
80
// move it back to the original input. It must have evaluated
81
// to a volume or bind mount but we cannot return host paths.
82
absContainerPath = containerPath
85
// Preserve the base path as specified by the user. The `filepath`
86
// packages likes to remove trailing slashes and dots that are crucial
88
absContainerPath = copy.PreserveBasePath(containerPath, absContainerPath)
89
resolvedPath = copy.PreserveBasePath(containerPath, resolvedPath)
91
info := &define.FileInfo{
92
IsDir: statInfo.IsDir,
93
Name: filepath.Base(absContainerPath),
96
ModTime: statInfo.ModTime,
97
LinkTarget: absContainerPath,
100
return info, resolvedRoot, resolvedPath, statErr
103
// secureStat extracts file info for path in a chroot'ed environment in root.
104
func secureStat(root string, path string) (*copier.StatForItem, error) {
108
// If root and path are equal, then dir must be empty and the glob must
110
if filepath.Clean(root) == filepath.Clean(path) {
113
glob, err = filepath.Rel(root, path)
119
globStats, err := copier.Stat(root, "", copier.StatOptions{}, []string{glob})
124
if len(globStats) != 1 {
125
return nil, fmt.Errorf("internal error: secureStat: expected 1 item but got %d", len(globStats))
127
if len(globStats) != 1 {
128
return nil, fmt.Errorf("internal error: secureStat: expected 1 result but got %d", len(globStats[0].Results))
131
// NOTE: the key in the map differ from `glob` when hitting symlink.
132
// Hence, we just take the first (and only) key/value pair.
133
for _, stat := range globStats[0].Results {
135
if stat.Error != "" {
136
statErr = errors.New(stat.Error)
138
// If necessary evaluate the symlink
140
target, err := copier.Eval(root, path, copier.EvalOptions{})
142
return nil, fmt.Errorf("evaluating symlink in container: %w", err)
144
// Need to make sure the symlink is relative to the root!
145
target = strings.TrimPrefix(target, root)
146
target = filepath.Join("/", target)
147
stat.ImmediateTarget = target
153
return nil, copy.ErrENOENT