diff options
-rw-r--r-- | libpod/container_copy_common.go | 213 | ||||
-rw-r--r-- | libpod/container_copy_freebsd.go | 13 | ||||
-rw-r--r-- | libpod/container_copy_linux.go | 222 | ||||
-rw-r--r-- | libpod/container_copy_unsupported.go | 4 | ||||
-rw-r--r-- | libpod/container_stat_common.go | 155 | ||||
-rw-r--r-- | libpod/container_stat_freebsd.go | 13 | ||||
-rw-r--r-- | libpod/container_stat_linux.go | 159 | ||||
-rw-r--r-- | libpod/container_stat_unsupported.go | 4 |
8 files changed, 415 insertions, 368 deletions
diff --git a/libpod/container_copy_common.go b/libpod/container_copy_common.go new file mode 100644 index 000000000..d07b4c692 --- /dev/null +++ b/libpod/container_copy_common.go @@ -0,0 +1,213 @@ +//go:build linux || freebsd +// +build linux freebsd + +package libpod + +import ( + "errors" + "io" + "path/filepath" + "strings" + + buildahCopiah "github.com/containers/buildah/copier" + "github.com/containers/buildah/pkg/chrootuser" + "github.com/containers/buildah/util" + "github.com/containers/podman/v4/libpod/define" + "github.com/containers/podman/v4/pkg/rootless" + "github.com/containers/storage/pkg/archive" + "github.com/containers/storage/pkg/idtools" + "github.com/opencontainers/runtime-spec/specs-go" + "github.com/sirupsen/logrus" +) + +func (c *Container) copyFromArchive(path string, chown, noOverwriteDirNonDir bool, rename map[string]string, reader io.Reader) (func() error, error) { + var ( + mountPoint string + resolvedRoot string + resolvedPath string + unmount func() + err error + ) + + // Make sure that "/" copies the *contents* of the mount point and not + // the directory. + if path == "/" { + path = "/." + } + + // Optimization: only mount if the container is not already. + if c.state.Mounted { + mountPoint = c.state.Mountpoint + unmount = func() {} + } else { + // NOTE: make sure to unmount in error paths. + mountPoint, err = c.mount() + if err != nil { + return nil, err + } + unmount = func() { + if err := c.unmount(false); err != nil { + logrus.Errorf("Failed to unmount container: %v", err) + } + } + } + + resolvedRoot, resolvedPath, err = c.resolveCopyTarget(mountPoint, path) + if err != nil { + unmount() + return nil, err + } + + var idPair *idtools.IDPair + if chown { + // Make sure we chown the files to the container's main user and group ID. + user, err := getContainerUser(c, mountPoint) + if err != nil { + unmount() + return nil, err + } + idPair = &idtools.IDPair{UID: int(user.UID), GID: int(user.GID)} + } + + decompressed, err := archive.DecompressStream(reader) + if err != nil { + unmount() + return nil, err + } + + logrus.Debugf("Container copy *to* %q (resolved: %q) on container %q (ID: %s)", path, resolvedPath, c.Name(), c.ID()) + + return func() error { + defer unmount() + defer decompressed.Close() + putOptions := buildahCopiah.PutOptions{ + UIDMap: c.config.IDMappings.UIDMap, + GIDMap: c.config.IDMappings.GIDMap, + ChownDirs: idPair, + ChownFiles: idPair, + NoOverwriteDirNonDir: noOverwriteDirNonDir, + NoOverwriteNonDirDir: noOverwriteDirNonDir, + Rename: rename, + } + + return c.joinMountAndExec( + func() error { + return buildahCopiah.Put(resolvedRoot, resolvedPath, putOptions, decompressed) + }, + ) + }, nil +} + +func (c *Container) copyToArchive(path string, writer io.Writer) (func() error, error) { + var ( + mountPoint string + unmount func() + err error + ) + + // Optimization: only mount if the container is not already. + if c.state.Mounted { + mountPoint = c.state.Mountpoint + unmount = func() {} + } else { + // NOTE: make sure to unmount in error paths. + mountPoint, err = c.mount() + if err != nil { + return nil, err + } + unmount = func() { + if err := c.unmount(false); err != nil { + logrus.Errorf("Failed to unmount container: %v", err) + } + } + } + + statInfo, resolvedRoot, resolvedPath, err := c.stat(mountPoint, path) + if err != nil { + unmount() + return nil, err + } + + // We optimistically chown to the host user. In case of a hypothetical + // container-to-container copy, the reading side will chown back to the + // container user. + user, err := getContainerUser(c, mountPoint) + if err != nil { + unmount() + return nil, err + } + hostUID, hostGID, err := util.GetHostIDs( + idtoolsToRuntimeSpec(c.config.IDMappings.UIDMap), + idtoolsToRuntimeSpec(c.config.IDMappings.GIDMap), + user.UID, + user.GID, + ) + if err != nil { + unmount() + return nil, err + } + idPair := idtools.IDPair{UID: int(hostUID), GID: int(hostGID)} + + logrus.Debugf("Container copy *from* %q (resolved: %q) on container %q (ID: %s)", path, resolvedPath, c.Name(), c.ID()) + + return func() error { + defer unmount() + getOptions := buildahCopiah.GetOptions{ + // Unless the specified points to ".", we want to copy the base directory. + KeepDirectoryNames: statInfo.IsDir && filepath.Base(path) != ".", + UIDMap: c.config.IDMappings.UIDMap, + GIDMap: c.config.IDMappings.GIDMap, + ChownDirs: &idPair, + ChownFiles: &idPair, + Excludes: []string{"dev", "proc", "sys"}, + // Ignore EPERMs when copying from rootless containers + // since we cannot read TTY devices. Those are owned + // by the host's root and hence "nobody" inside the + // container's user namespace. + IgnoreUnreadable: rootless.IsRootless() && c.state.State == define.ContainerStateRunning, + } + return c.joinMountAndExec( + func() error { + return buildahCopiah.Get(resolvedRoot, "", getOptions, []string{resolvedPath}, writer) + }, + ) + }, nil +} + +// getContainerUser returns the specs.User and ID mappings of the container. +func getContainerUser(container *Container, mountPoint string) (specs.User, error) { + userspec := container.config.User + + uid, gid, _, err := chrootuser.GetUser(mountPoint, userspec) + u := specs.User{ + UID: uid, + GID: gid, + Username: userspec, + } + + if !strings.Contains(userspec, ":") { + groups, err2 := chrootuser.GetAdditionalGroupsForUser(mountPoint, uint64(u.UID)) + if err2 != nil { + if !errors.Is(err2, chrootuser.ErrNoSuchUser) && err == nil { + err = err2 + } + } else { + u.AdditionalGids = groups + } + } + + return u, err +} + +// idtoolsToRuntimeSpec converts idtools ID mapping to the one of the runtime spec. +func idtoolsToRuntimeSpec(idMaps []idtools.IDMap) (convertedIDMap []specs.LinuxIDMapping) { + for _, idmap := range idMaps { + tempIDMap := specs.LinuxIDMapping{ + ContainerID: uint32(idmap.ContainerID), + HostID: uint32(idmap.HostID), + Size: uint32(idmap.Size), + } + convertedIDMap = append(convertedIDMap, tempIDMap) + } + return convertedIDMap +} diff --git a/libpod/container_copy_freebsd.go b/libpod/container_copy_freebsd.go new file mode 100644 index 000000000..218f3917f --- /dev/null +++ b/libpod/container_copy_freebsd.go @@ -0,0 +1,13 @@ +package libpod + +// On FreeBSD, the container's mounts are in the global mount +// namespace so we can just execute the function directly. +func (c *Container) joinMountAndExec(f func() error) error { + return f() +} + +// Similarly, we can just use resolvePath for both running and stopped +// containers. +func (c *Container) resolveCopyTarget(mountPoint string, containerPath string) (string, string, error) { + return c.resolvePath(mountPoint, containerPath) +} diff --git a/libpod/container_copy_linux.go b/libpod/container_copy_linux.go index 557fead1e..3b029f08f 100644 --- a/libpod/container_copy_linux.go +++ b/libpod/container_copy_linux.go @@ -1,226 +1,14 @@ -//go:build linux -// +build linux - package libpod import ( - "errors" "fmt" - "io" "os" - "path/filepath" "runtime" - "strings" - buildahCopiah "github.com/containers/buildah/copier" - "github.com/containers/buildah/pkg/chrootuser" - "github.com/containers/buildah/util" "github.com/containers/podman/v4/libpod/define" - "github.com/containers/podman/v4/pkg/rootless" - "github.com/containers/storage/pkg/archive" - "github.com/containers/storage/pkg/idtools" - "github.com/opencontainers/runtime-spec/specs-go" - "github.com/sirupsen/logrus" "golang.org/x/sys/unix" ) -func (c *Container) copyFromArchive(path string, chown, noOverwriteDirNonDir bool, rename map[string]string, reader io.Reader) (func() error, error) { - var ( - mountPoint string - resolvedRoot string - resolvedPath string - unmount func() - err error - ) - - // Make sure that "/" copies the *contents* of the mount point and not - // the directory. - if path == "/" { - path = "/." - } - - // Optimization: only mount if the container is not already. - if c.state.Mounted { - mountPoint = c.state.Mountpoint - unmount = func() {} - } else { - // NOTE: make sure to unmount in error paths. - mountPoint, err = c.mount() - if err != nil { - return nil, err - } - unmount = func() { - if err := c.unmount(false); err != nil { - logrus.Errorf("Failed to unmount container: %v", err) - } - } - } - - if c.state.State == define.ContainerStateRunning { - resolvedRoot = "/" - resolvedPath = c.pathAbs(path) - } else { - resolvedRoot, resolvedPath, err = c.resolvePath(mountPoint, path) - if err != nil { - unmount() - return nil, err - } - } - - var idPair *idtools.IDPair - if chown { - // Make sure we chown the files to the container's main user and group ID. - user, err := getContainerUser(c, mountPoint) - if err != nil { - unmount() - return nil, err - } - idPair = &idtools.IDPair{UID: int(user.UID), GID: int(user.GID)} - } - - decompressed, err := archive.DecompressStream(reader) - if err != nil { - unmount() - return nil, err - } - - logrus.Debugf("Container copy *to* %q (resolved: %q) on container %q (ID: %s)", path, resolvedPath, c.Name(), c.ID()) - - return func() error { - defer unmount() - defer decompressed.Close() - putOptions := buildahCopiah.PutOptions{ - UIDMap: c.config.IDMappings.UIDMap, - GIDMap: c.config.IDMappings.GIDMap, - ChownDirs: idPair, - ChownFiles: idPair, - NoOverwriteDirNonDir: noOverwriteDirNonDir, - NoOverwriteNonDirDir: noOverwriteDirNonDir, - Rename: rename, - } - - return c.joinMountAndExec( - func() error { - return buildahCopiah.Put(resolvedRoot, resolvedPath, putOptions, decompressed) - }, - ) - }, nil -} - -func (c *Container) copyToArchive(path string, writer io.Writer) (func() error, error) { - var ( - mountPoint string - unmount func() - err error - ) - - // Optimization: only mount if the container is not already. - if c.state.Mounted { - mountPoint = c.state.Mountpoint - unmount = func() {} - } else { - // NOTE: make sure to unmount in error paths. - mountPoint, err = c.mount() - if err != nil { - return nil, err - } - unmount = func() { - if err := c.unmount(false); err != nil { - logrus.Errorf("Failed to unmount container: %v", err) - } - } - } - - statInfo, resolvedRoot, resolvedPath, err := c.stat(mountPoint, path) - if err != nil { - unmount() - return nil, err - } - - // We optimistically chown to the host user. In case of a hypothetical - // container-to-container copy, the reading side will chown back to the - // container user. - user, err := getContainerUser(c, mountPoint) - if err != nil { - unmount() - return nil, err - } - hostUID, hostGID, err := util.GetHostIDs( - idtoolsToRuntimeSpec(c.config.IDMappings.UIDMap), - idtoolsToRuntimeSpec(c.config.IDMappings.GIDMap), - user.UID, - user.GID, - ) - if err != nil { - unmount() - return nil, err - } - idPair := idtools.IDPair{UID: int(hostUID), GID: int(hostGID)} - - logrus.Debugf("Container copy *from* %q (resolved: %q) on container %q (ID: %s)", path, resolvedPath, c.Name(), c.ID()) - - return func() error { - defer unmount() - getOptions := buildahCopiah.GetOptions{ - // Unless the specified points to ".", we want to copy the base directory. - KeepDirectoryNames: statInfo.IsDir && filepath.Base(path) != ".", - UIDMap: c.config.IDMappings.UIDMap, - GIDMap: c.config.IDMappings.GIDMap, - ChownDirs: &idPair, - ChownFiles: &idPair, - Excludes: []string{"dev", "proc", "sys"}, - // Ignore EPERMs when copying from rootless containers - // since we cannot read TTY devices. Those are owned - // by the host's root and hence "nobody" inside the - // container's user namespace. - IgnoreUnreadable: rootless.IsRootless() && c.state.State == define.ContainerStateRunning, - } - return c.joinMountAndExec( - func() error { - return buildahCopiah.Get(resolvedRoot, "", getOptions, []string{resolvedPath}, writer) - }, - ) - }, nil -} - -// getContainerUser returns the specs.User and ID mappings of the container. -func getContainerUser(container *Container, mountPoint string) (specs.User, error) { - userspec := container.config.User - - uid, gid, _, err := chrootuser.GetUser(mountPoint, userspec) - u := specs.User{ - UID: uid, - GID: gid, - Username: userspec, - } - - if !strings.Contains(userspec, ":") { - groups, err2 := chrootuser.GetAdditionalGroupsForUser(mountPoint, uint64(u.UID)) - if err2 != nil { - if !errors.Is(err2, chrootuser.ErrNoSuchUser) && err == nil { - err = err2 - } - } else { - u.AdditionalGids = groups - } - } - - return u, err -} - -// idtoolsToRuntimeSpec converts idtools ID mapping to the one of the runtime spec. -func idtoolsToRuntimeSpec(idMaps []idtools.IDMap) (convertedIDMap []specs.LinuxIDMapping) { - for _, idmap := range idMaps { - tempIDMap := specs.LinuxIDMapping{ - ContainerID: uint32(idmap.ContainerID), - HostID: uint32(idmap.HostID), - Size: uint32(idmap.Size), - } - convertedIDMap = append(convertedIDMap, tempIDMap) - } - return convertedIDMap -} - // joinMountAndExec executes the specified function `f` inside the container's // mount and PID namespace. That allows for having the exact view on the // container's file system. @@ -288,3 +76,13 @@ func (c *Container) joinMountAndExec(f func() error) error { }() return <-errChan } + +func (c *Container) resolveCopyTarget(mountPoint string, containerPath string) (string, string, error) { + // If the container is running, we will execute the copy + // inside the container's mount namespace so we return a path + // relative to the container's root. + if c.state.State == define.ContainerStateRunning { + return "/", c.pathAbs(containerPath), nil + } + return c.resolvePath(mountPoint, containerPath) +} diff --git a/libpod/container_copy_unsupported.go b/libpod/container_copy_unsupported.go index 62937279a..703b0a74e 100644 --- a/libpod/container_copy_unsupported.go +++ b/libpod/container_copy_unsupported.go @@ -1,5 +1,5 @@ -//go:build !linux -// +build !linux +//go:build !linux && !freebsd +// +build !linux,!freebsd package libpod diff --git a/libpod/container_stat_common.go b/libpod/container_stat_common.go new file mode 100644 index 000000000..e59a52ede --- /dev/null +++ b/libpod/container_stat_common.go @@ -0,0 +1,155 @@ +//go:build linux || freebsd +// +build linux freebsd + +package libpod + +import ( + "errors" + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/containers/buildah/copier" + "github.com/containers/podman/v4/libpod/define" + "github.com/containers/podman/v4/pkg/copy" +) + +// 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(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(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 + } + + statInfo, resolvedRoot, resolvedPath, statErr = c.statInContainer(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 + } + } + + switch { + case statInfo.IsSymlink: + // Symlinks are already evaluated and always relative to the + // container's mount point. + absContainerPath = statInfo.ImmediateTarget + case 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) + default: + // 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, fmt.Errorf("internal error: secureStat: expected 1 item but got %d", len(globStats)) + } + if len(globStats) != 1 { + return nil, fmt.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, fmt.Errorf("evaluating symlink in container: %w", err) + } + // 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 +} diff --git a/libpod/container_stat_freebsd.go b/libpod/container_stat_freebsd.go new file mode 100644 index 000000000..d1e0db348 --- /dev/null +++ b/libpod/container_stat_freebsd.go @@ -0,0 +1,13 @@ +package libpod + +import ( + "github.com/containers/buildah/copier" +) + +// On FreeBSD, jails use the global mount namespace, filtered to only +// the mounts the jail should see. This means that we can use +// statOnHost whether the container is running or not. +// container is running +func (c *Container) statInContainer(mountPoint string, containerPath string) (*copier.StatForItem, string, string, error) { + return c.statOnHost(mountPoint, containerPath) +} diff --git a/libpod/container_stat_linux.go b/libpod/container_stat_linux.go index dc3a524f5..5e5ef3c1a 100644 --- a/libpod/container_stat_linux.go +++ b/libpod/container_stat_linux.go @@ -1,18 +1,8 @@ -//go:build linux -// +build linux - package libpod import ( - "errors" - "fmt" - "os" - "path/filepath" - "strings" - "github.com/containers/buildah/copier" "github.com/containers/podman/v4/libpod/define" - "github.com/containers/podman/v4/pkg/copy" ) // statInsideMount stats the specified path *inside* the container's mount and PID @@ -34,150 +24,15 @@ func (c *Container) statInsideMount(containerPath string) (*copier.StatForItem, 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(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(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 - } - +// Calls either statOnHost or statInsideMount depending on whether the +// container is running +func (c *Container) statInContainer(mountPoint string, containerPath string) (*copier.StatForItem, string, string, error) { 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(containerPath) - } else { - // If the container is NOT running, we need to resolve the path - // on the host. - statInfo, resolvedRoot, resolvedPath, statErr = c.statOnHost(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 - } - } - - switch { - case statInfo.IsSymlink: - // Symlinks are already evaluated and always relative to the - // container's mount point. - absContainerPath = statInfo.ImmediateTarget - case 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) - default: - // 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 + return c.statInsideMount(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, fmt.Errorf("internal error: secureStat: expected 1 item but got %d", len(globStats)) - } - if len(globStats) != 1 { - return nil, fmt.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, fmt.Errorf("evaluating symlink in container: %w", err) - } - // 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 + // If the container is NOT running, we need to resolve the path + // on the host. + return c.statOnHost(mountPoint, containerPath) } diff --git a/libpod/container_stat_unsupported.go b/libpod/container_stat_unsupported.go index 2f1acd44d..e88b88bb1 100644 --- a/libpod/container_stat_unsupported.go +++ b/libpod/container_stat_unsupported.go @@ -1,5 +1,5 @@ -//go:build !linux -// +build !linux +//go:build !linux && !freebsd +// +build !linux,!freebsd package libpod |