aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorOpenShift Merge Robot <openshift-merge-robot@users.noreply.github.com>2022-09-21 14:32:13 +0200
committerGitHub <noreply@github.com>2022-09-21 14:32:13 +0200
commit7a189a6956ec438728695b69a2efbfd8e6aadd22 (patch)
tree807516cacbaf5608ab6af089360cff18baa72d30
parentffa73c58a52b470d9708a8ae38536fa0dc443d8b (diff)
parentbb160be12be137116c2ee2b5e448ec8756f2ed64 (diff)
downloadpodman-7a189a6956ec438728695b69a2efbfd8e6aadd22.tar.gz
podman-7a189a6956ec438728695b69a2efbfd8e6aadd22.tar.bz2
podman-7a189a6956ec438728695b69a2efbfd8e6aadd22.zip
Merge pull request #15856 from dfr/freebsd-copy
Add support for 'podman cp' on FreeBSD
-rw-r--r--libpod/container_copy_common.go213
-rw-r--r--libpod/container_copy_freebsd.go13
-rw-r--r--libpod/container_copy_linux.go222
-rw-r--r--libpod/container_copy_unsupported.go4
-rw-r--r--libpod/container_stat_common.go155
-rw-r--r--libpod/container_stat_freebsd.go13
-rw-r--r--libpod/container_stat_linux.go159
-rw-r--r--libpod/container_stat_unsupported.go4
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