aboutsummaryrefslogtreecommitdiff
path: root/libpod
diff options
context:
space:
mode:
Diffstat (limited to 'libpod')
-rw-r--r--libpod/boltdb_state.go53
-rw-r--r--libpod/boltdb_state_internal.go14
-rw-r--r--libpod/container.go10
-rw-r--r--libpod/container_api.go5
-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_exec.go3
-rw-r--r--libpod/container_graph.go91
-rw-r--r--libpod/container_inspect.go297
-rw-r--r--libpod/container_inspect_freebsd.go17
-rw-r--r--libpod/container_inspect_linux.go306
-rw-r--r--libpod/container_internal.go7
-rw-r--r--libpod/container_internal_common.go21
-rw-r--r--libpod/container_internal_test.go4
-rw-r--r--libpod/container_path_resolution.go30
-rw-r--r--libpod/container_path_resolution_test.go28
-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
-rw-r--r--libpod/define/config.go4
-rw-r--r--libpod/define/volume_inspect.go3
-rw-r--r--libpod/events.go2
-rw-r--r--libpod/events/config.go2
-rw-r--r--libpod/events/events.go8
-rw-r--r--libpod/events/journal_linux.go6
-rw-r--r--libpod/events/logfile.go5
-rw-r--r--libpod/events/logfile_test.go17
-rw-r--r--libpod/healthcheck.go7
-rw-r--r--libpod/networking_linux.go5
-rw-r--r--libpod/networking_machine.go3
-rw-r--r--libpod/networking_slirp4netns.go11
-rw-r--r--libpod/oci_conmon_common.go13
-rw-r--r--libpod/oci_conmon_exec_common.go5
-rw-r--r--libpod/plugin/volume_api.go14
-rw-r--r--libpod/pod_api.go2
-rw-r--r--libpod/runtime.go3
-rw-r--r--libpod/runtime_cstorage.go11
-rw-r--r--libpod/runtime_ctr.go54
-rw-r--r--libpod/runtime_img.go26
-rw-r--r--libpod/runtime_migrate.go3
-rw-r--r--libpod/runtime_pod_linux.go92
-rw-r--r--libpod/runtime_volume_linux.go63
-rw-r--r--libpod/state.go8
-rw-r--r--libpod/state_test.go3
-rw-r--r--libpod/volume.go17
-rw-r--r--libpod/volume_inspect.go1
-rw-r--r--libpod/volume_internal.go5
-rw-r--r--libpod/volume_internal_linux.go16
51 files changed, 1279 insertions, 799 deletions
diff --git a/libpod/boltdb_state.go b/libpod/boltdb_state.go
index 4fd95a3cf..77c234892 100644
--- a/libpod/boltdb_state.go
+++ b/libpod/boltdb_state.go
@@ -109,6 +109,7 @@ func NewBoltState(path string, runtime *Runtime) (State, error) {
runtimeConfigBkt,
exitCodeBkt,
exitCodeTimeStampBkt,
+ volCtrsBkt,
}
// Does the DB need an update?
@@ -2551,6 +2552,11 @@ func (s *BoltState) AddVolume(volume *Volume) error {
return err
}
+ volCtrsBkt, err := getVolumeContainersBucket(tx)
+ if err != nil {
+ return err
+ }
+
// Check if we already have a volume with the given name
volExists := allVolsBkt.Get(volName)
if volExists != nil {
@@ -2580,6 +2586,12 @@ func (s *BoltState) AddVolume(volume *Volume) error {
}
}
+ if volume.config.StorageID != "" {
+ if err := volCtrsBkt.Put([]byte(volume.config.StorageID), volName); err != nil {
+ return fmt.Errorf("storing volume %s container ID in DB: %w", volume.Name(), err)
+ }
+ }
+
if err := allVolsBkt.Put(volName, volName); err != nil {
return fmt.Errorf("storing volume %s in all volumes bucket in DB: %w", volume.Name(), err)
}
@@ -2619,6 +2631,11 @@ func (s *BoltState) RemoveVolume(volume *Volume) error {
return err
}
+ volCtrIDBkt, err := getVolumeContainersBucket(tx)
+ if err != nil {
+ return err
+ }
+
// Check if the volume exists
volDB := volBkt.Bucket(volName)
if volDB == nil {
@@ -2665,6 +2682,11 @@ func (s *BoltState) RemoveVolume(volume *Volume) error {
if err := volBkt.DeleteBucket(volName); err != nil {
return fmt.Errorf("removing volume %s from DB: %w", volume.Name(), err)
}
+ if volume.config.StorageID != "" {
+ if err := volCtrIDBkt.Delete([]byte(volume.config.StorageID)); err != nil {
+ return fmt.Errorf("removing volume %s container ID from DB: %w", volume.Name(), err)
+ }
+ }
return nil
})
@@ -3618,3 +3640,34 @@ func (s *BoltState) AllPods() ([]*Pod, error) {
return pods, nil
}
+
+// ContainerIDIsVolume checks if the given c/storage container ID is used as
+// backing storage for a volume.
+func (s *BoltState) ContainerIDIsVolume(id string) (bool, error) {
+ if !s.valid {
+ return false, define.ErrDBClosed
+ }
+
+ isVol := false
+
+ db, err := s.getDBCon()
+ if err != nil {
+ return false, err
+ }
+ defer s.deferredCloseDBCon(db)
+
+ err = db.View(func(tx *bolt.Tx) error {
+ volCtrsBkt, err := getVolumeContainersBucket(tx)
+ if err != nil {
+ return err
+ }
+
+ volName := volCtrsBkt.Get([]byte(id))
+ if volName != nil {
+ isVol = true
+ }
+
+ return nil
+ })
+ return isVol, err
+}
diff --git a/libpod/boltdb_state_internal.go b/libpod/boltdb_state_internal.go
index 87f1fa4eb..7f2d49b31 100644
--- a/libpod/boltdb_state_internal.go
+++ b/libpod/boltdb_state_internal.go
@@ -28,6 +28,7 @@ const (
execName = "exec"
aliasesName = "aliases"
runtimeConfigName = "runtime-config"
+ volumeCtrsName = "volume-ctrs"
exitCodeName = "exit-code"
exitCodeTimeStampName = "exit-code-time-stamp"
@@ -67,6 +68,7 @@ var (
dependenciesBkt = []byte(dependenciesName)
volDependenciesBkt = []byte(volCtrDependencies)
networksBkt = []byte(networksName)
+ volCtrsBkt = []byte(volumeCtrsName)
exitCodeBkt = []byte(exitCodeName)
exitCodeTimeStampBkt = []byte(exitCodeTimeStampName)
@@ -384,6 +386,14 @@ func getExitCodeTimeStampBucket(tx *bolt.Tx) (*bolt.Bucket, error) {
return bkt, nil
}
+func getVolumeContainersBucket(tx *bolt.Tx) (*bolt.Bucket, error) {
+ bkt := tx.Bucket(volCtrsBkt)
+ if bkt == nil {
+ return nil, fmt.Errorf("volume containers bucket not found in DB: %w", define.ErrDBBadConfig)
+ }
+ return bkt, nil
+}
+
func (s *BoltState) getContainerConfigFromDB(id []byte, config *ContainerConfig, ctrsBkt *bolt.Bucket) error {
ctrBkt := ctrsBkt.Bucket(id)
if ctrBkt == nil {
@@ -528,6 +538,9 @@ func (s *BoltState) getVolumeFromDB(name []byte, volume *Volume, volBkt *bolt.Bu
}
}
+ // Need this for UsesVolumeDriver() so set it now.
+ volume.runtime = s.runtime
+
// Retrieve volume driver
if volume.UsesVolumeDriver() {
plugin, err := s.runtime.getVolumePlugin(volume.config)
@@ -550,7 +563,6 @@ func (s *BoltState) getVolumeFromDB(name []byte, volume *Volume, volBkt *bolt.Bu
}
volume.lock = lock
- volume.runtime = s.runtime
volume.valid = true
return nil
diff --git a/libpod/container.go b/libpod/container.go
index bdedafd22..a4eb33c49 100644
--- a/libpod/container.go
+++ b/libpod/container.go
@@ -3,7 +3,7 @@ package libpod
import (
"bytes"
"fmt"
- "io/ioutil"
+ "io"
"net"
"os"
"strings"
@@ -351,12 +351,14 @@ func (c *Container) specFromState() (*spec.Spec, error) {
if f, err := os.Open(c.state.ConfigPath); err == nil {
returnSpec = new(spec.Spec)
- content, err := ioutil.ReadAll(f)
+ content, err := io.ReadAll(f)
if err != nil {
return nil, fmt.Errorf("reading container config: %w", err)
}
if err := json.Unmarshal(content, &returnSpec); err != nil {
- return nil, fmt.Errorf("unmarshalling container config: %w", err)
+ // Malformed spec, just use c.config.Spec instead
+ logrus.Warnf("Error unmarshalling container %s config: %v", c.ID(), err)
+ return c.config.Spec, nil
}
} else if !os.IsNotExist(err) {
// ignore when the file does not exist
@@ -988,7 +990,7 @@ func (c *Container) cGroupPath() (string, error) {
// the lookup.
// See #10602 for more details.
procPath := fmt.Sprintf("/proc/%d/cgroup", c.state.PID)
- lines, err := ioutil.ReadFile(procPath)
+ lines, err := os.ReadFile(procPath)
if err != nil {
// If the file doesn't exist, it means the container could have been terminated
// so report it.
diff --git a/libpod/container_api.go b/libpod/container_api.go
index dd47b4d12..be0ca0128 100644
--- a/libpod/container_api.go
+++ b/libpod/container_api.go
@@ -5,7 +5,6 @@ import (
"errors"
"fmt"
"io"
- "io/ioutil"
"net/http"
"os"
"sync"
@@ -479,7 +478,7 @@ func (c *Container) AddArtifact(name string, data []byte) error {
return define.ErrCtrRemoved
}
- return ioutil.WriteFile(c.getArtifactPath(name), data, 0o740)
+ return os.WriteFile(c.getArtifactPath(name), data, 0o740)
}
// GetArtifact reads the specified artifact file from the container
@@ -488,7 +487,7 @@ func (c *Container) GetArtifact(name string) ([]byte, error) {
return nil, define.ErrCtrRemoved
}
- return ioutil.ReadFile(c.getArtifactPath(name))
+ return os.ReadFile(c.getArtifactPath(name))
}
// RemoveArtifact deletes the specified artifacts file
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_exec.go b/libpod/container_exec.go
index 3a2cba52f..7896d1932 100644
--- a/libpod/container_exec.go
+++ b/libpod/container_exec.go
@@ -4,7 +4,6 @@ import (
"context"
"errors"
"fmt"
- "io/ioutil"
"net/http"
"os"
"path/filepath"
@@ -928,7 +927,7 @@ func (c *Container) readExecExitCode(sessionID string) (int, error) {
if err != nil {
return -1, err
}
- ec, err := ioutil.ReadFile(exitFile)
+ ec, err := os.ReadFile(exitFile)
if err != nil {
return -1, err
}
diff --git a/libpod/container_graph.go b/libpod/container_graph.go
index 96d61b756..d43579e4a 100644
--- a/libpod/container_graph.go
+++ b/libpod/container_graph.go
@@ -281,3 +281,94 @@ func startNode(ctx context.Context, node *containerNode, setError bool, ctrError
startNode(ctx, successor, ctrErrored, ctrErrors, ctrsVisited, restart)
}
}
+
+// Visit a node on the container graph and remove it, or set an error if it
+// failed to remove. Only intended for use in pod removal; do *not* use when
+// removing individual containers.
+// All containers are assumed to be *UNLOCKED* on running this function.
+// Container locks will be acquired as necessary.
+// Pod and infraID are optional. If a pod is given it must be *LOCKED*.
+func removeNode(ctx context.Context, node *containerNode, pod *Pod, force bool, timeout *uint, setError bool, ctrErrors map[string]error, ctrsVisited map[string]bool, ctrNamedVolumes map[string]*ContainerNamedVolume) {
+ // If we already visited this node, we're done.
+ if ctrsVisited[node.id] {
+ return
+ }
+
+ // Someone who depends on us failed.
+ // Mark us as failed and recurse.
+ if setError {
+ ctrsVisited[node.id] = true
+ ctrErrors[node.id] = fmt.Errorf("a container that depends on container %s could not be removed: %w", node.id, define.ErrCtrStateInvalid)
+
+ // Hit anyone who depends on us, set errors there as well.
+ for _, successor := range node.dependsOn {
+ removeNode(ctx, successor, pod, force, timeout, true, ctrErrors, ctrsVisited, ctrNamedVolumes)
+ }
+ }
+
+ // Does anyone still depend on us?
+ // Cannot remove if true. Once all our dependencies have been removed,
+ // we will be removed.
+ for _, dep := range node.dependedOn {
+ // The container that depends on us hasn't been removed yet.
+ // OK to continue on
+ if ok := ctrsVisited[dep.id]; !ok {
+ return
+ }
+ }
+
+ // Going to try to remove the node, mark us as visited
+ ctrsVisited[node.id] = true
+
+ ctrErrored := false
+
+ // Verify that all that depend on us are gone.
+ // Graph traversal should guarantee this is true, but this isn't that
+ // expensive, and it's better to be safe.
+ for _, dep := range node.dependedOn {
+ if _, err := node.container.runtime.GetContainer(dep.id); err == nil {
+ ctrErrored = true
+ ctrErrors[node.id] = fmt.Errorf("a container that depends on container %s still exists: %w", node.id, define.ErrDepExists)
+ }
+ }
+
+ // Lock the container
+ node.container.lock.Lock()
+
+ // Gate all subsequent bits behind a ctrErrored check - we don't want to
+ // proceed if a previous step failed.
+ if !ctrErrored {
+ if err := node.container.syncContainer(); err != nil {
+ ctrErrored = true
+ ctrErrors[node.id] = err
+ }
+ }
+
+ if !ctrErrored {
+ for _, vol := range node.container.config.NamedVolumes {
+ ctrNamedVolumes[vol.Name] = vol
+ }
+
+ if pod != nil && pod.state.InfraContainerID == node.id {
+ pod.state.InfraContainerID = ""
+ if err := pod.save(); err != nil {
+ ctrErrored = true
+ ctrErrors[node.id] = fmt.Errorf("error removing infra container %s from pod %s: %w", node.id, pod.ID(), err)
+ }
+ }
+ }
+
+ if !ctrErrored {
+ if err := node.container.runtime.removeContainer(ctx, node.container, force, false, true, false, timeout); err != nil {
+ ctrErrored = true
+ ctrErrors[node.id] = err
+ }
+ }
+
+ node.container.lock.Unlock()
+
+ // Recurse to anyone who we depend on and remove them
+ for _, successor := range node.dependsOn {
+ removeNode(ctx, successor, pod, force, timeout, ctrErrored, ctrErrors, ctrsVisited, ctrNamedVolumes)
+ }
+}
diff --git a/libpod/container_inspect.go b/libpod/container_inspect.go
index b72d843b6..e4089efa6 100644
--- a/libpod/container_inspect.go
+++ b/libpod/container_inspect.go
@@ -3,20 +3,15 @@ package libpod
import (
"errors"
"fmt"
- "sort"
"strings"
- "github.com/containers/common/pkg/config"
"github.com/containers/podman/v4/libpod/define"
"github.com/containers/podman/v4/libpod/driver"
"github.com/containers/podman/v4/pkg/util"
"github.com/containers/storage/types"
units "github.com/docker/go-units"
spec "github.com/opencontainers/runtime-spec/specs-go"
- "github.com/opencontainers/runtime-tools/generate"
- "github.com/opencontainers/runtime-tools/validate"
"github.com/sirupsen/logrus"
- "github.com/syndtr/gocapability/capability"
)
// inspectLocked inspects a container for low-level information.
@@ -163,8 +158,6 @@ func (c *Container) getContainerInspectData(size bool, driverData *define.Driver
Driver: driverData.Name,
MountLabel: config.MountLabel,
ProcessLabel: config.ProcessLabel,
- EffectiveCaps: ctrSpec.Process.Capabilities.Effective,
- BoundingCaps: ctrSpec.Process.Capabilities.Bounding,
AppArmorProfile: ctrSpec.Process.ApparmorProfile,
ExecIDs: execIDs,
GraphDriver: driverData,
@@ -173,6 +166,10 @@ func (c *Container) getContainerInspectData(size bool, driverData *define.Driver
IsInfra: c.IsInfra(),
IsService: c.IsService(),
}
+ if ctrSpec.Process.Capabilities != nil {
+ data.EffectiveCaps = ctrSpec.Process.Capabilities.Effective
+ data.BoundingCaps = ctrSpec.Process.Capabilities.Bounding
+ }
if c.state.ConfigPath != "" {
data.OCIConfigPath = c.state.ConfigPath
@@ -484,11 +481,6 @@ func (c *Container) generateInspectContainerHostConfig(ctrSpec *spec.Spec, named
hostConfig.ShmSize = c.config.ShmSize
hostConfig.Runtime = "oci"
- // This is very expensive to initialize.
- // So we don't want to initialize it unless we absolutely have to - IE,
- // there are things that require a major:minor to path translation.
- var deviceNodes map[string]string
-
// Annotations
if ctrSpec.Annotations != nil {
hostConfig.ContainerIDFile = ctrSpec.Annotations[define.InspectAnnotationCIDFile]
@@ -506,109 +498,8 @@ func (c *Container) generateInspectContainerHostConfig(ctrSpec *spec.Spec, named
}
}
- // Resource limits
- if ctrSpec.Linux != nil {
- if ctrSpec.Linux.Resources != nil {
- if ctrSpec.Linux.Resources.CPU != nil {
- if ctrSpec.Linux.Resources.CPU.Shares != nil {
- hostConfig.CpuShares = *ctrSpec.Linux.Resources.CPU.Shares
- }
- if ctrSpec.Linux.Resources.CPU.Period != nil {
- hostConfig.CpuPeriod = *ctrSpec.Linux.Resources.CPU.Period
- }
- if ctrSpec.Linux.Resources.CPU.Quota != nil {
- hostConfig.CpuQuota = *ctrSpec.Linux.Resources.CPU.Quota
- }
- if ctrSpec.Linux.Resources.CPU.RealtimePeriod != nil {
- hostConfig.CpuRealtimePeriod = *ctrSpec.Linux.Resources.CPU.RealtimePeriod
- }
- if ctrSpec.Linux.Resources.CPU.RealtimeRuntime != nil {
- hostConfig.CpuRealtimeRuntime = *ctrSpec.Linux.Resources.CPU.RealtimeRuntime
- }
- hostConfig.CpusetCpus = ctrSpec.Linux.Resources.CPU.Cpus
- hostConfig.CpusetMems = ctrSpec.Linux.Resources.CPU.Mems
- }
- if ctrSpec.Linux.Resources.Memory != nil {
- if ctrSpec.Linux.Resources.Memory.Limit != nil {
- hostConfig.Memory = *ctrSpec.Linux.Resources.Memory.Limit
- }
- if ctrSpec.Linux.Resources.Memory.Reservation != nil {
- hostConfig.MemoryReservation = *ctrSpec.Linux.Resources.Memory.Reservation
- }
- if ctrSpec.Linux.Resources.Memory.Swap != nil {
- hostConfig.MemorySwap = *ctrSpec.Linux.Resources.Memory.Swap
- }
- if ctrSpec.Linux.Resources.Memory.Swappiness != nil {
- hostConfig.MemorySwappiness = int64(*ctrSpec.Linux.Resources.Memory.Swappiness)
- } else {
- // Swappiness has a default of -1
- hostConfig.MemorySwappiness = -1
- }
- if ctrSpec.Linux.Resources.Memory.DisableOOMKiller != nil {
- hostConfig.OomKillDisable = *ctrSpec.Linux.Resources.Memory.DisableOOMKiller
- }
- }
- if ctrSpec.Linux.Resources.Pids != nil {
- hostConfig.PidsLimit = ctrSpec.Linux.Resources.Pids.Limit
- }
- hostConfig.CgroupConf = ctrSpec.Linux.Resources.Unified
- if ctrSpec.Linux.Resources.BlockIO != nil {
- if ctrSpec.Linux.Resources.BlockIO.Weight != nil {
- hostConfig.BlkioWeight = *ctrSpec.Linux.Resources.BlockIO.Weight
- }
- hostConfig.BlkioWeightDevice = []define.InspectBlkioWeightDevice{}
- for _, dev := range ctrSpec.Linux.Resources.BlockIO.WeightDevice {
- key := fmt.Sprintf("%d:%d", dev.Major, dev.Minor)
- // TODO: how do we handle LeafWeight vs
- // Weight? For now, ignore anything
- // without Weight set.
- if dev.Weight == nil {
- logrus.Infof("Ignoring weight device %s as it lacks a weight", key)
- continue
- }
- if deviceNodes == nil {
- nodes, err := util.FindDeviceNodes()
- if err != nil {
- return nil, err
- }
- deviceNodes = nodes
- }
- path, ok := deviceNodes[key]
- if !ok {
- logrus.Infof("Could not locate weight device %s in system devices", key)
- continue
- }
- weightDev := define.InspectBlkioWeightDevice{}
- weightDev.Path = path
- weightDev.Weight = *dev.Weight
- hostConfig.BlkioWeightDevice = append(hostConfig.BlkioWeightDevice, weightDev)
- }
-
- readBps, err := blkioDeviceThrottle(deviceNodes, ctrSpec.Linux.Resources.BlockIO.ThrottleReadBpsDevice)
- if err != nil {
- return nil, err
- }
- hostConfig.BlkioDeviceReadBps = readBps
-
- writeBps, err := blkioDeviceThrottle(deviceNodes, ctrSpec.Linux.Resources.BlockIO.ThrottleWriteBpsDevice)
- if err != nil {
- return nil, err
- }
- hostConfig.BlkioDeviceWriteBps = writeBps
-
- readIops, err := blkioDeviceThrottle(deviceNodes, ctrSpec.Linux.Resources.BlockIO.ThrottleReadIOPSDevice)
- if err != nil {
- return nil, err
- }
- hostConfig.BlkioDeviceReadIOps = readIops
-
- writeIops, err := blkioDeviceThrottle(deviceNodes, ctrSpec.Linux.Resources.BlockIO.ThrottleWriteIOPSDevice)
- if err != nil {
- return nil, err
- }
- hostConfig.BlkioDeviceWriteIOps = writeIops
- }
- }
+ if err := c.platformInspectContainerHostConfig(ctrSpec, hostConfig); err != nil {
+ return nil, err
}
// NanoCPUs.
@@ -659,182 +550,6 @@ func (c *Container) generateInspectContainerHostConfig(ctrSpec *spec.Spec, named
hostConfig.PortBindings = make(map[string][]define.InspectHostPort)
}
- // Cap add and cap drop.
- // We need a default set of capabilities to compare against.
- // The OCI generate package has one, and is commonly used, so we'll
- // use it.
- // Problem: there are 5 sets of capabilities.
- // Use the bounding set for this computation, it's the most encompassing
- // (but still not perfect).
- capAdd := []string{}
- capDrop := []string{}
- // No point in continuing if we got a spec without a Process block...
- if ctrSpec.Process != nil {
- // Max an O(1) lookup table for default bounding caps.
- boundingCaps := make(map[string]bool)
- g, err := generate.New("linux")
- if err != nil {
- return nil, err
- }
- if !hostConfig.Privileged {
- for _, cap := range g.Config.Process.Capabilities.Bounding {
- boundingCaps[cap] = true
- }
- } else {
- // If we are privileged, use all caps.
- for _, cap := range capability.List() {
- if g.HostSpecific && cap > validate.LastCap() {
- continue
- }
- boundingCaps[fmt.Sprintf("CAP_%s", strings.ToUpper(cap.String()))] = true
- }
- }
- // Iterate through spec caps.
- // If it's not in default bounding caps, it was added.
- // If it is, delete from the default set. Whatever remains after
- // we finish are the dropped caps.
- for _, cap := range ctrSpec.Process.Capabilities.Bounding {
- if _, ok := boundingCaps[cap]; ok {
- delete(boundingCaps, cap)
- } else {
- capAdd = append(capAdd, cap)
- }
- }
- for cap := range boundingCaps {
- capDrop = append(capDrop, cap)
- }
- // Sort CapDrop so it displays in consistent order (GH #9490)
- sort.Strings(capDrop)
- }
- hostConfig.CapAdd = capAdd
- hostConfig.CapDrop = capDrop
- switch {
- case c.config.IPCNsCtr != "":
- hostConfig.IpcMode = fmt.Sprintf("container:%s", c.config.IPCNsCtr)
- case ctrSpec.Linux != nil:
- // Locate the spec's IPC namespace.
- // If there is none, it's ipc=host.
- // If there is one and it has a path, it's "ns:".
- // If no path, it's default - the empty string.
- for _, ns := range ctrSpec.Linux.Namespaces {
- if ns.Type == spec.IPCNamespace {
- if ns.Path != "" {
- hostConfig.IpcMode = fmt.Sprintf("ns:%s", ns.Path)
- } else {
- break
- }
- }
- }
- case c.config.NoShm:
- hostConfig.IpcMode = "none"
- case c.config.NoShmShare:
- hostConfig.IpcMode = "private"
- }
- if hostConfig.IpcMode == "" {
- hostConfig.IpcMode = "shareable"
- }
-
- // Cgroup namespace mode
- cgroupMode := ""
- if c.config.CgroupNsCtr != "" {
- cgroupMode = fmt.Sprintf("container:%s", c.config.CgroupNsCtr)
- } else if ctrSpec.Linux != nil {
- // Locate the spec's cgroup namespace
- // If there is none, it's cgroup=host.
- // If there is one and it has a path, it's "ns:".
- // If there is no path, it's private.
- for _, ns := range ctrSpec.Linux.Namespaces {
- if ns.Type == spec.CgroupNamespace {
- if ns.Path != "" {
- cgroupMode = fmt.Sprintf("ns:%s", ns.Path)
- } else {
- cgroupMode = "private"
- }
- }
- }
- if cgroupMode == "" {
- cgroupMode = "host"
- }
- }
- hostConfig.CgroupMode = cgroupMode
-
- // Cgroup parent
- // Need to check if it's the default, and not print if so.
- defaultCgroupParent := ""
- switch c.CgroupManager() {
- case config.CgroupfsCgroupsManager:
- defaultCgroupParent = CgroupfsDefaultCgroupParent
- case config.SystemdCgroupsManager:
- defaultCgroupParent = SystemdDefaultCgroupParent
- }
- if c.config.CgroupParent != defaultCgroupParent {
- hostConfig.CgroupParent = c.config.CgroupParent
- }
- hostConfig.CgroupManager = c.CgroupManager()
-
- // PID namespace mode
- pidMode := ""
- if c.config.PIDNsCtr != "" {
- pidMode = fmt.Sprintf("container:%s", c.config.PIDNsCtr)
- } else if ctrSpec.Linux != nil {
- // Locate the spec's PID namespace.
- // If there is none, it's pid=host.
- // If there is one and it has a path, it's "ns:".
- // If there is no path, it's default - the empty string.
- for _, ns := range ctrSpec.Linux.Namespaces {
- if ns.Type == spec.PIDNamespace {
- if ns.Path != "" {
- pidMode = fmt.Sprintf("ns:%s", ns.Path)
- } else {
- pidMode = "private"
- }
- break
- }
- }
- if pidMode == "" {
- pidMode = "host"
- }
- }
- hostConfig.PidMode = pidMode
-
- // UTS namespace mode
- utsMode := c.NamespaceMode(spec.UTSNamespace, ctrSpec)
-
- hostConfig.UTSMode = utsMode
-
- // User namespace mode
- usernsMode := ""
- if c.config.UserNsCtr != "" {
- usernsMode = fmt.Sprintf("container:%s", c.config.UserNsCtr)
- } else if ctrSpec.Linux != nil {
- // Locate the spec's user namespace.
- // If there is none, it's default - the empty string.
- // If there is one, it's "private" if no path, or "ns:" if
- // there's a path.
-
- for _, ns := range ctrSpec.Linux.Namespaces {
- if ns.Type == spec.UserNamespace {
- if ns.Path != "" {
- usernsMode = fmt.Sprintf("ns:%s", ns.Path)
- } else {
- usernsMode = "private"
- }
- }
- }
- }
- hostConfig.UsernsMode = usernsMode
- if c.config.IDMappings.UIDMap != nil && c.config.IDMappings.GIDMap != nil {
- hostConfig.IDMappings = generateIDMappings(c.config.IDMappings)
- }
- // Devices
- // Do not include if privileged - assumed that all devices will be
- // included.
- var err error
- hostConfig.Devices, err = c.GetDevices(hostConfig.Privileged, *ctrSpec, deviceNodes)
- if err != nil {
- return nil, err
- }
-
// Ulimits
hostConfig.Ulimits = []define.InspectUlimit{}
if ctrSpec.Process != nil {
diff --git a/libpod/container_inspect_freebsd.go b/libpod/container_inspect_freebsd.go
new file mode 100644
index 000000000..8b4e8df87
--- /dev/null
+++ b/libpod/container_inspect_freebsd.go
@@ -0,0 +1,17 @@
+package libpod
+
+import (
+ "github.com/containers/podman/v4/libpod/define"
+ spec "github.com/opencontainers/runtime-spec/specs-go"
+)
+
+func (c *Container) platformInspectContainerHostConfig(ctrSpec *spec.Spec, hostConfig *define.InspectContainerHostConfig) error {
+ // Not sure what to put here. FreeBSD jails use pids from the
+ // global pool but can only see their own pids.
+ hostConfig.PidMode = "host"
+
+ // UTS namespace mode
+ hostConfig.UTSMode = c.NamespaceMode(spec.UTSNamespace, ctrSpec)
+
+ return nil
+}
diff --git a/libpod/container_inspect_linux.go b/libpod/container_inspect_linux.go
new file mode 100644
index 000000000..355690d70
--- /dev/null
+++ b/libpod/container_inspect_linux.go
@@ -0,0 +1,306 @@
+package libpod
+
+import (
+ "fmt"
+ "sort"
+ "strings"
+
+ "github.com/containers/common/pkg/config"
+ "github.com/containers/podman/v4/libpod/define"
+ "github.com/containers/podman/v4/pkg/util"
+ spec "github.com/opencontainers/runtime-spec/specs-go"
+ "github.com/opencontainers/runtime-tools/generate"
+ "github.com/opencontainers/runtime-tools/validate"
+ "github.com/sirupsen/logrus"
+ "github.com/syndtr/gocapability/capability"
+)
+
+func (c *Container) platformInspectContainerHostConfig(ctrSpec *spec.Spec, hostConfig *define.InspectContainerHostConfig) error {
+ // This is very expensive to initialize.
+ // So we don't want to initialize it unless we absolutely have to - IE,
+ // there are things that require a major:minor to path translation.
+ var deviceNodes map[string]string
+
+ // Resource limits
+ if ctrSpec.Linux != nil {
+ if ctrSpec.Linux.Resources != nil {
+ if ctrSpec.Linux.Resources.CPU != nil {
+ if ctrSpec.Linux.Resources.CPU.Shares != nil {
+ hostConfig.CpuShares = *ctrSpec.Linux.Resources.CPU.Shares
+ }
+ if ctrSpec.Linux.Resources.CPU.Period != nil {
+ hostConfig.CpuPeriod = *ctrSpec.Linux.Resources.CPU.Period
+ }
+ if ctrSpec.Linux.Resources.CPU.Quota != nil {
+ hostConfig.CpuQuota = *ctrSpec.Linux.Resources.CPU.Quota
+ }
+ if ctrSpec.Linux.Resources.CPU.RealtimePeriod != nil {
+ hostConfig.CpuRealtimePeriod = *ctrSpec.Linux.Resources.CPU.RealtimePeriod
+ }
+ if ctrSpec.Linux.Resources.CPU.RealtimeRuntime != nil {
+ hostConfig.CpuRealtimeRuntime = *ctrSpec.Linux.Resources.CPU.RealtimeRuntime
+ }
+ hostConfig.CpusetCpus = ctrSpec.Linux.Resources.CPU.Cpus
+ hostConfig.CpusetMems = ctrSpec.Linux.Resources.CPU.Mems
+ }
+ if ctrSpec.Linux.Resources.Memory != nil {
+ if ctrSpec.Linux.Resources.Memory.Limit != nil {
+ hostConfig.Memory = *ctrSpec.Linux.Resources.Memory.Limit
+ }
+ if ctrSpec.Linux.Resources.Memory.Reservation != nil {
+ hostConfig.MemoryReservation = *ctrSpec.Linux.Resources.Memory.Reservation
+ }
+ if ctrSpec.Linux.Resources.Memory.Swap != nil {
+ hostConfig.MemorySwap = *ctrSpec.Linux.Resources.Memory.Swap
+ }
+ if ctrSpec.Linux.Resources.Memory.Swappiness != nil {
+ hostConfig.MemorySwappiness = int64(*ctrSpec.Linux.Resources.Memory.Swappiness)
+ } else {
+ // Swappiness has a default of -1
+ hostConfig.MemorySwappiness = -1
+ }
+ if ctrSpec.Linux.Resources.Memory.DisableOOMKiller != nil {
+ hostConfig.OomKillDisable = *ctrSpec.Linux.Resources.Memory.DisableOOMKiller
+ }
+ }
+ if ctrSpec.Linux.Resources.Pids != nil {
+ hostConfig.PidsLimit = ctrSpec.Linux.Resources.Pids.Limit
+ }
+ hostConfig.CgroupConf = ctrSpec.Linux.Resources.Unified
+ if ctrSpec.Linux.Resources.BlockIO != nil {
+ if ctrSpec.Linux.Resources.BlockIO.Weight != nil {
+ hostConfig.BlkioWeight = *ctrSpec.Linux.Resources.BlockIO.Weight
+ }
+ hostConfig.BlkioWeightDevice = []define.InspectBlkioWeightDevice{}
+ for _, dev := range ctrSpec.Linux.Resources.BlockIO.WeightDevice {
+ key := fmt.Sprintf("%d:%d", dev.Major, dev.Minor)
+ // TODO: how do we handle LeafWeight vs
+ // Weight? For now, ignore anything
+ // without Weight set.
+ if dev.Weight == nil {
+ logrus.Infof("Ignoring weight device %s as it lacks a weight", key)
+ continue
+ }
+ if deviceNodes == nil {
+ nodes, err := util.FindDeviceNodes()
+ if err != nil {
+ return err
+ }
+ deviceNodes = nodes
+ }
+ path, ok := deviceNodes[key]
+ if !ok {
+ logrus.Infof("Could not locate weight device %s in system devices", key)
+ continue
+ }
+ weightDev := define.InspectBlkioWeightDevice{}
+ weightDev.Path = path
+ weightDev.Weight = *dev.Weight
+ hostConfig.BlkioWeightDevice = append(hostConfig.BlkioWeightDevice, weightDev)
+ }
+
+ readBps, err := blkioDeviceThrottle(deviceNodes, ctrSpec.Linux.Resources.BlockIO.ThrottleReadBpsDevice)
+ if err != nil {
+ return err
+ }
+ hostConfig.BlkioDeviceReadBps = readBps
+
+ writeBps, err := blkioDeviceThrottle(deviceNodes, ctrSpec.Linux.Resources.BlockIO.ThrottleWriteBpsDevice)
+ if err != nil {
+ return err
+ }
+ hostConfig.BlkioDeviceWriteBps = writeBps
+
+ readIops, err := blkioDeviceThrottle(deviceNodes, ctrSpec.Linux.Resources.BlockIO.ThrottleReadIOPSDevice)
+ if err != nil {
+ return err
+ }
+ hostConfig.BlkioDeviceReadIOps = readIops
+
+ writeIops, err := blkioDeviceThrottle(deviceNodes, ctrSpec.Linux.Resources.BlockIO.ThrottleWriteIOPSDevice)
+ if err != nil {
+ return err
+ }
+ hostConfig.BlkioDeviceWriteIOps = writeIops
+ }
+ }
+ }
+
+ // Cap add and cap drop.
+ // We need a default set of capabilities to compare against.
+ // The OCI generate package has one, and is commonly used, so we'll
+ // use it.
+ // Problem: there are 5 sets of capabilities.
+ // Use the bounding set for this computation, it's the most encompassing
+ // (but still not perfect).
+ capAdd := []string{}
+ capDrop := []string{}
+ // No point in continuing if we got a spec without a Process block...
+ if ctrSpec.Process != nil {
+ // Max an O(1) lookup table for default bounding caps.
+ boundingCaps := make(map[string]bool)
+ g, err := generate.New("linux")
+ if err != nil {
+ return err
+ }
+ if !hostConfig.Privileged {
+ for _, cap := range g.Config.Process.Capabilities.Bounding {
+ boundingCaps[cap] = true
+ }
+ } else {
+ // If we are privileged, use all caps.
+ for _, cap := range capability.List() {
+ if g.HostSpecific && cap > validate.LastCap() {
+ continue
+ }
+ boundingCaps[fmt.Sprintf("CAP_%s", strings.ToUpper(cap.String()))] = true
+ }
+ }
+ // Iterate through spec caps.
+ // If it's not in default bounding caps, it was added.
+ // If it is, delete from the default set. Whatever remains after
+ // we finish are the dropped caps.
+ for _, cap := range ctrSpec.Process.Capabilities.Bounding {
+ if _, ok := boundingCaps[cap]; ok {
+ delete(boundingCaps, cap)
+ } else {
+ capAdd = append(capAdd, cap)
+ }
+ }
+ for cap := range boundingCaps {
+ capDrop = append(capDrop, cap)
+ }
+ // Sort CapDrop so it displays in consistent order (GH #9490)
+ sort.Strings(capDrop)
+ }
+ hostConfig.CapAdd = capAdd
+ hostConfig.CapDrop = capDrop
+ switch {
+ case c.config.IPCNsCtr != "":
+ hostConfig.IpcMode = fmt.Sprintf("container:%s", c.config.IPCNsCtr)
+ case ctrSpec.Linux != nil:
+ // Locate the spec's IPC namespace.
+ // If there is none, it's ipc=host.
+ // If there is one and it has a path, it's "ns:".
+ // If no path, it's default - the empty string.
+ for _, ns := range ctrSpec.Linux.Namespaces {
+ if ns.Type == spec.IPCNamespace {
+ if ns.Path != "" {
+ hostConfig.IpcMode = fmt.Sprintf("ns:%s", ns.Path)
+ } else {
+ break
+ }
+ }
+ }
+ case c.config.NoShm:
+ hostConfig.IpcMode = "none"
+ case c.config.NoShmShare:
+ hostConfig.IpcMode = "private"
+ }
+ if hostConfig.IpcMode == "" {
+ hostConfig.IpcMode = "shareable"
+ }
+
+ // Cgroup namespace mode
+ cgroupMode := ""
+ if c.config.CgroupNsCtr != "" {
+ cgroupMode = fmt.Sprintf("container:%s", c.config.CgroupNsCtr)
+ } else if ctrSpec.Linux != nil {
+ // Locate the spec's cgroup namespace
+ // If there is none, it's cgroup=host.
+ // If there is one and it has a path, it's "ns:".
+ // If there is no path, it's private.
+ for _, ns := range ctrSpec.Linux.Namespaces {
+ if ns.Type == spec.CgroupNamespace {
+ if ns.Path != "" {
+ cgroupMode = fmt.Sprintf("ns:%s", ns.Path)
+ } else {
+ cgroupMode = "private"
+ }
+ }
+ }
+ if cgroupMode == "" {
+ cgroupMode = "host"
+ }
+ }
+ hostConfig.CgroupMode = cgroupMode
+
+ // Cgroup parent
+ // Need to check if it's the default, and not print if so.
+ defaultCgroupParent := ""
+ switch c.CgroupManager() {
+ case config.CgroupfsCgroupsManager:
+ defaultCgroupParent = CgroupfsDefaultCgroupParent
+ case config.SystemdCgroupsManager:
+ defaultCgroupParent = SystemdDefaultCgroupParent
+ }
+ if c.config.CgroupParent != defaultCgroupParent {
+ hostConfig.CgroupParent = c.config.CgroupParent
+ }
+ hostConfig.CgroupManager = c.CgroupManager()
+
+ // PID namespace mode
+ pidMode := ""
+ if c.config.PIDNsCtr != "" {
+ pidMode = fmt.Sprintf("container:%s", c.config.PIDNsCtr)
+ } else if ctrSpec.Linux != nil {
+ // Locate the spec's PID namespace.
+ // If there is none, it's pid=host.
+ // If there is one and it has a path, it's "ns:".
+ // If there is no path, it's default - the empty string.
+ for _, ns := range ctrSpec.Linux.Namespaces {
+ if ns.Type == spec.PIDNamespace {
+ if ns.Path != "" {
+ pidMode = fmt.Sprintf("ns:%s", ns.Path)
+ } else {
+ pidMode = "private"
+ }
+ break
+ }
+ }
+ if pidMode == "" {
+ pidMode = "host"
+ }
+ }
+ hostConfig.PidMode = pidMode
+
+ // UTS namespace mode
+ utsMode := c.NamespaceMode(spec.UTSNamespace, ctrSpec)
+
+ hostConfig.UTSMode = utsMode
+
+ // User namespace mode
+ usernsMode := ""
+ if c.config.UserNsCtr != "" {
+ usernsMode = fmt.Sprintf("container:%s", c.config.UserNsCtr)
+ } else if ctrSpec.Linux != nil {
+ // Locate the spec's user namespace.
+ // If there is none, it's default - the empty string.
+ // If there is one, it's "private" if no path, or "ns:" if
+ // there's a path.
+
+ for _, ns := range ctrSpec.Linux.Namespaces {
+ if ns.Type == spec.UserNamespace {
+ if ns.Path != "" {
+ usernsMode = fmt.Sprintf("ns:%s", ns.Path)
+ } else {
+ usernsMode = "private"
+ }
+ }
+ }
+ }
+ hostConfig.UsernsMode = usernsMode
+ if c.config.IDMappings.UIDMap != nil && c.config.IDMappings.GIDMap != nil {
+ hostConfig.IDMappings = generateIDMappings(c.config.IDMappings)
+ }
+ // Devices
+ // Do not include if privileged - assumed that all devices will be
+ // included.
+ var err error
+ hostConfig.Devices, err = c.GetDevices(hostConfig.Privileged, *ctrSpec, deviceNodes)
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
diff --git a/libpod/container_internal.go b/libpod/container_internal.go
index 994243805..9bf93412d 100644
--- a/libpod/container_internal.go
+++ b/libpod/container_internal.go
@@ -6,7 +6,6 @@ import (
"errors"
"fmt"
"io"
- "io/ioutil"
"os"
"path/filepath"
"strconv"
@@ -201,7 +200,7 @@ func (c *Container) waitForExitFileAndSync() error {
// This assumes the exit file already exists.
func (c *Container) handleExitFile(exitFile string, fi os.FileInfo) error {
c.state.FinishedTime = ctime.Created(fi)
- statusCodeStr, err := ioutil.ReadFile(exitFile)
+ statusCodeStr, err := os.ReadFile(exitFile)
if err != nil {
return fmt.Errorf("failed to read exit file for container %s: %w", c.ID(), err)
}
@@ -2089,7 +2088,7 @@ func (c *Container) saveSpec(spec *spec.Spec) error {
if err != nil {
return fmt.Errorf("exporting runtime spec for container %s to JSON: %w", c.ID(), err)
}
- if err := ioutil.WriteFile(jsonPath, fileJSON, 0644); err != nil {
+ if err := os.WriteFile(jsonPath, fileJSON, 0644); err != nil {
return fmt.Errorf("writing runtime spec JSON for container %s to disk: %w", c.ID(), err)
}
@@ -2343,7 +2342,7 @@ func (c *Container) extractSecretToCtrStorage(secr *ContainerSecret) error {
if err != nil {
return fmt.Errorf("unable to extract secret: %w", err)
}
- err = ioutil.WriteFile(secretFile, data, 0644)
+ err = os.WriteFile(secretFile, data, 0644)
if err != nil {
return fmt.Errorf("unable to create %s: %w", secretFile, err)
}
diff --git a/libpod/container_internal_common.go b/libpod/container_internal_common.go
index c7f59aba5..29107d4b6 100644
--- a/libpod/container_internal_common.go
+++ b/libpod/container_internal_common.go
@@ -8,7 +8,6 @@ import (
"errors"
"fmt"
"io"
- "io/ioutil"
"math"
"os"
"os/user"
@@ -110,7 +109,11 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) {
// If the flag to mount all devices is set for a privileged container, add
// all the devices from the host's machine into the container
if c.config.MountAllDevices {
- if err := util.AddPrivilegedDevices(&g); err != nil {
+ systemdMode := false
+ if c.config.Systemd != nil {
+ systemdMode = *c.config.Systemd
+ }
+ if err := util.AddPrivilegedDevices(&g, systemdMode); err != nil {
return nil, err
}
}
@@ -531,7 +534,7 @@ func (c *Container) isWorkDirSymlink(resolvedPath string) bool {
}
if resolvedSymlink != "" {
_, resolvedSymlinkWorkdir, err := c.resolvePath(c.state.Mountpoint, resolvedSymlink)
- if isPathOnVolume(c, resolvedSymlinkWorkdir) || isPathOnBindMount(c, resolvedSymlinkWorkdir) {
+ if isPathOnVolume(c, resolvedSymlinkWorkdir) || isPathOnMount(c, resolvedSymlinkWorkdir) {
// Resolved symlink exists on external volume or mount
return true
}
@@ -564,7 +567,7 @@ func (c *Container) resolveWorkDir() error {
// If the specified workdir is a subdir of a volume or mount,
// we don't need to do anything. The runtime is taking care of
// that.
- if isPathOnVolume(c, workdir) || isPathOnBindMount(c, workdir) {
+ if isPathOnVolume(c, workdir) || isPathOnMount(c, workdir) {
logrus.Debugf("Workdir %q resolved to a volume or mount", workdir)
return nil
}
@@ -788,7 +791,7 @@ func (c *Container) createCheckpointImage(ctx context.Context, options Container
}
// Export checkpoint into temporary tar file
- tmpDir, err := ioutil.TempDir("", "checkpoint_image_")
+ tmpDir, err := os.MkdirTemp("", "checkpoint_image_")
if err != nil {
return err
}
@@ -2442,7 +2445,7 @@ func (c *Container) generatePasswdAndGroup() (string, string, error) {
if err != nil {
return "", "", fmt.Errorf("creating path to container %s /etc/passwd: %w", c.ID(), err)
}
- orig, err := ioutil.ReadFile(originPasswdFile)
+ orig, err := os.ReadFile(originPasswdFile)
if err != nil && !os.IsNotExist(err) {
return "", "", err
}
@@ -2488,7 +2491,7 @@ func (c *Container) generatePasswdAndGroup() (string, string, error) {
if err != nil {
return "", "", fmt.Errorf("creating path to container %s /etc/group: %w", c.ID(), err)
}
- orig, err := ioutil.ReadFile(originGroupFile)
+ orig, err := os.ReadFile(originGroupFile)
if err != nil && !os.IsNotExist(err) {
return "", "", err
}
@@ -2659,7 +2662,7 @@ func (c *Container) fixVolumePermissions(v *ContainerNamedVolume) error {
return nil
}
-func (c *Container) relabel(src, mountLabel string, recurse bool) error {
+func (c *Container) relabel(src, mountLabel string, shared bool) error {
if !selinux.GetEnabled() || mountLabel == "" {
return nil
}
@@ -2674,7 +2677,7 @@ func (c *Container) relabel(src, mountLabel string, recurse bool) error {
return nil
}
}
- return label.Relabel(src, mountLabel, recurse)
+ return label.Relabel(src, mountLabel, shared)
}
func (c *Container) ChangeHostPathOwnership(src string, recurse bool, uid, gid int) error {
diff --git a/libpod/container_internal_test.go b/libpod/container_internal_test.go
index 1b4e62e91..46a2da544 100644
--- a/libpod/container_internal_test.go
+++ b/libpod/container_internal_test.go
@@ -3,7 +3,7 @@ package libpod
import (
"context"
"fmt"
- "io/ioutil"
+ "os"
"path/filepath"
"runtime"
"testing"
@@ -60,7 +60,7 @@ func TestPostDeleteHooks(t *testing.T) {
for _, p := range []string{statePath, copyPath} {
path := p
t.Run(path, func(t *testing.T) {
- content, err := ioutil.ReadFile(path)
+ content, err := os.ReadFile(path)
if err != nil {
t.Fatal(err)
}
diff --git a/libpod/container_path_resolution.go b/libpod/container_path_resolution.go
index 35622d623..cd86df540 100644
--- a/libpod/container_path_resolution.go
+++ b/libpod/container_path_resolution.go
@@ -119,15 +119,29 @@ func findVolume(c *Container, containerPath string) (*Volume, error) {
return nil, nil
}
+// isSubDir checks whether path is a subdirectory of root.
+func isSubDir(path, root string) bool {
+ // check if the specified container path is below a bind mount.
+ rel, err := filepath.Rel(root, path)
+ if err != nil {
+ return false
+ }
+ return rel != ".." && !strings.HasPrefix(rel, "../")
+}
+
// isPathOnVolume returns true if the specified containerPath is a subdir of any
// Volume's destination.
func isPathOnVolume(c *Container, containerPath string) bool {
cleanedContainerPath := filepath.Clean(containerPath)
for _, vol := range c.config.NamedVolumes {
- if cleanedContainerPath == filepath.Clean(vol.Dest) {
+ cleanedDestination := filepath.Clean(vol.Dest)
+ if cleanedContainerPath == cleanedDestination {
return true
}
- for dest := vol.Dest; dest != "/" && dest != "."; dest = filepath.Dir(dest) {
+ if isSubDir(cleanedContainerPath, cleanedDestination) {
+ return true
+ }
+ for dest := cleanedDestination; dest != "/" && dest != "."; dest = filepath.Dir(dest) {
if cleanedContainerPath == dest {
return true
}
@@ -152,15 +166,19 @@ func findBindMount(c *Container, containerPath string) *specs.Mount {
return nil
}
-/// isPathOnBindMount returns true if the specified containerPath is a subdir of any
+/// isPathOnMount returns true if the specified containerPath is a subdir of any
// Mount's destination.
-func isPathOnBindMount(c *Container, containerPath string) bool {
+func isPathOnMount(c *Container, containerPath string) bool {
cleanedContainerPath := filepath.Clean(containerPath)
for _, m := range c.config.Spec.Mounts {
- if cleanedContainerPath == filepath.Clean(m.Destination) {
+ cleanedDestination := filepath.Clean(m.Destination)
+ if cleanedContainerPath == cleanedDestination {
+ return true
+ }
+ if isSubDir(cleanedContainerPath, cleanedDestination) {
return true
}
- for dest := m.Destination; dest != "/" && dest != "."; dest = filepath.Dir(dest) {
+ for dest := cleanedDestination; dest != "/" && dest != "."; dest = filepath.Dir(dest) {
if cleanedContainerPath == dest {
return true
}
diff --git a/libpod/container_path_resolution_test.go b/libpod/container_path_resolution_test.go
new file mode 100644
index 000000000..f906c752d
--- /dev/null
+++ b/libpod/container_path_resolution_test.go
@@ -0,0 +1,28 @@
+package libpod
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestIsSubDir(t *testing.T) {
+ assert.True(t, isSubDir("/foo", "/foo"))
+ assert.True(t, isSubDir("/foo/bar", "/foo"))
+ assert.True(t, isSubDir("/foo/bar", "/foo/"))
+ assert.True(t, isSubDir("/foo/bar", "/foo//"))
+ assert.True(t, isSubDir("/foo/bar/", "/foo"))
+ assert.True(t, isSubDir("/foo/bar/baz/", "/foo"))
+ assert.True(t, isSubDir("/foo/bar/baz/", "/foo/bar"))
+ assert.True(t, isSubDir("/foo/bar/baz/", "/foo/bar/"))
+ assert.False(t, isSubDir("/foo/bar/baz/", "/foobar/"))
+ assert.False(t, isSubDir("/foo/bar/baz/../../", "/foobar/"))
+ assert.False(t, isSubDir("/foo/bar/baz/", "../foo/bar"))
+ assert.False(t, isSubDir("/foo/bar/baz/", "../foo/"))
+ assert.False(t, isSubDir("/foo/bar/baz/", "../foo"))
+ assert.False(t, isSubDir("/", ".."))
+ assert.False(t, isSubDir("//", ".."))
+ assert.False(t, isSubDir("//", "../"))
+ assert.False(t, isSubDir("//", "..//"))
+ assert.True(t, isSubDir("/foo/bar/baz/../../", "/foo/"))
+}
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
diff --git a/libpod/define/config.go b/libpod/define/config.go
index 1fad5cc9a..0427206ed 100644
--- a/libpod/define/config.go
+++ b/libpod/define/config.go
@@ -40,6 +40,10 @@ type InfoData struct {
// itself.
const VolumeDriverLocal = "local"
+// VolumeDriverImage is the "image" volume driver. It is managed by Libpod and
+// uses volumes backed by an image.
+const VolumeDriverImage = "image"
+
const (
OCIManifestDir = "oci-dir"
OCIArchive = "oci-archive"
diff --git a/libpod/define/volume_inspect.go b/libpod/define/volume_inspect.go
index 76120647c..4d6f12080 100644
--- a/libpod/define/volume_inspect.go
+++ b/libpod/define/volume_inspect.go
@@ -58,6 +58,9 @@ type InspectVolumeData struct {
NeedsChown bool `json:"NeedsChown,omitempty"`
// Timeout is the specified driver timeout if given
Timeout uint `json:"Timeout,omitempty"`
+ // StorageID is the ID of the container backing the volume in c/storage.
+ // Only used with Image Volumes.
+ StorageID string `json:"StorageID,omitempty"`
}
type VolumeReload struct {
diff --git a/libpod/events.go b/libpod/events.go
index 2f9799114..31a857a7d 100644
--- a/libpod/events.go
+++ b/libpod/events.go
@@ -34,6 +34,7 @@ func (c *Container) newContainerEvent(status events.Status) {
e.Details = events.Details{
ID: e.ID,
+ PodID: c.PodID(),
Attributes: c.Labels(),
}
@@ -59,6 +60,7 @@ func (c *Container) newContainerExitedEvent(exitCode int32) {
e.Name = c.Name()
e.Image = c.config.RootfsImageName
e.Type = events.Container
+ e.PodID = c.PodID()
e.ContainerExitCode = int(exitCode)
e.Details = events.Details{
diff --git a/libpod/events/config.go b/libpod/events/config.go
index 4ea45a00e..28bc87a34 100644
--- a/libpod/events/config.go
+++ b/libpod/events/config.go
@@ -50,6 +50,8 @@ type Event struct {
type Details struct {
// ID is the event ID
ID string
+ // PodID is the ID of the pod associated with the container.
+ PodID string `json:",omitempty"`
// Attributes can be used to describe specifics about the event
// in the case of a container event, labels for example
Attributes map[string]string
diff --git a/libpod/events/events.go b/libpod/events/events.go
index 764481e51..2105a3b89 100644
--- a/libpod/events/events.go
+++ b/libpod/events/events.go
@@ -76,7 +76,13 @@ func (e *Event) ToHumanReadable(truncate bool) string {
}
switch e.Type {
case Container, Pod:
- humanFormat = fmt.Sprintf("%s %s %s %s (image=%s, name=%s, health_status=%s", e.Time, e.Type, e.Status, id, e.Image, e.Name, e.HealthStatus)
+ humanFormat = fmt.Sprintf("%s %s %s %s (image=%s, name=%s", e.Time, e.Type, e.Status, id, e.Image, e.Name)
+ if e.PodID != "" {
+ humanFormat += fmt.Sprintf(", pod_id=%s", e.PodID)
+ }
+ if e.HealthStatus != "" {
+ humanFormat += fmt.Sprintf(", health_status=%s", e.HealthStatus)
+ }
// check if the container has labels and add it to the output
if len(e.Attributes) > 0 {
for k, v := range e.Attributes {
diff --git a/libpod/events/journal_linux.go b/libpod/events/journal_linux.go
index 4986502a2..e303a205d 100644
--- a/libpod/events/journal_linux.go
+++ b/libpod/events/journal_linux.go
@@ -50,6 +50,9 @@ func (e EventJournalD) Write(ee Event) error {
if ee.ContainerExitCode != 0 {
m["PODMAN_EXIT_CODE"] = strconv.Itoa(ee.ContainerExitCode)
}
+ if ee.PodID != "" {
+ m["PODMAN_POD_ID"] = ee.PodID
+ }
// If we have container labels, we need to convert them to a string so they
// can be recorded with the event
if len(ee.Details.Attributes) > 0 {
@@ -161,6 +164,7 @@ func newEventFromJournalEntry(entry *sdjournal.JournalEntry) (*Event, error) {
case Container, Pod:
newEvent.ID = entry.Fields["PODMAN_ID"]
newEvent.Image = entry.Fields["PODMAN_IMAGE"]
+ newEvent.PodID = entry.Fields["PODMAN_POD_ID"]
if code, ok := entry.Fields["PODMAN_EXIT_CODE"]; ok {
intCode, err := strconv.Atoi(code)
if err != nil {
@@ -179,7 +183,7 @@ func newEventFromJournalEntry(entry *sdjournal.JournalEntry) (*Event, error) {
// if we have labels, add them to the event
if len(labels) > 0 {
- newEvent.Details = Details{Attributes: labels}
+ newEvent.Attributes = labels
}
}
newEvent.HealthStatus = entry.Fields["PODMAN_HEALTH_STATUS"]
diff --git a/libpod/events/logfile.go b/libpod/events/logfile.go
index d749a0d4d..bb0f461e3 100644
--- a/libpod/events/logfile.go
+++ b/libpod/events/logfile.go
@@ -9,7 +9,6 @@ import (
"errors"
"fmt"
"io"
- "io/ioutil"
"os"
"path"
"path/filepath"
@@ -204,11 +203,11 @@ func truncate(filePath string) error {
size := origFinfo.Size()
threshold := size / 2
- tmp, err := ioutil.TempFile(path.Dir(filePath), "")
+ tmp, err := os.CreateTemp(path.Dir(filePath), "")
if err != nil {
// Retry in /tmp in case creating a tmp file in the same
// directory has failed.
- tmp, err = ioutil.TempFile("", "")
+ tmp, err = os.CreateTemp("", "")
if err != nil {
return err
}
diff --git a/libpod/events/logfile_test.go b/libpod/events/logfile_test.go
index 302533c12..50141168e 100644
--- a/libpod/events/logfile_test.go
+++ b/libpod/events/logfile_test.go
@@ -1,7 +1,6 @@
package events
import (
- "io/ioutil"
"os"
"testing"
@@ -29,7 +28,7 @@ func TestRotateLog(t *testing.T) {
}
for _, test := range tests {
- tmp, err := ioutil.TempFile("", "log-rotation-")
+ tmp, err := os.CreateTemp("", "log-rotation-")
require.NoError(t, err)
defer os.Remove(tmp.Name())
defer tmp.Close()
@@ -84,7 +83,7 @@ func TestTruncationOutput(t *testing.T) {
10
`
// Create dummy file
- tmp, err := ioutil.TempFile("", "log-rotation")
+ tmp, err := os.CreateTemp("", "log-rotation")
require.NoError(t, err)
defer os.Remove(tmp.Name())
defer tmp.Close()
@@ -94,11 +93,11 @@ func TestTruncationOutput(t *testing.T) {
require.NoError(t, err)
// Truncate the file
- beforeTruncation, err := ioutil.ReadFile(tmp.Name())
+ beforeTruncation, err := os.ReadFile(tmp.Name())
require.NoError(t, err)
err = truncate(tmp.Name())
require.NoError(t, err)
- afterTruncation, err := ioutil.ReadFile(tmp.Name())
+ afterTruncation, err := os.ReadFile(tmp.Name())
require.NoError(t, err)
// Test if rotation was successful
@@ -116,9 +115,9 @@ func TestRenameLog(t *testing.T) {
5
`
// Create two dummy files
- source, err := ioutil.TempFile("", "removing")
+ source, err := os.CreateTemp("", "removing")
require.NoError(t, err)
- target, err := ioutil.TempFile("", "renaming")
+ target, err := os.CreateTemp("", "renaming")
require.NoError(t, err)
// Write to source dummy file
@@ -126,11 +125,11 @@ func TestRenameLog(t *testing.T) {
require.NoError(t, err)
// Rename the files
- beforeRename, err := ioutil.ReadFile(source.Name())
+ beforeRename, err := os.ReadFile(source.Name())
require.NoError(t, err)
err = renameLog(source.Name(), target.Name())
require.NoError(t, err)
- afterRename, err := ioutil.ReadFile(target.Name())
+ afterRename, err := os.ReadFile(target.Name())
require.NoError(t, err)
// Test if renaming was successful
diff --git a/libpod/healthcheck.go b/libpod/healthcheck.go
index e835af9f0..a589f2787 100644
--- a/libpod/healthcheck.go
+++ b/libpod/healthcheck.go
@@ -5,7 +5,6 @@ import (
"context"
"errors"
"fmt"
- "io/ioutil"
"os"
"path/filepath"
"strings"
@@ -208,7 +207,7 @@ func (c *Container) updateHealthStatus(status string) error {
if err != nil {
return fmt.Errorf("unable to marshall healthchecks for writing status: %w", err)
}
- return ioutil.WriteFile(c.healthCheckLogPath(), newResults, 0700)
+ return os.WriteFile(c.healthCheckLogPath(), newResults, 0700)
}
// UpdateHealthCheckLog parses the health check results and writes the log
@@ -242,7 +241,7 @@ func (c *Container) updateHealthCheckLog(hcl define.HealthCheckLog, inStartPerio
if err != nil {
return fmt.Errorf("unable to marshall healthchecks for writing: %w", err)
}
- return ioutil.WriteFile(c.healthCheckLogPath(), newResults, 0700)
+ return os.WriteFile(c.healthCheckLogPath(), newResults, 0700)
}
// HealthCheckLogPath returns the path for where the health check log is
@@ -259,7 +258,7 @@ func (c *Container) getHealthCheckLog() (define.HealthCheckResults, error) {
if _, err := os.Stat(c.healthCheckLogPath()); os.IsNotExist(err) {
return healthCheck, nil
}
- b, err := ioutil.ReadFile(c.healthCheckLogPath())
+ b, err := os.ReadFile(c.healthCheckLogPath())
if err != nil {
return healthCheck, fmt.Errorf("failed to read health check log file: %w", err)
}
diff --git a/libpod/networking_linux.go b/libpod/networking_linux.go
index e27ec8e9d..6ea56ade5 100644
--- a/libpod/networking_linux.go
+++ b/libpod/networking_linux.go
@@ -8,7 +8,6 @@ import (
"crypto/sha256"
"errors"
"fmt"
- "io/ioutil"
"net"
"os"
"os/exec"
@@ -303,7 +302,7 @@ func (r *RootlessNetNS) Cleanup(runtime *Runtime) error {
if err != nil {
logrus.Error(err)
}
- b, err := ioutil.ReadFile(r.getPath(rootlessNetNsSilrp4netnsPidFile))
+ b, err := os.ReadFile(r.getPath(rootlessNetNsSilrp4netnsPidFile))
if err == nil {
var i int
i, err = strconv.Atoi(string(b))
@@ -445,7 +444,7 @@ func (r *Runtime) GetRootlessNetNs(new bool) (*RootlessNetNS, error) {
// create pid file for the slirp4netns process
// this is need to kill the process in the cleanup
pid := strconv.Itoa(cmd.Process.Pid)
- err = ioutil.WriteFile(filepath.Join(rootlessNetNsDir, rootlessNetNsSilrp4netnsPidFile), []byte(pid), 0700)
+ err = os.WriteFile(filepath.Join(rootlessNetNsDir, rootlessNetNsSilrp4netnsPidFile), []byte(pid), 0700)
if err != nil {
return nil, fmt.Errorf("unable to write rootless-netns slirp4netns pid file: %w", err)
}
diff --git a/libpod/networking_machine.go b/libpod/networking_machine.go
index 7b8eb94df..dce335c0a 100644
--- a/libpod/networking_machine.go
+++ b/libpod/networking_machine.go
@@ -6,7 +6,6 @@ import (
"errors"
"fmt"
"io"
- "io/ioutil"
"net"
"net/http"
"strconv"
@@ -109,7 +108,7 @@ func makeMachineRequest(ctx context.Context, client *http.Client, url string, bu
}
func annotateGvproxyResponseError(r io.Reader) error {
- b, err := ioutil.ReadAll(r)
+ b, err := io.ReadAll(r)
if err == nil && len(b) > 0 {
return fmt.Errorf("something went wrong with the request: %q", string(b))
}
diff --git a/libpod/networking_slirp4netns.go b/libpod/networking_slirp4netns.go
index d4ec9082b..4026b6b48 100644
--- a/libpod/networking_slirp4netns.go
+++ b/libpod/networking_slirp4netns.go
@@ -8,7 +8,6 @@ import (
"errors"
"fmt"
"io"
- "io/ioutil"
"net"
"os"
"os/exec"
@@ -324,7 +323,7 @@ func (r *Runtime) setupSlirp4netns(ctr *Container, netns ns.NetNS) error {
// correct value assigned so DAD is disabled for it
// Also make sure to change this value back to the original after slirp4netns
// is ready in case users rely on this sysctl.
- orgValue, err := ioutil.ReadFile(ipv6ConfDefaultAcceptDadSysctl)
+ orgValue, err := os.ReadFile(ipv6ConfDefaultAcceptDadSysctl)
if err != nil {
netnsReadyWg.Done()
// on ipv6 disabled systems the sysctl does not exists
@@ -334,7 +333,7 @@ func (r *Runtime) setupSlirp4netns(ctr *Container, netns ns.NetNS) error {
}
return err
}
- err = ioutil.WriteFile(ipv6ConfDefaultAcceptDadSysctl, []byte("0"), 0644)
+ err = os.WriteFile(ipv6ConfDefaultAcceptDadSysctl, []byte("0"), 0644)
netnsReadyWg.Done()
if err != nil {
return err
@@ -342,7 +341,7 @@ func (r *Runtime) setupSlirp4netns(ctr *Container, netns ns.NetNS) error {
// wait until slirp4nets is ready before resetting this value
slirpReadyWg.Wait()
- return ioutil.WriteFile(ipv6ConfDefaultAcceptDadSysctl, orgValue, 0644)
+ return os.WriteFile(ipv6ConfDefaultAcceptDadSysctl, orgValue, 0644)
})
if err != nil {
logrus.Warnf("failed to set net.ipv6.conf.default.accept_dad sysctl: %v", err)
@@ -486,7 +485,7 @@ func waitForSync(syncR *os.File, cmd *exec.Cmd, logFile io.ReadSeeker, timeout t
if _, err := logFile.Seek(0, 0); err != nil {
logrus.Errorf("Could not seek log file: %q", err)
}
- logContent, err := ioutil.ReadAll(logFile)
+ logContent, err := io.ReadAll(logFile)
if err != nil {
return fmt.Errorf("%s failed: %w", prog, err)
}
@@ -730,7 +729,7 @@ func (c *Container) reloadRootlessRLKPortMapping() error {
if err != nil {
return fmt.Errorf("port reloading failed: %w", err)
}
- b, err := ioutil.ReadAll(conn)
+ b, err := io.ReadAll(conn)
if err != nil {
return fmt.Errorf("port reloading failed: %w", err)
}
diff --git a/libpod/oci_conmon_common.go b/libpod/oci_conmon_common.go
index 53dddd064..cbdbad02d 100644
--- a/libpod/oci_conmon_common.go
+++ b/libpod/oci_conmon_common.go
@@ -10,7 +10,6 @@ import (
"errors"
"fmt"
"io"
- "io/ioutil"
"net"
"net/http"
"os"
@@ -232,7 +231,7 @@ func (r *ConmonOCIRuntime) UpdateContainerStatus(ctr *Container) error {
}
if err := cmd.Start(); err != nil {
- out, err2 := ioutil.ReadAll(errPipe)
+ out, err2 := io.ReadAll(errPipe)
if err2 != nil {
return fmt.Errorf("getting container %s state: %w", ctr.ID(), err)
}
@@ -254,7 +253,7 @@ func (r *ConmonOCIRuntime) UpdateContainerStatus(ctr *Container) error {
if err := errPipe.Close(); err != nil {
return err
}
- out, err := ioutil.ReadAll(outPipe)
+ out, err := io.ReadAll(outPipe)
if err != nil {
return fmt.Errorf("reading stdout: %s: %w", ctr.ID(), err)
}
@@ -335,7 +334,7 @@ func generateResourceFile(res *spec.LinuxResources) (string, []string, error) {
return "", flags, nil
}
- f, err := ioutil.TempFile("", "podman")
+ f, err := os.CreateTemp("", "podman")
if err != nil {
return "", nil, err
}
@@ -1398,7 +1397,7 @@ func newPipe() (*os.File, *os.File, error) {
func readConmonPidFile(pidFile string) (int, error) {
// Let's try reading the Conmon pid at the same time.
if pidFile != "" {
- contents, err := ioutil.ReadFile(pidFile)
+ contents, err := os.ReadFile(pidFile)
if err != nil {
return -1, err
}
@@ -1447,7 +1446,7 @@ func readConmonPipeData(runtimeName string, pipe *os.File, ociLog string) (int,
case ss := <-ch:
if ss.err != nil {
if ociLog != "" {
- ociLogData, err := ioutil.ReadFile(ociLog)
+ ociLogData, err := os.ReadFile(ociLog)
if err == nil {
var ociErr ociError
if err := json.Unmarshal(ociLogData, &ociErr); err == nil {
@@ -1460,7 +1459,7 @@ func readConmonPipeData(runtimeName string, pipe *os.File, ociLog string) (int,
logrus.Debugf("Received: %d", ss.si.Data)
if ss.si.Data < 0 {
if ociLog != "" {
- ociLogData, err := ioutil.ReadFile(ociLog)
+ ociLogData, err := os.ReadFile(ociLog)
if err == nil {
var ociErr ociError
if err := json.Unmarshal(ociLogData, &ociErr); err == nil {
diff --git a/libpod/oci_conmon_exec_common.go b/libpod/oci_conmon_exec_common.go
index e5080942b..24113bd8d 100644
--- a/libpod/oci_conmon_exec_common.go
+++ b/libpod/oci_conmon_exec_common.go
@@ -3,7 +3,6 @@ package libpod
import (
"errors"
"fmt"
- "io/ioutil"
"net/http"
"os"
"os/exec"
@@ -665,7 +664,7 @@ func attachExecHTTP(c *Container, sessionID string, r *http.Request, w http.Resp
// prepareProcessExec returns the path of the process.json used in runc exec -p
// caller is responsible to close the returned *os.File if needed.
func (c *Container) prepareProcessExec(options *ExecOptions, env []string, sessionID string) (*os.File, error) {
- f, err := ioutil.TempFile(c.execBundlePath(sessionID), "exec-process-")
+ f, err := os.CreateTemp(c.execBundlePath(sessionID), "exec-process-")
if err != nil {
return nil, err
}
@@ -764,7 +763,7 @@ func (c *Container) prepareProcessExec(options *ExecOptions, env []string, sessi
return nil, err
}
- if err := ioutil.WriteFile(f.Name(), processJSON, 0644); err != nil {
+ if err := os.WriteFile(f.Name(), processJSON, 0644); err != nil {
return nil, err
}
return f, nil
diff --git a/libpod/plugin/volume_api.go b/libpod/plugin/volume_api.go
index 522895798..c595937ae 100644
--- a/libpod/plugin/volume_api.go
+++ b/libpod/plugin/volume_api.go
@@ -5,7 +5,7 @@ import (
"context"
"errors"
"fmt"
- "io/ioutil"
+ "io"
"net"
"net/http"
"os"
@@ -95,7 +95,7 @@ func validatePlugin(newPlugin *VolumePlugin) error {
}
// Read and decode the body so we can tell if this is a volume plugin.
- respBytes, err := ioutil.ReadAll(resp.Body)
+ respBytes, err := io.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("reading activation response body from plugin %s: %w", newPlugin.Name, err)
}
@@ -252,7 +252,7 @@ func (p *VolumePlugin) handleErrorResponse(resp *http.Response, endpoint, volNam
// Let's interpret anything other than 200 as an error.
// If there isn't an error, don't even bother decoding the response.
if resp.StatusCode != 200 {
- errResp, err := ioutil.ReadAll(resp.Body)
+ errResp, err := io.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("reading response body from volume plugin %s: %w", p.Name, err)
}
@@ -307,7 +307,7 @@ func (p *VolumePlugin) ListVolumes() ([]*volume.Volume, error) {
return nil, err
}
- volumeRespBytes, err := ioutil.ReadAll(resp.Body)
+ volumeRespBytes, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("reading response body from volume plugin %s: %w", p.Name, err)
}
@@ -342,7 +342,7 @@ func (p *VolumePlugin) GetVolume(req *volume.GetRequest) (*volume.Volume, error)
return nil, err
}
- getRespBytes, err := ioutil.ReadAll(resp.Body)
+ getRespBytes, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("reading response body from volume plugin %s: %w", p.Name, err)
}
@@ -398,7 +398,7 @@ func (p *VolumePlugin) GetVolumePath(req *volume.PathRequest) (string, error) {
return "", err
}
- pathRespBytes, err := ioutil.ReadAll(resp.Body)
+ pathRespBytes, err := io.ReadAll(resp.Body)
if err != nil {
return "", fmt.Errorf("reading response body from volume plugin %s: %w", p.Name, err)
}
@@ -435,7 +435,7 @@ func (p *VolumePlugin) MountVolume(req *volume.MountRequest) (string, error) {
return "", err
}
- mountRespBytes, err := ioutil.ReadAll(resp.Body)
+ mountRespBytes, err := io.ReadAll(resp.Body)
if err != nil {
return "", fmt.Errorf("reading response body from volume plugin %s: %w", p.Name, err)
}
diff --git a/libpod/pod_api.go b/libpod/pod_api.go
index 1bd686ddc..924d43436 100644
--- a/libpod/pod_api.go
+++ b/libpod/pod_api.go
@@ -40,7 +40,7 @@ func (p *Pod) startInitContainers(ctx context.Context) error {
icLock := initCon.lock
icLock.Lock()
var time *uint
- if err := p.runtime.removeContainer(ctx, initCon, false, false, true, time); err != nil {
+ if err := p.runtime.removeContainer(ctx, initCon, false, false, true, false, time); err != nil {
icLock.Unlock()
return fmt.Errorf("failed to remove once init container %s: %w", initCon.ID(), err)
}
diff --git a/libpod/runtime.go b/libpod/runtime.go
index 83c9f53e2..f250d759c 100644
--- a/libpod/runtime.go
+++ b/libpod/runtime.go
@@ -1091,6 +1091,9 @@ func (r *Runtime) getVolumePlugin(volConfig *VolumeConfig) (*plugin.VolumePlugin
pluginPath, ok := r.config.Engine.VolumePlugins[name]
if !ok {
+ if name == define.VolumeDriverImage {
+ return nil, nil
+ }
return nil, fmt.Errorf("no volume plugin with name %s available: %w", name, define.ErrMissingPlugin)
}
diff --git a/libpod/runtime_cstorage.go b/libpod/runtime_cstorage.go
index 372434b49..5917b7931 100644
--- a/libpod/runtime_cstorage.go
+++ b/libpod/runtime_cstorage.go
@@ -86,6 +86,17 @@ func (r *Runtime) RemoveStorageContainer(idOrName string, force bool) error {
return fmt.Errorf("refusing to remove %q as it exists in libpod as container %s: %w", idOrName, ctr.ID, define.ErrCtrExists)
}
+ // Error out if this is an image-backed volume
+ allVols, err := r.state.AllVolumes()
+ if err != nil {
+ return err
+ }
+ for _, vol := range allVols {
+ if vol.config.Driver == define.VolumeDriverImage && vol.config.StorageID == ctr.ID {
+ return fmt.Errorf("refusing to remove %q as it exists in libpod as an image-backed volume %s: %w", idOrName, vol.Name(), define.ErrCtrExists)
+ }
+ }
+
if !force {
timesMounted, err := r.store.Mounted(ctr.ID)
if err != nil {
diff --git a/libpod/runtime_ctr.go b/libpod/runtime_ctr.go
index 1f032dd6b..bb30078cb 100644
--- a/libpod/runtime_ctr.go
+++ b/libpod/runtime_ctr.go
@@ -512,7 +512,7 @@ func (r *Runtime) setupContainer(ctx context.Context, ctr *Container) (_ *Contai
volOptions = append(volOptions, parsedOptions...)
}
}
- newVol, err := r.newVolume(false, volOptions...)
+ newVol, err := r.newVolume(ctx, false, volOptions...)
if err != nil {
return nil, fmt.Errorf("creating named volume %q: %w", vol.Name, err)
}
@@ -581,7 +581,7 @@ func (r *Runtime) setupContainer(ctx context.Context, ctr *Container) (_ *Contai
// be removed also if and only if the container is the sole user
// Otherwise, RemoveContainer will return an error if the container is running
func (r *Runtime) RemoveContainer(ctx context.Context, c *Container, force bool, removeVolume bool, timeout *uint) error {
- return r.removeContainer(ctx, c, force, removeVolume, false, timeout)
+ return r.removeContainer(ctx, c, force, removeVolume, false, false, timeout)
}
// Internal function to remove a container.
@@ -589,7 +589,9 @@ func (r *Runtime) RemoveContainer(ctx context.Context, c *Container, force bool,
// removePod is used only when removing pods. It instructs Podman to ignore
// infra container protections, and *not* remove from the database (as pod
// remove will handle that).
-func (r *Runtime) removeContainer(ctx context.Context, c *Container, force, removeVolume, removePod bool, timeout *uint) error {
+// ignoreDeps is *DANGEROUS* and should not be used outside of a very specific
+// context (alternate pod removal code, where graph traversal is not possible).
+func (r *Runtime) removeContainer(ctx context.Context, c *Container, force, removeVolume, removePod, ignoreDeps bool, timeout *uint) error {
if !c.valid {
if ok, _ := r.state.HasContainer(c.ID()); !ok {
// Container probably already removed
@@ -618,25 +620,27 @@ func (r *Runtime) removeContainer(ctx context.Context, c *Container, force, remo
// pod.
var pod *Pod
runtime := c.runtime
- if c.config.Pod != "" && !removePod {
+ if c.config.Pod != "" {
pod, err = r.state.Pod(c.config.Pod)
if err != nil {
return fmt.Errorf("container %s is in pod %s, but pod cannot be retrieved: %w", c.ID(), pod.ID(), err)
}
- // Lock the pod while we're removing container
- if pod.config.LockID == c.config.LockID {
- return fmt.Errorf("container %s and pod %s share lock ID %d: %w", c.ID(), pod.ID(), c.config.LockID, define.ErrWillDeadlock)
- }
- pod.lock.Lock()
- defer pod.lock.Unlock()
- if err := pod.updatePod(); err != nil {
- return err
- }
+ if !removePod {
+ // Lock the pod while we're removing container
+ if pod.config.LockID == c.config.LockID {
+ return fmt.Errorf("container %s and pod %s share lock ID %d: %w", c.ID(), pod.ID(), c.config.LockID, define.ErrWillDeadlock)
+ }
+ pod.lock.Lock()
+ defer pod.lock.Unlock()
+ if err := pod.updatePod(); err != nil {
+ return err
+ }
- infraID := pod.state.InfraContainerID
- if c.ID() == infraID {
- return fmt.Errorf("container %s is the infra container of pod %s and cannot be removed without removing the pod", c.ID(), pod.ID())
+ infraID := pod.state.InfraContainerID
+ if c.ID() == infraID {
+ return fmt.Errorf("container %s is the infra container of pod %s and cannot be removed without removing the pod", c.ID(), pod.ID())
+ }
}
}
@@ -696,7 +700,7 @@ func (r *Runtime) removeContainer(ctx context.Context, c *Container, force, remo
// Check that no other containers depend on the container.
// Only used if not removing a pod - pods guarantee that all
// deps will be evicted at the same time.
- if !removePod {
+ if !ignoreDeps {
deps, err := r.state.ContainerInUse(c)
if err != nil {
return err
@@ -777,13 +781,11 @@ func (r *Runtime) removeContainer(ctx context.Context, c *Container, force, remo
if c.config.Pod != "" {
// If we're removing the pod, the container will be evicted
// from the state elsewhere
- if !removePod {
- if err := r.state.RemoveContainerFromPod(pod, c); err != nil {
- if cleanupErr == nil {
- cleanupErr = err
- } else {
- logrus.Errorf("Removing container %s from database: %v", c.ID(), err)
- }
+ if err := r.state.RemoveContainerFromPod(pod, c); err != nil {
+ if cleanupErr == nil {
+ cleanupErr = err
+ } else {
+ logrus.Errorf("Removing container %s from database: %v", c.ID(), err)
}
}
} else {
@@ -872,7 +874,7 @@ func (r *Runtime) evictContainer(ctx context.Context, idOrName string, removeVol
if err == nil {
logrus.Infof("Container %s successfully retrieved from state, attempting normal removal", id)
// Assume force = true for the evict case
- err = r.removeContainer(ctx, tmpCtr, true, removeVolume, false, timeout)
+ err = r.removeContainer(ctx, tmpCtr, true, removeVolume, false, false, timeout)
if !tmpCtr.valid {
// If the container is marked invalid, remove succeeded
// in kicking it out of the state - no need to continue.
@@ -1034,7 +1036,7 @@ func (r *Runtime) RemoveDepend(ctx context.Context, rmCtr *Container, force bool
}
report := reports.RmReport{Id: rmCtr.ID(), RawInput: rmCtr.ID()}
- report.Err = r.removeContainer(ctx, rmCtr, force, removeVolume, false, timeout)
+ report.Err = r.removeContainer(ctx, rmCtr, force, removeVolume, false, false, timeout)
return append(rmReports, &report), nil
}
diff --git a/libpod/runtime_img.go b/libpod/runtime_img.go
index 5510b2af6..d8e88ca50 100644
--- a/libpod/runtime_img.go
+++ b/libpod/runtime_img.go
@@ -5,7 +5,6 @@ import (
"errors"
"fmt"
"io"
- "io/ioutil"
"os"
buildahDefine "github.com/containers/buildah/define"
@@ -47,11 +46,28 @@ func (r *Runtime) RemoveContainersForImageCallback(ctx context.Context) libimage
return fmt.Errorf("removing image %s: container %s using image could not be removed: %w", imageID, ctr.ID(), err)
}
} else {
- if err := r.removeContainer(ctx, ctr, true, false, false, timeout); err != nil {
+ if err := r.removeContainer(ctx, ctr, true, false, false, false, timeout); err != nil {
return fmt.Errorf("removing image %s: container %s using image could not be removed: %w", imageID, ctr.ID(), err)
}
}
}
+
+ // Need to handle volumes with the image driver
+ vols, err := r.state.AllVolumes()
+ if err != nil {
+ return err
+ }
+ for _, vol := range vols {
+ if vol.config.Driver != define.VolumeDriverImage || vol.config.StorageImageID != imageID {
+ continue
+ }
+ // Do a force removal of the volume, and all containers
+ // using it.
+ if err := r.RemoveVolume(ctx, vol, true, nil); err != nil {
+ return fmt.Errorf("removing image %s: volume %s backed by image could not be removed: %w", imageID, vol.Name(), err)
+ }
+ }
+
// Note that `libimage` will take care of removing any leftover
// containers from the storage.
return nil
@@ -75,6 +91,10 @@ func (r *Runtime) IsExternalContainerCallback(_ context.Context) libimage.IsExte
if errors.Is(err, define.ErrNoSuchCtr) {
return true, nil
}
+ isVol, err := r.state.ContainerIDIsVolume(idOrName)
+ if err == nil && !isVol {
+ return true, nil
+ }
return false, nil
}
}
@@ -105,7 +125,7 @@ func (r *Runtime) Build(ctx context.Context, options buildahDefine.BuildOptions,
// DownloadFromFile reads all of the content from the reader and temporarily
// saves in it $TMPDIR/importxyz, which is deleted after the image is imported
func DownloadFromFile(reader *os.File) (string, error) {
- outFile, err := ioutil.TempFile(util.Tmpdir(), "import")
+ outFile, err := os.CreateTemp(util.Tmpdir(), "import")
if err != nil {
return "", fmt.Errorf("creating file: %w", err)
}
diff --git a/libpod/runtime_migrate.go b/libpod/runtime_migrate.go
index 36901d4d0..df1a1f1cb 100644
--- a/libpod/runtime_migrate.go
+++ b/libpod/runtime_migrate.go
@@ -5,7 +5,6 @@ package libpod
import (
"fmt"
- "io/ioutil"
"os"
"path/filepath"
"strconv"
@@ -23,7 +22,7 @@ func (r *Runtime) stopPauseProcess() error {
if err != nil {
return fmt.Errorf("could not get pause process pid file path: %w", err)
}
- data, err := ioutil.ReadFile(pausePidPath)
+ data, err := os.ReadFile(pausePidPath)
if err != nil {
if os.IsNotExist(err) {
return nil
diff --git a/libpod/runtime_pod_linux.go b/libpod/runtime_pod_linux.go
index 3eeef69d8..24e9f3da7 100644
--- a/libpod/runtime_pod_linux.go
+++ b/libpod/runtime_pod_linux.go
@@ -17,6 +17,7 @@ import (
"github.com/containers/podman/v4/libpod/events"
"github.com/containers/podman/v4/pkg/rootless"
"github.com/containers/podman/v4/pkg/specgen"
+ "github.com/hashicorp/go-multierror"
"github.com/sirupsen/logrus"
)
@@ -191,29 +192,9 @@ func (r *Runtime) SavePod(pod *Pod) error {
return nil
}
-func (r *Runtime) removePod(ctx context.Context, p *Pod, removeCtrs, force bool, timeout *uint) error {
- if err := p.updatePod(); err != nil {
- return err
- }
-
- ctrs, err := r.state.PodContainers(p)
- if err != nil {
- return err
- }
- numCtrs := len(ctrs)
-
- // If the only running container in the pod is the pause container, remove the pod and container unconditionally.
- pauseCtrID := p.state.InfraContainerID
- if numCtrs == 1 && ctrs[0].ID() == pauseCtrID {
- removeCtrs = true
- force = true
- }
- if !removeCtrs && numCtrs > 0 {
- return fmt.Errorf("pod %s contains containers and cannot be removed: %w", p.ID(), define.ErrCtrExists)
- }
-
- ctrNamedVolumes := make(map[string]*ContainerNamedVolume)
-
+// DO NOT USE THIS FUNCTION DIRECTLY. Use removePod(), below. It will call
+// removeMalformedPod() if necessary.
+func (r *Runtime) removeMalformedPod(ctx context.Context, p *Pod, ctrs []*Container, force bool, timeout *uint, ctrNamedVolumes map[string]*ContainerNamedVolume) error {
var removalErr error
for _, ctr := range ctrs {
err := func() error {
@@ -231,7 +212,7 @@ func (r *Runtime) removePod(ctx context.Context, p *Pod, removeCtrs, force bool,
ctrNamedVolumes[vol.Name] = vol
}
- return r.removeContainer(ctx, ctr, force, false, true, timeout)
+ return r.removeContainer(ctx, ctr, force, false, true, true, timeout)
}()
if removalErr == nil {
@@ -261,6 +242,69 @@ func (r *Runtime) removePod(ctx context.Context, p *Pod, removeCtrs, force bool,
return err
}
+ return nil
+}
+
+func (r *Runtime) removePod(ctx context.Context, p *Pod, removeCtrs, force bool, timeout *uint) error {
+ if err := p.updatePod(); err != nil {
+ return err
+ }
+
+ ctrs, err := r.state.PodContainers(p)
+ if err != nil {
+ return err
+ }
+ numCtrs := len(ctrs)
+
+ // If the only running container in the pod is the pause container, remove the pod and container unconditionally.
+ pauseCtrID := p.state.InfraContainerID
+ if numCtrs == 1 && ctrs[0].ID() == pauseCtrID {
+ removeCtrs = true
+ force = true
+ }
+ if !removeCtrs && numCtrs > 0 {
+ return fmt.Errorf("pod %s contains containers and cannot be removed: %w", p.ID(), define.ErrCtrExists)
+ }
+
+ var removalErr error
+ ctrNamedVolumes := make(map[string]*ContainerNamedVolume)
+
+ // Build a graph of all containers in the pod.
+ graph, err := BuildContainerGraph(ctrs)
+ if err != nil {
+ // We have to allow the pod to be removed.
+ // But let's only do it if force is set.
+ if !force {
+ return fmt.Errorf("cannot create container graph for pod %s: %w", p.ID(), err)
+ }
+
+ removalErr = fmt.Errorf("creating container graph for pod %s failed, fell back to loop removal: %w", p.ID(), err)
+
+ if err := r.removeMalformedPod(ctx, p, ctrs, force, timeout, ctrNamedVolumes); err != nil {
+ logrus.Errorf("Error creating container graph for pod %s: %v. Falling back to loop removal.", p.ID(), err)
+ return err
+ }
+ } else {
+ ctrErrors := make(map[string]error)
+ ctrsVisited := make(map[string]bool)
+
+ for _, node := range graph.notDependedOnNodes {
+ removeNode(ctx, node, p, force, timeout, false, ctrErrors, ctrsVisited, ctrNamedVolumes)
+ }
+
+ // This is gross, but I don't want to change the signature on
+ // removePod - especially since any change here eventually has
+ // to map down to one error unless we want to make a breaking
+ // API change.
+ if len(ctrErrors) > 0 {
+ var allErrs error
+ for id, err := range ctrErrors {
+ allErrs = multierror.Append(allErrs, fmt.Errorf("removing container %s from pod %s: %w", id, p.ID(), err))
+ }
+ return allErrs
+ }
+ }
+
for volName := range ctrNamedVolumes {
volume, err := r.state.Volume(volName)
if err != nil && !errors.Is(err, define.ErrNoSuchVolume) {
diff --git a/libpod/runtime_volume_linux.go b/libpod/runtime_volume_linux.go
index c9a4a7dc1..c59417979 100644
--- a/libpod/runtime_volume_linux.go
+++ b/libpod/runtime_volume_linux.go
@@ -15,6 +15,7 @@ import (
"github.com/containers/podman/v4/libpod/define"
"github.com/containers/podman/v4/libpod/events"
volplugin "github.com/containers/podman/v4/libpod/plugin"
+ "github.com/containers/storage"
"github.com/containers/storage/drivers/quota"
"github.com/containers/storage/pkg/idtools"
"github.com/containers/storage/pkg/stringid"
@@ -22,18 +23,20 @@ import (
"github.com/sirupsen/logrus"
)
+const volumeSuffix = "+volume"
+
// NewVolume creates a new empty volume
func (r *Runtime) NewVolume(ctx context.Context, options ...VolumeCreateOption) (*Volume, error) {
if !r.valid {
return nil, define.ErrRuntimeStopped
}
- return r.newVolume(false, options...)
+ return r.newVolume(ctx, false, options...)
}
// newVolume creates a new empty volume with the given options.
// The createPluginVolume can be set to true to make it not create the volume in the volume plugin,
// this is required for the UpdateVolumePlugins() function. If you are not sure, set this to false.
-func (r *Runtime) newVolume(noCreatePluginVolume bool, options ...VolumeCreateOption) (_ *Volume, deferredErr error) {
+func (r *Runtime) newVolume(ctx context.Context, noCreatePluginVolume bool, options ...VolumeCreateOption) (_ *Volume, deferredErr error) {
volume := newVolume(r)
for _, option := range options {
if err := option(volume); err != nil {
@@ -83,6 +86,50 @@ func (r *Runtime) newVolume(noCreatePluginVolume bool, options ...VolumeCreateOp
return nil, fmt.Errorf("invalid mount option %s for driver 'local': %w", key, define.ErrInvalidArg)
}
}
+ } else if volume.config.Driver == define.VolumeDriverImage && !volume.UsesVolumeDriver() {
+ logrus.Debugf("Creating image-based volume")
+ var imgString string
+ // Validate options
+ for key, val := range volume.config.Options {
+ switch strings.ToLower(key) {
+ case "image":
+ imgString = val
+ default:
+ return nil, fmt.Errorf("invalid mount option %s for driver 'image': %w", key, define.ErrInvalidArg)
+ }
+ }
+
+ if imgString == "" {
+ return nil, fmt.Errorf("must provide an image name when creating a volume with the image driver: %w", define.ErrInvalidArg)
+ }
+
+ // Look up the image
+ image, _, err := r.libimageRuntime.LookupImage(imgString, nil)
+ if err != nil {
+ return nil, fmt.Errorf("looking up image %s to create volume failed: %w", imgString, err)
+ }
+
+ // Generate a c/storage name and ID for the volume.
+ // Use characters Podman does not allow for the name, to ensure
+ // no collision with containers.
+ volume.config.StorageID = stringid.GenerateRandomID()
+ volume.config.StorageName = volume.config.Name + volumeSuffix
+ volume.config.StorageImageID = image.ID()
+
+ // Create a backing container in c/storage.
+ storageConfig := storage.ContainerOptions{
+ LabelOpts: []string{"filetype:container_file_t:s0"},
+ }
+ if _, err := r.storageService.CreateContainerStorage(ctx, r.imageContext, imgString, image.ID(), volume.config.StorageName, volume.config.StorageID, storageConfig); err != nil {
+ return nil, fmt.Errorf("creating backing storage for image driver: %w", err)
+ }
+ defer func() {
+ if deferredErr != nil {
+ if err := r.storageService.DeleteContainer(volume.config.StorageID); err != nil {
+ logrus.Errorf("Error removing volume %s backing storage: %v", volume.config.Name, err)
+ }
+ }
+ }()
}
// Now we get conditional: we either need to make the volume in the
@@ -196,7 +243,7 @@ func (r *Runtime) UpdateVolumePlugins(ctx context.Context) *define.VolumeReload
}
for _, vol := range vols {
allPluginVolumes[vol.Name] = struct{}{}
- if _, err := r.newVolume(true, WithVolumeName(vol.Name), WithVolumeDriver(driverName)); err != nil {
+ if _, err := r.newVolume(ctx, true, WithVolumeName(vol.Name), WithVolumeDriver(driverName)); err != nil {
// If the volume exists this is not an error, just ignore it and log. It is very likely
// that the volume from the plugin was already in our db.
if !errors.Is(err, define.ErrVolumeExists) {
@@ -324,7 +371,7 @@ func (r *Runtime) removeVolume(ctx context.Context, v *Volume, force bool, timeo
logrus.Debugf("Removing container %s (depends on volume %q)", ctr.ID(), v.Name())
- if err := r.removeContainer(ctx, ctr, force, false, false, timeout); err != nil {
+ if err := r.removeContainer(ctx, ctr, force, false, false, false, timeout); err != nil {
return fmt.Errorf("removing container %s that depends on volume %s: %w", ctr.ID(), v.Name(), err)
}
}
@@ -375,6 +422,14 @@ func (r *Runtime) removeVolume(ctx context.Context, v *Volume, force bool, timeo
return fmt.Errorf("volume %s could not be removed from plugin %s: %w", v.Name(), v.Driver(), err)
}
}
+ } else if v.config.Driver == define.VolumeDriverImage {
+ if err := v.runtime.storageService.DeleteContainer(v.config.StorageID); err != nil {
+ // Storage container is already gone, no problem.
+ if !(errors.Is(err, storage.ErrNotAContainer) || errors.Is(err, storage.ErrContainerUnknown)) {
+ return fmt.Errorf("removing volume %s storage: %w", v.Name(), err)
+ }
+ logrus.Infof("Storage for volume %s already removed", v.Name())
+ }
}
// Remove the volume from the state
diff --git a/libpod/state.go b/libpod/state.go
index 4fbd3c302..9d9604563 100644
--- a/libpod/state.go
+++ b/libpod/state.go
@@ -144,6 +144,14 @@ type State interface {
// As with RemoveExecSession, container state will not be modified.
RemoveContainerExecSessions(ctr *Container) error
+ // ContainerIDIsVolume checks if the given container ID is in use by a
+ // volume.
+ // Some volumes are backed by a c/storage container. These do not have a
+ // corresponding Container struct in Libpod, but rather a Volume.
+ // This determines if a given ID from c/storage is used as a backend by
+ // a Podman volume.
+ ContainerIDIsVolume(id string) (bool, error)
+
// PLEASE READ FULL DESCRIPTION BEFORE USING.
// Rewrite a container's configuration.
// This function breaks libpod's normal prohibition on a read-only
diff --git a/libpod/state_test.go b/libpod/state_test.go
index 3c1fe8f63..7664f7c00 100644
--- a/libpod/state_test.go
+++ b/libpod/state_test.go
@@ -1,7 +1,6 @@
package libpod
import (
- "io/ioutil"
"os"
"path/filepath"
"strings"
@@ -35,7 +34,7 @@ var (
// Get an empty BoltDB state for use in tests
func getEmptyBoltState() (_ State, _ string, _ lock.Manager, retErr error) {
- tmpDir, err := ioutil.TempDir("", tmpDirPrefix)
+ tmpDir, err := os.MkdirTemp("", tmpDirPrefix)
if err != nil {
return nil, "", nil, err
}
diff --git a/libpod/volume.go b/libpod/volume.go
index a054e4032..2d4ea4280 100644
--- a/libpod/volume.go
+++ b/libpod/volume.go
@@ -57,6 +57,15 @@ type VolumeConfig struct {
DisableQuota bool `json:"disableQuota,omitempty"`
// Timeout allows users to override the default driver timeout of 5 seconds
Timeout *uint `json:"timeout,omitempty"`
+ // StorageName is the name of the volume in c/storage. Only used for
+ // image volumes.
+ StorageName string `json:"storageName,omitempty"`
+ // StorageID is the ID of the volume in c/storage. Only used for image
+ // volumes.
+ StorageID string `json:"storageID,omitempty"`
+ // StorageImageID is the ID of the image the volume was based off of.
+ // Only used for image volumes.
+ StorageImageID string `json:"storageImageID,omitempty"`
}
// VolumeState holds the volume's mutable state.
@@ -149,7 +158,7 @@ func (v *Volume) MountCount() (uint, error) {
// Internal-only helper for volume mountpoint
func (v *Volume) mountPoint() string {
- if v.UsesVolumeDriver() {
+ if v.UsesVolumeDriver() || v.config.Driver == define.VolumeDriverImage {
return v.state.MountPoint
}
@@ -250,6 +259,12 @@ func (v *Volume) IsDangling() (bool, error) {
// drivers are pluggable backends for volumes that will manage the storage and
// mounting.
func (v *Volume) UsesVolumeDriver() bool {
+ if v.config.Driver == define.VolumeDriverImage {
+ if _, ok := v.runtime.config.Engine.VolumePlugins[v.config.Driver]; ok {
+ return true
+ }
+ return false
+ }
return !(v.config.Driver == define.VolumeDriverLocal || v.config.Driver == "")
}
diff --git a/libpod/volume_inspect.go b/libpod/volume_inspect.go
index 73441576b..31fbd5eff 100644
--- a/libpod/volume_inspect.go
+++ b/libpod/volume_inspect.go
@@ -64,6 +64,7 @@ func (v *Volume) Inspect() (*define.InspectVolumeData, error) {
data.MountCount = v.state.MountCount
data.NeedsCopyUp = v.state.NeedsCopyUp
data.NeedsChown = v.state.NeedsChown
+ data.StorageID = v.config.StorageID
if v.config.Timeout != nil {
data.Timeout = *v.config.Timeout
diff --git a/libpod/volume_internal.go b/libpod/volume_internal.go
index 43c3f9b0b..14b852f8e 100644
--- a/libpod/volume_internal.go
+++ b/libpod/volume_internal.go
@@ -39,6 +39,11 @@ func (v *Volume) needsMount() bool {
return true
}
+ // Image driver always needs mount
+ if v.config.Driver == define.VolumeDriverImage {
+ return true
+ }
+
// Commit 28138dafcc added the UID and GID options to this map
// However we should only mount when options other than uid and gid are set.
// see https://github.com/containers/podman/issues/10620
diff --git a/libpod/volume_internal_linux.go b/libpod/volume_internal_linux.go
index cfd60554d..440bceec3 100644
--- a/libpod/volume_internal_linux.go
+++ b/libpod/volume_internal_linux.go
@@ -66,6 +66,15 @@ func (v *Volume) mount() error {
v.state.MountCount++
v.state.MountPoint = mountPoint
return v.save()
+ } else if v.config.Driver == define.VolumeDriverImage {
+ mountPoint, err := v.runtime.storageService.MountContainerImage(v.config.StorageID)
+ if err != nil {
+ return fmt.Errorf("mounting volume %s image failed: %w", v.Name(), err)
+ }
+
+ v.state.MountCount++
+ v.state.MountPoint = mountPoint
+ return v.save()
}
volDevice := v.config.Options["device"]
@@ -161,6 +170,13 @@ func (v *Volume) unmount(force bool) error {
v.state.MountPoint = ""
return v.save()
+ } else if v.config.Driver == define.VolumeDriverImage {
+ if _, err := v.runtime.storageService.UnmountContainerImage(v.config.StorageID, force); err != nil {
+ return fmt.Errorf("unmounting volume %s image: %w", v.Name(), err)
+ }
+
+ v.state.MountPoint = ""
+ return v.save()
}
// Unmount the volume