aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Containerfile-testvol10
-rw-r--r--Makefile30
-rw-r--r--RELEASE_PROCESS.md2
-rw-r--r--cmd/podman-mac-helper/main.go4
-rw-r--r--cmd/podman/containers/cleanup.go2
-rw-r--r--cmd/podman/containers/port.go2
-rw-r--r--cmd/podman/machine/init.go19
-rw-r--r--cmd/podman/machine/list.go2
-rw-r--r--cmd/podman/registry/config.go4
-rw-r--r--cmd/podman/system/connection/add.go2
-rw-r--r--cmd/podman/validate/args.go4
-rw-r--r--cmd/podman/volumes/reload.go52
-rw-r--r--cmd/winpath/main.go2
-rw-r--r--commands-demo.md2
-rw-r--r--contrib/cirrus/lib.sh6
-rwxr-xr-xcontrib/cirrus/logformatter53
-rwxr-xr-xcontrib/cirrus/runner.sh5
-rw-r--r--contrib/podmanimage/README.md4
-rw-r--r--docs/source/Tutorials.rst6
-rw-r--r--docs/source/markdown/podman-container-cleanup.1.md8
-rw-r--r--docs/source/markdown/podman-container.1.md2
-rw-r--r--docs/source/markdown/podman-create.1.md13
-rw-r--r--docs/source/markdown/podman-port.1.md2
-rw-r--r--docs/source/markdown/podman-run.1.md15
-rw-r--r--docs/source/markdown/podman-volume-create.1.md3
-rw-r--r--docs/source/markdown/podman-volume-reload.1.md29
-rw-r--r--docs/source/markdown/podman-volume.1.md1
-rw-r--r--docs/tutorials/README.md6
-rw-r--r--docs/tutorials/basic_networking.md15
-rw-r--r--docs/tutorials/podman_tutorial.md2
-rw-r--r--go.mod6
-rw-r--r--go.sum32
-rw-r--r--libpod/boltdb_state.go248
-rw-r--r--libpod/boltdb_state_internal.go22
-rw-r--r--libpod/container.go4
-rw-r--r--libpod/container_api.go97
-rw-r--r--libpod/container_config.go1
-rw-r--r--libpod/container_exec.go21
-rw-r--r--libpod/container_internal.go96
-rw-r--r--libpod/container_internal_linux.go9
-rw-r--r--libpod/define/errors.go4
-rw-r--r--libpod/define/volume_inspect.go6
-rw-r--r--libpod/events.go13
-rw-r--r--libpod/events/config.go4
-rw-r--r--libpod/events/events.go4
-rw-r--r--libpod/events/journal_linux.go2
-rw-r--r--libpod/healthcheck.go17
-rw-r--r--libpod/networking_linux.go4
-rw-r--r--libpod/oci_conmon_attach_linux.go2
-rw-r--r--libpod/oci_conmon_linux.go229
-rw-r--r--libpod/options.go2
-rw-r--r--libpod/plugin/volume_api.go18
-rw-r--r--libpod/pod_internal.go2
-rw-r--r--libpod/runtime.go8
-rw-r--r--libpod/runtime_ctr.go16
-rw-r--r--libpod/runtime_pod_linux.go32
-rw-r--r--libpod/runtime_volume.go2
-rw-r--r--libpod/runtime_volume_linux.go100
-rw-r--r--libpod/state.go9
-rw-r--r--libpod/stats.go24
-rw-r--r--libpod/util_linux.go27
-rw-r--r--libpod/volume_internal.go6
-rw-r--r--pkg/api/handlers/compat/containers_stats.go25
-rw-r--r--pkg/api/handlers/libpod/containers_stats.go2
-rw-r--r--pkg/api/handlers/utils/containers.go1
-rw-r--r--pkg/api/server/listener_api.go2
-rw-r--r--pkg/bindings/connection.go2
-rw-r--r--pkg/criu/criu.go43
-rw-r--r--pkg/criu/criu_linux.go44
-rw-r--r--pkg/criu/criu_unsupported.go8
-rw-r--r--pkg/domain/entities/engine_container.go1
-rw-r--r--pkg/domain/entities/events.go10
-rw-r--r--pkg/domain/entities/volumes.go5
-rw-r--r--pkg/domain/filters/volumes.go8
-rw-r--r--pkg/domain/infra/abi/containers.go36
-rw-r--r--pkg/domain/infra/abi/pods.go101
-rw-r--r--pkg/domain/infra/abi/system.go6
-rw-r--r--pkg/domain/infra/abi/volumes.go5
-rw-r--r--pkg/domain/infra/runtime_libpod.go4
-rw-r--r--pkg/domain/infra/tunnel/helpers.go4
-rw-r--r--pkg/domain/infra/tunnel/volumes.go4
-rw-r--r--pkg/machine/e2e/list_test.go23
-rw-r--r--pkg/machine/ignition.go4
-rw-r--r--pkg/machine/qemu/machine.go14
-rw-r--r--pkg/namespaces/namespaces.go2
-rw-r--r--pkg/rootless/rootless_linux.go4
-rw-r--r--pkg/signal/signal_common.go17
-rw-r--r--pkg/signal/signal_linux.go17
-rw-r--r--pkg/signal/signal_linux_mipsx.go17
-rw-r--r--pkg/signal/signal_unix.go11
-rw-r--r--pkg/signal/signal_unsupported.go11
-rw-r--r--pkg/specgen/generate/container_create.go13
-rw-r--r--pkg/specgen/generate/oci.go23
-rw-r--r--pkg/specgen/volumes.go16
-rw-r--r--pkg/systemd/generate/containers.go2
-rw-r--r--pkg/util/mountOpts.go7
-rw-r--r--rootless.md4
-rw-r--r--test/apiv2/27-containersEvents.at4
-rw-r--r--test/e2e/checkpoint_test.go41
-rw-r--r--test/e2e/common_test.go2
-rw-r--r--test/e2e/config.go2
-rw-r--r--test/e2e/create_staticip_test.go2
-rw-r--r--test/e2e/create_staticmac_test.go2
-rw-r--r--test/e2e/events_test.go21
-rw-r--r--test/e2e/image_scp_test.go23
-rw-r--r--test/e2e/run_staticip_test.go2
-rw-r--r--test/e2e/run_volume_test.go36
-rw-r--r--test/e2e/system_connection_test.go24
-rw-r--r--test/e2e/volume_ls_test.go31
-rw-r--r--test/e2e/volume_plugin_test.go68
-rw-r--r--test/framework/framework.go2
-rw-r--r--test/system/030-run.bats12
-rw-r--r--test/system/150-login.bats2
-rw-r--r--test/system/160-volumes.bats23
-rw-r--r--test/system/200-pod.bats41
-rw-r--r--test/system/500-networking.bats14
-rw-r--r--test/system/helpers.bash31
-rw-r--r--test/testvol/Containerfile9
-rw-r--r--test/testvol/create.go26
-rw-r--r--test/testvol/list.go32
-rw-r--r--test/testvol/main.go23
-rw-r--r--test/testvol/remove.go26
-rw-r--r--test/testvol/util.go29
-rw-r--r--troubleshooting.md4
-rw-r--r--vendor/github.com/containers/common/pkg/cgroups/blkio.go3
-rw-r--r--vendor/github.com/containers/common/pkg/cgroups/blkio_linux.go161
-rw-r--r--vendor/github.com/containers/common/pkg/cgroups/cgroups.go62
-rw-r--r--vendor/github.com/containers/common/pkg/cgroups/cgroups_linux.go575
-rw-r--r--vendor/github.com/containers/common/pkg/cgroups/cpu.go74
-rw-r--r--vendor/github.com/containers/common/pkg/cgroups/cpu_linux.go100
-rw-r--r--vendor/github.com/containers/common/pkg/cgroups/cpuset.go41
-rw-r--r--vendor/github.com/containers/common/pkg/cgroups/cpuset_linux.go57
-rw-r--r--vendor/github.com/containers/common/pkg/cgroups/memory.go3
-rw-r--r--vendor/github.com/containers/common/pkg/cgroups/memory_linux.go78
-rw-r--r--vendor/github.com/containers/common/pkg/cgroups/pids.go3
-rw-r--r--vendor/github.com/containers/common/pkg/cgroups/pids_linux.go71
-rw-r--r--vendor/github.com/containers/common/pkg/cgroups/systemd.go7
-rw-r--r--vendor/github.com/containers/common/pkg/cgroups/systemd_linux.go167
-rw-r--r--vendor/github.com/containers/common/pkg/cgroups/utils.go176
-rw-r--r--vendor/github.com/containers/common/pkg/cgroups/utils_linux.go146
-rw-r--r--vendor/github.com/containers/common/pkg/parse/parse.go7
-rw-r--r--vendor/github.com/containers/common/pkg/seccomp/filter.go3
-rw-r--r--vendor/github.com/opencontainers/runc/libcontainer/cgroups/cgroups.go15
-rw-r--r--vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/blkio.go311
-rw-r--r--vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/cpu.go129
-rw-r--r--vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/cpuacct.go166
-rw-r--r--vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/cpuset.go245
-rw-r--r--vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/devices.go39
-rw-r--r--vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/error.go15
-rw-r--r--vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/freezer.go158
-rw-r--r--vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/fs.go264
-rw-r--r--vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/hugetlb.go62
-rw-r--r--vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/memory.go348
-rw-r--r--vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/name.go31
-rw-r--r--vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/net_cls.go32
-rw-r--r--vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/net_prio.go30
-rw-r--r--vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/paths.go186
-rw-r--r--vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/perf_event.go24
-rw-r--r--vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/pids.go62
-rw-r--r--vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/rdma.go25
-rw-r--r--vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs2/cpu.go87
-rw-r--r--vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs2/cpuset.go28
-rw-r--r--vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs2/create.go152
-rw-r--r--vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs2/defaultpath.go99
-rw-r--r--vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs2/freezer.go127
-rw-r--r--vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs2/fs2.go271
-rw-r--r--vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs2/hugetlb.go48
-rw-r--r--vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs2/io.go193
-rw-r--r--vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs2/memory.go216
-rw-r--r--vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs2/pids.go72
-rw-r--r--vendor/github.com/opencontainers/runc/libcontainer/cgroups/fscommon/rdma.go121
-rw-r--r--vendor/github.com/opencontainers/runc/libcontainer/cgroups/fscommon/utils.go145
-rw-r--r--vendor/github.com/opencontainers/runc/libcontainer/configs/blkio_device.go8
-rw-r--r--vendor/github.com/opencontainers/runc/libcontainer/configs/config.go3
-rw-r--r--vendor/github.com/opencontainers/runc/libcontainer/configs/mount.go6
-rw-r--r--vendor/github.com/opencontainers/runc/libcontainer/utils/utils.go19
-rw-r--r--vendor/github.com/seccomp/libseccomp-golang/CHANGELOG25
-rw-r--r--vendor/github.com/seccomp/libseccomp-golang/README.md32
-rw-r--r--vendor/github.com/seccomp/libseccomp-golang/SECURITY.md1
-rw-r--r--vendor/github.com/seccomp/libseccomp-golang/seccomp.go15
-rw-r--r--vendor/github.com/seccomp/libseccomp-golang/seccomp_internal.go17
-rw-r--r--vendor/github.com/stretchr/testify/assert/assertion_compare.go24
-rw-r--r--vendor/modules.txt12
183 files changed, 7159 insertions, 840 deletions
diff --git a/Containerfile-testvol b/Containerfile-testvol
deleted file mode 100644
index 6ff45064b..000000000
--- a/Containerfile-testvol
+++ /dev/null
@@ -1,10 +0,0 @@
-FROM golang:1.15-alpine AS build-img
-COPY ./test/testvol/ /go/src/github.com/containers/podman/cmd/testvol/
-COPY ./vendor /go/src/github.com/containers/podman/vendor/
-WORKDIR /go/src/github.com/containers/podman
-RUN go build -o /testvol ./cmd/testvol
-
-FROM alpine
-COPY --from=build-img /testvol /usr/local/bin
-WORKDIR /
-ENTRYPOINT ["/usr/local/bin/testvol"]
diff --git a/Makefile b/Makefile
index 38ea7a256..b6e143cea 100644
--- a/Makefile
+++ b/Makefile
@@ -51,10 +51,20 @@ BUILDTAGS ?= \
$(shell hack/libsubid_tag.sh) \
exclude_graphdriver_devicemapper \
seccomp
+ifeq ($(shell uname -s),FreeBSD)
+# Use bash for make's shell function - the default shell on FreeBSD
+# has a command builtin is not compatible with the way its used below
+SHELL := $(shell command -v bash)
+endif
PYTHON ?= $(shell command -v python3 python|head -n1)
PKG_MANAGER ?= $(shell command -v dnf yum|head -n1)
# ~/.local/bin is not in PATH on all systems
PRE_COMMIT = $(shell command -v bin/venv/bin/pre-commit ~/.local/bin/pre-commit pre-commit | head -n1)
+ifeq ($(shell uname -s),FreeBSD)
+SED=gsed
+else
+SED=sed
+endif
# This isn't what we actually build; it's a superset, used for target
# dependencies. Basically: all *.go and *.c files, except *_test.go,
@@ -180,7 +190,11 @@ default: all
all: binaries docs
.PHONY: binaries
+ifeq ($(shell uname -s),FreeBSD)
+binaries: podman podman-remote ## Build podman and podman-remote binaries
+else
binaries: podman podman-remote rootlessport ## Build podman, podman-remote and rootlessport binaries
+endif
# Extract text following double-# for targets, as their description for
# the `help` target. Otherwise These simple-substitutions are resolved
@@ -225,11 +239,11 @@ test/checkseccomp/checkseccomp: $(wildcard test/checkseccomp/*.go)
.PHONY: test/testvol/testvol
test/testvol/testvol: $(wildcard test/testvol/*.go)
- $(GOCMD) build $(BUILDFLAGS) $(GO_LDFLAGS) '$(LDFLAGS_PODMAN)' -o $@ ./test/testvol
+ $(GOCMD) build -o $@ ./test/testvol
-.PHONY: volume-plugin-test-image
+.PHONY: volume-plugin-test-img
volume-plugin-test-img:
- podman build -t quay.io/libpod/volume-plugin-test-img -f Containerfile-testvol .
+ ./bin/podman build --network none -t quay.io/libpod/volume-plugin-test-img:$$(date +%Y%m%d) -f ./test/testvol/Containerfile .
.PHONY: test/goecho/goecho
test/goecho/goecho: $(wildcard test/goecho/*.go)
@@ -423,7 +437,7 @@ $(MANPAGES): %: %.md .install.md2man docdir
### replaces "\" at the end of a line with two spaces
### this ensures that manpages are renderd correctly
- @sed -e 's/\((podman[^)]*\.md\(#.*\)\?)\)//g' \
+ @$(SED) -e 's/\((podman[^)]*\.md\(#.*\)\?)\)//g' \
-e 's/\[\(podman[^]]*\)\]/\1/g' \
-e 's/\[\([^]]*\)](http[^)]\+)/\1/g' \
-e 's;<\(/\)\?\(a\|a\s\+[^>]*\|sup\)>;;g' \
@@ -739,7 +753,9 @@ install.bin:
install ${SELINUXOPT} -m 755 bin/podman $(DESTDIR)$(BINDIR)/podman
test -z "${SELINUXOPT}" || chcon --verbose --reference=$(DESTDIR)$(BINDIR)/podman bin/podman
install ${SELINUXOPT} -d -m 755 $(DESTDIR)$(LIBEXECPODMAN)
+ifneq ($(shell uname -s),FreeBSD)
install ${SELINUXOPT} -m 755 bin/rootlessport $(DESTDIR)$(LIBEXECPODMAN)/rootlessport
+endif
test -z "${SELINUXOPT}" || chcon --verbose --reference=$(DESTDIR)$(LIBEXECPODMAN)/rootlessport bin/rootlessport
install ${SELINUXOPT} -m 755 -d ${DESTDIR}${TMPFILESDIR}
install ${SELINUXOPT} -m 644 contrib/tmpfile/podman.conf ${DESTDIR}${TMPFILESDIR}/podman.conf
@@ -753,9 +769,9 @@ install.modules-load: # This should only be used by distros which might use ipta
install.man:
install ${SELINUXOPT} -d -m 755 $(DESTDIR)$(MANDIR)/man1
install ${SELINUXOPT} -d -m 755 $(DESTDIR)$(MANDIR)/man5
- install ${SELINUXOPT} -m 644 $(filter %.1,$(MANPAGES_DEST)) -t $(DESTDIR)$(MANDIR)/man1
- install ${SELINUXOPT} -m 644 $(filter %.5,$(MANPAGES_DEST)) -t $(DESTDIR)$(MANDIR)/man5
- install ${SELINUXOPT} -m 644 docs/source/markdown/links/*1 -t $(DESTDIR)$(MANDIR)/man1
+ install ${SELINUXOPT} -m 644 $(filter %.1,$(MANPAGES_DEST)) $(DESTDIR)$(MANDIR)/man1
+ install ${SELINUXOPT} -m 644 $(filter %.5,$(MANPAGES_DEST)) $(DESTDIR)$(MANDIR)/man5
+ install ${SELINUXOPT} -m 644 docs/source/markdown/links/*1 $(DESTDIR)$(MANDIR)/man1
.PHONY: install.completions
install.completions:
diff --git a/RELEASE_PROCESS.md b/RELEASE_PROCESS.md
index 3f63e5466..66cc74693 100644
--- a/RELEASE_PROCESS.md
+++ b/RELEASE_PROCESS.md
@@ -162,7 +162,7 @@ spelled with complete minutiae.
release branch (`git checkout upstream/vX.Y`).
1. Create a new local working-branch to develop the release PR,
`git checkout -b bump_vX.Y.Z`.
- 1. Lookup the *COMMIT ID* of the last release,
+ 1. Look up the *COMMIT ID* of the last release,
`git log -1 $(git tag | sort -V | tail -1)`.
1. Edit `version/version.go` and bump the `Version` value to the new
release version. If there were API changes, also bump `APIVersion` value.
diff --git a/cmd/podman-mac-helper/main.go b/cmd/podman-mac-helper/main.go
index 8d995519f..735d9898f 100644
--- a/cmd/podman-mac-helper/main.go
+++ b/cmd/podman-mac-helper/main.go
@@ -73,7 +73,7 @@ func getUserInfo(name string) (string, string, string, error) {
entry := readCapped(output)
elements := strings.Split(entry, ":")
if len(elements) < 9 || elements[0] != name {
- return "", "", "", errors.New("Could not lookup user")
+ return "", "", "", errors.New("Could not look up user")
}
return elements[0], elements[2], elements[8], nil
@@ -90,7 +90,7 @@ func getUser() (string, string, string, error) {
_, uid, home, err := getUserInfo(name)
if err != nil {
- return "", "", "", fmt.Errorf("could not lookup user: %s", name)
+ return "", "", "", fmt.Errorf("could not look up user: %s", name)
}
id, err := strconv.Atoi(uid)
if err != nil {
diff --git a/cmd/podman/containers/cleanup.go b/cmd/podman/containers/cleanup.go
index a63e413fe..18cec097c 100644
--- a/cmd/podman/containers/cleanup.go
+++ b/cmd/podman/containers/cleanup.go
@@ -23,7 +23,7 @@ var (
cleanupCommand = &cobra.Command{
Annotations: map[string]string{registry.EngineMode: registry.ABIMode},
Use: "cleanup [options] CONTAINER [CONTAINER...]",
- Short: "Cleanup network and mountpoints of one or more containers",
+ Short: "Clean up network and mountpoints of one or more containers",
Long: cleanupDescription,
RunE: cleanup,
Args: func(cmd *cobra.Command, args []string) error {
diff --git a/cmd/podman/containers/port.go b/cmd/podman/containers/port.go
index f10bdd5b4..fdb2f6c46 100644
--- a/cmd/podman/containers/port.go
+++ b/cmd/podman/containers/port.go
@@ -15,7 +15,7 @@ import (
)
var (
- portDescription = `List port mappings for the CONTAINER, or lookup the public-facing port that is NAT-ed to the PRIVATE_PORT
+ portDescription = `List port mappings for the CONTAINER, or look up the public-facing port that is NAT-ed to the PRIVATE_PORT
`
portCommand = &cobra.Command{
Use: "port [options] CONTAINER [PORT]",
diff --git a/cmd/podman/machine/init.go b/cmd/podman/machine/init.go
index 612c36057..9d464ad37 100644
--- a/cmd/podman/machine/init.go
+++ b/cmd/podman/machine/init.go
@@ -109,7 +109,7 @@ func init() {
flags.BoolVar(&initOpts.Rootful, rootfulFlagName, false, "Whether this machine should prefer rootful container execution")
}
-func initMachine(_ *cobra.Command, args []string) error {
+func initMachine(cmd *cobra.Command, args []string) error {
var (
err error
vm machine.VM
@@ -147,17 +147,12 @@ func initMachine(_ *cobra.Command, args []string) error {
fmt.Println("Machine init complete")
if now {
- err = vm.Start(initOpts.Name, machine.StartOptions{})
- if err == nil {
- fmt.Printf("Machine %q started successfully\n", initOpts.Name)
- newMachineEvent(events.Start, events.Event{Name: initOpts.Name})
- }
- } else {
- extra := ""
- if initOpts.Name != defaultMachineName {
- extra = " " + initOpts.Name
- }
- fmt.Printf("To start your machine run:\n\n\tpodman machine start%s\n\n", extra)
+ return start(cmd, args)
+ }
+ extra := ""
+ if initOpts.Name != defaultMachineName {
+ extra = " " + initOpts.Name
}
+ fmt.Printf("To start your machine run:\n\n\tpodman machine start%s\n\n", extra)
return err
}
diff --git a/cmd/podman/machine/list.go b/cmd/podman/machine/list.go
index bb14d4a67..1ffb8690c 100644
--- a/cmd/podman/machine/list.go
+++ b/cmd/podman/machine/list.go
@@ -138,7 +138,7 @@ func outputTemplate(cmd *cobra.Command, responses []*ListReporter) error {
switch {
case cmd.Flags().Changed("format"):
row = cmd.Flag("format").Value.String()
- listFlag.noHeading = !report.HasTable(row)
+ printHeader = report.HasTable(row)
row = report.NormalizeFormat(row)
default:
row = cmd.Flag("format").Value.String()
diff --git a/cmd/podman/registry/config.go b/cmd/podman/registry/config.go
index b5c9b359c..e06de034d 100644
--- a/cmd/podman/registry/config.go
+++ b/cmd/podman/registry/config.go
@@ -92,7 +92,7 @@ func setXdgDirs() error {
return nil
}
- // Setup XDG_RUNTIME_DIR
+ // Set up XDG_RUNTIME_DIR
if _, found := os.LookupEnv("XDG_RUNTIME_DIR"); !found {
dir, err := util.GetRuntimeDir()
if err != nil {
@@ -110,7 +110,7 @@ func setXdgDirs() error {
}
}
- // Setup XDG_CONFIG_HOME
+ // Set up XDG_CONFIG_HOME
if _, found := os.LookupEnv("XDG_CONFIG_HOME"); !found {
cfgHomeDir, err := util.GetRootlessConfigHomeDir()
if err != nil {
diff --git a/cmd/podman/system/connection/add.go b/cmd/podman/system/connection/add.go
index 387de3c58..d77a39bcc 100644
--- a/cmd/podman/system/connection/add.go
+++ b/cmd/podman/system/connection/add.go
@@ -188,7 +188,7 @@ func GetUserInfo(uri *url.URL) (*url.Userinfo, error) {
if u, found := os.LookupEnv("_CONTAINERS_ROOTLESS_UID"); found {
usr, err = user.LookupId(u)
if err != nil {
- return nil, errors.Wrapf(err, "failed to lookup rootless user")
+ return nil, errors.Wrapf(err, "failed to look up rootless user")
}
} else {
usr, err = user.Current()
diff --git a/cmd/podman/validate/args.go b/cmd/podman/validate/args.go
index 4c40581c6..ae405e0e5 100644
--- a/cmd/podman/validate/args.go
+++ b/cmd/podman/validate/args.go
@@ -73,9 +73,9 @@ func CheckAllLatestAndIDFile(c *cobra.Command, args []string, ignoreArgLen bool,
specifiedLatest, _ = c.Flags().GetBool("latest")
if c.Flags().Lookup("all") == nil || c.Flags().Lookup("latest") == nil {
if idFileFlag == "" {
- return errors.New("unable to lookup values for 'latest' or 'all'")
+ return errors.New("unable to look up values for 'latest' or 'all'")
} else if c.Flags().Lookup(idFileFlag) == nil {
- return errors.Errorf("unable to lookup values for 'latest', 'all', or '%s'", idFileFlag)
+ return errors.Errorf("unable to look up values for 'latest', 'all', or '%s'", idFileFlag)
}
}
}
diff --git a/cmd/podman/volumes/reload.go b/cmd/podman/volumes/reload.go
new file mode 100644
index 000000000..d0d76fb88
--- /dev/null
+++ b/cmd/podman/volumes/reload.go
@@ -0,0 +1,52 @@
+package volumes
+
+import (
+ "fmt"
+
+ "github.com/containers/common/pkg/completion"
+ "github.com/containers/podman/v4/cmd/podman/registry"
+ "github.com/containers/podman/v4/cmd/podman/utils"
+ "github.com/containers/podman/v4/cmd/podman/validate"
+ "github.com/spf13/cobra"
+)
+
+var (
+ reloadDescription = `Check all configured volume plugins and update the libpod database with all available volumes.
+
+ Existing volumes are also removed from the database when they are no longer present in the plugin.`
+ reloadCommand = &cobra.Command{
+ Use: "reload",
+ Args: validate.NoArgs,
+ Short: "reload all volumes from volume plugins",
+ Long: reloadDescription,
+ RunE: reload,
+ ValidArgsFunction: completion.AutocompleteNone,
+ }
+)
+
+func init() {
+ registry.Commands = append(registry.Commands, registry.CliCommand{
+ Command: reloadCommand,
+ Parent: volumeCmd,
+ })
+}
+
+func reload(cmd *cobra.Command, args []string) error {
+ report, err := registry.ContainerEngine().VolumeReload(registry.Context())
+ if err != nil {
+ return err
+ }
+ printReload("Added", report.Added)
+ printReload("Removed", report.Removed)
+ errs := (utils.OutputErrors)(report.Errors)
+ return errs.PrintErrors()
+}
+
+func printReload(typ string, values []string) {
+ if len(values) > 0 {
+ fmt.Println(typ + ":")
+ for _, name := range values {
+ fmt.Println(name)
+ }
+ }
+}
diff --git a/cmd/winpath/main.go b/cmd/winpath/main.go
index b7aa7330d..bb57e39de 100644
--- a/cmd/winpath/main.go
+++ b/cmd/winpath/main.go
@@ -131,7 +131,7 @@ func removePathFromRegistry(path string) error {
k, err := registry.OpenKey(registry.CURRENT_USER, Environment, registry.READ|registry.WRITE)
if err != nil {
if errors.Is(err, fs.ErrNotExist) {
- // Nothing to cleanup, the Environment registry key does not exist.
+ // Nothing to clean up, the Environment registry key does not exist.
return nil
}
return err
diff --git a/commands-demo.md b/commands-demo.md
index ececf0a22..50e2873b2 100644
--- a/commands-demo.md
+++ b/commands-demo.md
@@ -11,7 +11,7 @@
| [podman-commit(1)](https://podman.readthedocs.io/en/latest/markdown/podman-commit.1.html) | Create new image based on the changed container |
| [podman-container(1)](https://podman.readthedocs.io/en/latest/managecontainers.html) | Manage Containers |
| [podman-container-checkpoint(1)](https://podman.readthedocs.io/en/latest/markdown/podman-container-checkpoint.1.html) | Checkpoints one or more running containers |
-| [podman-container-cleanup(1)](https://podman.readthedocs.io/en/latest/markdown/podman-container-cleanup.1.html) | Cleanup the container's network and mountpoints |
+| [podman-container-cleanup(1)](https://podman.readthedocs.io/en/latest/markdown/podman-container-cleanup.1.html) | Clean up the container's network and mountpoints |
| [podman-container-exists(1)](https://podman.readthedocs.io/en/latest/markdown/podman-container-exists.1.html) | Check if an container exists in local storage |
| [podman-container-prune(1)](https://podman.readthedocs.io/en/latest/markdown/podman-container-prune.1.html) | Remove all stopped containers from local storage |
| [podman-container-restore(1)](https://podman.readthedocs.io/en/latest/markdown/podman-container-restore.1.html) | Restores one or more containers from a checkpoint |
diff --git a/contrib/cirrus/lib.sh b/contrib/cirrus/lib.sh
index 724f7c3d5..2624af385 100644
--- a/contrib/cirrus/lib.sh
+++ b/contrib/cirrus/lib.sh
@@ -173,7 +173,7 @@ setup_rootless() {
ssh-keygen -t ed25519 -P "" -f "/home/$ROOTLESS_USER/.ssh/id_ed25519"
ssh-keygen -t rsa -P "" -f "/home/$ROOTLESS_USER/.ssh/id_rsa"
- msg "Setup authorized_keys"
+ msg "Set up authorized_keys"
cat $HOME/.ssh/*.pub /home/$ROOTLESS_USER/.ssh/*.pub >> $HOME/.ssh/authorized_keys
cat $HOME/.ssh/*.pub /home/$ROOTLESS_USER/.ssh/*.pub >> /home/$ROOTLESS_USER/.ssh/authorized_keys
@@ -186,9 +186,9 @@ setup_rootless() {
# never be any non-localhost connections made from tests (using strict-mode).
# If there are, it's either a security problem or a broken test, both of which
# we want to lead to test failures.
- msg " setup known_hosts for $USER"
+ msg " set up known_hosts for $USER"
ssh-keyscan localhost > /root/.ssh/known_hosts
- msg " setup known_hosts for $ROOTLESS_USER"
+ msg " set up known_hosts for $ROOTLESS_USER"
# Maintain access-permission consistency with all other .ssh files.
install -Z -m 700 -o $ROOTLESS_USER -g $ROOTLESS_USER \
/root/.ssh/known_hosts /home/$ROOTLESS_USER/.ssh/known_hosts
diff --git a/contrib/cirrus/logformatter b/contrib/cirrus/logformatter
index e45f03df9..59969c3e7 100755
--- a/contrib/cirrus/logformatter
+++ b/contrib/cirrus/logformatter
@@ -190,6 +190,22 @@ END_HTML
print { $out_fh } "<h2>Synopsis</h2>\n<hr/>\n",
job_synopsis($test_name), "<hr/>\n";
+ # FOR DEBUGGING: dump environment, but in HTML comments to not clutter
+ # This is safe. There is a TOKEN envariable, but it's not sensitive.
+ # There are no sensitive/secret values in our execution environment,
+ # but we're careful anyway. $SECRET_ENV_RE is set in lib.sh
+ my $filter_re = $ENV{SECRET_ENV_RE} || 'ACCOUNT|GC[EP]|PASSW|SECRET|TOKEN';
+ $filter_re .= '|BASH_FUNC'; # These are long and un-useful
+
+ print { $out_fh } "<!-- Environment: -->\n";
+ for my $e (sort keys %ENV) {
+ next if $e =~ /$filter_re/;
+
+ my $val = escapeHTML($ENV{$e});
+ $val =~ s/--/-&#x002D;/g; # double dash not valid in comments
+ printf { $out_fh } "<!-- %-20s %s -->\n", $e, $val;
+ }
+
# State variables
my $previous_timestamp = ''; # timestamp of previous line
my $cirrus_task; # Cirrus task number, used for linking
@@ -538,27 +554,24 @@ END_HTML
# If Cirrus magic envariables are available, write a link to results.
# FIXME: it'd be so nice to make this a clickable live link.
#
- # STATIC_MAGIC_BLOB is the name of a google-storage bucket. It is
- # unlikely to change often, but if it does you will suddenly start
- # seeing errors when trying to view formatted logs:
- #
- # AccessDeniedAccess denied.Anonymous caller does not have storage.objects.get access to the Google Cloud Storage object.
- #
- # This happened in July 2020 when github.com/containers/libpod was
- # renamed to podman. If something like that ever happens again, you
- # will need to get the new magic blob value from:
- #
- # https://console.cloud.google.com/storage/browser?project=libpod-218412
+ # As of June 2022 we use the Cirrus API[1] as the source of our logs,
+ # instead of linking directly to googleapis.com. This will allow us
+ # to abstract cloud-specific details, so we can one day use Amazon cloud.
+ # See #14569 for more info.
#
- # You will also probably need to set the bucket Public by clicking on
- # the bucket name, then the Permissions tab. This is safe, since this
- # project is fully open-source.
- if ($have_formatted_log && $ENV{CIRRUS_TASK_ID}) {
- my $URL_BASE = "https://storage.googleapis.com";
- my $STATIC_MAGIC_BLOB = "cirrus-ci-6707778565701632-fcae48";
- my $ARTIFACT_NAME = "html";
-
- my $URL = "${URL_BASE}/${STATIC_MAGIC_BLOB}/artifacts/$ENV{CIRRUS_REPO_FULL_NAME}/$ENV{CIRRUS_TASK_ID}/${ARTIFACT_NAME}/${outfile}";
+ # [1] https://cirrus-ci.org/guide/writing-tasks/#latest-build-artifacts
+ if ($have_formatted_log && $ENV{CIRRUS_BUILD_ID} && $ENV{CIRRUS_TASK_NAME}) {
+ my $URL_BASE = "https://api.cirrus-ci.com";
+ my $build_id = $ENV{CIRRUS_BUILD_ID};
+ my $task_name = $ENV{CIRRUS_TASK_NAME};
+
+ # Escape spaces in task names ("int fedora 35 podman root etc")
+ $task_name =~ s/\s/%20/g;
+
+ # URL is long and cumbersome and duplicaty. The task name cannot be
+ # reduced; the file name could, but I choose to leave it because I
+ # sometimes download HTML logs and oh how I hate "log.html" filenames.
+ my $URL = "${URL_BASE}/v1/artifact/build/$build_id/$task_name/html/${outfile}";
print "\n\nAnnotated results:\n $URL\n";
}
diff --git a/contrib/cirrus/runner.sh b/contrib/cirrus/runner.sh
index b9f43f395..d49286ad3 100755
--- a/contrib/cirrus/runner.sh
+++ b/contrib/cirrus/runner.sh
@@ -142,7 +142,10 @@ exec_container() {
# Line-separated arguments which include shell-escaped special characters
declare -a envargs
while read -r var_val; do
- envargs+=("-e $var_val")
+ # Pass "-e VAR" on the command line, not "-e VAR=value". Podman can
+ # do a much better job of transmitting the value than we can,
+ # especially when value includes spaces.
+ envargs+=("-e" "$(awk -F= '{print $1}' <<<$var_val)")
done <<<"$(passthrough_envars)"
# VM Images and Container images are built using (nearly) identical operations.
diff --git a/contrib/podmanimage/README.md b/contrib/podmanimage/README.md
index b4ef81d84..0f4f715ad 100644
--- a/contrib/podmanimage/README.md
+++ b/contrib/podmanimage/README.md
@@ -32,7 +32,9 @@ The container images are:
* `quay.io/podman/upstream:latest` - This image is built daily using the latest
code found in this GitHub repository. Due to the image changing frequently,
it's not guaranteed to be stable or even executable. The image is built with
- [the upstream Containerfile](upstream/Containerfile).
+ [the upstream Containerfile](upstream/Containerfile). Note the actual compilation
+ of upstream podman [occurs continuously in
+ COPR](https://copr.fedorainfracloud.org/coprs/rhcontainerbot/podman-next/).
## Sample Usage
diff --git a/docs/source/Tutorials.rst b/docs/source/Tutorials.rst
index c2cbcb8a9..024e6847c 100644
--- a/docs/source/Tutorials.rst
+++ b/docs/source/Tutorials.rst
@@ -4,11 +4,11 @@ Tutorials
=========
Here are a number of useful tutorials to get you up and running with Podman. If you are familiar with the Docker `Container Engine`_ the command in Podman_ should be quite familiar. If you are brand new to containers, take a look at our `Introduction`.
-* `Basic Setup and Use of Podman <https://github.com/containers/podman/blob/main/docs/tutorials/podman_tutorial.md>`_: Learn how to setup Podman and perform some basic commands with the utility.
-* `Basic Setup and Use of Podman in a Rootless environment <https://github.com/containers/podman/blob/main/docs/tutorials/rootless_tutorial.md>`_: The steps required to setup rootless Podman are enumerated.
+* `Basic Setup and Use of Podman <https://github.com/containers/podman/blob/main/docs/tutorials/podman_tutorial.md>`_: Learn how to set up Podman and perform some basic commands with the utility.
+* `Basic Setup and Use of Podman in a Rootless environment <https://github.com/containers/podman/blob/main/docs/tutorials/rootless_tutorial.md>`_: The steps required to set up rootless Podman are enumerated.
* `Podman for Windows <https://github.com/containers/podman/blob/main/docs/tutorials/podman-for-windows.md>`_: A guide to installing and using Podman on Windows.
* `Podman Remote Clients on Mac/Windows <https://github.com/containers/podman/blob/main/docs/tutorials/mac_win_client.md>`_: Advanced setup for connecting to a remote Linux system using the Podman remote client on Mac and Windows.
-* `How to sign and distribute container images using Podman <https://github.com/containers/podman/blob/main/docs/tutorials/image_signing.md>`_: Learn how to setup and use image signing with Podman.
+* `How to sign and distribute container images using Podman <https://github.com/containers/podman/blob/main/docs/tutorials/image_signing.md>`_: Learn how to set up and use image signing with Podman.
* `Podman remote-client tutorial <https://github.com/containers/podman/blob/main/docs/tutorials/remote_client.md>`_: A brief how-to on using the Podman remote-client.
* `How to use libpod for custom/derivative projects <https://github.com/containers/podman/blob/main/docs/tutorials/podman-derivative-api.md>`_: How the libpod API can be used within your own project.
* `How to use Podman's Go RESTful bindings <https://github.com/containers/podman/tree/main/pkg/bindings>`_: An introduction to using our RESTful Golang bindings in an external application.
diff --git a/docs/source/markdown/podman-container-cleanup.1.md b/docs/source/markdown/podman-container-cleanup.1.md
index 0f182eded..0ad09efd3 100644
--- a/docs/source/markdown/podman-container-cleanup.1.md
+++ b/docs/source/markdown/podman-container-cleanup.1.md
@@ -1,7 +1,7 @@
% podman-container-cleanup(1)
## NAME
-podman\-container\-cleanup - Cleanup the container's network and mountpoints
+podman\-container\-cleanup - Clean up the container's network and mountpoints
## SYNOPSIS
**podman container cleanup** [*options*] *container* [*container* ...]
@@ -13,7 +13,7 @@ Sometimes container mount points and network stacks can remain if the podman com
## OPTIONS
#### **--all**, **-a**
-Cleanup all *containers*.\
+Clean up all *containers*.\
The default is **false**.\
*IMPORTANT: This OPTION does not need a container name or ID as input argument.*
@@ -40,12 +40,12 @@ After cleanup, remove the image entirely.\
The default is **false**.
## EXAMPLES
-Cleanup the container "mywebserver".
+Clean up the container "mywebserver".
```
$ podman container cleanup mywebserver
```
-Cleanup the containers with the names "mywebserver", "myflaskserver", "860a4b23".
+Clean up the containers with the names "mywebserver", "myflaskserver", "860a4b23".
```
$ podman container cleanup mywebserver myflaskserver 860a4b23
```
diff --git a/docs/source/markdown/podman-container.1.md b/docs/source/markdown/podman-container.1.md
index 36623c718..a66e2789d 100644
--- a/docs/source/markdown/podman-container.1.md
+++ b/docs/source/markdown/podman-container.1.md
@@ -15,7 +15,7 @@ The container command allows you to manage containers
| --------- | --------------------------------------------------- | ---------------------------------------------------------------------------- |
| attach | [podman-attach(1)](podman-attach.1.md) | Attach to a running container. |
| checkpoint | [podman-container-checkpoint(1)](podman-container-checkpoint.1.md) | Checkpoints one or more running containers. |
-| cleanup | [podman-container-cleanup(1)](podman-container-cleanup.1.md) | Cleanup the container's network and mountpoints. |
+| cleanup | [podman-container-cleanup(1)](podman-container-cleanup.1.md) | Clean up the container's network and mountpoints. |
| clone | [podman-container-clone(1)](podman-container-clone.1.md) | Creates a copy of an existing container. |
| commit | [podman-commit(1)](podman-commit.1.md) | Create new image based on the changed container. |
| cp | [podman-cp(1)](podman-cp.1.md) | Copy files/folders between a container and the local filesystem. |
diff --git a/docs/source/markdown/podman-create.1.md b/docs/source/markdown/podman-create.1.md
index 624b0b384..425ce7bcc 100644
--- a/docs/source/markdown/podman-create.1.md
+++ b/docs/source/markdown/podman-create.1.md
@@ -654,7 +654,7 @@ Current supported mount TYPEs are **bind**, **volume**, **image**, **tmpfs** and
· bind-propagation: shared, slave, private, unbindable, rshared, rslave, runbindable, or rprivate(default). See also mount(2).
- . bind-nonrecursive: do not setup a recursive bind mount. By default it is recursive.
+ . bind-nonrecursive: do not set up a recursive bind mount. By default it is recursive.
. relabel: shared, private.
@@ -1295,13 +1295,14 @@ The _options_ is a comma-separated list and can be:
* **rw**|**ro**
* **z**|**Z**
-* [**r**]**shared**|[**r**]**slave**|[**r**]**private**[**r**]**unbindable**
-* [**r**]**bind**
-* [**no**]**exec**
-* [**no**]**dev**
-* [**no**]**suid**
* [**O**]
* [**U**]
+* [**no**]**copy**
+* [**no**]**dev**
+* [**no**]**exec**
+* [**no**]**suid**
+* [**r**]**bind**
+* [**r**]**shared**|[**r**]**slave**|[**r**]**private**[**r**]**unbindable**
The `CONTAINER-DIR` must be an absolute path such as `/src/docs`. The volume
will be mounted into the container at this directory.
diff --git a/docs/source/markdown/podman-port.1.md b/docs/source/markdown/podman-port.1.md
index a72fc12bf..ebfeeccd7 100644
--- a/docs/source/markdown/podman-port.1.md
+++ b/docs/source/markdown/podman-port.1.md
@@ -9,7 +9,7 @@ podman\-port - List port mappings for a container
**podman container port** [*options*] *container* [*private-port*[/*proto*]]
## DESCRIPTION
-List port mappings for the *container* or lookup the public-facing port that is NAT-ed to the *private-port*.
+List port mappings for the *container* or look up the public-facing port that is NAT-ed to the *private-port*.
## OPTIONS
diff --git a/docs/source/markdown/podman-run.1.md b/docs/source/markdown/podman-run.1.md
index 3b886e466..5b45c3350 100644
--- a/docs/source/markdown/podman-run.1.md
+++ b/docs/source/markdown/podman-run.1.md
@@ -679,7 +679,7 @@ Current supported mount TYPEs are **bind**, **volume**, **image**, **tmpfs** and
· bind-propagation: shared, slave, private, unbindable, rshared, rslave, runbindable, or rprivate(default). See also mount(2).
- . bind-nonrecursive: do not setup a recursive bind mount. By default it is recursive.
+ . bind-nonrecursive: do not set up a recursive bind mount. By default it is recursive.
. relabel: shared, private.
@@ -1362,13 +1362,14 @@ The _options_ is a comma-separated list and can be: <sup>[[1]](#Footnote1)</sup>
* **rw**|**ro**
* **z**|**Z**
-* [**r**]**shared**|[**r**]**slave**|[**r**]**private**[**r**]**unbindable**
-* [**r**]**bind**
-* [**no**]**exec**
-* [**no**]**dev**
-* [**no**]**suid**
* [**O**]
* [**U**]
+* [**no**]**copy**
+* [**no**]**dev**
+* [**no**]**exec**
+* [**no**]**suid**
+* [**r**]**bind**
+* [**r**]**shared**|[**r**]**slave**|[**r**]**private**[**r**]**unbindable**
The `CONTAINER-DIR` must be an absolute path such as `/src/docs`. The volume
will be mounted into the container at this directory.
@@ -1883,7 +1884,7 @@ $ podman run --uidmap 0:30000:7000 --gidmap 0:30000:7000 fedora echo hello
Podman allows for the configuration of storage by changing the values
in the _/etc/container/storage.conf_ or by using global options. This
-shows how to setup and use fuse-overlayfs for a one time run of busybox
+shows how to set up and use fuse-overlayfs for a one time run of busybox
using global options.
```
diff --git a/docs/source/markdown/podman-volume-create.1.md b/docs/source/markdown/podman-volume-create.1.md
index 31e109791..32b10da84 100644
--- a/docs/source/markdown/podman-volume-create.1.md
+++ b/docs/source/markdown/podman-volume-create.1.md
@@ -31,9 +31,10 @@ Set metadata for a volume (e.g., --label mykey=value).
Set driver specific options.
For the default driver, **local**, this allows a volume to be configured to mount a filesystem on the host.
-For the `local` driver the following options are supported: `type`, `device`, and `o`.
+For the `local` driver the following options are supported: `type`, `device`, `o`, and `[no]copy`.
The `type` option sets the type of the filesystem to be mounted, and is equivalent to the `-t` flag to **mount(8)**.
The `device` option sets the device to be mounted, and is equivalent to the `device` argument to **mount(8)**.
+The `copy` option enables copying files from the container image path where the mount is created to the newly created volume on the first run. `copy` is the default.
The `o` option sets options for the mount, and is equivalent to the `-o` flag to **mount(8)** with these exceptions:
diff --git a/docs/source/markdown/podman-volume-reload.1.md b/docs/source/markdown/podman-volume-reload.1.md
new file mode 100644
index 000000000..5b9e9b9ac
--- /dev/null
+++ b/docs/source/markdown/podman-volume-reload.1.md
@@ -0,0 +1,29 @@
+% podman-volume-reload(1)
+
+## NAME
+podman\-volume\-reload - Reload all volumes from volumes plugins
+
+## SYNOPSIS
+**podman volume reload**
+
+## DESCRIPTION
+
+**podman volume reload** checks all configured volume plugins and updates the libpod database with all available volumes.
+Existing volumes are also removed from the database when they are no longer present in the plugin.
+
+This command it is best effort and cannot guarantee a perfect state because plugins can be modified from the outside at any time.
+
+Note: This command is not supported with podman-remote.
+
+## EXAMPLES
+
+```
+$ podman volume reload
+Added:
+vol6
+Removed:
+t3
+```
+
+## SEE ALSO
+**[podman(1)](podman.1.md)**, **[podman-volume(1)](podman-volume.1.md)**
diff --git a/docs/source/markdown/podman-volume.1.md b/docs/source/markdown/podman-volume.1.md
index 476d58591..a437590b3 100644
--- a/docs/source/markdown/podman-volume.1.md
+++ b/docs/source/markdown/podman-volume.1.md
@@ -21,6 +21,7 @@ podman volume is a set of subcommands that manage volumes.
| ls | [podman-volume-ls(1)](podman-volume-ls.1.md) | List all the available volumes. |
| mount | [podman-volume-mount(1)](podman-volume-mount.1.md) | Mount a volume filesystem. |
| prune | [podman-volume-prune(1)](podman-volume-prune.1.md) | Remove all unused volumes. |
+| reload | [podman-volume-reload(1)](podman-volume-reload.1.md) | Reload all volumes from volumes plugins. |
| rm | [podman-volume-rm(1)](podman-volume-rm.1.md) | Remove one or more volumes. |
| unmount | [podman-volume-unmount(1)](podman-volume-unmount.1.md) | Unmount a volume. |
diff --git a/docs/tutorials/README.md b/docs/tutorials/README.md
index 2a3c85c55..c7c1a3616 100644
--- a/docs/tutorials/README.md
+++ b/docs/tutorials/README.md
@@ -6,11 +6,11 @@
**[Introduction Tutorial](podman_tutorial.md)**
-Learn how to setup Podman and perform some basic commands with the utility.
+Learn how to set up Podman and perform some basic commands with the utility.
**[Basic Setup and Use of Podman in a Rootless environment](rootless_tutorial.md)**
-The steps required to setup rootless Podman are enumerated.
+The steps required to set up rootless Podman are enumerated.
**[Setup Mac/Windows](mac_win_client.md)**
@@ -26,7 +26,7 @@ How the libpod API can be used within your own project.
**[Image Signing](image_signing.md)**
-Learn how to setup and use image signing with Podman.
+Learn how to set up and use image signing with Podman.
**[Basic Networking](basic_networking.md)**
diff --git a/docs/tutorials/basic_networking.md b/docs/tutorials/basic_networking.md
index b6f53175b..0a6034e7a 100644
--- a/docs/tutorials/basic_networking.md
+++ b/docs/tutorials/basic_networking.md
@@ -13,13 +13,14 @@ Each setup is supported with an example.
## Differences between rootful and rootless container networking
-One of the guiding factors on networking for containers with Podman is going to be
-whether or not the container is run by a root user or not. This is because unprivileged
-users cannot create networking interfaces on the host. Therefore, with rootful
-containers, the default networking mode is to use netavark.
-For rootless, the default network
-mode is slirp4netns. Because of the limited privileges, slirp4netns lacks some of
-the features of networking; for example, slirp4netns cannot give containers a
+One of the guiding factors on networking for containers with Podman is going to
+be whether or not the container is run by a root user or not. This is because
+unprivileged users cannot create networking interfaces on the host. Therefore,
+for rootless containers, the default network mode is slirp4netns. Because of the
+limited privileges, slirp4netns lacks some of the features of networking
+compared to rootful Podman's networking; for example, slirp4netns cannot give
+containers a routable IP address. The default networking mode for rootful
+containers on the other side is netavark, which allows a container to have a
routable IP address.
## Firewalls
diff --git a/docs/tutorials/podman_tutorial.md b/docs/tutorials/podman_tutorial.md
index 83f1e5e1e..a371189e9 100644
--- a/docs/tutorials/podman_tutorial.md
+++ b/docs/tutorials/podman_tutorial.md
@@ -142,7 +142,7 @@ podman rm --latest
You can verify the deletion of the container by running *podman ps -a*.
## Integration Tests
-For more information on how to setup and run the integration tests in your environment, checkout the Integration Tests [README.md](../../test/README.md)
+For more information on how to set up and run the integration tests in your environment, checkout the Integration Tests [README.md](../../test/README.md)
## More information
diff --git a/go.mod b/go.mod
index 6141fe007..933aed725 100644
--- a/go.mod
+++ b/go.mod
@@ -12,7 +12,7 @@ require (
github.com/containernetworking/cni v1.1.1
github.com/containernetworking/plugins v1.1.1
github.com/containers/buildah v1.26.1-0.20220609225314-e66309ebde8c
- github.com/containers/common v0.48.1-0.20220608111710-dbecabbe82c9
+ github.com/containers/common v0.48.1-0.20220627112538-97d9656daba8
github.com/containers/conmon v2.0.20+incompatible
github.com/containers/image/v5 v5.21.2-0.20220617075545-929f14a56f5c
github.com/containers/ocicrypt v1.1.5
@@ -57,7 +57,7 @@ require (
github.com/sirupsen/logrus v1.8.1
github.com/spf13/cobra v1.5.0
github.com/spf13/pflag v1.0.5
- github.com/stretchr/testify v1.7.4
+ github.com/stretchr/testify v1.7.5
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635
github.com/uber/jaeger-client-go v2.30.0+incompatible
github.com/ulikunitz/xz v0.5.10
@@ -75,3 +75,5 @@ require (
)
require github.com/docker/libnetwork v0.8.0-dev.2.0.20190625141545-5a177b73e316 // indirect
+
+replace github.com/opencontainers/runc => github.com/opencontainers/runc v1.1.1-0.20220617142545-8b9452f75cbc
diff --git a/go.sum b/go.sum
index 4440331cd..bc754b1e4 100644
--- a/go.sum
+++ b/go.sum
@@ -198,8 +198,6 @@ github.com/charithe/durationcheck v0.0.9/go.mod h1:SSbRIBVfMjCi/kEB6K65XEA83D6pr
github.com/chavacava/garif v0.0.0-20210405164556-e8a0a408d6af/go.mod h1:Qjyv4H3//PWVzTeCezG2b9IRn6myJxJSr4TD/xo6ojU=
github.com/checkpoint-restore/checkpointctl v0.0.0-20220321135231-33f4a66335f0 h1:txB5jvhzUCSiiQmqmMWpo5CEB7Gj/Hq5Xqi7eaPl8ko=
github.com/checkpoint-restore/checkpointctl v0.0.0-20220321135231-33f4a66335f0/go.mod h1:67kWC1PXQLR3lM/mmNnu3Kzn7K4TSWZAGUuQP1JSngk=
-github.com/checkpoint-restore/go-criu/v4 v4.1.0/go.mod h1:xUQBLp4RLc5zJtWY++yjOoMoB5lihDt7fai+75m+rGw=
-github.com/checkpoint-restore/go-criu/v5 v5.0.0/go.mod h1:cfwC0EG7HMUenopBsUf9d89JlCLQIfgVcNsNN0t6T2M=
github.com/checkpoint-restore/go-criu/v5 v5.2.0/go.mod h1:E/eQpaFtUKGOOSEBZgmKAcn+zUUwWxqcaKZlF54wK8E=
github.com/checkpoint-restore/go-criu/v5 v5.3.0 h1:wpFFOoomK3389ue2lAb0Boag6XPht5QYpipxmSNL4d8=
github.com/checkpoint-restore/go-criu/v5 v5.3.0/go.mod h1:E/eQpaFtUKGOOSEBZgmKAcn+zUUwWxqcaKZlF54wK8E=
@@ -213,7 +211,6 @@ github.com/cilium/ebpf v0.0.0-20200110133405-4032b1d8aae3/go.mod h1:MA5e5Lr8slmE
github.com/cilium/ebpf v0.0.0-20200702112145-1c8d4c9ef775/go.mod h1:7cR51M8ViRLIdUjrmSXlK9pkrsDlLHbO8jiB8X8JnOc=
github.com/cilium/ebpf v0.2.0/go.mod h1:To2CFviqOWL/M0gIMsvSMlqe7em/l1ALkX1PyjrX2Qs=
github.com/cilium/ebpf v0.4.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs=
-github.com/cilium/ebpf v0.6.2/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs=
github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA=
github.com/cilium/ebpf v0.9.0/go.mod h1:+OhNOIXx/Fnu1IE8bJz2dzOA+VSfyTfdNUVdlQnxUFY=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
@@ -340,12 +337,14 @@ github.com/containernetworking/plugins v1.1.1 h1:+AGfFigZ5TiQH00vhR8qPeSatj53eNG
github.com/containernetworking/plugins v1.1.1/go.mod h1:Sr5TH/eBsGLXK/h71HeLfX19sZPp3ry5uHSkI4LPxV8=
github.com/containers/buildah v1.26.1-0.20220609225314-e66309ebde8c h1:/fKyiLFFuceBPZGJ0Lig7ElURhfsslAOw1BOcItD+X8=
github.com/containers/buildah v1.26.1-0.20220609225314-e66309ebde8c/go.mod h1:b0L+u2Dam7soWGn5sVTK31L++Xrf80AbGvK5z9D2+lw=
-github.com/containers/common v0.48.1-0.20220608111710-dbecabbe82c9 h1:sK+TNC8oUBkruZTIqwYJrENetSLQnk+goBVyLiqsJq8=
github.com/containers/common v0.48.1-0.20220608111710-dbecabbe82c9/go.mod h1:WBLwq+i7bicCpH54V70HM6s7jqDAESTlYnd05XXp0ac=
+github.com/containers/common v0.48.1-0.20220627112538-97d9656daba8 h1:d9CnUqml4SeEWGfQ781UR4quow9xdf7Q0hYqBYFRH4E=
+github.com/containers/common v0.48.1-0.20220627112538-97d9656daba8/go.mod h1:UDe7OTpNdtJA2T80Sp7yB0yTaj79f4kMNQbTsNxsqoY=
github.com/containers/conmon v2.0.20+incompatible h1:YbCVSFSCqFjjVwHTPINGdMX1F6JXHGTUje2ZYobNrkg=
github.com/containers/conmon v2.0.20+incompatible/go.mod h1:hgwZ2mtuDrppv78a/cOBNiCm6O0UMWGx1mu7P00nu5I=
github.com/containers/image/v5 v5.21.2-0.20220511203756-fe4fd4ed8be4/go.mod h1:OsX9sFexyGF0FCNAjfcVFv3IwMqDyLyV/WQY/roLPcE=
github.com/containers/image/v5 v5.21.2-0.20220520105616-e594853d6471/go.mod h1:KntCBNQn3qOuZmQuJ38ORyTozmWXiuo05Vef2S0Sm5M=
+github.com/containers/image/v5 v5.21.2-0.20220615100411-a78a00792916/go.mod h1:hEf8L08Hrh/3fK4vLf5l7988MJmij2swfCBUzqgnhF4=
github.com/containers/image/v5 v5.21.2-0.20220617075545-929f14a56f5c h1:U2F/FaMt8gPP8sIpBfvXMCP5gAZfyxoYZ7lmu0dwsXc=
github.com/containers/image/v5 v5.21.2-0.20220617075545-929f14a56f5c/go.mod h1:hEf8L08Hrh/3fK4vLf5l7988MJmij2swfCBUzqgnhF4=
github.com/containers/libtrust v0.0.0-20200511145503-9c3a6c22cd9a h1:spAGlqziZjCJL25C6F1zsQY05tfCKE9F5YwtEWWe6hU=
@@ -365,6 +364,7 @@ github.com/containers/storage v1.38.0/go.mod h1:lBzt28gAk5ADZuRtwdndRJyqX22vnRaX
github.com/containers/storage v1.40.2/go.mod h1:zUyPC3CFIGR1OhY1CKkffxgw9+LuH76PGvVcFj38dgs=
github.com/containers/storage v1.41.0/go.mod h1:Pb0l5Sm/89kolX3o2KolKQ5cCHk5vPNpJrhNaLcdS5s=
github.com/containers/storage v1.41.1-0.20220607143333-8951d0153bf6/go.mod h1:6XQ68cEG8ojfP/m3HIupFV1rZsnqeFmaE8N1ctBP94Y=
+github.com/containers/storage v1.41.1-0.20220614214904-388efef4bf7e/go.mod h1:6XQ68cEG8ojfP/m3HIupFV1rZsnqeFmaE8N1ctBP94Y=
github.com/containers/storage v1.41.1-0.20220616120034-7df64288ef35 h1:6o+kw2z0BrdJ1wNYUbwLVzlb/65KPmZSy+32GNfppzM=
github.com/containers/storage v1.41.1-0.20220616120034-7df64288ef35/go.mod h1:6XQ68cEG8ojfP/m3HIupFV1rZsnqeFmaE8N1ctBP94Y=
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
@@ -399,7 +399,6 @@ github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7Do
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw=
github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
-github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4=
github.com/cyphar/filepath-securejoin v0.2.3 h1:YX6ebbZCZP7VkM3scTTokDgBL2TY741X51MTk3ycuNI=
github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c/go.mod h1:Ct2BUK8SB0YC1SMSibvLzxjeJLnrYEVLULFNiHY9YfQ=
@@ -1053,19 +1052,8 @@ github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799/go.mod
github.com/opencontainers/image-spec v1.0.3-0.20211202193544-a5463b7f9c84/go.mod h1:Qnt1q4cjDNQI9bT832ziho5Iw2BhK8o1KwLOwW56VP4=
github.com/opencontainers/image-spec v1.0.3-0.20220114050600-8b9d41f48198 h1:+czc/J8SlhPKLOtVLMQc+xDCFBT73ZStMsRhSsUhsSg=
github.com/opencontainers/image-spec v1.0.3-0.20220114050600-8b9d41f48198/go.mod h1:j4h1pJW6ZcJTgMZWP3+7RlG3zTaP02aDZ/Qw0sppK7Q=
-github.com/opencontainers/runc v0.0.0-20190115041553-12f6a991201f/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
-github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
-github.com/opencontainers/runc v1.0.0-rc8.0.20190926000215-3e425f80a8c9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
-github.com/opencontainers/runc v1.0.0-rc9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
-github.com/opencontainers/runc v1.0.0-rc93/go.mod h1:3NOsor4w32B2tC0Zbl8Knk4Wg84SM2ImC1fxBuqJ/H0=
-github.com/opencontainers/runc v1.0.2/go.mod h1:aTaHFFwQXuA71CiyxOdFFIorAoemI04suvGRQFzWTD0=
-github.com/opencontainers/runc v1.0.3/go.mod h1:aTaHFFwQXuA71CiyxOdFFIorAoemI04suvGRQFzWTD0=
-github.com/opencontainers/runc v1.1.0/go.mod h1:Tj1hFw6eFWp/o33uxGf5yF2BX5yz2Z6iptFpuvbbKqc=
-github.com/opencontainers/runc v1.1.1-0.20220607072441-a7a45d7d2721/go.mod h1:QvA0UNe48mC1JxcXq0sENIR38+/LdJMLNxuAvtFBhxA=
-github.com/opencontainers/runc v1.1.1/go.mod h1:Tj1hFw6eFWp/o33uxGf5yF2BX5yz2Z6iptFpuvbbKqc=
-github.com/opencontainers/runc v1.1.2/go.mod h1:Tj1hFw6eFWp/o33uxGf5yF2BX5yz2Z6iptFpuvbbKqc=
-github.com/opencontainers/runc v1.1.3 h1:vIXrkId+0/J2Ymu2m7VjGvbSlAId9XNRPhn2p4b+d8w=
-github.com/opencontainers/runc v1.1.3/go.mod h1:1J5XiS+vdZ3wCyZybsuxXZWGrgSr8fFJHLXuG2PsnNg=
+github.com/opencontainers/runc v1.1.1-0.20220617142545-8b9452f75cbc h1:qjkUzmFsOFbQyjObybk40mRida83j5IHRaKzLGdBbEU=
+github.com/opencontainers/runc v1.1.1-0.20220617142545-8b9452f75cbc/go.mod h1:wUOQGsiKae6VzA/UvlCK3cO+pHk8F2VQHlIoITEfMM8=
github.com/opencontainers/runtime-spec v0.1.2-0.20190507144316-5b71a03e2700/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
github.com/opencontainers/runtime-spec v1.0.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
github.com/opencontainers/runtime-spec v1.0.2-0.20190207185410-29686dbc5559/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
@@ -1198,10 +1186,10 @@ github.com/sclevine/spec v1.2.0/go.mod h1:W4J29eT/Kzv7/b9IWLB055Z+qvVC9vt0Arko24
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/sebdah/goldie/v2 v2.5.3 h1:9ES/mNN+HNUbNWpVAlrzuZ7jE+Nrczbj8uFRjM7624Y=
github.com/sebdah/goldie/v2 v2.5.3/go.mod h1:oZ9fp0+se1eapSRjfYbsV/0Hqhbuu3bJVvKI/NNtssI=
-github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo=
github.com/seccomp/libseccomp-golang v0.9.2-0.20210429002308-3879420cc921/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg=
-github.com/seccomp/libseccomp-golang v0.9.2-0.20220502022130-f33da4d89646 h1:RpforrEYXWkmGwJHIGnLZ3tTWStkjVVstwzNGqxX2Ds=
github.com/seccomp/libseccomp-golang v0.9.2-0.20220502022130-f33da4d89646/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg=
+github.com/seccomp/libseccomp-golang v0.10.0 h1:aA4bp+/Zzi0BnWZ2F1wgNBs5gTpm+na2rWM6M9YjLpY=
+github.com/seccomp/libseccomp-golang v0.10.0/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg=
github.com/securego/gosec/v2 v2.9.1/go.mod h1:oDcDLcatOJxkCGaCaq8lua1jTnYf6Sou4wdiJ1n4iHc=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
@@ -1280,8 +1268,8 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
-github.com/stretchr/testify v1.7.4 h1:wZRexSlwd7ZXfKINDLsO4r7WBt3gTKONc6K/VesHvHM=
-github.com/stretchr/testify v1.7.4/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
+github.com/stretchr/testify v1.7.5 h1:s5PTfem8p8EbKQOctVV53k6jCJt3UX4IEJzwh+C324Q=
+github.com/stretchr/testify v1.7.5/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/sylabs/sif/v2 v2.7.0/go.mod h1:TiyBWsgWeh5yBeQFNuQnvROwswqK7YJT8JA1L53bsXQ=
github.com/sylabs/sif/v2 v2.7.1 h1:XXt9AP39sQfsMCGOGQ/XP9H47yqZOvAonalkaCaNIYM=
diff --git a/libpod/boltdb_state.go b/libpod/boltdb_state.go
index c3db6152a..471f64b84 100644
--- a/libpod/boltdb_state.go
+++ b/libpod/boltdb_state.go
@@ -5,8 +5,10 @@ import (
"fmt"
"net"
"os"
+ "strconv"
"strings"
"sync"
+ "time"
"github.com/containers/common/libnetwork/types"
"github.com/containers/podman/v4/libpod/define"
@@ -63,6 +65,13 @@ type BoltState struct {
// initially created the database. This must match for any further instances
// that access the database, to ensure that state mismatches with
// containers/storage do not occur.
+// - exitCodeBucket/exitCodeTimeStampBucket: (#14559) exit codes must be part
+// of the database to resolve a previous race condition when one process waits
+// for the exit file to be written and another process removes it along with
+// the container during auto-removal. The same race would happen trying to
+// read the exit code from the containers bucket. Hence, exit codes go into
+// their own bucket. To avoid the rather expensive JSON (un)marshaling, we
+// have two buckets: one for the exit codes, the other for the timestamps.
// NewBoltState creates a new bolt-backed state database
func NewBoltState(path string, runtime *Runtime) (State, error) {
@@ -98,6 +107,8 @@ func NewBoltState(path string, runtime *Runtime) (State, error) {
allVolsBkt,
execBkt,
runtimeConfigBkt,
+ exitCodeBkt,
+ exitCodeTimeStampBkt,
}
// Does the DB need an update?
@@ -192,6 +203,45 @@ func (s *BoltState) Refresh() error {
return err
}
+ exitCodeBucket, err := getExitCodeBucket(tx)
+ if err != nil {
+ return err
+ }
+
+ timeStampBucket, err := getExitCodeTimeStampBucket(tx)
+ if err != nil {
+ return err
+ }
+
+ // Clear all exec exit codes
+ toRemoveExitCodes := []string{}
+ err = exitCodeBucket.ForEach(func(id, _ []byte) error {
+ toRemoveExitCodes = append(toRemoveExitCodes, string(id))
+ return nil
+ })
+ if err != nil {
+ return errors.Wrapf(err, "error reading exit codes bucket")
+ }
+ for _, id := range toRemoveExitCodes {
+ if err := exitCodeBucket.Delete([]byte(id)); err != nil {
+ return errors.Wrapf(err, "error removing exit code for ID %s", id)
+ }
+ }
+
+ toRemoveTimeStamps := []string{}
+ err = timeStampBucket.ForEach(func(id, _ []byte) error {
+ toRemoveTimeStamps = append(toRemoveTimeStamps, string(id))
+ return nil
+ })
+ if err != nil {
+ return errors.Wrapf(err, "reading timestamps bucket")
+ }
+ for _, id := range toRemoveTimeStamps {
+ if err := timeStampBucket.Delete([]byte(id)); err != nil {
+ return errors.Wrapf(err, "removing timestamp for ID %s", id)
+ }
+ }
+
// Iterate through all IDs. Check if they are containers.
// If they are, unmarshal their state, and then clear
// PID, mountpoint, and state for all of them
@@ -1341,6 +1391,204 @@ func (s *BoltState) GetContainerConfig(id string) (*ContainerConfig, error) {
return config, nil
}
+// AddContainerExitCode adds the exit code for the specified container to the database.
+func (s *BoltState) AddContainerExitCode(id string, exitCode int32) error {
+ if len(id) == 0 {
+ return define.ErrEmptyID
+ }
+
+ if !s.valid {
+ return define.ErrDBClosed
+ }
+
+ db, err := s.getDBCon()
+ if err != nil {
+ return err
+ }
+ defer s.deferredCloseDBCon(db)
+
+ rawID := []byte(id)
+ rawExitCode := []byte(strconv.Itoa(int(exitCode)))
+ rawTimeStamp, err := time.Now().MarshalText()
+ if err != nil {
+ return fmt.Errorf("marshaling exit-code time stamp: %w", err)
+ }
+
+ return db.Update(func(tx *bolt.Tx) error {
+ exitCodeBucket, err := getExitCodeBucket(tx)
+ if err != nil {
+ return err
+ }
+ timeStampBucket, err := getExitCodeTimeStampBucket(tx)
+ if err != nil {
+ return err
+ }
+
+ if err := exitCodeBucket.Put(rawID, rawExitCode); err != nil {
+ return fmt.Errorf("adding exit code of container %s to DB: %w", id, err)
+ }
+ if err := timeStampBucket.Put(rawID, rawTimeStamp); err != nil {
+ if rmErr := exitCodeBucket.Delete(rawID); rmErr != nil {
+ logrus.Errorf("Removing exit code of container %s from DB: %v", id, rmErr)
+ }
+ return fmt.Errorf("adding exit-code time stamp of container %s to DB: %w", id, err)
+ }
+
+ return nil
+ })
+}
+
+// GetContainerExitCode returns the exit code for the specified container.
+func (s *BoltState) GetContainerExitCode(id string) (int32, error) {
+ if len(id) == 0 {
+ return -1, define.ErrEmptyID
+ }
+
+ if !s.valid {
+ return -1, define.ErrDBClosed
+ }
+
+ db, err := s.getDBCon()
+ if err != nil {
+ return -1, err
+ }
+ defer s.deferredCloseDBCon(db)
+
+ rawID := []byte(id)
+ result := int32(-1)
+ return result, db.View(func(tx *bolt.Tx) error {
+ exitCodeBucket, err := getExitCodeBucket(tx)
+ if err != nil {
+ return err
+ }
+
+ rawExitCode := exitCodeBucket.Get(rawID)
+ if rawExitCode == nil {
+ return fmt.Errorf("getting exit code of container %s from DB: %w", id, define.ErrNoSuchExitCode)
+ }
+
+ exitCode, err := strconv.Atoi(string(rawExitCode))
+ if err != nil {
+ return fmt.Errorf("converting raw exit code %v of container %s: %w", rawExitCode, id, err)
+ }
+
+ result = int32(exitCode)
+ return nil
+ })
+}
+
+// GetContainerExitCodeTimeStamp returns the time stamp when the exit code of
+// the specified container was added to the database.
+func (s *BoltState) GetContainerExitCodeTimeStamp(id string) (*time.Time, error) {
+ if len(id) == 0 {
+ return nil, define.ErrEmptyID
+ }
+
+ if !s.valid {
+ return nil, define.ErrDBClosed
+ }
+
+ db, err := s.getDBCon()
+ if err != nil {
+ return nil, err
+ }
+ defer s.deferredCloseDBCon(db)
+
+ rawID := []byte(id)
+ var result time.Time
+ return &result, db.View(func(tx *bolt.Tx) error {
+ timeStampBucket, err := getExitCodeTimeStampBucket(tx)
+ if err != nil {
+ return err
+ }
+
+ rawTimeStamp := timeStampBucket.Get(rawID)
+ if rawTimeStamp == nil {
+ return fmt.Errorf("getting exit-code time stamp of container %s from DB: %w", id, define.ErrNoSuchExitCode)
+ }
+
+ if err := result.UnmarshalText(rawTimeStamp); err != nil {
+ return fmt.Errorf("converting raw time stamp %v of container %s from DB: %w", rawTimeStamp, id, err)
+ }
+
+ return nil
+ })
+}
+
+// PruneExitCodes removes exit codes older than 5 minutes.
+func (s *BoltState) PruneContainerExitCodes() error {
+ if !s.valid {
+ return define.ErrDBClosed
+ }
+
+ db, err := s.getDBCon()
+ if err != nil {
+ return err
+ }
+ defer s.deferredCloseDBCon(db)
+
+ toRemoveIDs := []string{}
+
+ threshold := time.Minute * 5
+ err = db.View(func(tx *bolt.Tx) error {
+ timeStampBucket, err := getExitCodeTimeStampBucket(tx)
+ if err != nil {
+ return err
+ }
+
+ return timeStampBucket.ForEach(func(rawID, rawTimeStamp []byte) error {
+ var timeStamp time.Time
+ if err := timeStamp.UnmarshalText(rawTimeStamp); err != nil {
+ return fmt.Errorf("converting raw time stamp %v of container %s from DB: %w", rawTimeStamp, string(rawID), err)
+ }
+ if time.Since(timeStamp) > threshold {
+ toRemoveIDs = append(toRemoveIDs, string(rawID))
+ }
+ return nil
+ })
+ })
+ if err != nil {
+ return errors.Wrapf(err, "reading exit codes to prune")
+ }
+
+ if len(toRemoveIDs) > 0 {
+ err = db.Update(func(tx *bolt.Tx) error {
+ exitCodeBucket, err := getExitCodeBucket(tx)
+ if err != nil {
+ return err
+ }
+ timeStampBucket, err := getExitCodeTimeStampBucket(tx)
+ if err != nil {
+ return err
+ }
+
+ var finalErr error
+ for _, id := range toRemoveIDs {
+ rawID := []byte(id)
+ if err := exitCodeBucket.Delete(rawID); err != nil {
+ if finalErr != nil {
+ logrus.Error(finalErr)
+ }
+ finalErr = fmt.Errorf("removing exit code of container %s from DB: %w", id, err)
+ }
+ if err := timeStampBucket.Delete(rawID); err != nil {
+ if finalErr != nil {
+ logrus.Error(finalErr)
+ }
+ finalErr = fmt.Errorf("removing exit code timestamp of container %s from DB: %w", id, err)
+ }
+ }
+
+ return finalErr
+ })
+ if err != nil {
+ return errors.Wrapf(err, "pruning exit codes")
+ }
+ }
+
+ return nil
+}
+
// AddExecSession adds an exec session to the state.
func (s *BoltState) AddExecSession(ctr *Container, session *ExecSession) error {
if !s.valid {
diff --git a/libpod/boltdb_state_internal.go b/libpod/boltdb_state_internal.go
index d6f035af9..edba78d6d 100644
--- a/libpod/boltdb_state_internal.go
+++ b/libpod/boltdb_state_internal.go
@@ -29,6 +29,9 @@ const (
aliasesName = "aliases"
runtimeConfigName = "runtime-config"
+ exitCodeName = "exit-code"
+ exitCodeTimeStampName = "exit-code-time-stamp"
+
configName = "config"
stateName = "state"
dependenciesName = "dependencies"
@@ -65,6 +68,9 @@ var (
volDependenciesBkt = []byte(volCtrDependencies)
networksBkt = []byte(networksName)
+ exitCodeBkt = []byte(exitCodeName)
+ exitCodeTimeStampBkt = []byte(exitCodeTimeStampName)
+
configKey = []byte(configName)
stateKey = []byte(stateName)
netNSKey = []byte(netNSName)
@@ -362,6 +368,22 @@ func getRuntimeConfigBucket(tx *bolt.Tx) (*bolt.Bucket, error) {
return bkt, nil
}
+func getExitCodeBucket(tx *bolt.Tx) (*bolt.Bucket, error) {
+ bkt := tx.Bucket(exitCodeBkt)
+ if bkt == nil {
+ return nil, errors.Wrapf(define.ErrDBBadConfig, "exit-code container bucket not found in DB")
+ }
+ return bkt, nil
+}
+
+func getExitCodeTimeStampBucket(tx *bolt.Tx) (*bolt.Bucket, error) {
+ bkt := tx.Bucket(exitCodeTimeStampBkt)
+ if bkt == nil {
+ return nil, errors.Wrapf(define.ErrDBBadConfig, "exit-code time stamp bucket not found in DB")
+ }
+ return bkt, nil
+}
+
func (s *BoltState) getContainerConfigFromDB(id []byte, config *ContainerConfig, ctrsBkt *bolt.Bucket) error {
ctrBkt := ctrsBkt.Bucket(id)
if ctrBkt == nil {
diff --git a/libpod/container.go b/libpod/container.go
index 04a4ae64a..3a15cfbdb 100644
--- a/libpod/container.go
+++ b/libpod/container.go
@@ -518,7 +518,7 @@ func (c *Container) PortMappings() ([]types.PortMapping, error) {
if len(c.config.NetNsCtr) > 0 {
netNsCtr, err := c.runtime.GetContainer(c.config.NetNsCtr)
if err != nil {
- return nil, errors.Wrapf(err, "unable to lookup network namespace for container %s", c.ID())
+ return nil, errors.Wrapf(err, "unable to look up network namespace for container %s", c.ID())
}
return netNsCtr.PortMappings()
}
@@ -657,7 +657,7 @@ func (c *Container) Hostname() string {
utsNsCtr, err := c.runtime.GetContainer(c.config.UTSNsCtr)
if err != nil {
// should we return an error here?
- logrus.Errorf("unable to lookup uts namespace for container %s: %v", c.ID(), err)
+ logrus.Errorf("unable to look up uts namespace for container %s: %v", c.ID(), err)
return ""
}
return utsNsCtr.Hostname()
diff --git a/libpod/container_api.go b/libpod/container_api.go
index b064d3528..c14fe95b0 100644
--- a/libpod/container_api.go
+++ b/libpod/container_api.go
@@ -2,6 +2,7 @@ package libpod
import (
"context"
+ "fmt"
"io"
"io/ioutil"
"net/http"
@@ -490,41 +491,84 @@ func (c *Container) RemoveArtifact(name string) error {
// Wait blocks until the container exits and returns its exit code.
func (c *Container) Wait(ctx context.Context) (int32, error) {
- return c.WaitWithInterval(ctx, DefaultWaitInterval)
+ return c.WaitForExit(ctx, DefaultWaitInterval)
}
-// WaitWithInterval blocks until the container to exit and returns its exit
-// code. The argument is the interval at which checks the container's status.
-func (c *Container) WaitWithInterval(ctx context.Context, waitTimeout time.Duration) (int32, error) {
+// WaitForExit blocks until the container exits and returns its exit code. The
+// argument is the interval at which checks the container's status.
+func (c *Container) WaitForExit(ctx context.Context, pollInterval time.Duration) (int32, error) {
if !c.valid {
return -1, define.ErrCtrRemoved
}
- exitFile, err := c.exitFilePath()
- if err != nil {
- return -1, err
- }
- chWait := make(chan error, 1)
+ id := c.ID()
+ var conmonTimer time.Timer
+ conmonTimerSet := false
- go func() {
- <-ctx.Done()
- chWait <- define.ErrCanceled
- }()
+ getExitCode := func() (bool, int32, error) {
+ containerRemoved := false
+ if !c.batched {
+ c.lock.Lock()
+ defer c.lock.Unlock()
+ }
- for {
- // ignore errors here (with exception of cancellation), it is only used to avoid waiting
- // too long.
- _, e := WaitForFile(exitFile, chWait, waitTimeout)
- if e == define.ErrCanceled {
- return -1, define.ErrCanceled
+ if err := c.syncContainer(); err != nil {
+ if !errors.Is(err, define.ErrNoSuchCtr) {
+ return false, -1, err
+ }
+ containerRemoved = true
+ }
+
+ // If conmon is not alive anymore set a timer to make sure
+ // we're returning even if conmon has forcefully been killed.
+ if !conmonTimerSet && !containerRemoved {
+ conmonAlive, err := c.ociRuntime.CheckConmonRunning(c)
+ switch {
+ case errors.Is(err, define.ErrNoSuchCtr):
+ containerRemoved = true
+ case err != nil:
+ return false, -1, err
+ case !conmonAlive:
+ timerDuration := time.Second * 20
+ conmonTimer = *time.NewTimer(timerDuration)
+ conmonTimerSet = true
+ }
+ }
+
+ if !containerRemoved {
+ // If conmon is dead for more than $timerDuration or if the
+ // container has exited properly, try to look up the exit code.
+ select {
+ case <-conmonTimer.C:
+ logrus.Debugf("Exceeded conmon timeout waiting for container %s to exit", id)
+ default:
+ if !c.ensureState(define.ContainerStateExited, define.ContainerStateConfigured) {
+ return false, -1, nil
+ }
+ }
}
- stopped, code, err := c.isStopped()
+ exitCode, err := c.runtime.state.GetContainerExitCode(id)
+ if err != nil {
+ return true, -1, err
+ }
+
+ return true, exitCode, nil
+ }
+
+ for {
+ hasExited, exitCode, err := getExitCode()
+ if hasExited {
+ return exitCode, err
+ }
if err != nil {
return -1, err
}
- if stopped {
- return code, nil
+ select {
+ case <-ctx.Done():
+ return -1, fmt.Errorf("waiting for exit code of container %s canceled", id)
+ default:
+ time.Sleep(pollInterval)
}
}
}
@@ -551,11 +595,12 @@ func (c *Container) WaitForConditionWithInterval(ctx context.Context, waitTimeou
wantedStates := make(map[define.ContainerStatus]bool, len(conditions))
for _, condition := range conditions {
- if condition == define.ContainerStateStopped || condition == define.ContainerStateExited {
+ switch condition {
+ case define.ContainerStateExited, define.ContainerStateStopped:
waitForExit = true
- continue
+ default:
+ wantedStates[condition] = true
}
- wantedStates[condition] = true
}
trySend := func(code int32, err error) {
@@ -572,7 +617,7 @@ func (c *Container) WaitForConditionWithInterval(ctx context.Context, waitTimeou
go func() {
defer wg.Done()
- code, err := c.WaitWithInterval(ctx, waitTimeout)
+ code, err := c.WaitForExit(ctx, waitTimeout)
trySend(code, err)
}()
}
diff --git a/libpod/container_config.go b/libpod/container_config.go
index 45ff03d58..544c45a8c 100644
--- a/libpod/container_config.go
+++ b/libpod/container_config.go
@@ -424,7 +424,6 @@ type InfraInherit struct {
CapDrop []string `json:"cap_drop,omitempty"`
HostDeviceList []spec.LinuxDevice `json:"host_device_list,omitempty"`
ImageVolumes []*specgen.ImageVolume `json:"image_volumes,omitempty"`
- InfraResources *spec.LinuxResources `json:"resource_limits,omitempty"`
Mounts []spec.Mount `json:"mounts,omitempty"`
NoNewPrivileges bool `json:"no_new_privileges,omitempty"`
OverlayVolumes []*specgen.OverlayVolume `json:"overlay_volumes,omitempty"`
diff --git a/libpod/container_exec.go b/libpod/container_exec.go
index be00c6fbe..b112273d0 100644
--- a/libpod/container_exec.go
+++ b/libpod/container_exec.go
@@ -277,9 +277,13 @@ func (c *Container) ExecStart(sessionID string) error {
return c.save()
}
+func (c *Container) ExecStartAndAttach(sessionID string, streams *define.AttachStreams, newSize *define.TerminalSize) error {
+ return c.execStartAndAttach(sessionID, streams, newSize, false)
+}
+
// ExecStartAndAttach starts and attaches to an exec session in a container.
// newSize resizes the tty to this size before the process is started, must be nil if the exec session has no tty
-func (c *Container) ExecStartAndAttach(sessionID string, streams *define.AttachStreams, newSize *define.TerminalSize) error {
+func (c *Container) execStartAndAttach(sessionID string, streams *define.AttachStreams, newSize *define.TerminalSize, isHealthcheck bool) error {
if !c.batched {
c.lock.Lock()
defer c.lock.Unlock()
@@ -315,7 +319,12 @@ func (c *Container) ExecStartAndAttach(sessionID string, streams *define.AttachS
return err
}
- c.newContainerEvent(events.Exec)
+ if isHealthcheck {
+ c.newContainerEvent(events.HealthStatus)
+ } else {
+ c.newContainerEvent(events.Exec)
+ }
+
logrus.Debugf("Successfully started exec session %s in container %s", session.ID(), c.ID())
var lastErr error
@@ -743,10 +752,14 @@ func (c *Container) ExecResize(sessionID string, newSize define.TerminalSize) er
return c.ociRuntime.ExecAttachResize(c, sessionID, newSize)
}
+func (c *Container) Exec(config *ExecConfig, streams *define.AttachStreams, resize <-chan define.TerminalSize) (int, error) {
+ return c.exec(config, streams, resize, false)
+}
+
// Exec emulates the old Libpod exec API, providing a single call to create,
// run, and remove an exec session. Returns exit code and error. Exit code is
// not guaranteed to be set sanely if error is not nil.
-func (c *Container) Exec(config *ExecConfig, streams *define.AttachStreams, resize <-chan define.TerminalSize) (int, error) {
+func (c *Container) exec(config *ExecConfig, streams *define.AttachStreams, resize <-chan define.TerminalSize, isHealthcheck bool) (int, error) {
sessionID, err := c.ExecCreate(config)
if err != nil {
return -1, err
@@ -780,7 +793,7 @@ func (c *Container) Exec(config *ExecConfig, streams *define.AttachStreams, resi
}()
}
- if err := c.ExecStartAndAttach(sessionID, streams, size); err != nil {
+ if err := c.execStartAndAttach(sessionID, streams, size, isHealthcheck); err != nil {
return -1, err
}
diff --git a/libpod/container_internal.go b/libpod/container_internal.go
index fd451f9ef..64696cc27 100644
--- a/libpod/container_internal.go
+++ b/libpod/container_internal.go
@@ -21,6 +21,7 @@ import (
"github.com/containers/common/pkg/cgroups"
"github.com/containers/common/pkg/chown"
"github.com/containers/common/pkg/config"
+ cutil "github.com/containers/common/pkg/util"
"github.com/containers/podman/v4/libpod/define"
"github.com/containers/podman/v4/libpod/events"
"github.com/containers/podman/v4/pkg/ctime"
@@ -219,7 +220,7 @@ func (c *Container) handleExitFile(exitFile string, fi os.FileInfo) error {
// Write an event for the container's death
c.newContainerExitedEvent(c.state.ExitCode)
- return nil
+ return c.runtime.state.AddContainerExitCode(c.ID(), c.state.ExitCode)
}
func (c *Container) shouldRestart() bool {
@@ -290,7 +291,7 @@ func (c *Container) handleRestartPolicy(ctx context.Context) (_ bool, retErr err
return false, err
}
- // setup slirp4netns again because slirp4netns will die when conmon exits
+ // set up slirp4netns again because slirp4netns will die when conmon exits
if c.config.NetMode.IsSlirp4netns() {
err := c.runtime.setupSlirp4netns(c, c.state.NetNS)
if err != nil {
@@ -298,7 +299,7 @@ func (c *Container) handleRestartPolicy(ctx context.Context) (_ bool, retErr err
}
}
- // setup rootlesskit port forwarder again since it dies when conmon exits
+ // set up rootlesskit port forwarder again since it dies when conmon exits
// we use rootlesskit port forwarder only as rootless and when bridge network is used
if rootless.IsRootless() && c.config.NetMode.IsBridge() && len(c.config.PortMappings) > 0 {
err := c.runtime.setupRootlessPortMappingViaRLK(c, c.state.NetNS.Path(), c.state.NetworkStatus)
@@ -589,7 +590,7 @@ func (c *Container) teardownStorage() error {
}
if err := c.cleanupStorage(); err != nil {
- return errors.Wrapf(err, "failed to cleanup container %s storage", c.ID())
+ return errors.Wrapf(err, "failed to clean up container %s storage", c.ID())
}
if err := c.runtime.storageService.DeleteContainer(c.ID()); err != nil {
@@ -784,20 +785,6 @@ func (c *Container) getArtifactPath(name string) string {
return filepath.Join(c.config.StaticDir, artifactsDir, name)
}
-// Used with Wait() to determine if a container has exited
-func (c *Container) isStopped() (bool, int32, error) {
- if !c.batched {
- c.lock.Lock()
- defer c.lock.Unlock()
- }
- err := c.syncContainer()
- if err != nil {
- return true, -1, err
- }
-
- return !c.ensureState(define.ContainerStateRunning, define.ContainerStatePaused, define.ContainerStateStopping), c.state.ExitCode, nil
-}
-
// save container state to the database
func (c *Container) save() error {
if err := c.runtime.state.SaveContainer(c); err != nil {
@@ -1282,13 +1269,6 @@ func (c *Container) stop(timeout uint) error {
}
}
- // Check if conmon is still alive.
- // If it is not, we won't be getting an exit file.
- conmonAlive, err := c.ociRuntime.CheckConmonRunning(c)
- if err != nil {
- return err
- }
-
// Set the container state to "stopping" and unlock the container
// before handing it over to conmon to unblock other commands. #8501
// demonstrates nicely that a high stop timeout will block even simple
@@ -1341,21 +1321,18 @@ func (c *Container) stop(timeout uint) error {
}
c.newContainerEvent(events.Stop)
-
- c.state.PID = 0
- c.state.ConmonPID = 0
c.state.StoppedByUser = true
+ conmonAlive, err := c.ociRuntime.CheckConmonRunning(c)
+ if err != nil {
+ return err
+ }
if !conmonAlive {
- // Conmon is dead, so we can't expect an exit code.
- c.state.ExitCode = -1
- c.state.FinishedTime = time.Now()
- c.state.State = define.ContainerStateStopped
- if err := c.save(); err != nil {
- logrus.Errorf("Saving container %s status: %v", c.ID(), err)
+ if err := c.checkExitFile(); err != nil {
+ return err
}
- return errors.Wrapf(define.ErrConmonDead, "container %s conmon process missing, cannot retrieve exit code", c.ID())
+ return c.save()
}
if err := c.save(); err != nil {
@@ -1663,30 +1640,16 @@ func (c *Container) mountNamedVolume(v *ContainerNamedVolume, mountpoint string)
if err := vol.update(); err != nil {
return nil, err
}
- if vol.state.NeedsCopyUp {
+ _, hasNoCopy := vol.config.Options["nocopy"]
+ if vol.state.NeedsCopyUp && !cutil.StringInSlice("nocopy", v.Options) && !hasNoCopy {
logrus.Debugf("Copying up contents from container %s to volume %s", c.ID(), vol.Name())
- // If the volume is not empty, we should not copy up.
- volMount := vol.mountPoint()
- contents, err := ioutil.ReadDir(volMount)
- if err != nil {
- return nil, errors.Wrapf(err, "error listing contents of volume %s mountpoint when copying up from container %s", vol.Name(), c.ID())
- }
- if len(contents) > 0 {
- // The volume is not empty. It was likely modified
- // outside of Podman. For safety, let's not copy up into
- // it. Fixes CVE-2020-1726.
- return vol, nil
- }
-
srcDir, err := securejoin.SecureJoin(mountpoint, v.Dest)
if err != nil {
return nil, errors.Wrapf(err, "error calculating destination path to copy up container %s volume %s", c.ID(), vol.Name())
}
// Do a manual stat on the source directory to verify existence.
// Skip the rest if it exists.
- // TODO: Should this be stat or lstat? I'm using lstat because I
- // think copy-up doesn't happen when the source is a link.
srcStat, err := os.Lstat(srcDir)
if err != nil {
if os.IsNotExist(err) {
@@ -1712,6 +1675,19 @@ func (c *Container) mountNamedVolume(v *ContainerNamedVolume, mountpoint string)
return vol, nil
}
+ // If the volume is not empty, we should not copy up.
+ volMount := vol.mountPoint()
+ contents, err := ioutil.ReadDir(volMount)
+ if err != nil {
+ return nil, errors.Wrapf(err, "error listing contents of volume %s mountpoint when copying up from container %s", vol.Name(), c.ID())
+ }
+ if len(contents) > 0 {
+ // The volume is not empty. It was likely modified
+ // outside of Podman. For safety, let's not copy up into
+ // it. Fixes CVE-2020-1726.
+ return vol, nil
+ }
+
// Set NeedsCopyUp to false since we are about to do first copy
// Do not copy second time.
vol.state.NeedsCopyUp = false
@@ -1784,7 +1760,7 @@ func (c *Container) cleanupStorage() error {
overlayBasePath := filepath.Dir(c.state.Mountpoint)
if err := overlay.Unmount(overlayBasePath); err != nil {
if cleanupErr != nil {
- logrus.Errorf("Failed to cleanup overlay mounts for %s: %v", c.ID(), err)
+ logrus.Errorf("Failed to clean up overlay mounts for %s: %v", c.ID(), err)
}
cleanupErr = err
}
@@ -1801,7 +1777,7 @@ func (c *Container) cleanupStorage() error {
if err := c.cleanupOverlayMounts(); err != nil {
// If the container can't remove content report the error
- logrus.Errorf("Failed to cleanup overlay mounts for %s: %v", c.ID(), err)
+ logrus.Errorf("Failed to clean up overlay mounts for %s: %v", c.ID(), err)
cleanupErr = err
}
@@ -1880,7 +1856,7 @@ func (c *Container) cleanup(ctx context.Context) error {
// we cannot use the dependency container lock due ABBA deadlocks
if lock, err := lockfile.GetLockfile(hoststFile); err == nil {
lock.Lock()
- // make sure to ignore ENOENT error in case the netns container was cleanup before this one
+ // make sure to ignore ENOENT error in case the netns container was cleaned up before this one
if err := etchosts.Remove(hoststFile, getLocalhostHostEntry(c)); err != nil && !errors.Is(err, os.ErrNotExist) {
// this error is not fatal we still want to do proper cleanup
logrus.Errorf("failed to remove hosts entry from the netns containers /etc/hosts: %v", err)
@@ -1939,6 +1915,18 @@ func (c *Container) cleanup(ctx context.Context) error {
}
}
+ // Prune the exit codes of other container during clean up.
+ // Since Podman is no daemon, we have to clean them up somewhere.
+ // Cleanup seems like a good place as it's not performance
+ // critical.
+ if err := c.runtime.state.PruneContainerExitCodes(); err != nil {
+ if lastError == nil {
+ lastError = err
+ } else {
+ logrus.Errorf("Pruning container exit codes: %v", err)
+ }
+ }
+
return lastError
}
diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go
index 245fb587d..0f4bf0f55 100644
--- a/libpod/container_internal_linux.go
+++ b/libpod/container_internal_linux.go
@@ -311,7 +311,7 @@ func (c *Container) cleanupNetwork() error {
// Stop the container's network namespace (if it has one)
if err := c.runtime.teardownNetNS(c); err != nil {
- logrus.Errorf("Unable to cleanup network for container %s: %q", c.ID(), err)
+ logrus.Errorf("Unable to clean up network for container %s: %q", c.ID(), err)
}
c.state.NetNS = nil
@@ -870,6 +870,7 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) {
if err != nil {
return nil, err
}
+
g.SetLinuxCgroupsPath(cgroupPath)
// Warning: CDI may alter g.Config in place.
@@ -1141,7 +1142,7 @@ func (c *Container) addCheckpointImageMetadata(importBuilder *buildah.Builder) e
return fmt.Errorf("getting host info: %v", err)
}
- criuVersion, err := criu.GetCriuVestion()
+ criuVersion, err := criu.GetCriuVersion()
if err != nil {
return fmt.Errorf("getting criu version: %v", err)
}
@@ -1210,7 +1211,7 @@ func (c *Container) createCheckpointImage(ctx context.Context, options Container
if err != nil {
return err
}
- // Clean-up buildah working container
+ // Clean up buildah working container
defer func() {
if err := importBuilder.Delete(); err != nil {
logrus.Errorf("Image builder delete failed: %v", err)
@@ -1504,7 +1505,7 @@ func (c *Container) checkpoint(ctx context.Context, options ContainerCheckpointO
c.state.Restored = false
c.state.RestoredTime = time.Time{}
- // Cleanup Storage and Network
+ // Clean up Storage and Network
if err := c.cleanup(ctx); err != nil {
return nil, 0, err
}
diff --git a/libpod/define/errors.go b/libpod/define/errors.go
index f5a7c73e5..9757a85b1 100644
--- a/libpod/define/errors.go
+++ b/libpod/define/errors.go
@@ -24,6 +24,10 @@ var (
// not exist.
ErrNoSuchExecSession = errors.New("no such exec session")
+ // ErrNoSuchExitCode indicates that the requested container exit code
+ // does not exist.
+ ErrNoSuchExitCode = errors.New("no such exit code")
+
// ErrDepExists indicates that the current object has dependencies and
// cannot be removed before them.
ErrDepExists = errors.New("dependency exists")
diff --git a/libpod/define/volume_inspect.go b/libpod/define/volume_inspect.go
index fac179176..aaa23b4fc 100644
--- a/libpod/define/volume_inspect.go
+++ b/libpod/define/volume_inspect.go
@@ -57,3 +57,9 @@ type InspectVolumeData struct {
// UID/GID.
NeedsChown bool `json:"NeedsChown,omitempty"`
}
+
+type VolumeReload struct {
+ Added []string
+ Removed []string
+ Errors []error
+}
diff --git a/libpod/events.go b/libpod/events.go
index f09d8402a..bb50df92d 100644
--- a/libpod/events.go
+++ b/libpod/events.go
@@ -33,6 +33,16 @@ func (c *Container) newContainerEvent(status events.Status) {
Attributes: c.Labels(),
}
+ // if the current event is a HealthStatus event, we need to get the current
+ // status of the container to pass to the event
+ if status == events.HealthStatus {
+ containerHealthStatus, err := c.healthCheckStatus()
+ if err != nil {
+ e.HealthStatus = fmt.Sprintf("%v", err)
+ }
+ e.HealthStatus = containerHealthStatus
+ }
+
if err := c.runtime.eventer.Write(e); err != nil {
logrus.Errorf("Unable to write pod event: %q", err)
}
@@ -151,6 +161,9 @@ func (r *Runtime) GetEvents(ctx context.Context, filters []string) ([]*events.Ev
// GetLastContainerEvent takes a container name or ID and an event status and returns
// the last occurrence of the container event
func (r *Runtime) GetLastContainerEvent(ctx context.Context, nameOrID string, containerEvent events.Status) (*events.Event, error) {
+ // FIXME: events should be read in reverse order!
+ // https://github.com/containers/podman/issues/14579
+
// check to make sure the event.Status is valid
if _, err := events.StringToStatus(containerEvent.String()); err != nil {
return nil, err
diff --git a/libpod/events/config.go b/libpod/events/config.go
index 2e7016136..a678baa2d 100644
--- a/libpod/events/config.go
+++ b/libpod/events/config.go
@@ -40,6 +40,8 @@ type Event struct {
Time time.Time
// Type of event that occurred
Type Type
+ // Health status of the current container
+ HealthStatus string `json:"health_status,omitempty"`
Details
}
@@ -141,6 +143,8 @@ const (
Exited Status = "died"
// Export ...
Export Status = "export"
+ // HealthStatus ...
+ HealthStatus Status = "health_status"
// History ...
History Status = "history"
// Import ...
diff --git a/libpod/events/events.go b/libpod/events/events.go
index a30e0f1ca..a8001ab95 100644
--- a/libpod/events/events.go
+++ b/libpod/events/events.go
@@ -76,7 +76,7 @@ func (e *Event) ToHumanReadable(truncate bool) string {
}
switch e.Type {
case Container, Pod:
- humanFormat = fmt.Sprintf("%s %s %s %s (image=%s, name=%s", e.Time, e.Type, e.Status, id, e.Image, e.Name)
+ 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)
// check if the container has labels and add it to the output
if len(e.Attributes) > 0 {
for k, v := range e.Attributes {
@@ -168,6 +168,8 @@ func StringToStatus(name string) (Status, error) {
return Exited, nil
case Export.String():
return Export, nil
+ case HealthStatus.String():
+ return HealthStatus, nil
case History.String():
return History, nil
case Import.String():
diff --git a/libpod/events/journal_linux.go b/libpod/events/journal_linux.go
index d21b60c68..036638d34 100644
--- a/libpod/events/journal_linux.go
+++ b/libpod/events/journal_linux.go
@@ -58,6 +58,7 @@ func (e EventJournalD) Write(ee Event) error {
}
m["PODMAN_LABELS"] = string(b)
}
+ m["PODMAN_HEALTH_STATUS"] = ee.HealthStatus
case Network:
m["PODMAN_ID"] = ee.ID
m["PODMAN_NETWORK_NAME"] = ee.Network
@@ -213,6 +214,7 @@ func newEventFromJournalEntry(entry *sdjournal.JournalEntry) (*Event, error) {
newEvent.Details = Details{Attributes: labels}
}
}
+ newEvent.HealthStatus = entry.Fields["PODMAN_HEALTH_STATUS"]
case Network:
newEvent.ID = entry.Fields["PODMAN_ID"]
newEvent.Network = entry.Fields["PODMAN_NETWORK_NAME"]
diff --git a/libpod/healthcheck.go b/libpod/healthcheck.go
index 40af9aec3..95c70b60e 100644
--- a/libpod/healthcheck.go
+++ b/libpod/healthcheck.go
@@ -26,7 +26,7 @@ const (
func (r *Runtime) HealthCheck(name string) (define.HealthCheckStatus, error) {
container, err := r.LookupContainer(name)
if err != nil {
- return define.HealthCheckContainerNotFound, errors.Wrapf(err, "unable to lookup %s to perform a health check", name)
+ return define.HealthCheckContainerNotFound, errors.Wrapf(err, "unable to look up %s to perform a health check", name)
}
hcStatus, err := checkHealthCheckCanBeRun(container)
if err == nil {
@@ -90,7 +90,7 @@ func (c *Container) runHealthCheck() (define.HealthCheckStatus, error) {
hcResult := define.HealthCheckSuccess
config := new(ExecConfig)
config.Command = newCommand
- exitCode, hcErr := c.Exec(config, streams, nil)
+ exitCode, hcErr := c.exec(config, streams, nil, true)
if hcErr != nil {
errCause := errors.Cause(hcErr)
hcResult = define.HealthCheckFailure
@@ -232,18 +232,27 @@ func (c *Container) getHealthCheckLog() (define.HealthCheckResults, error) {
// HealthCheckStatus returns the current state of a container with a healthcheck
func (c *Container) HealthCheckStatus() (string, error) {
+ c.lock.Lock()
+ defer c.lock.Unlock()
+ return c.healthCheckStatus()
+}
+
+// Internal function to return the current state of a container with a healthcheck.
+// This function does not lock the container.
+func (c *Container) healthCheckStatus() (string, error) {
if !c.HasHealthCheck() {
return "", errors.Errorf("container %s has no defined healthcheck", c.ID())
}
- c.lock.Lock()
- defer c.lock.Unlock()
+
if err := c.syncContainer(); err != nil {
return "", err
}
+
results, err := c.getHealthCheckLog()
if err != nil {
return "", errors.Wrapf(err, "unable to get healthcheck log for %s", c.ID())
}
+
return results.Status, nil
}
diff --git a/libpod/networking_linux.go b/libpod/networking_linux.go
index cb1547a93..a83423c9f 100644
--- a/libpod/networking_linux.go
+++ b/libpod/networking_linux.go
@@ -291,7 +291,7 @@ func (r *RootlessNetNS) Do(toRun func() error) error {
return err
}
-// Cleanup the rootless network namespace if needed.
+// Clean up the rootless network namespace if needed.
// It checks if we have running containers with the bridge network mode.
// Cleanup() expects that r.Lock is locked
func (r *RootlessNetNS) Cleanup(runtime *Runtime) error {
@@ -783,7 +783,7 @@ func (r *Runtime) teardownNetwork(ns string, opts types.NetworkOptions) error {
// execute the cni setup in the rootless net ns
err = rootlessNetNS.Do(tearDownPod)
if cerr := rootlessNetNS.Cleanup(r); cerr != nil {
- logrus.WithError(err).Error("failed to cleanup rootless netns")
+ logrus.WithError(err).Error("failed to clean up rootless netns")
}
rootlessNetNS.Lock.Unlock()
} else {
diff --git a/libpod/oci_conmon_attach_linux.go b/libpod/oci_conmon_attach_linux.go
index 155a8fbc3..26f9ba083 100644
--- a/libpod/oci_conmon_attach_linux.go
+++ b/libpod/oci_conmon_attach_linux.go
@@ -120,7 +120,7 @@ func (r *ConmonOCIRuntime) Attach(c *Container, params *AttachOptions) error {
// conmon will then send the exit code of the exec process, or an error in the exec session
// startFd must be the input side of the fd.
// newSize resizes the tty to this size before the process is started, must be nil if the exec session has no tty
-// conmon will wait to start the exec session until the parent process has setup the console socket.
+// conmon will wait to start the exec session until the parent process has set up the console socket.
// Once attachToExec successfully attaches to the console socket, the child conmon process responsible for calling runtime exec
// will read from the output side of start fd, thus learning to start the child process.
// Thus, the order goes as follow:
diff --git a/libpod/oci_conmon_linux.go b/libpod/oci_conmon_linux.go
index fde8624b0..7a9ae7ee5 100644
--- a/libpod/oci_conmon_linux.go
+++ b/libpod/oci_conmon_linux.go
@@ -23,6 +23,9 @@ import (
"text/template"
"time"
+ runcconfig "github.com/opencontainers/runc/libcontainer/configs"
+ "github.com/opencontainers/runc/libcontainer/devices"
+
"github.com/containers/common/pkg/cgroups"
"github.com/containers/common/pkg/config"
conmonConfig "github.com/containers/conmon/runner/config"
@@ -264,11 +267,6 @@ func (r *ConmonOCIRuntime) CreateContainer(ctr *Container, restoreOptions *Conta
// status, but will instead only check for the existence of the conmon exit file
// and update state to stopped if it exists.
func (r *ConmonOCIRuntime) UpdateContainerStatus(ctr *Container) error {
- exitFile, err := r.ExitFilePath(ctr)
- if err != nil {
- return err
- }
-
runtimeDir, err := util.GetRuntimeDir()
if err != nil {
return err
@@ -340,22 +338,10 @@ func (r *ConmonOCIRuntime) UpdateContainerStatus(ctr *Container) error {
// Only grab exit status if we were not already stopped
// If we were, it should already be in the database
if ctr.state.State == define.ContainerStateStopped && oldState != define.ContainerStateStopped {
- var fi os.FileInfo
- chWait := make(chan error)
- defer close(chWait)
-
- _, err := WaitForFile(exitFile, chWait, time.Second*5)
- if err == nil {
- fi, err = os.Stat(exitFile)
- }
- if err != nil {
- ctr.state.ExitCode = -1
- ctr.state.FinishedTime = time.Now()
- logrus.Errorf("No exit file for container %s found: %v", ctr.ID(), err)
- return nil
+ if _, err := ctr.Wait(context.Background()); err != nil {
+ logrus.Errorf("Waiting for container %s to exit: %v", ctr.ID(), err)
}
-
- return ctr.handleExitFile(exitFile, fi)
+ return nil
}
// Handle ContainerStateStopping - keep it unless the container
@@ -1166,7 +1152,6 @@ func (r *ConmonOCIRuntime) createOCIContainer(ctr *Container, restoreOptions *Co
}).Debugf("running conmon: %s", r.conmonPath)
cmd := exec.Command(r.conmonPath, args...)
- cmd.Dir = ctr.bundlePath()
cmd.SysProcAttr = &syscall.SysProcAttr{
Setpgid: true,
}
@@ -1354,8 +1339,6 @@ func (r *ConmonOCIRuntime) sharedConmonArgs(ctr *Container, cuuid, bundlePath, p
logDriverArg = define.NoLogging
case define.PassthroughLogging:
logDriverArg = define.PassthroughLogging
- case define.JSONLogging:
- fallthrough
//lint:ignore ST1015 the default case has to be here
default: //nolint:stylecheck,gocritic
// No case here should happen except JSONLogging, but keep this here in case the options are extended
@@ -1365,6 +1348,8 @@ func (r *ConmonOCIRuntime) sharedConmonArgs(ctr *Container, cuuid, bundlePath, p
// to get here, either a user would specify `--log-driver ""`, or this came from another place in libpod
// since the former case is obscure, and the latter case isn't an error, let's silently fallthrough
fallthrough
+ case define.JSONLogging:
+ fallthrough
case define.KubernetesLogging:
logDriverArg = fmt.Sprintf("%s:%s", define.KubernetesLogging, logPath)
}
@@ -1451,9 +1436,14 @@ func (r *ConmonOCIRuntime) moveConmonToCgroupAndSignal(ctr *Container, cmd *exec
// TODO: This should be a switch - we are not guaranteed that
// there are only 2 valid cgroup managers
cgroupParent := ctr.CgroupParent()
+ cgroupPath := filepath.Join(ctr.config.CgroupParent, "conmon")
+ Resource := ctr.Spec().Linux.Resources
+ cgroupResources, err := GetLimits(Resource)
+ if err != nil {
+ logrus.StandardLogger().Log(logLevel, "Could not get ctr resources")
+ }
if ctr.CgroupManager() == config.SystemdCgroupsManager {
unitName := createUnitName("libpod-conmon", ctr.ID())
-
realCgroupParent := cgroupParent
splitParent := strings.Split(cgroupParent, "/")
if strings.HasSuffix(cgroupParent, ".slice") && len(splitParent) > 1 {
@@ -1465,8 +1455,7 @@ func (r *ConmonOCIRuntime) moveConmonToCgroupAndSignal(ctr *Container, cmd *exec
logrus.StandardLogger().Logf(logLevel, "Failed to add conmon to systemd sandbox cgroup: %v", err)
}
} else {
- cgroupPath := filepath.Join(ctr.config.CgroupParent, "conmon")
- control, err := cgroups.New(cgroupPath, &spec.LinuxResources{})
+ control, err := cgroups.New(cgroupPath, &cgroupResources)
if err != nil {
logrus.StandardLogger().Logf(logLevel, "Failed to add conmon to cgroupfs sandbox cgroup: %v", err)
} else if err := control.AddPid(cmd.Process.Pid); err != nil {
@@ -1748,3 +1737,191 @@ func httpAttachNonTerminalCopy(container *net.UnixConn, http *bufio.ReadWriter,
}
}
}
+
+// GetLimits converts spec resource limits to cgroup consumable limits
+func GetLimits(resource *spec.LinuxResources) (runcconfig.Resources, error) {
+ if resource == nil {
+ resource = &spec.LinuxResources{}
+ }
+ final := &runcconfig.Resources{}
+ devs := []*devices.Rule{}
+
+ // Devices
+ for _, entry := range resource.Devices {
+ if entry.Major == nil || entry.Minor == nil {
+ continue
+ }
+ runeType := 'a'
+ switch entry.Type {
+ case "b":
+ runeType = 'b'
+ case "c":
+ runeType = 'c'
+ }
+
+ devs = append(devs, &devices.Rule{
+ Type: devices.Type(runeType),
+ Major: *entry.Major,
+ Minor: *entry.Minor,
+ Permissions: devices.Permissions(entry.Access),
+ Allow: entry.Allow,
+ })
+ }
+ final.Devices = devs
+
+ // HugepageLimits
+ pageLimits := []*runcconfig.HugepageLimit{}
+ for _, entry := range resource.HugepageLimits {
+ pageLimits = append(pageLimits, &runcconfig.HugepageLimit{
+ Pagesize: entry.Pagesize,
+ Limit: entry.Limit,
+ })
+ }
+ final.HugetlbLimit = pageLimits
+
+ // Networking
+ netPriorities := []*runcconfig.IfPrioMap{}
+ if resource.Network != nil {
+ for _, entry := range resource.Network.Priorities {
+ netPriorities = append(netPriorities, &runcconfig.IfPrioMap{
+ Interface: entry.Name,
+ Priority: int64(entry.Priority),
+ })
+ }
+ }
+ final.NetPrioIfpriomap = netPriorities
+ rdma := make(map[string]runcconfig.LinuxRdma)
+ for name, entry := range resource.Rdma {
+ rdma[name] = runcconfig.LinuxRdma{HcaHandles: entry.HcaHandles, HcaObjects: entry.HcaObjects}
+ }
+ final.Rdma = rdma
+
+ // Memory
+ if resource.Memory != nil {
+ if resource.Memory.Limit != nil {
+ final.Memory = *resource.Memory.Limit
+ }
+ if resource.Memory.Reservation != nil {
+ final.MemoryReservation = *resource.Memory.Reservation
+ }
+ if resource.Memory.Swap != nil {
+ final.MemorySwap = *resource.Memory.Swap
+ }
+ if resource.Memory.Swappiness != nil {
+ final.MemorySwappiness = resource.Memory.Swappiness
+ }
+ }
+
+ // CPU
+ if resource.CPU != nil {
+ if resource.CPU.Period != nil {
+ final.CpuPeriod = *resource.CPU.Period
+ }
+ if resource.CPU.Quota != nil {
+ final.CpuQuota = *resource.CPU.Quota
+ }
+ if resource.CPU.RealtimePeriod != nil {
+ final.CpuRtPeriod = *resource.CPU.RealtimePeriod
+ }
+ if resource.CPU.RealtimeRuntime != nil {
+ final.CpuRtRuntime = *resource.CPU.RealtimeRuntime
+ }
+ if resource.CPU.Shares != nil {
+ final.CpuShares = *resource.CPU.Shares
+ }
+ final.CpusetCpus = resource.CPU.Cpus
+ final.CpusetMems = resource.CPU.Mems
+ }
+
+ // BlkIO
+ if resource.BlockIO != nil {
+ if len(resource.BlockIO.ThrottleReadBpsDevice) > 0 {
+ for _, entry := range resource.BlockIO.ThrottleReadBpsDevice {
+ throttle := &runcconfig.ThrottleDevice{}
+ dev := &runcconfig.BlockIODevice{
+ Major: entry.Major,
+ Minor: entry.Minor,
+ }
+ throttle.BlockIODevice = *dev
+ throttle.Rate = entry.Rate
+ final.BlkioThrottleReadBpsDevice = append(final.BlkioThrottleReadBpsDevice, throttle)
+ }
+ }
+ if len(resource.BlockIO.ThrottleWriteBpsDevice) > 0 {
+ for _, entry := range resource.BlockIO.ThrottleWriteBpsDevice {
+ throttle := &runcconfig.ThrottleDevice{}
+ dev := &runcconfig.BlockIODevice{
+ Major: entry.Major,
+ Minor: entry.Minor,
+ }
+ throttle.BlockIODevice = *dev
+ throttle.Rate = entry.Rate
+ final.BlkioThrottleWriteBpsDevice = append(final.BlkioThrottleWriteBpsDevice, throttle)
+ }
+ }
+ if len(resource.BlockIO.ThrottleReadIOPSDevice) > 0 {
+ for _, entry := range resource.BlockIO.ThrottleReadIOPSDevice {
+ throttle := &runcconfig.ThrottleDevice{}
+ dev := &runcconfig.BlockIODevice{
+ Major: entry.Major,
+ Minor: entry.Minor,
+ }
+ throttle.BlockIODevice = *dev
+ throttle.Rate = entry.Rate
+ final.BlkioThrottleReadIOPSDevice = append(final.BlkioThrottleReadIOPSDevice, throttle)
+ }
+ }
+ if len(resource.BlockIO.ThrottleWriteIOPSDevice) > 0 {
+ for _, entry := range resource.BlockIO.ThrottleWriteIOPSDevice {
+ throttle := &runcconfig.ThrottleDevice{}
+ dev := &runcconfig.BlockIODevice{
+ Major: entry.Major,
+ Minor: entry.Minor,
+ }
+ throttle.BlockIODevice = *dev
+ throttle.Rate = entry.Rate
+ final.BlkioThrottleWriteIOPSDevice = append(final.BlkioThrottleWriteIOPSDevice, throttle)
+ }
+ }
+ if resource.BlockIO.LeafWeight != nil {
+ final.BlkioLeafWeight = *resource.BlockIO.LeafWeight
+ }
+ if resource.BlockIO.Weight != nil {
+ final.BlkioWeight = *resource.BlockIO.Weight
+ }
+ if len(resource.BlockIO.WeightDevice) > 0 {
+ for _, entry := range resource.BlockIO.WeightDevice {
+ weight := &runcconfig.WeightDevice{}
+ dev := &runcconfig.BlockIODevice{
+ Major: entry.Major,
+ Minor: entry.Minor,
+ }
+ if entry.Weight != nil {
+ weight.Weight = *entry.Weight
+ }
+ if entry.LeafWeight != nil {
+ weight.LeafWeight = *entry.LeafWeight
+ }
+ weight.BlockIODevice = *dev
+ final.BlkioWeightDevice = append(final.BlkioWeightDevice, weight)
+ }
+ }
+ }
+
+ // Pids
+ if resource.Pids != nil {
+ final.PidsLimit = resource.Pids.Limit
+ }
+
+ // Networking
+ if resource.Network != nil {
+ if resource.Network.ClassID != nil {
+ final.NetClsClassid = *resource.Network.ClassID
+ }
+ }
+
+ // Unified state
+ final.Unified = resource.Unified
+
+ return *final, nil
+}
diff --git a/libpod/options.go b/libpod/options.go
index 8b3b07efa..9a29fb279 100644
--- a/libpod/options.go
+++ b/libpod/options.go
@@ -1812,7 +1812,7 @@ func WithHostDevice(dev []specs.LinuxDevice) CtrCreateOption {
}
}
-// WithSelectedPasswordManagement makes it so that the container either does or does not setup /etc/passwd or /etc/group
+// WithSelectedPasswordManagement makes it so that the container either does or does not set up /etc/passwd or /etc/group
func WithSelectedPasswordManagement(passwd *bool) CtrCreateOption {
return func(c *Container) error {
if c.valid {
diff --git a/libpod/plugin/volume_api.go b/libpod/plugin/volume_api.go
index f997ccf22..2de7db32c 100644
--- a/libpod/plugin/volume_api.go
+++ b/libpod/plugin/volume_api.go
@@ -197,13 +197,13 @@ func (p *VolumePlugin) verifyReachable() error {
// Send a request to the volume plugin for handling.
// Callers *MUST* close the response when they are done.
-func (p *VolumePlugin) sendRequest(toJSON interface{}, hasBody bool, endpoint string) (*http.Response, error) {
+func (p *VolumePlugin) sendRequest(toJSON interface{}, endpoint string) (*http.Response, error) {
var (
reqJSON []byte
err error
)
- if hasBody {
+ if toJSON != nil {
reqJSON, err = json.Marshal(toJSON)
if err != nil {
return nil, errors.Wrapf(err, "error marshalling request JSON for volume plugin %s endpoint %s", p.Name, endpoint)
@@ -274,7 +274,7 @@ func (p *VolumePlugin) CreateVolume(req *volume.CreateRequest) error {
logrus.Infof("Creating volume %s using plugin %s", req.Name, p.Name)
- resp, err := p.sendRequest(req, true, createPath)
+ resp, err := p.sendRequest(req, createPath)
if err != nil {
return err
}
@@ -291,7 +291,7 @@ func (p *VolumePlugin) ListVolumes() ([]*volume.Volume, error) {
logrus.Infof("Listing volumes using plugin %s", p.Name)
- resp, err := p.sendRequest(nil, false, listPath)
+ resp, err := p.sendRequest(nil, listPath)
if err != nil {
return nil, err
}
@@ -326,7 +326,7 @@ func (p *VolumePlugin) GetVolume(req *volume.GetRequest) (*volume.Volume, error)
logrus.Infof("Getting volume %s using plugin %s", req.Name, p.Name)
- resp, err := p.sendRequest(req, true, getPath)
+ resp, err := p.sendRequest(req, getPath)
if err != nil {
return nil, err
}
@@ -361,7 +361,7 @@ func (p *VolumePlugin) RemoveVolume(req *volume.RemoveRequest) error {
logrus.Infof("Removing volume %s using plugin %s", req.Name, p.Name)
- resp, err := p.sendRequest(req, true, removePath)
+ resp, err := p.sendRequest(req, removePath)
if err != nil {
return err
}
@@ -382,7 +382,7 @@ func (p *VolumePlugin) GetVolumePath(req *volume.PathRequest) (string, error) {
logrus.Infof("Getting volume %s path using plugin %s", req.Name, p.Name)
- resp, err := p.sendRequest(req, true, hostVirtualPath)
+ resp, err := p.sendRequest(req, hostVirtualPath)
if err != nil {
return "", err
}
@@ -419,7 +419,7 @@ func (p *VolumePlugin) MountVolume(req *volume.MountRequest) (string, error) {
logrus.Infof("Mounting volume %s using plugin %s for container %s", req.Name, p.Name, req.ID)
- resp, err := p.sendRequest(req, true, mountPath)
+ resp, err := p.sendRequest(req, mountPath)
if err != nil {
return "", err
}
@@ -455,7 +455,7 @@ func (p *VolumePlugin) UnmountVolume(req *volume.UnmountRequest) error {
logrus.Infof("Unmounting volume %s using plugin %s for container %s", req.Name, p.Name, req.ID)
- resp, err := p.sendRequest(req, true, unmountPath)
+ resp, err := p.sendRequest(req, unmountPath)
if err != nil {
return err
}
diff --git a/libpod/pod_internal.go b/libpod/pod_internal.go
index 41f745e6c..1502bcb06 100644
--- a/libpod/pod_internal.go
+++ b/libpod/pod_internal.go
@@ -69,7 +69,7 @@ func (p *Pod) refresh() error {
if p.config.UsePodCgroup {
switch p.runtime.config.Engine.CgroupManager {
case config.SystemdCgroupsManager:
- cgroupPath, err := systemdSliceFromPath(p.config.CgroupParent, fmt.Sprintf("libpod_pod_%s", p.ID()))
+ cgroupPath, err := systemdSliceFromPath(p.config.CgroupParent, fmt.Sprintf("libpod_pod_%s", p.ID()), p.ResourceLim())
if err != nil {
logrus.Errorf("Creating Cgroup for pod %s: %v", p.ID(), err)
}
diff --git a/libpod/runtime.go b/libpod/runtime.go
index 6c8a99846..11ec750b1 100644
--- a/libpod/runtime.go
+++ b/libpod/runtime.go
@@ -135,7 +135,7 @@ func SetXdgDirs() error {
return nil
}
- // Setup XDG_RUNTIME_DIR
+ // Set up XDG_RUNTIME_DIR
runtimeDir := os.Getenv("XDG_RUNTIME_DIR")
if runtimeDir == "" {
@@ -156,7 +156,7 @@ func SetXdgDirs() error {
}
}
- // Setup XDG_CONFIG_HOME
+ // Set up XDG_CONFIG_HOME
if cfgHomeDir := os.Getenv("XDG_CONFIG_HOME"); cfgHomeDir == "" {
cfgHomeDir, err := util.GetRootlessConfigHomeDir()
if err != nil {
@@ -450,7 +450,7 @@ func makeRuntime(runtime *Runtime) (retErr error) {
}
}()
- // Setup the eventer
+ // Set up the eventer
eventer, err := runtime.newEventer()
if err != nil {
return err
@@ -539,7 +539,7 @@ func makeRuntime(runtime *Runtime) (retErr error) {
}
}
- // the store is only setup when we are in the userns so we do the same for the network interface
+ // the store is only set up when we are in the userns so we do the same for the network interface
if !needsUserns {
netBackend, netInterface, err := network.NetworkBackend(runtime.store, runtime.config, runtime.syslog)
if err != nil {
diff --git a/libpod/runtime_ctr.go b/libpod/runtime_ctr.go
index bdfc102ba..459514d47 100644
--- a/libpod/runtime_ctr.go
+++ b/libpod/runtime_ctr.go
@@ -502,7 +502,7 @@ func (r *Runtime) setupContainer(ctx context.Context, ctr *Container) (_ *Contai
volOptions = append(volOptions, parsedOptions...)
}
}
- newVol, err := r.newVolume(volOptions...)
+ newVol, err := r.newVolume(false, volOptions...)
if err != nil {
return nil, errors.Wrapf(err, "error creating named volume %q", vol.Name)
}
@@ -755,7 +755,7 @@ func (r *Runtime) removeContainer(ctx context.Context, c *Container, force, remo
if cleanupErr == nil {
cleanupErr = err
} else {
- logrus.Errorf("Cleanup storage: %v", err)
+ logrus.Errorf("Cleaning up storage: %v", err)
}
}
@@ -805,16 +805,16 @@ func (r *Runtime) removeContainer(ctx context.Context, c *Container, force, remo
if !volume.Anonymous() {
continue
}
- if err := runtime.removeVolume(ctx, volume, false, timeout); err != nil && errors.Cause(err) != define.ErrNoSuchVolume {
+ if err := runtime.removeVolume(ctx, volume, false, timeout, false); err != nil && errors.Cause(err) != define.ErrNoSuchVolume {
if errors.Cause(err) == define.ErrVolumeBeingUsed {
// Ignore error, since podman will report original error
volumesFrom, _ := c.volumesFrom()
if len(volumesFrom) > 0 {
- logrus.Debugf("Cleanup volume not possible since volume is in use (%s)", v)
+ logrus.Debugf("Cleaning up volume not possible since volume is in use (%s)", v)
continue
}
}
- logrus.Errorf("Cleanup volume (%s): %v", v, err)
+ logrus.Errorf("Cleaning up volume (%s): %v", v, err)
}
}
}
@@ -963,8 +963,8 @@ func (r *Runtime) evictContainer(ctx context.Context, idOrName string, removeVol
if !volume.Anonymous() {
continue
}
- if err := r.removeVolume(ctx, volume, false, timeout); err != nil && err != define.ErrNoSuchVolume && err != define.ErrVolumeBeingUsed {
- logrus.Errorf("Cleanup volume (%s): %v", v, err)
+ if err := r.removeVolume(ctx, volume, false, timeout, false); err != nil && err != define.ErrNoSuchVolume && err != define.ErrVolumeBeingUsed {
+ logrus.Errorf("Cleaning up volume (%s): %v", v, err)
}
}
}
@@ -1111,7 +1111,7 @@ func (r *Runtime) GetContainersByList(containers []string) ([]*Container, error)
for _, inputContainer := range containers {
ctr, err := r.LookupContainer(inputContainer)
if err != nil {
- return ctrs, errors.Wrapf(err, "unable to lookup container %s", inputContainer)
+ return ctrs, errors.Wrapf(err, "unable to look up container %s", inputContainer)
}
ctrs = append(ctrs, ctr)
}
diff --git a/libpod/runtime_pod_linux.go b/libpod/runtime_pod_linux.go
index dcc3a044f..00017ca21 100644
--- a/libpod/runtime_pod_linux.go
+++ b/libpod/runtime_pod_linux.go
@@ -17,7 +17,7 @@ import (
"github.com/containers/podman/v4/libpod/events"
"github.com/containers/podman/v4/pkg/rootless"
"github.com/containers/podman/v4/pkg/specgen"
- spec "github.com/opencontainers/runtime-spec/specs-go"
+ runcconfig "github.com/opencontainers/runc/libcontainer/configs"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
@@ -66,6 +66,7 @@ func (r *Runtime) NewPod(ctx context.Context, p specgen.PodSpecGenerator, option
case config.CgroupfsCgroupsManager:
canUseCgroup := !rootless.IsRootless() || isRootlessCgroupSet(pod.config.CgroupParent)
if canUseCgroup {
+ // need to actually create parent here
if pod.config.CgroupParent == "" {
pod.config.CgroupParent = CgroupfsDefaultCgroupParent
} else if strings.HasSuffix(path.Base(pod.config.CgroupParent), ".slice") {
@@ -73,12 +74,26 @@ func (r *Runtime) NewPod(ctx context.Context, p specgen.PodSpecGenerator, option
}
// If we are set to use pod cgroups, set the cgroup parent that
// all containers in the pod will share
- // No need to create it with cgroupfs - the first container to
- // launch should do it for us
if pod.config.UsePodCgroup {
pod.state.CgroupPath = filepath.Join(pod.config.CgroupParent, pod.ID())
if p.InfraContainerSpec != nil {
p.InfraContainerSpec.CgroupParent = pod.state.CgroupPath
+ res, err := GetLimits(p.InfraContainerSpec.ResourceLimits)
+ if err != nil {
+ return nil, err
+ }
+ // Need to both create and update the cgroup
+ // rather than create a new path in c/common for pod cgroup creation
+ // just create as if it is a ctr and then update figures out that we need to
+ // populate the resource limits on the pod level
+ cgc, err := cgroups.New(pod.state.CgroupPath, &res)
+ if err != nil {
+ return nil, err
+ }
+ err = cgc.Update(&res)
+ if err != nil {
+ return nil, err
+ }
}
}
}
@@ -95,7 +110,7 @@ func (r *Runtime) NewPod(ctx context.Context, p specgen.PodSpecGenerator, option
// If we are set to use pod cgroups, set the cgroup parent that
// all containers in the pod will share
if pod.config.UsePodCgroup {
- cgroupPath, err := systemdSliceFromPath(pod.config.CgroupParent, fmt.Sprintf("libpod_pod_%s", pod.ID()))
+ cgroupPath, err := systemdSliceFromPath(pod.config.CgroupParent, fmt.Sprintf("libpod_pod_%s", pod.ID()), p.InfraContainerSpec.ResourceLimits)
if err != nil {
return nil, errors.Wrapf(err, "unable to create pod cgroup for pod %s", pod.ID())
}
@@ -239,9 +254,8 @@ func (r *Runtime) removePod(ctx context.Context, p *Pod, removeCtrs, force bool,
}
// New resource limits
- resLimits := new(spec.LinuxResources)
- resLimits.Pids = new(spec.LinuxPids)
- resLimits.Pids.Limit = 1 // Inhibit forks with very low pids limit
+ resLimits := new(runcconfig.Resources)
+ resLimits.PidsLimit = 1 // Inhibit forks with very low pids limit
// Don't try if we failed to retrieve the cgroup
if err == nil {
@@ -301,7 +315,7 @@ func (r *Runtime) removePod(ctx context.Context, p *Pod, removeCtrs, force bool,
if !volume.Anonymous() {
continue
}
- if err := r.removeVolume(ctx, volume, false, timeout); err != nil {
+ if err := r.removeVolume(ctx, volume, false, timeout, false); err != nil {
if errors.Cause(err) == define.ErrNoSuchVolume || errors.Cause(err) == define.ErrVolumeRemoved {
continue
}
@@ -321,7 +335,7 @@ func (r *Runtime) removePod(ctx context.Context, p *Pod, removeCtrs, force bool,
switch p.runtime.config.Engine.CgroupManager {
case config.SystemdCgroupsManager:
- if err := deleteSystemdCgroup(p.state.CgroupPath); err != nil {
+ if err := deleteSystemdCgroup(p.state.CgroupPath, p.ResourceLim()); err != nil {
if removalErr == nil {
removalErr = errors.Wrapf(err, "error removing pod %s cgroup", p.ID())
} else {
diff --git a/libpod/runtime_volume.go b/libpod/runtime_volume.go
index 21bf8aefc..6872db21d 100644
--- a/libpod/runtime_volume.go
+++ b/libpod/runtime_volume.go
@@ -33,7 +33,7 @@ func (r *Runtime) RemoveVolume(ctx context.Context, v *Volume, force bool, timeo
return nil
}
}
- return r.removeVolume(ctx, v, force, timeout)
+ return r.removeVolume(ctx, v, force, timeout, false)
}
// GetVolume retrieves a volume given its full name.
diff --git a/libpod/runtime_volume_linux.go b/libpod/runtime_volume_linux.go
index f8788e183..17a48be86 100644
--- a/libpod/runtime_volume_linux.go
+++ b/libpod/runtime_volume_linux.go
@@ -5,6 +5,7 @@ package libpod
import (
"context"
+ "fmt"
"os"
"path/filepath"
"strings"
@@ -25,11 +26,13 @@ func (r *Runtime) NewVolume(ctx context.Context, options ...VolumeCreateOption)
if !r.valid {
return nil, define.ErrRuntimeStopped
}
- return r.newVolume(options...)
+ return r.newVolume(false, options...)
}
-// newVolume creates a new empty volume
-func (r *Runtime) newVolume(options ...VolumeCreateOption) (_ *Volume, deferredErr error) {
+// 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) {
volume := newVolume(r)
for _, option := range options {
if err := option(volume); err != nil {
@@ -73,7 +76,7 @@ func (r *Runtime) newVolume(options ...VolumeCreateOption) (_ *Volume, deferredE
return nil, errors.Wrapf(err, "invalid volume option %s for driver 'local'", key)
}
}
- case "o", "type", "uid", "gid", "size", "inodes", "noquota":
+ case "o", "type", "uid", "gid", "size", "inodes", "noquota", "copy", "nocopy":
// Do nothing, valid keys
default:
return nil, errors.Wrapf(define.ErrInvalidArg, "invalid mount option %s for driver 'local'", key)
@@ -83,7 +86,7 @@ func (r *Runtime) newVolume(options ...VolumeCreateOption) (_ *Volume, deferredE
// Now we get conditional: we either need to make the volume in the
// volume plugin, or on disk if not using a plugin.
- if volume.plugin != nil {
+ if volume.plugin != nil && !noCreatePluginVolume {
// We can't chown, or relabel, or similar the path the volume is
// using, because it's not managed by us.
// TODO: reevaluate this once we actually have volume plugins in
@@ -164,6 +167,85 @@ func (r *Runtime) newVolume(options ...VolumeCreateOption) (_ *Volume, deferredE
return volume, nil
}
+// UpdateVolumePlugins reads all volumes from all configured volume plugins and
+// imports them into the libpod db. It also checks if existing libpod volumes
+// are removed in the plugin, in this case we try to remove it from libpod.
+// On errors we continue and try to do as much as possible. all errors are
+// returned as array in the returned struct.
+// This function has many race conditions, it is best effort but cannot guarantee
+// a perfect state since plugins can be modified from the outside at any time.
+func (r *Runtime) UpdateVolumePlugins(ctx context.Context) *define.VolumeReload {
+ var (
+ added []string
+ removed []string
+ errs []error
+ allPluginVolumes = map[string]struct{}{}
+ )
+
+ for driverName, socket := range r.config.Engine.VolumePlugins {
+ driver, err := volplugin.GetVolumePlugin(driverName, socket)
+ if err != nil {
+ errs = append(errs, err)
+ continue
+ }
+ vols, err := driver.ListVolumes()
+ if err != nil {
+ errs = append(errs, fmt.Errorf("failed to read volumes from plugin %q: %w", driverName, err))
+ continue
+ }
+ for _, vol := range vols {
+ allPluginVolumes[vol.Name] = struct{}{}
+ if _, err := r.newVolume(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) {
+ errs = append(errs, err)
+ continue
+ }
+ logrus.Infof("Volume %q already exists: %v", vol.Name, err)
+ continue
+ }
+ added = append(added, vol.Name)
+ }
+ }
+
+ libpodVolumes, err := r.state.AllVolumes()
+ if err != nil {
+ errs = append(errs, fmt.Errorf("cannot delete dangling plugin volumes: failed to read libpod volumes: %w", err))
+ }
+ for _, vol := range libpodVolumes {
+ if vol.UsesVolumeDriver() {
+ if _, ok := allPluginVolumes[vol.Name()]; !ok {
+ // The volume is no longer in the plugin, lets remove it from the libpod db.
+ if err := r.removeVolume(ctx, vol, false, nil, true); err != nil {
+ if errors.Is(err, define.ErrVolumeBeingUsed) {
+ // Volume is still used by at least one container. This is very bad,
+ // the plugin no longer has this but we still need it.
+ errs = append(errs, fmt.Errorf("volume was removed from the plugin %q but containers still require it: %w", vol.config.Driver, err))
+ continue
+ }
+ if errors.Is(err, define.ErrNoSuchVolume) || errors.Is(err, define.ErrVolumeRemoved) || errors.Is(err, define.ErrMissingPlugin) {
+ // Volume was already removed, no problem just ignore it and continue.
+ continue
+ }
+
+ // some other error
+ errs = append(errs, err)
+ continue
+ }
+ // Volume was successfully removed
+ removed = append(removed, vol.Name())
+ }
+ }
+ }
+
+ return &define.VolumeReload{
+ Added: added,
+ Removed: removed,
+ Errors: errs,
+ }
+}
+
// makeVolumeInPluginIfNotExist makes a volume in the given volume plugin if it
// does not already exist.
func makeVolumeInPluginIfNotExist(name string, options map[string]string, plugin *volplugin.VolumePlugin) error {
@@ -197,8 +279,10 @@ func makeVolumeInPluginIfNotExist(name string, options map[string]string, plugin
return nil
}
-// removeVolume removes the specified volume from state as well tears down its mountpoint and storage
-func (r *Runtime) removeVolume(ctx context.Context, v *Volume, force bool, timeout *uint) error {
+// removeVolume removes the specified volume from state as well tears down its mountpoint and storage.
+// ignoreVolumePlugin is used to only remove the volume from the db and not the plugin,
+// this is required when the volume was already removed from the plugin, i.e. in UpdateVolumePlugins().
+func (r *Runtime) removeVolume(ctx context.Context, v *Volume, force bool, timeout *uint, ignoreVolumePlugin bool) error {
if !v.valid {
if ok, _ := r.state.HasVolume(v.Name()); !ok {
return nil
@@ -263,7 +347,7 @@ func (r *Runtime) removeVolume(ctx context.Context, v *Volume, force bool, timeo
var removalErr error
// If we use a volume plugin, we need to remove from the plugin.
- if v.UsesVolumeDriver() {
+ if v.UsesVolumeDriver() && !ignoreVolumePlugin {
canRemove := true
// Do we have a volume driver?
diff --git a/libpod/state.go b/libpod/state.go
index 471023769..4fbd3c302 100644
--- a/libpod/state.go
+++ b/libpod/state.go
@@ -111,6 +111,15 @@ type State interface {
// Return a container config from the database by full ID
GetContainerConfig(id string) (*ContainerConfig, error)
+ // Add the exit code for the specified container to the database.
+ AddContainerExitCode(id string, exitCode int32) error
+
+ // Return the exit code for the specified container.
+ GetContainerExitCode(id string) (int32, error)
+
+ // Remove exit codes older than 5 minutes.
+ PruneContainerExitCodes() error
+
// Add creates a reference to an exec session in the database.
// The container the exec session is attached to will be recorded.
// The container state will not be modified.
diff --git a/libpod/stats.go b/libpod/stats.go
index d2ffc3b32..eaac9d7d0 100644
--- a/libpod/stats.go
+++ b/libpod/stats.go
@@ -9,6 +9,8 @@ import (
"syscall"
"time"
+ runccgroup "github.com/opencontainers/runc/libcontainer/cgroups"
+
"github.com/containers/common/pkg/cgroups"
"github.com/containers/podman/v4/libpod/define"
"github.com/pkg/errors"
@@ -69,29 +71,29 @@ func (c *Container) GetContainerStats(previousStats *define.ContainerStats) (*de
// If the current total usage in the cgroup is less than what was previously
// recorded then it means the container was restarted and runs in a new cgroup
- if previousStats.Duration > cgroupStats.CPU.Usage.Total {
+ if previousStats.Duration > cgroupStats.CpuStats.CpuUsage.TotalUsage {
previousStats = &define.ContainerStats{}
}
previousCPU := previousStats.CPUNano
now := uint64(time.Now().UnixNano())
- stats.Duration = cgroupStats.CPU.Usage.Total
+ stats.Duration = cgroupStats.CpuStats.CpuUsage.TotalUsage
stats.UpTime = time.Duration(stats.Duration)
stats.CPU = calculateCPUPercent(cgroupStats, previousCPU, now, previousStats.SystemNano)
// calc the average cpu usage for the time the container is running
stats.AvgCPU = calculateCPUPercent(cgroupStats, 0, now, uint64(c.state.StartedTime.UnixNano()))
- stats.MemUsage = cgroupStats.Memory.Usage.Usage
+ stats.MemUsage = cgroupStats.MemoryStats.Usage.Usage
stats.MemLimit = c.getMemLimit()
stats.MemPerc = (float64(stats.MemUsage) / float64(stats.MemLimit)) * 100
stats.PIDs = 0
if conState == define.ContainerStateRunning || conState == define.ContainerStatePaused {
- stats.PIDs = cgroupStats.Pids.Current
+ stats.PIDs = cgroupStats.PidsStats.Current
}
stats.BlockInput, stats.BlockOutput = calculateBlockIO(cgroupStats)
- stats.CPUNano = cgroupStats.CPU.Usage.Total
- stats.CPUSystemNano = cgroupStats.CPU.Usage.Kernel
+ stats.CPUNano = cgroupStats.CpuStats.CpuUsage.TotalUsage
+ stats.CPUSystemNano = cgroupStats.CpuStats.CpuUsage.UsageInKernelmode
stats.SystemNano = now
- stats.PerCPU = cgroupStats.CPU.Usage.PerCPU
+ stats.PerCPU = cgroupStats.CpuStats.CpuUsage.PercpuUsage
// Handle case where the container is not in a network namespace
if netStats != nil {
stats.NetInput = netStats.TxBytes
@@ -133,10 +135,10 @@ func (c *Container) getMemLimit() uint64 {
// previousCPU is the last value of stats.CPU.Usage.Total measured at the time previousSystem.
// (now - previousSystem) is the time delta in nanoseconds, between the measurement in previousCPU
// and the updated value in stats.
-func calculateCPUPercent(stats *cgroups.Metrics, previousCPU, now, previousSystem uint64) float64 {
+func calculateCPUPercent(stats *runccgroup.Stats, previousCPU, now, previousSystem uint64) float64 {
var (
cpuPercent = 0.0
- cpuDelta = float64(stats.CPU.Usage.Total - previousCPU)
+ cpuDelta = float64(stats.CpuStats.CpuUsage.TotalUsage - previousCPU)
systemDelta = float64(now - previousSystem)
)
if systemDelta > 0.0 && cpuDelta > 0.0 {
@@ -146,8 +148,8 @@ func calculateCPUPercent(stats *cgroups.Metrics, previousCPU, now, previousSyste
return cpuPercent
}
-func calculateBlockIO(stats *cgroups.Metrics) (read uint64, write uint64) {
- for _, blkIOEntry := range stats.Blkio.IoServiceBytesRecursive {
+func calculateBlockIO(stats *runccgroup.Stats) (read uint64, write uint64) {
+ for _, blkIOEntry := range stats.BlkioStats.IoServiceBytesRecursive {
switch strings.ToLower(blkIOEntry.Op) {
case "read":
read += blkIOEntry.Value
diff --git a/libpod/util_linux.go b/libpod/util_linux.go
index fe98056dc..414d1bff9 100644
--- a/libpod/util_linux.go
+++ b/libpod/util_linux.go
@@ -11,6 +11,7 @@ import (
"github.com/containers/common/pkg/cgroups"
"github.com/containers/podman/v4/libpod/define"
"github.com/containers/podman/v4/pkg/rootless"
+ spec "github.com/opencontainers/runtime-spec/specs-go"
"github.com/opencontainers/selinux/go-selinux/label"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
@@ -20,7 +21,7 @@ import (
// systemdSliceFromPath makes a new systemd slice under the given parent with
// the given name.
// The parent must be a slice. The name must NOT include ".slice"
-func systemdSliceFromPath(parent, name string) (string, error) {
+func systemdSliceFromPath(parent, name string, resources *spec.LinuxResources) (string, error) {
cgroupPath, err := assembleSystemdCgroupName(parent, name)
if err != nil {
return "", err
@@ -28,7 +29,7 @@ func systemdSliceFromPath(parent, name string) (string, error) {
logrus.Debugf("Created cgroup path %s for parent %s and name %s", cgroupPath, parent, name)
- if err := makeSystemdCgroup(cgroupPath); err != nil {
+ if err := makeSystemdCgroup(cgroupPath, resources); err != nil {
return "", errors.Wrapf(err, "error creating cgroup %s", cgroupPath)
}
@@ -45,8 +46,12 @@ func getDefaultSystemdCgroup() string {
}
// makeSystemdCgroup creates a systemd Cgroup at the given location.
-func makeSystemdCgroup(path string) error {
- controller, err := cgroups.NewSystemd(getDefaultSystemdCgroup())
+func makeSystemdCgroup(path string, resources *spec.LinuxResources) error {
+ res, err := GetLimits(resources)
+ if err != nil {
+ return err
+ }
+ controller, err := cgroups.NewSystemd(getDefaultSystemdCgroup(), &res)
if err != nil {
return err
}
@@ -54,12 +59,20 @@ func makeSystemdCgroup(path string) error {
if rootless.IsRootless() {
return controller.CreateSystemdUserUnit(path, rootless.GetRootlessUID())
}
- return controller.CreateSystemdUnit(path)
+ err = controller.CreateSystemdUnit(path)
+ if err != nil {
+ return err
+ }
+ return nil
}
// deleteSystemdCgroup deletes the systemd cgroup at the given location
-func deleteSystemdCgroup(path string) error {
- controller, err := cgroups.NewSystemd(getDefaultSystemdCgroup())
+func deleteSystemdCgroup(path string, resources *spec.LinuxResources) error {
+ res, err := GetLimits(resources)
+ if err != nil {
+ return err
+ }
+ controller, err := cgroups.NewSystemd(getDefaultSystemdCgroup(), &res)
if err != nil {
return err
}
diff --git a/libpod/volume_internal.go b/libpod/volume_internal.go
index e0ebb729d..24522c0f9 100644
--- a/libpod/volume_internal.go
+++ b/libpod/volume_internal.go
@@ -55,6 +55,12 @@ func (v *Volume) needsMount() bool {
if _, ok := v.config.Options["NOQUOTA"]; ok {
index++
}
+ if _, ok := v.config.Options["nocopy"]; ok {
+ index++
+ }
+ if _, ok := v.config.Options["copy"]; ok {
+ index++
+ }
// when uid or gid is set there is also the "o" option
// set so we have to ignore this one as well
if index > 0 {
diff --git a/pkg/api/handlers/compat/containers_stats.go b/pkg/api/handlers/compat/containers_stats.go
index 66743ce06..12c5283fc 100644
--- a/pkg/api/handlers/compat/containers_stats.go
+++ b/pkg/api/handlers/compat/containers_stats.go
@@ -12,6 +12,7 @@ import (
api "github.com/containers/podman/v4/pkg/api/types"
docker "github.com/docker/docker/api/types"
"github.com/gorilla/schema"
+ runccgroups "github.com/opencontainers/runc/libcontainer/cgroups"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
@@ -58,7 +59,7 @@ func StatsContainer(w http.ResponseWriter, r *http.Request) {
flusher.Flush()
}
- // Setup JSON encoder for streaming.
+ // Set up JSON encoder for streaming.
coder.SetEscapeHTML(true)
var preRead time.Time
var preCPUStats CPUStats
@@ -133,7 +134,7 @@ streamLabel: // A label to flatten the scope
}
cfg := ctnr.Config()
- memoryLimit := cgroupStat.Memory.Usage.Limit
+ memoryLimit := cgroupStat.MemoryStats.Usage.Limit
if cfg.Spec.Linux != nil && cfg.Spec.Linux.Resources != nil && cfg.Spec.Linux.Resources.Memory != nil && *cfg.Spec.Linux.Resources.Memory.Limit > 0 {
memoryLimit = uint64(*cfg.Spec.Linux.Resources.Memory.Limit)
}
@@ -144,11 +145,11 @@ streamLabel: // A label to flatten the scope
Read: time.Now(),
PreRead: preRead,
PidsStats: docker.PidsStats{
- Current: cgroupStat.Pids.Current,
+ Current: cgroupStat.PidsStats.Current,
Limit: 0,
},
BlkioStats: docker.BlkioStats{
- IoServiceBytesRecursive: toBlkioStatEntry(cgroupStat.Blkio.IoServiceBytesRecursive),
+ IoServiceBytesRecursive: toBlkioStatEntry(cgroupStat.BlkioStats.IoServiceBytesRecursive),
IoServicedRecursive: nil,
IoQueuedRecursive: nil,
IoServiceTimeRecursive: nil,
@@ -159,14 +160,14 @@ streamLabel: // A label to flatten the scope
},
CPUStats: CPUStats{
CPUUsage: docker.CPUUsage{
- TotalUsage: cgroupStat.CPU.Usage.Total,
- PercpuUsage: cgroupStat.CPU.Usage.PerCPU,
- UsageInKernelmode: cgroupStat.CPU.Usage.Kernel,
- UsageInUsermode: cgroupStat.CPU.Usage.Total - cgroupStat.CPU.Usage.Kernel,
+ TotalUsage: cgroupStat.CpuStats.CpuUsage.TotalUsage,
+ PercpuUsage: cgroupStat.CpuStats.CpuUsage.PercpuUsage,
+ UsageInKernelmode: cgroupStat.CpuStats.CpuUsage.UsageInKernelmode,
+ UsageInUsermode: cgroupStat.CpuStats.CpuUsage.TotalUsage - cgroupStat.CpuStats.CpuUsage.UsageInKernelmode,
},
CPU: stats.CPU,
SystemUsage: systemUsage,
- OnlineCPUs: uint32(len(cgroupStat.CPU.Usage.PerCPU)),
+ OnlineCPUs: uint32(len(cgroupStat.CpuStats.CpuUsage.PercpuUsage)),
ThrottlingData: docker.ThrottlingData{
Periods: 0,
ThrottledPeriods: 0,
@@ -175,8 +176,8 @@ streamLabel: // A label to flatten the scope
},
PreCPUStats: preCPUStats,
MemoryStats: docker.MemoryStats{
- Usage: cgroupStat.Memory.Usage.Usage,
- MaxUsage: cgroupStat.Memory.Usage.Limit,
+ Usage: cgroupStat.MemoryStats.Usage.Usage,
+ MaxUsage: cgroupStat.MemoryStats.Usage.Limit,
Stats: nil,
Failcnt: 0,
Limit: memoryLimit,
@@ -216,7 +217,7 @@ streamLabel: // A label to flatten the scope
}
}
-func toBlkioStatEntry(entries []cgroups.BlkIOEntry) []docker.BlkioStatEntry {
+func toBlkioStatEntry(entries []runccgroups.BlkioStatEntry) []docker.BlkioStatEntry {
results := make([]docker.BlkioStatEntry, len(entries))
for i, e := range entries {
bits, err := json.Marshal(e)
diff --git a/pkg/api/handlers/libpod/containers_stats.go b/pkg/api/handlers/libpod/containers_stats.go
index d34254fd7..46d722a3d 100644
--- a/pkg/api/handlers/libpod/containers_stats.go
+++ b/pkg/api/handlers/libpod/containers_stats.go
@@ -66,7 +66,7 @@ func StatsContainer(w http.ResponseWriter, r *http.Request) {
flusher.Flush()
}
- // Setup JSON encoder for streaming.
+ // Set up JSON encoder for streaming.
coder := json.NewEncoder(w)
coder.SetEscapeHTML(true)
diff --git a/pkg/api/handlers/utils/containers.go b/pkg/api/handlers/utils/containers.go
index 8588b49ba..1795f6ce1 100644
--- a/pkg/api/handlers/utils/containers.go
+++ b/pkg/api/handlers/utils/containers.go
@@ -191,7 +191,6 @@ func waitDockerCondition(ctx context.Context, containerName string, interval tim
var notRunningStates = []define.ContainerStatus{
define.ContainerStateCreated,
define.ContainerStateRemoving,
- define.ContainerStateStopped,
define.ContainerStateExited,
define.ContainerStateConfigured,
}
diff --git a/pkg/api/server/listener_api.go b/pkg/api/server/listener_api.go
index 2d02df7dc..aaaf6688e 100644
--- a/pkg/api/server/listener_api.go
+++ b/pkg/api/server/listener_api.go
@@ -11,7 +11,7 @@ import (
// ListenUnix follows stdlib net.Listen() API, providing a unix listener for given path
// ListenUnix will delete and create files/directories as needed
func ListenUnix(network string, path string) (net.Listener, error) {
- // setup custom listener for API server
+ // set up custom listener for API server
err := os.MkdirAll(filepath.Dir(path), 0770)
if err != nil {
return nil, errors.Wrapf(err, "api.ListenUnix() failed to create %s", filepath.Dir(path))
diff --git a/pkg/bindings/connection.go b/pkg/bindings/connection.go
index b418b8430..6b3576f31 100644
--- a/pkg/bindings/connection.go
+++ b/pkg/bindings/connection.go
@@ -95,7 +95,7 @@ func NewConnectionWithIdentity(ctx context.Context, uri string, identity string)
return nil, errors.Wrapf(err, "Value of CONTAINER_HOST is not a valid url: %s", uri)
}
- // Now we setup the http Client to use the connection above
+ // Now we set up the http Client to use the connection above
var connection Connection
switch _url.Scheme {
case "ssh":
diff --git a/pkg/criu/criu.go b/pkg/criu/criu.go
index 6570159d7..0b0bbff5d 100644
--- a/pkg/criu/criu.go
+++ b/pkg/criu/criu.go
@@ -1,51 +1,8 @@
-//go:build linux
-// +build linux
-
package criu
-import (
- "github.com/checkpoint-restore/go-criu/v5"
- "github.com/checkpoint-restore/go-criu/v5/rpc"
-
- "google.golang.org/protobuf/proto"
-)
-
// MinCriuVersion for Podman at least CRIU 3.11 is required
const MinCriuVersion = 31100
// PodCriuVersion is the version of CRIU needed for
// checkpointing and restoring containers out of and into Pods.
const PodCriuVersion = 31600
-
-// CheckForCriu uses CRIU's go bindings to check if the CRIU
-// binary exists and if it at least the version Podman needs.
-func CheckForCriu(version int) bool {
- c := criu.MakeCriu()
- result, err := c.IsCriuAtLeast(version)
- if err != nil {
- return false
- }
- return result
-}
-
-func GetCriuVestion() (int, error) {
- c := criu.MakeCriu()
- return c.GetCriuVersion()
-}
-
-func MemTrack() bool {
- features, err := criu.MakeCriu().FeatureCheck(
- &rpc.CriuFeatures{
- MemTrack: proto.Bool(true),
- },
- )
- if err != nil {
- return false
- }
-
- if features == nil || features.MemTrack == nil {
- return false
- }
-
- return *features.MemTrack
-}
diff --git a/pkg/criu/criu_linux.go b/pkg/criu/criu_linux.go
new file mode 100644
index 000000000..c28e23fd7
--- /dev/null
+++ b/pkg/criu/criu_linux.go
@@ -0,0 +1,44 @@
+//go:build linux
+// +build linux
+
+package criu
+
+import (
+ "github.com/checkpoint-restore/go-criu/v5"
+ "github.com/checkpoint-restore/go-criu/v5/rpc"
+
+ "google.golang.org/protobuf/proto"
+)
+
+// CheckForCriu uses CRIU's go bindings to check if the CRIU
+// binary exists and if it at least the version Podman needs.
+func CheckForCriu(version int) bool {
+ c := criu.MakeCriu()
+ result, err := c.IsCriuAtLeast(version)
+ if err != nil {
+ return false
+ }
+ return result
+}
+
+func MemTrack() bool {
+ features, err := criu.MakeCriu().FeatureCheck(
+ &rpc.CriuFeatures{
+ MemTrack: proto.Bool(true),
+ },
+ )
+ if err != nil {
+ return false
+ }
+
+ if features == nil || features.MemTrack == nil {
+ return false
+ }
+
+ return *features.MemTrack
+}
+
+func GetCriuVersion() (int, error) {
+ c := criu.MakeCriu()
+ return c.GetCriuVersion()
+}
diff --git a/pkg/criu/criu_unsupported.go b/pkg/criu/criu_unsupported.go
index 3e3ed9c6c..437482a0e 100644
--- a/pkg/criu/criu_unsupported.go
+++ b/pkg/criu/criu_unsupported.go
@@ -3,6 +3,14 @@
package criu
+func CheckForCriu(version int) bool {
+ return false
+}
+
func MemTrack() bool {
return false
}
+
+func GetCriuVersion() (int, error) {
+ return MinCriuVersion, nil
+}
diff --git a/pkg/domain/entities/engine_container.go b/pkg/domain/entities/engine_container.go
index df42876f6..e4eb808b4 100644
--- a/pkg/domain/entities/engine_container.go
+++ b/pkg/domain/entities/engine_container.go
@@ -104,4 +104,5 @@ type ContainerEngine interface {
VolumePrune(ctx context.Context, options VolumePruneOptions) ([]*reports.PruneReport, error)
VolumeRm(ctx context.Context, namesOrIds []string, opts VolumeRmOptions) ([]*VolumeRmReport, error)
VolumeUnmount(ctx context.Context, namesOrIds []string) ([]*VolumeUnmountReport, error)
+ VolumeReload(ctx context.Context) (*VolumeReloadReport, error)
}
diff --git a/pkg/domain/entities/events.go b/pkg/domain/entities/events.go
index d8ba0f1d3..de218b285 100644
--- a/pkg/domain/entities/events.go
+++ b/pkg/domain/entities/events.go
@@ -14,6 +14,7 @@ type Event struct {
// TODO: it would be nice to have full control over the types at some
// point and fork such Docker types.
dockerEvents.Message
+ HealthStatus string
}
// ConvertToLibpodEvent converts an entities event to a libpod one.
@@ -44,6 +45,7 @@ func ConvertToLibpodEvent(e Event) *libpodEvents.Event {
Status: status,
Time: time.Unix(0, e.TimeNano),
Type: t,
+ HealthStatus: e.HealthStatus,
Details: libpodEvents.Details{
Attributes: details,
},
@@ -59,7 +61,7 @@ func ConvertToEntitiesEvent(e libpodEvents.Event) *Event {
attributes["image"] = e.Image
attributes["name"] = e.Name
attributes["containerExitCode"] = strconv.Itoa(e.ContainerExitCode)
- return &Event{dockerEvents.Message{
+ message := dockerEvents.Message{
// Compatibility with clients that still look for deprecated API elements
Status: e.Status.String(),
ID: e.ID,
@@ -73,5 +75,9 @@ func ConvertToEntitiesEvent(e libpodEvents.Event) *Event {
Scope: "local",
Time: e.Time.Unix(),
TimeNano: e.Time.UnixNano(),
- }}
+ }
+ return &Event{
+ message,
+ e.HealthStatus,
+ }
}
diff --git a/pkg/domain/entities/volumes.go b/pkg/domain/entities/volumes.go
index 556df16c1..9a06b2238 100644
--- a/pkg/domain/entities/volumes.go
+++ b/pkg/domain/entities/volumes.go
@@ -54,6 +54,11 @@ type VolumeListReport struct {
VolumeConfigResponse
}
+// VolumeReloadReport describes the response from reload volume plugins
+type VolumeReloadReport struct {
+ define.VolumeReload
+}
+
/*
* Docker API compatibility types
*/
diff --git a/pkg/domain/filters/volumes.go b/pkg/domain/filters/volumes.go
index e88bd4228..a18e6332c 100644
--- a/pkg/domain/filters/volumes.go
+++ b/pkg/domain/filters/volumes.go
@@ -2,6 +2,7 @@ package filters
import (
"net/url"
+ "regexp"
"strings"
"github.com/containers/podman/v4/libpod"
@@ -15,9 +16,12 @@ func GenerateVolumeFilters(filters url.Values) ([]libpod.VolumeFilter, error) {
for _, val := range v {
switch filter {
case "name":
- nameVal := val
+ nameRegexp, err := regexp.Compile(val)
+ if err != nil {
+ return nil, err
+ }
vf = append(vf, func(v *libpod.Volume) bool {
- return nameVal == v.Name()
+ return nameRegexp.MatchString(v.Name())
})
case "driver":
driverVal := val
diff --git a/pkg/domain/infra/abi/containers.go b/pkg/domain/infra/abi/containers.go
index c7cd0cb56..281e448f6 100644
--- a/pkg/domain/infra/abi/containers.go
+++ b/pkg/domain/infra/abi/containers.go
@@ -16,7 +16,6 @@ import (
"github.com/containers/image/v5/manifest"
"github.com/containers/podman/v4/libpod"
"github.com/containers/podman/v4/libpod/define"
- "github.com/containers/podman/v4/libpod/events"
"github.com/containers/podman/v4/libpod/logs"
"github.com/containers/podman/v4/pkg/checkpoint"
"github.com/containers/podman/v4/pkg/domain/entities"
@@ -38,7 +37,7 @@ import (
)
// getContainersAndInputByContext gets containers whether all, latest, or a slice of names/ids
-// is specified. It also returns a list of the corresponding input name used to lookup each container.
+// is specified. It also returns a list of the corresponding input name used to look up each container.
func getContainersAndInputByContext(all, latest bool, names []string, runtime *libpod.Runtime) (ctrs []*libpod.Container, rawInput []string, err error) {
var ctr *libpod.Container
ctrs = []*libpod.Container{}
@@ -183,7 +182,7 @@ func (ic *ContainerEngine) ContainerStop(ctx context.Context, namesOrIds []strin
if err != nil {
// Issue #7384 and #11384: If the container is configured for
// auto-removal, it might already have been removed at this point.
- // We still need to to cleanup since we do not know if the other cleanup process is successful
+ // We still need to clean up since we do not know if the other cleanup process is successful
if c.AutoRemove() && (errors.Is(err, define.ErrNoSuchCtr) || errors.Is(err, define.ErrCtrRemoved)) {
return nil
}
@@ -488,7 +487,7 @@ func (ic *ContainerEngine) ContainerTop(ctx context.Context, options entities.To
container, err = ic.Libpod.LookupContainer(options.NameOrID)
}
if err != nil {
- return nil, errors.Wrap(err, "unable to lookup requested container")
+ return nil, errors.Wrap(err, "unable to look up requested container")
}
// Run Top.
@@ -635,13 +634,13 @@ func (ic *ContainerEngine) ContainerRestore(ctx context.Context, namesOrIds []st
containers, err = getContainersByContext(false, options.Latest, namesOrIds, ic.Libpod)
default:
for _, nameOrID := range namesOrIds {
- logrus.Debugf("lookup container: %q", nameOrID)
+ logrus.Debugf("look up container: %q", nameOrID)
ctr, err := ic.Libpod.LookupContainer(nameOrID)
if err == nil {
containers = append(containers, ctr)
} else {
// If container was not found, check if this is a checkpoint image
- logrus.Debugf("lookup image: %q", nameOrID)
+ logrus.Debugf("look up image: %q", nameOrID)
img, _, err := ic.Libpod.LibimageRuntime().LookupImage(nameOrID, nil)
if err != nil {
return nil, fmt.Errorf("no such container or image: %s", nameOrID)
@@ -939,6 +938,7 @@ func (ic *ContainerEngine) ContainerStart(ctx context.Context, namesOrIds []stri
}
return reports, errors.Wrapf(err, "unable to start container %s", ctr.ID())
}
+
exitCode = ic.GetContainerExitCode(ctx, ctr)
reports = append(reports, &entities.ContainerStartReport{
Id: ctr.ID(),
@@ -1099,25 +1099,11 @@ func (ic *ContainerEngine) ContainerRun(ctx context.Context, opts entities.Conta
func (ic *ContainerEngine) GetContainerExitCode(ctx context.Context, ctr *libpod.Container) int {
exitCode, err := ctr.Wait(ctx)
- if err == nil {
- return int(exitCode)
- }
- if errors.Cause(err) != define.ErrNoSuchCtr {
- logrus.Errorf("Could not retrieve exit code: %v", err)
+ if err != nil {
+ logrus.Errorf("Waiting for container %s: %v", ctr.ID(), err)
return define.ExecErrorCodeNotFound
}
- // Make 4 attempt with 0.25s backoff between each for 1 second total
- var event *events.Event
- for i := 0; i < 4; i++ {
- event, err = ic.Libpod.GetLastContainerEvent(ctx, ctr.ID(), events.Exited)
- if err != nil {
- time.Sleep(250 * time.Millisecond)
- continue
- }
- return event.ContainerExitCode
- }
- logrus.Errorf("Could not retrieve exit code from event: %v", err)
- return define.ExecErrorCodeNotFound
+ return int(exitCode)
}
func (ic *ContainerEngine) ContainerLogs(ctx context.Context, containers []string, options entities.ContainerLogsOptions) error {
@@ -1194,12 +1180,12 @@ func (ic *ContainerEngine) ContainerCleanup(ctx context.Context, namesOrIds []st
var timeout *uint
err = ic.Libpod.RemoveContainer(ctx, ctr, false, true, timeout)
if err != nil {
- report.RmErr = errors.Wrapf(err, "failed to cleanup and remove container %v", ctr.ID())
+ report.RmErr = errors.Wrapf(err, "failed to clean up and remove container %v", ctr.ID())
}
} else {
err := ctr.Cleanup(ctx)
if err != nil {
- report.CleanErr = errors.Wrapf(err, "failed to cleanup container %v", ctr.ID())
+ report.CleanErr = errors.Wrapf(err, "failed to clean up container %v", ctr.ID())
}
}
diff --git a/pkg/domain/infra/abi/pods.go b/pkg/domain/infra/abi/pods.go
index 8638f4783..1dca8c580 100644
--- a/pkg/domain/infra/abi/pods.go
+++ b/pkg/domain/infra/abi/pods.go
@@ -393,7 +393,7 @@ func (ic *ContainerEngine) PodTop(ctx context.Context, options entities.PodTopOp
pod, err = ic.Libpod.LookupPod(options.NameOrID)
}
if err != nil {
- return nil, errors.Wrap(err, "unable to lookup requested container")
+ return nil, errors.Wrap(err, "unable to look up requested container")
}
// Run Top.
@@ -402,6 +402,56 @@ func (ic *ContainerEngine) PodTop(ctx context.Context, options entities.PodTopOp
return report, err
}
+func (ic *ContainerEngine) listPodReportFromPod(p *libpod.Pod) (*entities.ListPodsReport, error) {
+ status, err := p.GetPodStatus()
+ if err != nil {
+ return nil, err
+ }
+ cons, err := p.AllContainers()
+ if err != nil {
+ return nil, err
+ }
+ lpcs := make([]*entities.ListPodContainer, len(cons))
+ for i, c := range cons {
+ state, err := c.State()
+ if err != nil {
+ return nil, err
+ }
+ lpcs[i] = &entities.ListPodContainer{
+ Id: c.ID(),
+ Names: c.Name(),
+ Status: state.String(),
+ }
+ }
+ infraID, err := p.InfraContainerID()
+ if err != nil {
+ return nil, err
+ }
+ networks := []string{}
+ if len(infraID) > 0 {
+ infra, err := p.InfraContainer()
+ if err != nil {
+ return nil, err
+ }
+ networks, err = infra.Networks()
+ if err != nil {
+ return nil, err
+ }
+ }
+ return &entities.ListPodsReport{
+ Cgroup: p.CgroupParent(),
+ Containers: lpcs,
+ Created: p.CreatedTime(),
+ Id: p.ID(),
+ InfraId: infraID,
+ Name: p.Name(),
+ Namespace: p.Namespace(),
+ Networks: networks,
+ Status: status,
+ Labels: p.Labels(),
+ }, nil
+}
+
func (ic *ContainerEngine) PodPs(ctx context.Context, options entities.PodPSOptions) ([]*entities.ListPodsReport, error) {
var (
err error
@@ -431,53 +481,14 @@ func (ic *ContainerEngine) PodPs(ctx context.Context, options entities.PodPSOpti
reports := make([]*entities.ListPodsReport, 0, len(pds))
for _, p := range pds {
- var lpcs []*entities.ListPodContainer
- status, err := p.GetPodStatus()
- if err != nil {
- return nil, err
- }
- cons, err := p.AllContainers()
+ r, err := ic.listPodReportFromPod(p)
if err != nil {
- return nil, err
- }
- for _, c := range cons {
- state, err := c.State()
- if err != nil {
- return nil, err
+ if errors.Is(err, define.ErrNoSuchPod) || errors.Is(err, define.ErrNoSuchCtr) {
+ continue
}
- lpcs = append(lpcs, &entities.ListPodContainer{
- Id: c.ID(),
- Names: c.Name(),
- Status: state.String(),
- })
- }
- infraID, err := p.InfraContainerID()
- if err != nil {
return nil, err
}
- networks := []string{}
- if len(infraID) > 0 {
- infra, err := p.InfraContainer()
- if err != nil {
- return nil, err
- }
- networks, err = infra.Networks()
- if err != nil {
- return nil, err
- }
- }
- reports = append(reports, &entities.ListPodsReport{
- Cgroup: p.CgroupParent(),
- Containers: lpcs,
- Created: p.CreatedTime(),
- Id: p.ID(),
- InfraId: infraID,
- Name: p.Name(),
- Namespace: p.Namespace(),
- Networks: networks,
- Status: status,
- Labels: p.Labels(),
- })
+ reports = append(reports, r)
}
return reports, nil
}
@@ -494,7 +505,7 @@ func (ic *ContainerEngine) PodInspect(ctx context.Context, options entities.PodI
pod, err = ic.Libpod.LookupPod(options.NameOrID)
}
if err != nil {
- return nil, errors.Wrap(err, "unable to lookup requested container")
+ return nil, errors.Wrap(err, "unable to look up requested container")
}
inspect, err := pod.Inspect()
if err != nil {
diff --git a/pkg/domain/infra/abi/system.go b/pkg/domain/infra/abi/system.go
index 6be37c87f..6e26026d4 100644
--- a/pkg/domain/infra/abi/system.go
+++ b/pkg/domain/infra/abi/system.go
@@ -404,9 +404,9 @@ func (ic *ContainerEngine) Unshare(ctx context.Context, args []string, options e
}
// Make sure to unlock, unshare can run for a long time.
rootlessNetNS.Lock.Unlock()
- // We do not want to cleanup the netns after unshare.
- // The problem is that we cannot know if we need to cleanup and
- // secondly unshare should allow user to setup the namespace with
+ // We do not want to clean up the netns after unshare.
+ // The problem is that we cannot know if we need to clean up and
+ // secondly unshare should allow user to set up the namespace with
// special things, e.g. potentially macvlan or something like that.
return rootlessNetNS.Do(unshare)
}
diff --git a/pkg/domain/infra/abi/volumes.go b/pkg/domain/infra/abi/volumes.go
index a9c53c140..1186d8e81 100644
--- a/pkg/domain/infra/abi/volumes.go
+++ b/pkg/domain/infra/abi/volumes.go
@@ -211,3 +211,8 @@ func (ic *ContainerEngine) VolumeUnmount(ctx context.Context, nameOrIDs []string
return reports, nil
}
+
+func (ic *ContainerEngine) VolumeReload(ctx context.Context) (*entities.VolumeReloadReport, error) {
+ report := ic.Libpod.UpdateVolumePlugins(ctx)
+ return &entities.VolumeReloadReport{VolumeReload: *report}, nil
+}
diff --git a/pkg/domain/infra/runtime_libpod.go b/pkg/domain/infra/runtime_libpod.go
index 03e7ffb5d..162025969 100644
--- a/pkg/domain/infra/runtime_libpod.go
+++ b/pkg/domain/infra/runtime_libpod.go
@@ -342,7 +342,7 @@ func ParseIDMapping(mode namespaces.UsernsMode, uidMapSlice, gidMapSlice []strin
options.HostUIDMapping = false
options.HostGIDMapping = false
- // Simply ignore the setting and do not setup an inner namespace for root as it is a no-op
+ // Simply ignore the setting and do not set up an inner namespace for root as it is a no-op
return &options, nil
}
@@ -394,7 +394,7 @@ func ParseIDMapping(mode namespaces.UsernsMode, uidMapSlice, gidMapSlice []strin
// StartWatcher starts a new SIGHUP go routine for the current config.
func StartWatcher(rt *libpod.Runtime) {
- // Setup the signal notifier
+ // Set up the signal notifier
ch := make(chan os.Signal, 1)
signal.Notify(ch, syscall.SIGHUP)
diff --git a/pkg/domain/infra/tunnel/helpers.go b/pkg/domain/infra/tunnel/helpers.go
index 5b14fac37..6c043465c 100644
--- a/pkg/domain/infra/tunnel/helpers.go
+++ b/pkg/domain/infra/tunnel/helpers.go
@@ -20,7 +20,7 @@ func getContainersByContext(contextWithConnection context.Context, all, ignore b
func getContainersAndInputByContext(contextWithConnection context.Context, all, ignore bool, namesOrIDs []string) ([]entities.ListContainer, []string, error) {
if all && len(namesOrIDs) > 0 {
- return nil, nil, errors.New("cannot lookup containers and all")
+ return nil, nil, errors.New("cannot look up containers and all")
}
options := new(containers.ListOptions).WithAll(true).WithSync(true)
allContainers, err := containers.List(contextWithConnection, options)
@@ -77,7 +77,7 @@ func getContainersAndInputByContext(contextWithConnection context.Context, all,
func getPodsByContext(contextWithConnection context.Context, all bool, namesOrIDs []string) ([]*entities.ListPodsReport, error) {
if all && len(namesOrIDs) > 0 {
- return nil, errors.New("cannot lookup specific pods and all")
+ return nil, errors.New("cannot look up specific pods and all")
}
allPods, err := pods.List(contextWithConnection, nil)
diff --git a/pkg/domain/infra/tunnel/volumes.go b/pkg/domain/infra/tunnel/volumes.go
index 33e090148..6ec35e836 100644
--- a/pkg/domain/infra/tunnel/volumes.go
+++ b/pkg/domain/infra/tunnel/volumes.go
@@ -108,3 +108,7 @@ func (ic *ContainerEngine) VolumeMount(ctx context.Context, nameOrIDs []string)
func (ic *ContainerEngine) VolumeUnmount(ctx context.Context, nameOrIDs []string) ([]*entities.VolumeUnmountReport, error) {
return nil, errors.New("unmounting volumes is not supported for remote clients")
}
+
+func (ic *ContainerEngine) VolumeReload(ctx context.Context) (*entities.VolumeReloadReport, error) {
+ return nil, errors.New("volume reload is not supported for remote clients")
+}
diff --git a/pkg/machine/e2e/list_test.go b/pkg/machine/e2e/list_test.go
index 1c8c6ac81..e2121e7bf 100644
--- a/pkg/machine/e2e/list_test.go
+++ b/pkg/machine/e2e/list_test.go
@@ -29,7 +29,7 @@ var _ = Describe("podman machine list", func() {
firstList, err := mb.setCmd(list).run()
Expect(err).NotTo(HaveOccurred())
Expect(firstList).Should(Exit(0))
- Expect(len(firstList.outputToStringSlice())).To(Equal(1)) // just the header
+ Expect(firstList.outputToStringSlice()).To(HaveLen(1)) // just the header
i := new(initMachine)
session, err := mb.setCmd(i.withImagePath(mb.imagePath)).run()
@@ -39,7 +39,7 @@ var _ = Describe("podman machine list", func() {
secondList, err := mb.setCmd(list).run()
Expect(err).NotTo(HaveOccurred())
Expect(secondList).To(Exit(0))
- Expect(len(secondList.outputToStringSlice())).To(Equal(2)) // one machine and the header
+ Expect(secondList.outputToStringSlice()).To(HaveLen(2)) // one machine and the header
})
It("list machines with quiet or noheading", func() {
@@ -51,12 +51,12 @@ var _ = Describe("podman machine list", func() {
firstList, err := mb.setCmd(list.withQuiet()).run()
Expect(err).NotTo(HaveOccurred())
Expect(firstList).Should(Exit(0))
- Expect(len(firstList.outputToStringSlice())).To(Equal(0)) // No header with quiet
+ Expect(firstList.outputToStringSlice()).To(HaveLen(0)) // No header with quiet
noheaderSession, err := mb.setCmd(list.withNoHeading()).run() // noheader
Expect(err).NotTo(HaveOccurred())
Expect(noheaderSession).Should(Exit(0))
- Expect(len(noheaderSession.outputToStringSlice())).To(Equal(0))
+ Expect(noheaderSession.outputToStringSlice()).To(HaveLen(0))
i := new(initMachine)
session, err := mb.setName(name1).setCmd(i.withImagePath(mb.imagePath)).run()
@@ -70,7 +70,7 @@ var _ = Describe("podman machine list", func() {
secondList, err := mb.setCmd(list.withQuiet()).run()
Expect(err).NotTo(HaveOccurred())
Expect(secondList).To(Exit(0))
- Expect(len(secondList.outputToStringSlice())).To(Equal(2)) // two machines, no header
+ Expect(secondList.outputToStringSlice()).To(HaveLen(2)) // two machines, no header
listNames := secondList.outputToStringSlice()
stripAsterisk(listNames)
@@ -116,10 +116,10 @@ var _ = Describe("podman machine list", func() {
// go format
list := new(listMachine)
- listSession, err := mb.setCmd(list.withFormat("{{.Name}}").withNoHeading()).run()
+ listSession, err := mb.setCmd(list.withFormat("{{.Name}}")).run()
Expect(err).NotTo(HaveOccurred())
Expect(listSession).To(Exit(0))
- Expect(len(listSession.outputToStringSlice())).To(Equal(1))
+ Expect(listSession.outputToStringSlice()).To(HaveLen(1))
listNames := listSession.outputToStringSlice()
stripAsterisk(listNames)
@@ -135,6 +135,15 @@ var _ = Describe("podman machine list", func() {
var listResponse []*machine.ListReporter
err = jsoniter.Unmarshal(listSession.Bytes(), &listResponse)
Expect(err).To(BeNil())
+
+ // table format includes the header
+ list = new(listMachine)
+ listSession3, err3 := mb.setCmd(list.withFormat("table {{.Name}}")).run()
+ Expect(err3).NotTo(HaveOccurred())
+ Expect(listSession3).To(Exit(0))
+ listNames3 := listSession3.outputToStringSlice()
+ Expect(listNames3).To(HaveLen(2))
+ Expect(listNames3).To(ContainSubstring("NAME"))
})
})
diff --git a/pkg/machine/ignition.go b/pkg/machine/ignition.go
index 35a9a30cb..f4602cc95 100644
--- a/pkg/machine/ignition.go
+++ b/pkg/machine/ignition.go
@@ -93,7 +93,7 @@ func NewIgnitionFile(ign DynamicIgnition) error {
tz string
)
// local means the same as the host
- // lookup where it is pointing to on the host
+ // look up where it is pointing to on the host
if ign.TimeZone == "local" {
tz, err = getLocalTimeZone()
if err != nil {
@@ -348,7 +348,7 @@ Delegate=memory pids cpu io
},
})
- // Setup /etc/subuid and /etc/subgid
+ // Set up /etc/subuid and /etc/subgid
for _, sub := range []string{"/etc/subuid", "/etc/subgid"} {
files = append(files, File{
Node: Node{
diff --git a/pkg/machine/qemu/machine.go b/pkg/machine/qemu/machine.go
index 288b2eeb0..3a1495021 100644
--- a/pkg/machine/qemu/machine.go
+++ b/pkg/machine/qemu/machine.go
@@ -209,7 +209,7 @@ func migrateVM(configPath string, config []byte, vm *MachineVM) error {
vm.Rootful = old.Rootful
vm.UID = old.UID
- // Backup the original config file
+ // Back up the original config file
if err := os.Rename(configPath, configPath+".orig"); err != nil {
return err
}
@@ -580,7 +580,7 @@ func (v *MachineVM) Start(name string, _ machine.StartOptions) error {
if !errors.Is(err, os.ErrNotExist) {
return err
}
- // lookup qemu again maybe the path was changed, https://github.com/containers/podman/issues/13394
+ // look up qemu again maybe the path was changed, https://github.com/containers/podman/issues/13394
cfg, err := config.Default()
if err != nil {
return err
@@ -1074,6 +1074,7 @@ func getVMInfos() ([]*machine.ListResponse, error) {
listEntry.RemoteUsername = vm.RemoteUsername
listEntry.IdentityPath = vm.IdentityPath
listEntry.CreatedAt = vm.Created
+ listEntry.Starting = vm.Starting
if listEntry.CreatedAt.IsZero() {
listEntry.CreatedAt = time.Now()
@@ -1087,6 +1088,7 @@ func getVMInfos() ([]*machine.ListResponse, error) {
if err != nil {
return err
}
+ listEntry.Running = state == machine.Running
if !vm.LastUp.IsZero() { // this means we have already written a time to the config
listEntry.LastUp = vm.LastUp
@@ -1097,12 +1099,6 @@ func getVMInfos() ([]*machine.ListResponse, error) {
return err
}
}
- switch state {
- case machine.Running:
- listEntry.Running = true
- case machine.Starting:
- listEntry.Starting = true
- }
listed = append(listed, listEntry)
}
@@ -1142,7 +1138,7 @@ func (p *Provider) CheckExclusiveActiveVM() (bool, string, error) {
}
// startHostNetworking runs a binary on the host system that allows users
-// to setup port forwarding to the podman virtual machine
+// to set up port forwarding to the podman virtual machine
func (v *MachineVM) startHostNetworking() (string, apiForwardingState, error) {
cfg, err := config.Default()
if err != nil {
diff --git a/pkg/namespaces/namespaces.go b/pkg/namespaces/namespaces.go
index c95f8e275..8eacb8da7 100644
--- a/pkg/namespaces/namespaces.go
+++ b/pkg/namespaces/namespaces.go
@@ -112,7 +112,7 @@ func (n UsernsMode) IsDefaultValue() bool {
return n == "" || n == defaultType
}
-// GetAutoOptions returns a AutoUserNsOptions with the settings to setup automatically
+// GetAutoOptions returns a AutoUserNsOptions with the settings to automatically set up
// a user namespace.
func (n UsernsMode) GetAutoOptions() (*types.AutoUserNsOptions, error) {
parts := strings.SplitN(string(n), ":", 2)
diff --git a/pkg/rootless/rootless_linux.go b/pkg/rootless/rootless_linux.go
index d0bdf0ffe..fde621b72 100644
--- a/pkg/rootless/rootless_linux.go
+++ b/pkg/rootless/rootless_linux.go
@@ -154,7 +154,7 @@ func tryMappingTool(uid bool, pid int, hostID int, mappings []idtools.IDMap) err
if output, err := cmd.CombinedOutput(); err != nil {
logrus.Errorf("running `%s`: %s", strings.Join(args, " "), output)
- errorStr := fmt.Sprintf("cannot setup namespace using %q", path)
+ errorStr := fmt.Sprintf("cannot set up namespace using %q", path)
if isSet, err := unshare.IsSetID(cmd.Path, mode, cap); err != nil {
logrus.Errorf("Failed to check for %s on %s: %v", idtype, path, err)
} else if !isSet {
@@ -303,7 +303,7 @@ func becomeRootInUserNS(pausePid, fileToRead string, fileOutput *os.File) (_ boo
if retErr != nil && pid > 0 {
if err := unix.Kill(pid, unix.SIGKILL); err != nil {
if err != unix.ESRCH {
- logrus.Errorf("Failed to cleanup process %d: %v", pid, err)
+ logrus.Errorf("Failed to clean up process %d: %v", pid, err)
}
}
C.reexec_in_user_namespace_wait(C.int(pid), 0)
diff --git a/pkg/signal/signal_common.go b/pkg/signal/signal_common.go
index fe5a76dae..fc1ecc04d 100644
--- a/pkg/signal/signal_common.go
+++ b/pkg/signal/signal_common.go
@@ -2,6 +2,8 @@ package signal
import (
"fmt"
+ "os"
+ "os/signal"
"strconv"
"strings"
"syscall"
@@ -39,3 +41,18 @@ func ParseSignalNameOrNumber(rawSignal string) (syscall.Signal, error) {
}
return -1, fmt.Errorf("invalid signal: %s", basename)
}
+
+// CatchAll catches all signals and relays them to the specified channel.
+func CatchAll(sigc chan os.Signal) {
+ handledSigs := make([]os.Signal, 0, len(SignalMap))
+ for _, s := range SignalMap {
+ handledSigs = append(handledSigs, s)
+ }
+ signal.Notify(sigc, handledSigs...)
+}
+
+// StopCatch stops catching the signals and closes the specified channel.
+func StopCatch(sigc chan os.Signal) {
+ signal.Stop(sigc)
+ close(sigc)
+}
diff --git a/pkg/signal/signal_linux.go b/pkg/signal/signal_linux.go
index a114ea019..5103b6033 100644
--- a/pkg/signal/signal_linux.go
+++ b/pkg/signal/signal_linux.go
@@ -9,8 +9,6 @@ package signal
// NOTE: this package has originally been copied from github.com/docker/docker.
import (
- "os"
- "os/signal"
"syscall"
"golang.org/x/sys/unix"
@@ -91,18 +89,3 @@ var SignalMap = map[string]syscall.Signal{
"RTMAX-1": sigrtmax - 1,
"RTMAX": sigrtmax,
}
-
-// CatchAll catches all signals and relays them to the specified channel.
-func CatchAll(sigc chan os.Signal) {
- handledSigs := make([]os.Signal, 0, len(SignalMap))
- for _, s := range SignalMap {
- handledSigs = append(handledSigs, s)
- }
- signal.Notify(sigc, handledSigs...)
-}
-
-// StopCatch stops catching the signals and closes the specified channel.
-func StopCatch(sigc chan os.Signal) {
- signal.Stop(sigc)
- close(sigc)
-}
diff --git a/pkg/signal/signal_linux_mipsx.go b/pkg/signal/signal_linux_mipsx.go
index 9021a10e7..cdf9ad4c5 100644
--- a/pkg/signal/signal_linux_mipsx.go
+++ b/pkg/signal/signal_linux_mipsx.go
@@ -10,8 +10,6 @@ package signal
// NOTE: this package has originally been copied from github.com/docker/docker.
import (
- "os"
- "os/signal"
"syscall"
"golang.org/x/sys/unix"
@@ -92,18 +90,3 @@ var SignalMap = map[string]syscall.Signal{
"RTMAX-1": sigrtmax - 1,
"RTMAX": sigrtmax,
}
-
-// CatchAll catches all signals and relays them to the specified channel.
-func CatchAll(sigc chan os.Signal) {
- handledSigs := make([]os.Signal, 0, len(SignalMap))
- for _, s := range SignalMap {
- handledSigs = append(handledSigs, s)
- }
- signal.Notify(sigc, handledSigs...)
-}
-
-// StopCatch stops catching the signals and closes the specified channel.
-func StopCatch(sigc chan os.Signal) {
- signal.Stop(sigc)
- close(sigc)
-}
diff --git a/pkg/signal/signal_unix.go b/pkg/signal/signal_unix.go
index 0f43e21b7..7919e3670 100644
--- a/pkg/signal/signal_unix.go
+++ b/pkg/signal/signal_unix.go
@@ -5,7 +5,6 @@
package signal
import (
- "os"
"syscall"
)
@@ -88,13 +87,3 @@ var SignalMap = map[string]syscall.Signal{
"RTMAX-1": sigrtmax - 1,
"RTMAX": sigrtmax,
}
-
-// CatchAll catches all signals and relays them to the specified channel.
-func CatchAll(sigc chan os.Signal) {
- panic("Unsupported on non-linux platforms")
-}
-
-// StopCatch stops catching the signals and closes the specified channel.
-func StopCatch(sigc chan os.Signal) {
- panic("Unsupported on non-linux platforms")
-}
diff --git a/pkg/signal/signal_unsupported.go b/pkg/signal/signal_unsupported.go
index 9d0cee317..19ae93a61 100644
--- a/pkg/signal/signal_unsupported.go
+++ b/pkg/signal/signal_unsupported.go
@@ -5,7 +5,6 @@
package signal
import (
- "os"
"syscall"
)
@@ -88,13 +87,3 @@ var SignalMap = map[string]syscall.Signal{
"RTMAX-1": sigrtmax - 1,
"RTMAX": sigrtmax,
}
-
-// CatchAll catches all signals and relays them to the specified channel.
-func CatchAll(sigc chan os.Signal) {
- panic("Unsupported on non-linux platforms")
-}
-
-// StopCatch stops catching the signals and closes the specified channel.
-func StopCatch(sigc chan os.Signal) {
- panic("Unsupported on non-linux platforms")
-}
diff --git a/pkg/specgen/generate/container_create.go b/pkg/specgen/generate/container_create.go
index 0dec943d1..6b2e90b22 100644
--- a/pkg/specgen/generate/container_create.go
+++ b/pkg/specgen/generate/container_create.go
@@ -180,10 +180,23 @@ func MakeContainer(ctx context.Context, rt *libpod.Runtime, s *specgen.SpecGener
if err != nil {
return nil, nil, nil, err
}
+ resources := runtimeSpec.Linux.Resources
+
+ // resources get overwrritten similarly to pod inheritance, manually assign here if there is a new value
+ marshalRes, err := json.Marshal(resources)
+ if err != nil {
+ return nil, nil, nil, err
+ }
+
err = json.Unmarshal(out, runtimeSpec.Linux)
if err != nil {
return nil, nil, nil, err
}
+
+ err = json.Unmarshal(marshalRes, runtimeSpec.Linux.Resources)
+ if err != nil {
+ return nil, nil, nil, err
+ }
}
if s.ResourceLimits != nil {
switch {
diff --git a/pkg/specgen/generate/oci.go b/pkg/specgen/generate/oci.go
index 19f55c9d8..1044854f4 100644
--- a/pkg/specgen/generate/oci.go
+++ b/pkg/specgen/generate/oci.go
@@ -298,8 +298,7 @@ func SpecGenToOCI(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.Runt
g.AddAnnotation(key, val)
}
- switch {
- case compatibleOptions.InfraResources == nil && s.ResourceLimits != nil:
+ if s.ResourceLimits != nil {
out, err := json.Marshal(s.ResourceLimits)
if err != nil {
return nil, err
@@ -308,29 +307,9 @@ func SpecGenToOCI(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.Runt
if err != nil {
return nil, err
}
- case s.ResourceLimits != nil: // if we have predefined resource limits we need to make sure we keep the infra and container limits
- originalResources, err := json.Marshal(s.ResourceLimits)
- if err != nil {
- return nil, err
- }
- infraResources, err := json.Marshal(compatibleOptions.InfraResources)
- if err != nil {
- return nil, err
- }
- err = json.Unmarshal(infraResources, s.ResourceLimits) // put infra's resource limits in the container
- if err != nil {
- return nil, err
- }
- err = json.Unmarshal(originalResources, s.ResourceLimits) // make sure we did not override anything
- if err != nil {
- return nil, err
- }
g.Config.Linux.Resources = s.ResourceLimits
- default:
- g.Config.Linux.Resources = compatibleOptions.InfraResources
}
// Devices
-
// set the default rule at the beginning of device configuration
if !inUserNS && !s.Privileged {
g.AddLinuxResourcesDevice(false, "", nil, nil, "rwm")
diff --git a/pkg/specgen/volumes.go b/pkg/specgen/volumes.go
index a7a1022b0..f272a5c11 100644
--- a/pkg/specgen/volumes.go
+++ b/pkg/specgen/volumes.go
@@ -1,6 +1,7 @@
package specgen
import (
+ "path/filepath"
"strings"
"github.com/containers/common/pkg/parse"
@@ -56,7 +57,6 @@ func GenVolumeMounts(volumeFlag []string) (map[string]spec.Mount, map[string]*Na
overlayVolumes := make(map[string]*OverlayVolume)
volumeFormatErr := errors.Errorf("incorrect volume format, should be [host-dir:]ctr-dir[:option]")
-
for _, vol := range volumeFlag {
var (
options []string
@@ -71,6 +71,20 @@ func GenVolumeMounts(volumeFlag []string) (map[string]spec.Mount, map[string]*Na
}
src = splitVol[0]
+
+ // Support relative paths beginning with ./
+ if strings.HasPrefix(src, "./") {
+ path, err := filepath.EvalSymlinks(src)
+ if err != nil {
+ return nil, nil, nil, err
+ }
+ src, err = filepath.Abs(path)
+ if err != nil {
+ return nil, nil, nil, err
+ }
+ splitVol[0] = src
+ }
+
if len(splitVol) == 1 {
// This is an anonymous named volume. Only thing given
// is destination.
diff --git a/pkg/systemd/generate/containers.go b/pkg/systemd/generate/containers.go
index d552e21ed..e953a1f1f 100644
--- a/pkg/systemd/generate/containers.go
+++ b/pkg/systemd/generate/containers.go
@@ -204,7 +204,7 @@ func generateContainerInfo(ctr *libpod.Container, options entities.GenerateSyste
} else {
runRoot = ctr.Runtime().RunRoot()
if runRoot == "" {
- return nil, errors.Errorf("could not lookup container's runroot: got empty string")
+ return nil, errors.Errorf("could not look up container's runroot: got empty string")
}
}
diff --git a/pkg/util/mountOpts.go b/pkg/util/mountOpts.go
index e37394619..d1dd75a82 100644
--- a/pkg/util/mountOpts.go
+++ b/pkg/util/mountOpts.go
@@ -25,7 +25,7 @@ type defaultMountOptions struct {
// The sourcePath variable, if not empty, contains a bind mount source.
func ProcessOptions(options []string, isTmpfs bool, sourcePath string) ([]string, error) {
var (
- foundWrite, foundSize, foundProp, foundMode, foundExec, foundSuid, foundDev, foundCopyUp, foundBind, foundZ, foundU, foundOverlay, foundIdmap bool
+ foundWrite, foundSize, foundProp, foundMode, foundExec, foundSuid, foundDev, foundCopyUp, foundBind, foundZ, foundU, foundOverlay, foundIdmap, foundCopy bool
)
newOptions := make([]string, 0, len(options))
@@ -55,6 +55,11 @@ func ProcessOptions(options []string, isTmpfs bool, sourcePath string) ([]string
}
switch splitOpt[0] {
+ case "copy", "nocopy":
+ if foundCopy {
+ return nil, errors.Wrapf(ErrDupeMntOption, "only one of 'nocopy' and 'copy' can be used")
+ }
+ foundCopy = true
case "O":
foundOverlay = true
case "volume-opt":
diff --git a/rootless.md b/rootless.md
index 39c961d2a..f5d78b80b 100644
--- a/rootless.md
+++ b/rootless.md
@@ -8,7 +8,7 @@ Contributors are more than welcomed to help with this work. If you decide to ca
* The kernel does not allow processes without CAP_NET_BIND_SERVICE to bind to low ports.
* You can modify the `net.ipv4.ip_unprivileged_port_start` sysctl to change the lowest port. For example `sysctl net.ipv4.ip_unprivileged_port_start=443` allows rootless Podman containers to bind to ports >= 443.
* “How To” documentation is patchy at best.
-* If /etc/subuid and /etc/subgid are not setup for a user, then podman commands
+* If /etc/subuid and /etc/subgid are not set up for a user, then podman commands
can easily fail
* This can be a big issue on machines using Network Based Password information (FreeIPA, Active Directory, LDAP)
* We are working to get support for NSSWITCH on the /etc/subuid and /etc/subgid files.
@@ -24,7 +24,7 @@ can easily fail
* NFS and parallel filesystems enforce file creation on different UIDs on the server side and does not understand User Namespace.
* When a container root process like YUM attempts to create a file owned by a different UID, NFS Server/GPFS denies the creation.
* Does not work with homedirs mounted with noexec/nodev
- * User can setup storage to point to other directories they can write to that are not mounted noexec/nodev
+ * User can set up storage to point to other directories they can write to that are not mounted noexec/nodev
* Support for using native overlayfs as an unprivileged user is only available for Podman version >= 3.1 on a Linux kernel version >= 5.12, otherwise the slower _fuse-overlayfs_ may be used.
* A few Linux distributions (e.g. Ubuntu) have supported even older Podman and Linux kernel versions by modifying the normal Linux kernel behaviour.
* Only other supported driver is VFS.
diff --git a/test/apiv2/27-containersEvents.at b/test/apiv2/27-containersEvents.at
index a86f2e353..e0a66e0ac 100644
--- a/test/apiv2/27-containersEvents.at
+++ b/test/apiv2/27-containersEvents.at
@@ -18,6 +18,10 @@ t GET "libpod/events?stream=false&since=$START" 200 \
'select(.status | contains("died")).Action=died' \
'select(.status | contains("died")).Actor.Attributes.containerExitCode=1'
+t GET "libpod/events?stream=false&since=$START" 200 \
+ 'select(.status | contains("start")).Action=start' \
+ 'select(.status | contains("start")).HealthStatus='\
+
# compat api, uses status=die (#12643)
t GET "events?stream=false&since=$START" 200 \
'select(.status | contains("start")).Action=start' \
diff --git a/test/e2e/checkpoint_test.go b/test/e2e/checkpoint_test.go
index 1da199714..1fa67e9ba 100644
--- a/test/e2e/checkpoint_test.go
+++ b/test/e2e/checkpoint_test.go
@@ -23,10 +23,31 @@ import (
func getRunString(input []string) []string {
// CRIU does not work with seccomp correctly on RHEL7 : seccomp=unconfined
- runString := []string{"run", "-it", "--security-opt", "seccomp=unconfined", "-d", "--ip", GetRandomIPAddress()}
+ runString := []string{"run", "--security-opt", "seccomp=unconfined", "-d", "--ip", GetRandomIPAddress()}
return append(runString, input...)
}
+// FIXME FIXME FIXME: workaround for #14653, please remove this function
+// and all calls to it once that bug is fixed.
+func fixmeFixme14653(podmanTest *PodmanTestIntegration, cid string) {
+ if !IsRemote() {
+ // Race condition only affects podman-remote
+ return
+ }
+
+ // Wait for container to truly go away
+ for i := 0; i < 5; i++ {
+ ps := podmanTest.Podman([]string{"container", "exists", cid})
+ ps.WaitWithDefaultTimeout()
+ if ps.ExitCode() == 1 {
+ // yay, it's gone
+ return
+ }
+ time.Sleep(time.Second)
+ }
+ // Fall through. Container still exists, but return anyway.
+}
+
var _ = Describe("Podman checkpoint", func() {
var (
tempdir string
@@ -478,6 +499,7 @@ var _ = Describe("Podman checkpoint", func() {
// As the container has been started with '--rm' it will be completely
// cleaned up after checkpointing.
Expect(result).Should(Exit(0))
+ fixmeFixme14653(podmanTest, cid)
Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0))
Expect(podmanTest.NumberOfContainers()).To(Equal(0))
@@ -530,6 +552,7 @@ var _ = Describe("Podman checkpoint", func() {
// As the container has been started with '--rm' it will be completely
// cleaned up after checkpointing.
Expect(result).Should(Exit(0))
+ fixmeFixme14653(podmanTest, cid)
Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0))
Expect(podmanTest.NumberOfContainers()).To(Equal(0))
@@ -548,6 +571,7 @@ var _ = Describe("Podman checkpoint", func() {
// As the container has been started with '--rm' it will be completely
// cleaned up after checkpointing.
Expect(result).Should(Exit(0))
+ fixmeFixme14653(podmanTest, cid)
Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0))
Expect(podmanTest.NumberOfContainers()).To(Equal(0))
@@ -566,6 +590,7 @@ var _ = Describe("Podman checkpoint", func() {
// As the container has been started with '--rm' it will be completely
// cleaned up after checkpointing.
Expect(result).Should(Exit(0))
+ fixmeFixme14653(podmanTest, cid)
Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0))
Expect(podmanTest.NumberOfContainers()).To(Equal(0))
@@ -584,6 +609,7 @@ var _ = Describe("Podman checkpoint", func() {
// As the container has been started with '--rm' it will be completely
// cleaned up after checkpointing.
Expect(result).Should(Exit(0))
+ fixmeFixme14653(podmanTest, cid)
Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0))
Expect(podmanTest.NumberOfContainers()).To(Equal(0))
@@ -645,6 +671,7 @@ var _ = Describe("Podman checkpoint", func() {
result.WaitWithDefaultTimeout()
Expect(result).Should(Exit(0))
+ fixmeFixme14653(podmanTest, cid)
Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0))
Expect(podmanTest.NumberOfContainers()).To(Equal(0))
@@ -694,6 +721,7 @@ var _ = Describe("Podman checkpoint", func() {
result.WaitWithDefaultTimeout()
Expect(result).Should(Exit(0))
+ fixmeFixme14653(podmanTest, cid)
Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0))
Expect(podmanTest.NumberOfContainers()).To(Equal(0))
@@ -735,6 +763,7 @@ var _ = Describe("Podman checkpoint", func() {
result.WaitWithDefaultTimeout()
Expect(result).Should(Exit(0))
+ fixmeFixme14653(podmanTest, cid)
Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0))
Expect(podmanTest.NumberOfContainers()).To(Equal(0))
@@ -772,6 +801,7 @@ var _ = Describe("Podman checkpoint", func() {
result.WaitWithDefaultTimeout()
Expect(result).Should(Exit(0))
+ fixmeFixme14653(podmanTest, cid)
Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0))
Expect(podmanTest.NumberOfContainers()).To(Equal(0))
@@ -821,6 +851,7 @@ var _ = Describe("Podman checkpoint", func() {
// As the container has been started with '--rm' it will be completely
// cleaned up after checkpointing.
Expect(result).Should(Exit(0))
+ fixmeFixme14653(podmanTest, cid)
Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0))
Expect(podmanTest.NumberOfContainers()).To(Equal(0))
@@ -890,6 +921,7 @@ var _ = Describe("Podman checkpoint", func() {
result = podmanTest.Podman([]string{"container", "checkpoint", cid, "-e", checkpointFileName})
result.WaitWithDefaultTimeout()
Expect(result).Should(Exit(0))
+ fixmeFixme14653(podmanTest, cid)
Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0))
Expect(podmanTest.NumberOfContainers()).To(Equal(0))
@@ -1044,6 +1076,7 @@ var _ = Describe("Podman checkpoint", func() {
// As the container has been started with '--rm' it will be completely
// cleaned up after checkpointing.
Expect(result).Should(Exit(0))
+ fixmeFixme14653(podmanTest, cid)
Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0))
Expect(podmanTest.NumberOfContainers()).To(Equal(0))
@@ -1140,6 +1173,7 @@ var _ = Describe("Podman checkpoint", func() {
// As the container has been started with '--rm' it will be completely
// cleaned up after checkpointing.
Expect(result).To(Exit(0))
+ fixmeFixme14653(podmanTest, cid)
Expect(podmanTest.NumberOfContainersRunning()).To(Equal(1))
Expect(podmanTest.NumberOfContainers()).To(Equal(1))
@@ -1252,6 +1286,7 @@ var _ = Describe("Podman checkpoint", func() {
// As the container has been started with '--rm' it will be completely
// cleaned up after checkpointing.
Expect(result).Should(Exit(0))
+ fixmeFixme14653(podmanTest, cid)
Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0))
Expect(podmanTest.NumberOfContainers()).To(Equal(0))
@@ -1296,6 +1331,7 @@ var _ = Describe("Podman checkpoint", func() {
// As the container has been started with '--rm' it will be completely
// cleaned up after checkpointing.
Expect(result).Should(Exit(0))
+ fixmeFixme14653(podmanTest, cid)
Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0))
Expect(podmanTest.NumberOfContainers()).To(Equal(0))
@@ -1489,6 +1525,7 @@ var _ = Describe("Podman checkpoint", func() {
// As the container has been started with '--rm' it will be completely
// cleaned up after checkpointing.
Expect(result).Should(Exit(0))
+ fixmeFixme14653(podmanTest, cid)
Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0))
Expect(podmanTest.NumberOfContainers()).To(Equal(0))
@@ -1573,6 +1610,7 @@ var _ = Describe("Podman checkpoint", func() {
// As the container has been started with '--rm' it will be completely
// cleaned up after checkpointing.
Expect(result).Should(Exit(0))
+ fixmeFixme14653(podmanTest, cid)
Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0))
Expect(podmanTest.NumberOfContainers()).To(Equal(0))
@@ -1651,6 +1689,7 @@ var _ = Describe("Podman checkpoint", func() {
// As the container has been started with '--rm' it will be completely
// cleaned up after checkpointing.
Expect(result).Should(Exit(0))
+ fixmeFixme14653(podmanTest, cid)
Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0))
Expect(podmanTest.NumberOfContainers()).To(Equal(0))
diff --git a/test/e2e/common_test.go b/test/e2e/common_test.go
index 194d592f4..261db8a9a 100644
--- a/test/e2e/common_test.go
+++ b/test/e2e/common_test.go
@@ -322,7 +322,7 @@ func PodmanTestCreateUtil(tempDir string, remote bool) *PodmanTestIntegration {
}
}
- // Setup registries.conf ENV variable
+ // Set up registries.conf ENV variable
p.setDefaultRegistriesConfigEnv()
// Rewrite the PodmanAsUser function
p.PodmanMakeOptions = p.makeOptions
diff --git a/test/e2e/config.go b/test/e2e/config.go
index 2ca8e2a15..fbcc9dfff 100644
--- a/test/e2e/config.go
+++ b/test/e2e/config.go
@@ -15,7 +15,7 @@ var (
healthcheck = "quay.io/libpod/alpine_healthcheck:latest"
ImageCacheDir = "/tmp/podman/imagecachedir"
fedoraToolbox = "registry.fedoraproject.org/fedora-toolbox:36"
- volumeTest = "quay.io/libpod/volume-plugin-test-img:latest"
+ volumeTest = "quay.io/libpod/volume-plugin-test-img:20220623"
// This image has seccomp profiles that blocks all syscalls.
// The intention behind blocking all syscalls is to prevent
diff --git a/test/e2e/create_staticip_test.go b/test/e2e/create_staticip_test.go
index 6fd88753b..85cc5023c 100644
--- a/test/e2e/create_staticip_test.go
+++ b/test/e2e/create_staticip_test.go
@@ -25,7 +25,7 @@ var _ = Describe("Podman create with --ip flag", func() {
}
podmanTest = PodmanTestCreate(tempdir)
podmanTest.Setup()
- // Cleanup the CNI networks used by the tests
+ // Clean up the CNI networks used by the tests
os.RemoveAll("/var/lib/cni/networks/podman")
})
diff --git a/test/e2e/create_staticmac_test.go b/test/e2e/create_staticmac_test.go
index f02d9c88b..32deb04a8 100644
--- a/test/e2e/create_staticmac_test.go
+++ b/test/e2e/create_staticmac_test.go
@@ -25,7 +25,7 @@ var _ = Describe("Podman run with --mac-address flag", func() {
}
podmanTest = PodmanTestCreate(tempdir)
podmanTest.Setup()
- // Cleanup the CNI networks used by the tests
+ // Clean up the CNI networks used by the tests
os.RemoveAll("/var/lib/cni/networks/podman")
})
diff --git a/test/e2e/events_test.go b/test/e2e/events_test.go
index 725118ab0..528fa143d 100644
--- a/test/e2e/events_test.go
+++ b/test/e2e/events_test.go
@@ -216,4 +216,25 @@ var _ = Describe("Podman events", func() {
Expect(result.OutputToString()).To(ContainSubstring("create"))
})
+ It("podman events health_status generated", func() {
+ session := podmanTest.Podman([]string{"run", "--name", "test-hc", "-dt", "--health-cmd", "echo working", "busybox"})
+ session.WaitWithDefaultTimeout()
+ Expect(session).Should(Exit(0))
+
+ for i := 0; i < 5; i++ {
+ hc := podmanTest.Podman([]string{"healthcheck", "run", "test-hc"})
+ hc.WaitWithDefaultTimeout()
+ exitCode := hc.ExitCode()
+ if exitCode == 0 || i == 4 {
+ break
+ }
+ time.Sleep(1 * time.Second)
+ }
+
+ result := podmanTest.Podman([]string{"events", "--stream=false", "--filter", "event=health_status"})
+ result.WaitWithDefaultTimeout()
+ Expect(result).Should(Exit(0))
+ Expect(len(result.OutputToStringArray())).To(BeNumerically(">=", 1), "Number of health_status events")
+ })
+
})
diff --git a/test/e2e/image_scp_test.go b/test/e2e/image_scp_test.go
index 2ad3cc75e..53681f05b 100644
--- a/test/e2e/image_scp_test.go
+++ b/test/e2e/image_scp_test.go
@@ -22,12 +22,10 @@ var _ = Describe("podman image scp", func() {
)
BeforeEach(func() {
-
ConfPath.Value, ConfPath.IsSet = os.LookupEnv("CONTAINERS_CONF")
conf, err := ioutil.TempFile("", "containersconf")
- if err != nil {
- panic(err)
- }
+ Expect(err).ToNot(HaveOccurred())
+
os.Setenv("CONTAINERS_CONF", conf.Name())
tempdir, err = CreateTempDirInTempDir()
if err != nil {
@@ -57,7 +55,7 @@ var _ = Describe("podman image scp", func() {
}
scp := podmanTest.Podman([]string{"image", "scp", "FOOBAR"})
scp.WaitWithDefaultTimeout()
- Expect(scp).To(ExitWithError())
+ Expect(scp).Should(ExitWithError())
})
It("podman image scp with proper connection", func() {
@@ -67,27 +65,28 @@ var _ = Describe("podman image scp", func() {
cmd := []string{"system", "connection", "add",
"--default",
"QA",
- "ssh://root@server.fubar.com:2222/run/podman/podman.sock",
+ "ssh://root@podman.test:2222/run/podman/podman.sock",
}
session := podmanTest.Podman(cmd)
session.WaitWithDefaultTimeout()
- Expect(session).To(Exit(0))
+ Expect(session).Should(Exit(0))
cfg, err := config.ReadCustomConfig()
Expect(err).ShouldNot(HaveOccurred())
- Expect(cfg.Engine).To(HaveField("ActiveService", "QA"))
+ Expect(cfg.Engine).Should(HaveField("ActiveService", "QA"))
Expect(cfg.Engine.ServiceDestinations).To(HaveKeyWithValue("QA",
config.Destination{
- URI: "ssh://root@server.fubar.com:2222/run/podman/podman.sock",
+ URI: "ssh://root@podman.test:2222/run/podman/podman.sock",
},
))
scp := podmanTest.Podman([]string{"image", "scp", ALPINE, "QA::"})
- scp.Wait(45)
+ scp.WaitWithDefaultTimeout()
// exit with error because we cannot make an actual ssh connection
// This tests that the input we are given is validated and prepared correctly
- // The error given should either be a missing image (due to testing suite complications) or a i/o timeout on ssh
- Expect(scp).To(ExitWithError())
+ // The error given should either be a missing image (due to testing suite complications) or a no such host timeout on ssh
+ Expect(scp).Should(ExitWithError())
+ Expect(scp.ErrorToString()).Should(ContainSubstring("no such host"))
})
diff --git a/test/e2e/run_staticip_test.go b/test/e2e/run_staticip_test.go
index af3f98d4b..09fb4e03c 100644
--- a/test/e2e/run_staticip_test.go
+++ b/test/e2e/run_staticip_test.go
@@ -28,7 +28,7 @@ var _ = Describe("Podman run with --ip flag", func() {
}
podmanTest = PodmanTestCreate(tempdir)
podmanTest.Setup()
- // Cleanup the CNI networks used by the tests
+ // Clean up the CNI networks used by the tests
os.RemoveAll("/var/lib/cni/networks/podman")
})
diff --git a/test/e2e/run_volume_test.go b/test/e2e/run_volume_test.go
index f31e62e42..8cc2a68de 100644
--- a/test/e2e/run_volume_test.go
+++ b/test/e2e/run_volume_test.go
@@ -452,6 +452,14 @@ var _ = Describe("Podman run with volumes", func() {
separateVolumeSession.WaitWithDefaultTimeout()
Expect(separateVolumeSession).Should(Exit(0))
Expect(separateVolumeSession.OutputToString()).To(Equal(baselineOutput))
+
+ copySession := podmanTest.Podman([]string{"run", "--rm", "-v", "testvol3:/etc/apk:copy", ALPINE, "stat", "-c", "%h", "/etc/apk/arch"})
+ copySession.WaitWithDefaultTimeout()
+ Expect(copySession).Should(Exit(0))
+
+ noCopySession := podmanTest.Podman([]string{"run", "--rm", "-v", "testvol4:/etc/apk:nocopy", ALPINE, "stat", "-c", "%h", "/etc/apk/arch"})
+ noCopySession.WaitWithDefaultTimeout()
+ Expect(noCopySession).Should(Exit(1))
})
It("podman named volume copyup symlink", func() {
@@ -953,4 +961,32 @@ USER testuser`, fedoraMinimal)
Expect(volMount).Should(Exit(0))
Expect(volMount.OutputToString()).To(Equal("1000:1000"))
})
+
+ It("podman run -v with a relative dir", func() {
+ mountPath := filepath.Join(podmanTest.TempDir, "vol")
+ err = os.Mkdir(mountPath, 0755)
+ Expect(err).ToNot(HaveOccurred())
+ defer func() {
+ err := os.RemoveAll(mountPath)
+ Expect(err).ToNot(HaveOccurred())
+ }()
+
+ f, err := os.CreateTemp(mountPath, "podman")
+ Expect(err).ToNot(HaveOccurred())
+
+ cwd, err := os.Getwd()
+ Expect(err).ToNot(HaveOccurred())
+
+ err = os.Chdir(mountPath)
+ Expect(err).ToNot(HaveOccurred())
+ defer func() {
+ err := os.Chdir(cwd)
+ Expect(err).ToNot(HaveOccurred())
+ }()
+
+ run := podmanTest.Podman([]string{"run", "-it", "--security-opt", "label=disable", "-v", "./:" + dest, ALPINE, "ls", dest})
+ run.WaitWithDefaultTimeout()
+ Expect(run).Should(Exit(0))
+ Expect(run.OutputToString()).Should(ContainSubstring(strings.TrimLeft("/vol/", f.Name())))
+ })
})
diff --git a/test/e2e/system_connection_test.go b/test/e2e/system_connection_test.go
index 2228c23b2..baa31424b 100644
--- a/test/e2e/system_connection_test.go
+++ b/test/e2e/system_connection_test.go
@@ -47,9 +47,7 @@ var _ = Describe("podman system connection", func() {
}
f := CurrentGinkgoTestDescription()
- _, _ = GinkgoWriter.Write(
- []byte(
- fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds())))
+ processTestResult(f)
})
Context("without running API service", func() {
@@ -58,7 +56,7 @@ var _ = Describe("podman system connection", func() {
"--default",
"--identity", "~/.ssh/id_rsa",
"QA",
- "ssh://root@server.fubar.com:2222/run/podman/podman.sock",
+ "ssh://root@podman.test:2222/run/podman/podman.sock",
}
session := podmanTest.Podman(cmd)
session.WaitWithDefaultTimeout()
@@ -67,10 +65,10 @@ var _ = Describe("podman system connection", func() {
cfg, err := config.ReadCustomConfig()
Expect(err).ShouldNot(HaveOccurred())
- Expect(cfg).To(HaveActiveService("QA"))
+ Expect(cfg).Should(HaveActiveService("QA"))
Expect(cfg).Should(VerifyService(
"QA",
- "ssh://root@server.fubar.com:2222/run/podman/podman.sock",
+ "ssh://root@podman.test:2222/run/podman/podman.sock",
"~/.ssh/id_rsa",
))
@@ -82,7 +80,7 @@ var _ = Describe("podman system connection", func() {
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0))
- Expect(config.ReadCustomConfig()).To(HaveActiveService("QE"))
+ Expect(config.ReadCustomConfig()).Should(HaveActiveService("QE"))
})
It("add UDS", func() {
@@ -141,7 +139,7 @@ var _ = Describe("podman system connection", func() {
"--default",
"--identity", "~/.ssh/id_rsa",
"QA",
- "ssh://root@server.fubar.com:2222/run/podman/podman.sock",
+ "ssh://root@podman.test:2222/run/podman/podman.sock",
})
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0))
@@ -155,8 +153,8 @@ var _ = Describe("podman system connection", func() {
cfg, err := config.ReadCustomConfig()
Expect(err).ShouldNot(HaveOccurred())
- Expect(cfg.Engine.ActiveService).To(BeEmpty())
- Expect(cfg.Engine.ServiceDestinations).To(BeEmpty())
+ Expect(cfg.Engine.ActiveService).Should(BeEmpty())
+ Expect(cfg.Engine.ServiceDestinations).Should(BeEmpty())
}
})
@@ -165,7 +163,7 @@ var _ = Describe("podman system connection", func() {
"--default",
"--identity", "~/.ssh/id_rsa",
"QA",
- "ssh://root@server.fubar.com:2222/run/podman/podman.sock",
+ "ssh://root@podman.test:2222/run/podman/podman.sock",
})
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0))
@@ -187,7 +185,7 @@ var _ = Describe("podman system connection", func() {
"--default",
"--identity", "~/.ssh/id_rsa",
name,
- "ssh://root@server.fubar.com:2222/run/podman/podman.sock",
+ "ssh://root@podman.test:2222/run/podman/podman.sock",
}
session := podmanTest.Podman(cmd)
session.WaitWithDefaultTimeout()
@@ -247,7 +245,7 @@ var _ = Describe("podman system connection", func() {
// podman-remote commands will be executed by ginkgo directly.
SkipIfContainerized("sshd is not available when running in a container")
SkipIfRemote("connection heuristic requires both podman and podman-remote binaries")
- SkipIfNotRootless(fmt.Sprintf("FIXME: setup ssh keys when root. uid(%d) euid(%d)", os.Getuid(), os.Geteuid()))
+ SkipIfNotRootless(fmt.Sprintf("FIXME: set up ssh keys when root. uid(%d) euid(%d)", os.Getuid(), os.Geteuid()))
SkipIfSystemdNotRunning("cannot test connection heuristic if systemd is not running")
SkipIfNotActive("sshd", "cannot test connection heuristic if sshd is not running")
})
diff --git a/test/e2e/volume_ls_test.go b/test/e2e/volume_ls_test.go
index 19f87fb8a..dcfb13f4e 100644
--- a/test/e2e/volume_ls_test.go
+++ b/test/e2e/volume_ls_test.go
@@ -152,6 +152,37 @@ var _ = Describe("Podman volume ls", func() {
Expect(lsDangling).Should(Exit(0))
Expect(lsDangling.OutputToString()).To(ContainSubstring(volName1))
})
+
+ It("podman ls volume with --filter name", func() {
+ volName1 := "volume1"
+ session := podmanTest.Podman([]string{"volume", "create", volName1})
+ session.WaitWithDefaultTimeout()
+ Expect(session).Should(Exit(0))
+
+ volName2 := "volume2"
+ session2 := podmanTest.Podman([]string{"volume", "create", volName2})
+ session2.WaitWithDefaultTimeout()
+ Expect(session2).Should(Exit(0))
+
+ session = podmanTest.Podman([]string{"volume", "ls", "--filter", "name=volume1*"})
+ session.WaitWithDefaultTimeout()
+ Expect(session).Should(Exit(0))
+ Expect(session.OutputToStringArray()).To(HaveLen(3))
+ Expect(session.OutputToStringArray()[1]).To(ContainSubstring(volName1))
+ Expect(session.OutputToStringArray()[2]).To(ContainSubstring(volName2))
+
+ session = podmanTest.Podman([]string{"volume", "ls", "--filter", "name=volumex"})
+ session.WaitWithDefaultTimeout()
+ Expect(session).Should(Exit(0))
+ Expect(session.OutputToStringArray()).To(BeEmpty())
+
+ session = podmanTest.Podman([]string{"volume", "ls", "--filter", "name=volume1"})
+ session.WaitWithDefaultTimeout()
+ Expect(session).Should(Exit(0))
+ Expect(session.OutputToStringArray()).To(HaveLen(2))
+ Expect(session.OutputToStringArray()[1]).To(ContainSubstring(volName1))
+ })
+
It("podman ls volume with multiple --filter flag", func() {
session := podmanTest.Podman([]string{"volume", "create", "--label", "foo=bar", "myvol"})
volName := session.OutputToString()
diff --git a/test/e2e/volume_plugin_test.go b/test/e2e/volume_plugin_test.go
index 4700afdb5..b585f8dd8 100644
--- a/test/e2e/volume_plugin_test.go
+++ b/test/e2e/volume_plugin_test.go
@@ -6,6 +6,7 @@ import (
"path/filepath"
. "github.com/containers/podman/v4/test/utils"
+ "github.com/containers/storage/pkg/stringid"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
. "github.com/onsi/gomega/gexec"
@@ -188,4 +189,71 @@ var _ = Describe("Podman volume plugins", func() {
rmAll.WaitWithDefaultTimeout()
Expect(rmAll).Should(Exit(0))
})
+
+ It("podman volume reload", func() {
+ podmanTest.AddImageToRWStore(volumeTest)
+
+ confFile := filepath.Join(podmanTest.TempDir, "containers.conf")
+ err := os.WriteFile(confFile, []byte(`[engine]
+[engine.volume_plugins]
+testvol5 = "/run/docker/plugins/testvol5.sock"`), 0o644)
+ Expect(err).ToNot(HaveOccurred())
+ os.Setenv("CONTAINERS_CONF", confFile)
+
+ pluginStatePath := filepath.Join(podmanTest.TempDir, "volumes")
+ err = os.Mkdir(pluginStatePath, 0755)
+ Expect(err).ToNot(HaveOccurred())
+
+ // Keep this distinct within tests to avoid multiple tests using the same plugin.
+ pluginName := "testvol5"
+ ctrName := "pluginCtr"
+ plugin := podmanTest.Podman([]string{"run", "--name", ctrName, "--security-opt", "label=disable", "-v", "/run/docker/plugins:/run/docker/plugins",
+ "-v", fmt.Sprintf("%v:%v", pluginStatePath, pluginStatePath), "-d", volumeTest, "--sock-name", pluginName, "--path", pluginStatePath})
+ plugin.WaitWithDefaultTimeout()
+ Expect(plugin).Should(Exit(0))
+
+ localvol := "local-" + stringid.GenerateNonCryptoID()
+ // create local volume
+ session := podmanTest.Podman([]string{"volume", "create", localvol})
+ session.WaitWithDefaultTimeout()
+ Expect(session).To(Exit(0))
+
+ vol1 := "vol1-" + stringid.GenerateNonCryptoID()
+ session = podmanTest.Podman([]string{"volume", "create", "--driver", pluginName, vol1})
+ session.WaitWithDefaultTimeout()
+ Expect(session).To(Exit(0))
+
+ // now create volume in plugin without podman
+ vol2 := "vol2-" + stringid.GenerateNonCryptoID()
+ plugin = podmanTest.Podman([]string{"exec", ctrName, "/usr/local/bin/testvol", "--sock-name", pluginName, "create", vol2})
+ plugin.WaitWithDefaultTimeout()
+ Expect(plugin).Should(Exit(0))
+
+ session = podmanTest.Podman([]string{"volume", "ls", "-q"})
+ session.WaitWithDefaultTimeout()
+ Expect(session).To(Exit(0))
+ Expect(session.OutputToStringArray()).To(ContainElements(localvol, vol1))
+ Expect(session.ErrorToString()).To(Equal("")) // make sure no errors are shown
+
+ plugin = podmanTest.Podman([]string{"exec", ctrName, "/usr/local/bin/testvol", "--sock-name", pluginName, "remove", vol1})
+ plugin.WaitWithDefaultTimeout()
+ Expect(plugin).Should(Exit(0))
+
+ // now reload volumes from plugins
+ session = podmanTest.Podman([]string{"volume", "reload"})
+ session.WaitWithDefaultTimeout()
+ Expect(session).To(Exit(0))
+ Expect(string(session.Out.Contents())).To(Equal(fmt.Sprintf(`Added:
+%s
+Removed:
+%s
+`, vol2, vol1)))
+ Expect(session.ErrorToString()).To(Equal("")) // make sure no errors are shown
+
+ session = podmanTest.Podman([]string{"volume", "ls", "-q"})
+ session.WaitWithDefaultTimeout()
+ Expect(session).To(Exit(0))
+ Expect(session.OutputToStringArray()).To(ContainElements(localvol, vol2))
+ Expect(session.ErrorToString()).To(Equal("")) // make no errors are shown
+ })
})
diff --git a/test/framework/framework.go b/test/framework/framework.go
index 57c6bda2a..26e8bf21c 100644
--- a/test/framework/framework.go
+++ b/test/framework/framework.go
@@ -37,7 +37,7 @@ func NilFunc(f *TestFramework) error {
func (t *TestFramework) Setup() {
// Global initialization for the whole framework goes in here
- // Setup the actual test suite
+ // Set up the actual test suite
gomega.Expect(t.setup(t)).To(gomega.Succeed())
}
diff --git a/test/system/030-run.bats b/test/system/030-run.bats
index 117d791d6..56cf4f266 100644
--- a/test/system/030-run.bats
+++ b/test/system/030-run.bats
@@ -376,17 +376,7 @@ json-file | f
while read driver do_check; do
msg=$(random_string 15)
run_podman run --name myctr --log-driver $driver $IMAGE echo $msg
-
- # Simple output check
- # Special case: 'json-file' emits a warning, the rest do not
- # ...but with podman-remote the warning is on the server only
- if [[ $do_check == 'f' ]] && ! is_remote; then # 'f' for 'fallback'
- is "${lines[0]}" ".* level=error msg=\"json-file logging specified but not supported. Choosing k8s-file logging instead\"" \
- "Fallback warning emitted"
- is "${lines[1]}" "$msg" "basic output sanity check (driver=$driver)"
- else
- is "$output" "$msg" "basic output sanity check (driver=$driver)"
- fi
+ is "$output" "$msg" "basic output sanity check (driver=$driver)"
# Simply confirm that podman preserved our argument as-is
run_podman inspect --format '{{.HostConfig.LogConfig.Type}}' myctr
diff --git a/test/system/150-login.bats b/test/system/150-login.bats
index 33b8438bf..dc902d5fe 100644
--- a/test/system/150-login.bats
+++ b/test/system/150-login.bats
@@ -314,7 +314,7 @@ function _test_skopeo_credential_sharing() {
fi
# Make sure socket is closed
- if { exec 3<> /dev/tcp/127.0.0.1/${PODMAN_LOGIN_REGISTRY_PORT}; } &>/dev/null; then
+ if ! port_is_free $PODMAN_LOGIN_REGISTRY_PORT; then
die "Socket still seems open"
fi
}
diff --git a/test/system/160-volumes.bats b/test/system/160-volumes.bats
index 797883ec6..da60112a0 100644
--- a/test/system/160-volumes.bats
+++ b/test/system/160-volumes.bats
@@ -64,6 +64,29 @@ function teardown() {
}
+# Filter volumes by name
+@test "podman volume filter --name" {
+ suffix=$(random_string)
+ prefix="volume"
+
+ for i in 1 2; do
+ myvolume=${prefix}_${i}_${suffix}
+ run_podman volume create $myvolume
+ is "$output" "$myvolume" "output from volume create $i"
+ done
+
+ run_podman volume ls --filter name=${prefix}_1.+ --format "{{.Name}}"
+ is "$output" "${prefix}_1_${suffix}" "--filter name=${prefix}_1.+ shows only one volume"
+
+ # The _1* is intentional as asterisk has different meaning in glob and regexp. Make sure this is regexp
+ run_podman volume ls --filter name=${prefix}_1* --format "{{.Name}}"
+ is "$output" "${prefix}_1_${suffix}.*${prefix}_2_${suffix}.*" "--filter name=${prefix}_1* shows ${prefix}_1_${suffix} and ${prefix}_2_${suffix}"
+
+ for i in 1 2; do
+ run_podman volume rm ${prefix}_${i}_${suffix}
+ done
+}
+
# Named volumes
@test "podman volume create / run" {
myvolume=myvol$(random_string)
diff --git a/test/system/200-pod.bats b/test/system/200-pod.bats
index 404ad67ec..92d3966be 100644
--- a/test/system/200-pod.bats
+++ b/test/system/200-pod.bats
@@ -472,4 +472,45 @@ spec:
run_podman pod rm $name-pod
}
+@test "pod resource limits" {
+ skip_if_remote "resource limits only implemented on non-remote"
+ if is_rootless; then
+ skip "only meaningful for rootful"
+ fi
+
+ local name1="resources1"
+ run_podman --cgroup-manager=systemd pod create --name=$name1 --cpus=5
+ run_podman --cgroup-manager=systemd pod start $name1
+ run_podman pod inspect --format '{{.CgroupPath}}' $name1
+ local path1="$output"
+ local actual1=$(< /sys/fs/cgroup/$path1/cpu.max)
+ is "$actual1" "500000 100000" "resource limits set properly"
+ run_podman pod --cgroup-manager=systemd rm -f $name1
+
+ local name2="resources2"
+ run_podman --cgroup-manager=cgroupfs pod create --cpus=5 --name=$name2
+ run_podman --cgroup-manager=cgroupfs pod start $name2
+ run_podman pod inspect --format '{{.CgroupPath}}' $name2
+ local path2="$output"
+ local actual2=$(< /sys/fs/cgroup/$path2/cpu.max)
+ is "$actual2" "500000 100000" "resource limits set properly"
+ run_podman --cgroup-manager=cgroupfs pod rm $name2
+}
+
+@test "podman pod ps doesn't race with pod rm" {
+ # create a few pods
+ for i in {0..10}; do
+ run_podman pod create
+ done
+
+ # and delete them
+ $PODMAN pod rm -a &
+
+ # pod ps should not fail while pods are deleted
+ run_podman pod ps -q
+
+ # wait for pod rm -a
+ wait
+}
+
# vim: filetype=sh
diff --git a/test/system/500-networking.bats b/test/system/500-networking.bats
index 2ad53620d..0d724985e 100644
--- a/test/system/500-networking.bats
+++ b/test/system/500-networking.bats
@@ -359,7 +359,7 @@ load helpers
run curl -s $SERVER/index.txt
is "$output" "$random_1" "curl 127.0.0.1:/index.txt"
- # cleanup the container
+ # clean up the container
run_podman rm -t 0 -f $cid
# test that we cannot remove the default network
@@ -549,7 +549,7 @@ load helpers
run curl --max-time 3 -s $SERVER/index.txt
is "$output" "$random_1" "curl 127.0.0.1:/index.txt should still work"
- # cleanup
+ # clean up
run_podman rm -t 0 -f $cid $background_cid
run_podman network rm -t 0 -f $netname $netname2
}
@@ -622,7 +622,7 @@ load helpers
run_podman rm -t 0 -f $cid
done
- # Cleanup network
+ # Clean up network
run_podman network rm -t 0 -f $netname
}
@@ -676,12 +676,12 @@ EOF
@test "podman run port forward range" {
for netmode in bridge slirp4netns:port_handler=slirp4netns slirp4netns:port_handler=rootlesskit; do
- local port=$(random_free_port)
- local end_port=$(( $port + 2 ))
- local range="$port-$end_port:$port-$end_port"
+ local range=$(random_free_port_range 3)
+ local port="${test%-*}"
+ local end_port="${test#-*}"
local random=$(random_string)
- run_podman run --network $netmode -p "$range" -d $IMAGE sleep inf
+ run_podman run --network $netmode -p "$range:$range" -d $IMAGE sleep inf
cid="$output"
for port in $(seq $port $end_port); do
run_podman exec -d $cid nc -l -p $port -e /bin/cat
diff --git a/test/system/helpers.bash b/test/system/helpers.bash
index 74b5ddc4b..273e8d2f5 100644
--- a/test/system/helpers.bash
+++ b/test/system/helpers.bash
@@ -284,7 +284,7 @@ function random_free_port() {
local port
for port in $(shuf -i ${range}); do
- if ! { exec {unused_fd}<> /dev/tcp/127.0.0.1/$port; } &>/dev/null; then
+ if port_is_free $port; then
echo $port
return
fi
@@ -293,6 +293,35 @@ function random_free_port() {
die "Could not find open port in range $range"
}
+function random_free_port_range() {
+ local size=${1?Usage: random_free_port_range SIZE (as in, number of ports)}
+
+ local maxtries=10
+ while [[ $maxtries -gt 0 ]]; do
+ local firstport=$(random_free_port)
+ local all_ports_free=1
+ for i in $(seq 2 $size); do
+ if ! port_is_free $((firstport + $i)); then
+ all_ports_free=
+ break
+ fi
+ done
+ if [[ -n "$all_ports_free" ]]; then
+ echo "$firstport-$((firstport + $size - 1))"
+ return
+ fi
+
+ maxtries=$((maxtries - 1))
+ done
+
+ die "Could not find free port range with size $size"
+}
+
+function port_is_free() {
+ local port=${1?Usage: port_is_free PORT}
+ ! { exec {unused_fd}<> /dev/tcp/127.0.0.1/$port; } &>/dev/null
+}
+
###################
# wait_for_port # Returns once port is available on host
###################
diff --git a/test/testvol/Containerfile b/test/testvol/Containerfile
new file mode 100644
index 000000000..32448f5a9
--- /dev/null
+++ b/test/testvol/Containerfile
@@ -0,0 +1,9 @@
+FROM docker.io/library/golang:1.18-alpine AS build-img
+COPY ./ /go/src/github.com/containers/podman/
+WORKDIR /go/src/github.com/containers/podman
+RUN GO111MODULE=off go build -o /testvol ./test/testvol
+
+FROM alpine
+COPY --from=build-img /testvol /usr/local/bin
+WORKDIR /
+ENTRYPOINT ["/usr/local/bin/testvol", "serve"]
diff --git a/test/testvol/create.go b/test/testvol/create.go
new file mode 100644
index 000000000..d29300f0b
--- /dev/null
+++ b/test/testvol/create.go
@@ -0,0 +1,26 @@
+package main
+
+import (
+ pluginapi "github.com/docker/go-plugins-helpers/volume"
+ "github.com/spf13/cobra"
+)
+
+var createCmd = &cobra.Command{
+ Use: "create NAME",
+ Short: "create a volume",
+ Long: `Create a volume in the volume plugin listening on --sock-name`,
+ Args: cobra.ExactArgs(1),
+ RunE: func(cmd *cobra.Command, args []string) error {
+ return createVol(config.sockName, args[0])
+ },
+}
+
+func createVol(sockName, volName string) error {
+ plugin, err := getPlugin(sockName)
+ if err != nil {
+ return err
+ }
+ createReq := new(pluginapi.CreateRequest)
+ createReq.Name = volName
+ return plugin.CreateVolume(createReq)
+}
diff --git a/test/testvol/list.go b/test/testvol/list.go
new file mode 100644
index 000000000..fea615a70
--- /dev/null
+++ b/test/testvol/list.go
@@ -0,0 +1,32 @@
+package main
+
+import (
+ "fmt"
+
+ "github.com/spf13/cobra"
+)
+
+var listCmd = &cobra.Command{
+ Use: "list",
+ Short: "list all volumes",
+ Long: `List all volumes from the volume plugin listening on --sock-name`,
+ Args: cobra.NoArgs,
+ RunE: func(cmd *cobra.Command, args []string) error {
+ return listVol(config.sockName)
+ },
+}
+
+func listVol(sockName string) error {
+ plugin, err := getPlugin(sockName)
+ if err != nil {
+ return err
+ }
+ vols, err := plugin.ListVolumes()
+ if err != nil {
+ return err
+ }
+ for _, vol := range vols {
+ fmt.Println(vol.Name)
+ }
+ return nil
+}
diff --git a/test/testvol/main.go b/test/testvol/main.go
index 30ab365b3..99c6fb694 100644
--- a/test/testvol/main.go
+++ b/test/testvol/main.go
@@ -14,13 +14,20 @@ import (
)
var rootCmd = &cobra.Command{
- Use: "testvol",
- Short: "testvol - volume plugin for Podman",
+ Use: "testvol",
+ Short: "testvol - volume plugin for Podman testing",
+ PersistentPreRunE: before,
+ SilenceUsage: true,
+}
+
+var serveCmd = &cobra.Command{
+ Use: "serve",
+ Short: "serve the volume plugin on the unix socket",
Long: `Creates simple directory volumes using the Volume Plugin API for testing volume plugin functionality`,
+ Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
return startServer(config.sockName)
},
- PersistentPreRunE: before,
}
// Configuration for the volume plugin
@@ -37,9 +44,12 @@ var config = cliConfig{
}
func init() {
- rootCmd.Flags().StringVar(&config.sockName, "sock-name", config.sockName, "Name of unix socket for plugin")
- rootCmd.Flags().StringVar(&config.path, "path", "", "Path to initialize state and mount points")
+ rootCmd.PersistentFlags().StringVar(&config.sockName, "sock-name", config.sockName, "Name of unix socket for plugin")
rootCmd.PersistentFlags().StringVar(&config.logLevel, "log-level", config.logLevel, "Log messages including and over the specified level: debug, info, warn, error, fatal, panic")
+
+ serveCmd.Flags().StringVar(&config.path, "path", "", "Path to initialize state and mount points")
+
+ rootCmd.AddCommand(serveCmd, createCmd, removeCmd, listCmd)
}
func before(cmd *cobra.Command, args []string) error {
@@ -59,11 +69,8 @@ func before(cmd *cobra.Command, args []string) error {
func main() {
if err := rootCmd.Execute(); err != nil {
- logrus.Errorf("Running volume plugin: %v", err)
os.Exit(1)
}
-
- os.Exit(0)
}
// startServer runs the HTTP server and responds to requests
diff --git a/test/testvol/remove.go b/test/testvol/remove.go
new file mode 100644
index 000000000..2839b0b50
--- /dev/null
+++ b/test/testvol/remove.go
@@ -0,0 +1,26 @@
+package main
+
+import (
+ pluginapi "github.com/docker/go-plugins-helpers/volume"
+ "github.com/spf13/cobra"
+)
+
+var removeCmd = &cobra.Command{
+ Use: "remove NAME",
+ Short: "remove a volume",
+ Long: `Remove a volume in the volume plugin listening on --sock-name`,
+ Args: cobra.ExactArgs(1),
+ RunE: func(cmd *cobra.Command, args []string) error {
+ return removeVol(config.sockName, args[0])
+ },
+}
+
+func removeVol(sockName, volName string) error {
+ plugin, err := getPlugin(sockName)
+ if err != nil {
+ return err
+ }
+ removeReq := new(pluginapi.RemoveRequest)
+ removeReq.Name = volName
+ return plugin.RemoveVolume(removeReq)
+}
diff --git a/test/testvol/util.go b/test/testvol/util.go
new file mode 100644
index 000000000..7a0aeba86
--- /dev/null
+++ b/test/testvol/util.go
@@ -0,0 +1,29 @@
+package main
+
+import (
+ "path/filepath"
+ "strings"
+
+ "github.com/containers/podman/v4/libpod/plugin"
+)
+
+const pluginSockDir = "/run/docker/plugins"
+
+func getSocketPath(pathOrName string) string {
+ if filepath.IsAbs(pathOrName) {
+ return pathOrName
+ }
+
+ // only a name join it with the default path
+ return filepath.Join(pluginSockDir, pathOrName+".sock")
+}
+
+func getPluginName(pathOrName string) string {
+ return strings.TrimSuffix(filepath.Base(pathOrName), ".sock")
+}
+
+func getPlugin(sockNameOrPath string) (*plugin.VolumePlugin, error) {
+ path := getSocketPath(sockNameOrPath)
+ name := getPluginName(sockNameOrPath)
+ return plugin.GetVolumePlugin(name, path)
+}
diff --git a/troubleshooting.md b/troubleshooting.md
index 4be925f71..05685c906 100644
--- a/troubleshooting.md
+++ b/troubleshooting.md
@@ -321,7 +321,7 @@ under `/var/lib/containers/storage`.
# restorecon -R -v /srv/containers
```
-The semanage command above tells SELinux to setup the default labeling of
+The semanage command above tells SELinux to set up the default labeling of
`/srv/containers` to match `/var/lib/containers`. The `restorecon` command
tells SELinux to apply the labels to the actual content.
@@ -387,7 +387,7 @@ error creating build container: Error committing the finished image: error addin
#### Solution
Choose one of the following:
- * Setup containers/storage in a different directory, not on an NFS share.
+ * Set up containers/storage in a different directory, not on an NFS share.
* Create a directory on a local file system.
* Edit `~/.config/containers/containers.conf` and point the `volume_path` option to that local directory. (Copy `/usr/share/containers/containers.conf` if `~/.config/containers/containers.conf` does not exist)
* Otherwise just run Podman as root, via `sudo podman`
diff --git a/vendor/github.com/containers/common/pkg/cgroups/blkio.go b/vendor/github.com/containers/common/pkg/cgroups/blkio.go
index 0fb61c757..a72a641c8 100644
--- a/vendor/github.com/containers/common/pkg/cgroups/blkio.go
+++ b/vendor/github.com/containers/common/pkg/cgroups/blkio.go
@@ -1,3 +1,6 @@
+//go:build !linux
+// +build !linux
+
package cgroups
import (
diff --git a/vendor/github.com/containers/common/pkg/cgroups/blkio_linux.go b/vendor/github.com/containers/common/pkg/cgroups/blkio_linux.go
new file mode 100644
index 000000000..98b8ae541
--- /dev/null
+++ b/vendor/github.com/containers/common/pkg/cgroups/blkio_linux.go
@@ -0,0 +1,161 @@
+//go:build linux
+// +build linux
+
+package cgroups
+
+import (
+ "bufio"
+ "os"
+ "path/filepath"
+ "strconv"
+ "strings"
+
+ "github.com/opencontainers/runc/libcontainer/cgroups"
+ "github.com/opencontainers/runc/libcontainer/cgroups/fs"
+ "github.com/opencontainers/runc/libcontainer/cgroups/fs2"
+ "github.com/opencontainers/runc/libcontainer/configs"
+ "github.com/pkg/errors"
+)
+
+type linuxBlkioHandler struct {
+ Blkio fs.BlkioGroup
+}
+
+func getBlkioHandler() *linuxBlkioHandler {
+ return &linuxBlkioHandler{}
+}
+
+// Apply set the specified constraints
+func (c *linuxBlkioHandler) Apply(ctr *CgroupControl, res *configs.Resources) error {
+ if ctr.cgroup2 {
+ man, err := fs2.NewManager(ctr.config, filepath.Join(cgroupRoot, ctr.config.Path))
+ if err != nil {
+ return err
+ }
+ return man.Set(res)
+
+ }
+ path := filepath.Join(cgroupRoot, Blkio, ctr.config.Path)
+ return c.Blkio.Set(path, res)
+}
+
+// Create the cgroup
+func (c *linuxBlkioHandler) Create(ctr *CgroupControl) (bool, error) {
+ if ctr.cgroup2 {
+ return false, nil
+ }
+ return ctr.createCgroupDirectory(Blkio)
+}
+
+// Destroy the cgroup
+func (c *linuxBlkioHandler) Destroy(ctr *CgroupControl) error {
+ return rmDirRecursively(ctr.getCgroupv1Path(Blkio))
+}
+
+// Stat fills a metrics structure with usage stats for the controller
+func (c *linuxBlkioHandler) Stat(ctr *CgroupControl, m *cgroups.Stats) error {
+ var ioServiceBytesRecursive []cgroups.BlkioStatEntry
+
+ if ctr.cgroup2 {
+ // more details on the io.stat file format:X https://facebookmicrosites.github.io/cgroup2/docs/io-controller.html
+ values, err := readCgroup2MapFile(ctr, "io.stat")
+ if err != nil {
+ return err
+ }
+ for k, v := range values {
+ d := strings.Split(k, ":")
+ if len(d) != 2 {
+ continue
+ }
+ minor, err := strconv.ParseUint(d[0], 10, 0)
+ if err != nil {
+ return err
+ }
+ major, err := strconv.ParseUint(d[1], 10, 0)
+ if err != nil {
+ return err
+ }
+
+ for _, item := range v {
+ d := strings.Split(item, "=")
+ if len(d) != 2 {
+ continue
+ }
+ op := d[0]
+
+ // Accommodate the cgroup v1 naming
+ switch op {
+ case "rbytes":
+ op = "read"
+ case "wbytes":
+ op = "write"
+ }
+
+ value, err := strconv.ParseUint(d[1], 10, 0)
+ if err != nil {
+ return err
+ }
+
+ entry := cgroups.BlkioStatEntry{
+ Op: op,
+ Major: major,
+ Minor: minor,
+ Value: value,
+ }
+ ioServiceBytesRecursive = append(ioServiceBytesRecursive, entry)
+ }
+ }
+ } else {
+ BlkioRoot := ctr.getCgroupv1Path(Blkio)
+
+ p := filepath.Join(BlkioRoot, "blkio.throttle.io_service_bytes_recursive")
+ f, err := os.Open(p)
+ if err != nil {
+ if os.IsNotExist(err) {
+ return nil
+ }
+ return errors.Wrapf(err, "open %s", p)
+ }
+ defer f.Close()
+
+ scanner := bufio.NewScanner(f)
+ for scanner.Scan() {
+ line := scanner.Text()
+ parts := strings.Fields(line)
+ if len(parts) < 3 {
+ continue
+ }
+ d := strings.Split(parts[0], ":")
+ if len(d) != 2 {
+ continue
+ }
+ minor, err := strconv.ParseUint(d[0], 10, 0)
+ if err != nil {
+ return err
+ }
+ major, err := strconv.ParseUint(d[1], 10, 0)
+ if err != nil {
+ return err
+ }
+
+ op := parts[1]
+
+ value, err := strconv.ParseUint(parts[2], 10, 0)
+ if err != nil {
+ return err
+ }
+ entry := cgroups.BlkioStatEntry{
+ Op: op,
+ Major: major,
+ Minor: minor,
+ Value: value,
+ }
+ ioServiceBytesRecursive = append(ioServiceBytesRecursive, entry)
+ }
+ if err := scanner.Err(); err != nil {
+ return errors.Wrapf(err, "parse %s", p)
+ }
+ }
+ m.BlkioStats.IoServiceBytesRecursive = ioServiceBytesRecursive
+ return nil
+}
diff --git a/vendor/github.com/containers/common/pkg/cgroups/cgroups.go b/vendor/github.com/containers/common/pkg/cgroups/cgroups.go
index 57997d652..eb903d3c3 100644
--- a/vendor/github.com/containers/common/pkg/cgroups/cgroups.go
+++ b/vendor/github.com/containers/common/pkg/cgroups/cgroups.go
@@ -1,8 +1,10 @@
+//go:build !linux
+// +build !linux
+
package cgroups
import (
"bufio"
- "bytes"
"context"
"fmt"
"io/ioutil"
@@ -248,47 +250,6 @@ func (c *CgroupControl) getCgroupv1Path(name string) string {
return filepath.Join(cgroupRoot, name, c.path)
}
-// createCgroupv2Path creates the cgroupv2 path and enables all the available controllers
-func createCgroupv2Path(path string) (deferredError error) {
- if !strings.HasPrefix(path, cgroupRoot+"/") {
- return fmt.Errorf("invalid cgroup path %s", path)
- }
- content, err := ioutil.ReadFile(cgroupRoot + "/cgroup.controllers")
- if err != nil {
- return err
- }
- ctrs := bytes.Fields(content)
- res := append([]byte("+"), bytes.Join(ctrs, []byte(" +"))...)
-
- current := "/sys/fs"
- elements := strings.Split(path, "/")
- for i, e := range elements[3:] {
- current = filepath.Join(current, e)
- if i > 0 {
- if err := os.Mkdir(current, 0o755); err != nil {
- if !os.IsExist(err) {
- return err
- }
- } else {
- // If the directory was created, be sure it is not left around on errors.
- defer func() {
- if deferredError != nil {
- os.Remove(current)
- }
- }()
- }
- }
- // We enable the controllers for all the path components except the last one. It is not allowed to add
- // PIDs if there are already enabled controllers.
- if i < len(elements[3:])-1 {
- if err := ioutil.WriteFile(filepath.Join(current, "cgroup.subtree_control"), res, 0o755); err != nil {
- return err
- }
- }
- }
- return nil
-}
-
// initialize initializes the specified hierarchy
func (c *CgroupControl) initialize() (err error) {
createdSoFar := map[string]controllerHandler{}
@@ -332,23 +293,6 @@ func (c *CgroupControl) initialize() (err error) {
return nil
}
-func (c *CgroupControl) createCgroupDirectory(controller string) (bool, error) {
- cPath := c.getCgroupv1Path(controller)
- _, err := os.Stat(cPath)
- if err == nil {
- return false, nil
- }
-
- if !os.IsNotExist(err) {
- return false, err
- }
-
- if err := os.MkdirAll(cPath, 0o755); err != nil {
- return false, errors.Wrapf(err, "error creating cgroup for %s", controller)
- }
- return true, nil
-}
-
func readFileAsUint64(path string) (uint64, error) {
data, err := ioutil.ReadFile(path)
if err != nil {
diff --git a/vendor/github.com/containers/common/pkg/cgroups/cgroups_linux.go b/vendor/github.com/containers/common/pkg/cgroups/cgroups_linux.go
new file mode 100644
index 000000000..4b72014bf
--- /dev/null
+++ b/vendor/github.com/containers/common/pkg/cgroups/cgroups_linux.go
@@ -0,0 +1,575 @@
+//go:build linux
+// +build linux
+
+package cgroups
+
+import (
+ "bufio"
+ "context"
+ "fmt"
+ "io/ioutil"
+ "math"
+ "os"
+ "path/filepath"
+ "strconv"
+ "strings"
+
+ "github.com/containers/storage/pkg/unshare"
+ systemdDbus "github.com/coreos/go-systemd/v22/dbus"
+ "github.com/godbus/dbus/v5"
+ "github.com/opencontainers/runc/libcontainer/cgroups"
+ "github.com/opencontainers/runc/libcontainer/cgroups/fs2"
+ "github.com/opencontainers/runc/libcontainer/configs"
+ "github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
+)
+
+var (
+ // ErrCgroupDeleted means the cgroup was deleted
+ ErrCgroupDeleted = errors.New("cgroup deleted")
+ // ErrCgroupV1Rootless means the cgroup v1 were attempted to be used in rootless environment
+ ErrCgroupV1Rootless = errors.New("no support for CGroups V1 in rootless environments")
+ ErrStatCgroup = errors.New("no cgroup available for gathering user statistics")
+)
+
+// CgroupControl controls a cgroup hierarchy
+type CgroupControl struct {
+ cgroup2 bool
+ config *configs.Cgroup
+ systemd bool
+ // List of additional cgroup subsystems joined that
+ // do not have a custom handler.
+ additionalControllers []controller
+}
+
+type controller struct {
+ name string
+ symlink bool
+}
+
+type controllerHandler interface {
+ Create(*CgroupControl) (bool, error)
+ Apply(*CgroupControl, *configs.Resources) error
+ Destroy(*CgroupControl) error
+ Stat(*CgroupControl, *cgroups.Stats) error
+}
+
+const (
+ cgroupRoot = "/sys/fs/cgroup"
+ // CPU is the cpu controller
+ CPU = "cpu"
+ // CPUAcct is the cpuacct controller
+ CPUAcct = "cpuacct"
+ // CPUset is the cpuset controller
+ CPUset = "cpuset"
+ // Memory is the memory controller
+ Memory = "memory"
+ // Pids is the pids controller
+ Pids = "pids"
+ // Blkio is the blkio controller
+ Blkio = "blkio"
+)
+
+var handlers map[string]controllerHandler
+
+func init() {
+ handlers = make(map[string]controllerHandler)
+ handlers[CPU] = getCPUHandler()
+ handlers[CPUset] = getCpusetHandler()
+ handlers[Memory] = getMemoryHandler()
+ handlers[Pids] = getPidsHandler()
+ handlers[Blkio] = getBlkioHandler()
+}
+
+// getAvailableControllers get the available controllers
+func getAvailableControllers(exclude map[string]controllerHandler, cgroup2 bool) ([]controller, error) {
+ if cgroup2 {
+ controllers := []controller{}
+ controllersFile := cgroupRoot + "/cgroup.controllers"
+ // rootless cgroupv2: check available controllers for current user, systemd or servicescope will inherit
+ if unshare.IsRootless() {
+ userSlice, err := getCgroupPathForCurrentProcess()
+ if err != nil {
+ return controllers, err
+ }
+ // userSlice already contains '/' so not adding here
+ basePath := cgroupRoot + userSlice
+ controllersFile = fmt.Sprintf("%s/cgroup.controllers", basePath)
+ }
+ controllersFileBytes, err := ioutil.ReadFile(controllersFile)
+ if err != nil {
+ return nil, errors.Wrapf(err, "failed while reading controllers for cgroup v2 from %q", controllersFile)
+ }
+ for _, controllerName := range strings.Fields(string(controllersFileBytes)) {
+ c := controller{
+ name: controllerName,
+ symlink: false,
+ }
+ controllers = append(controllers, c)
+ }
+ return controllers, nil
+ }
+
+ subsystems, _ := cgroupV1GetAllSubsystems()
+ controllers := []controller{}
+ // cgroupv1 and rootless: No subsystem is available: delegation is unsafe.
+ if unshare.IsRootless() {
+ return controllers, nil
+ }
+
+ for _, name := range subsystems {
+ if _, found := exclude[name]; found {
+ continue
+ }
+ fileInfo, err := os.Stat(cgroupRoot + "/" + name)
+ if err != nil {
+ continue
+ }
+ c := controller{
+ name: name,
+ symlink: !fileInfo.IsDir(),
+ }
+ controllers = append(controllers, c)
+ }
+
+ return controllers, nil
+}
+
+// GetAvailableControllers get string:bool map of all the available controllers
+func GetAvailableControllers(exclude map[string]controllerHandler, cgroup2 bool) ([]string, error) {
+ availableControllers, err := getAvailableControllers(exclude, cgroup2)
+ if err != nil {
+ return nil, err
+ }
+ controllerList := []string{}
+ for _, controller := range availableControllers {
+ controllerList = append(controllerList, controller.name)
+ }
+
+ return controllerList, nil
+}
+
+func cgroupV1GetAllSubsystems() ([]string, error) {
+ f, err := os.Open("/proc/cgroups")
+ if err != nil {
+ return nil, err
+ }
+ defer f.Close()
+
+ subsystems := []string{}
+
+ s := bufio.NewScanner(f)
+ for s.Scan() {
+ text := s.Text()
+ if text[0] != '#' {
+ parts := strings.Fields(text)
+ if len(parts) >= 4 && parts[3] != "0" {
+ subsystems = append(subsystems, parts[0])
+ }
+ }
+ }
+ if err := s.Err(); err != nil {
+ return nil, err
+ }
+ return subsystems, nil
+}
+
+func getCgroupPathForCurrentProcess() (string, error) {
+ path := fmt.Sprintf("/proc/%d/cgroup", os.Getpid())
+ f, err := os.Open(path)
+ if err != nil {
+ return "", err
+ }
+ defer f.Close()
+
+ cgroupPath := ""
+ s := bufio.NewScanner(f)
+ for s.Scan() {
+ text := s.Text()
+ procEntries := strings.SplitN(text, "::", 2)
+ // set process cgroupPath only if entry is valid
+ if len(procEntries) > 1 {
+ cgroupPath = procEntries[1]
+ }
+ }
+ if err := s.Err(); err != nil {
+ return cgroupPath, err
+ }
+ return cgroupPath, nil
+}
+
+// getCgroupv1Path is a helper function to get the cgroup v1 path
+func (c *CgroupControl) getCgroupv1Path(name string) string {
+ return filepath.Join(cgroupRoot, name, c.config.Path)
+}
+
+// initialize initializes the specified hierarchy
+func (c *CgroupControl) initialize() (err error) {
+ createdSoFar := map[string]controllerHandler{}
+ defer func() {
+ if err != nil {
+ for name, ctr := range createdSoFar {
+ if err := ctr.Destroy(c); err != nil {
+ logrus.Warningf("error cleaning up controller %s for %s", name, c.config.Path)
+ }
+ }
+ }
+ }()
+ if c.cgroup2 {
+ if err := createCgroupv2Path(filepath.Join(cgroupRoot, c.config.Path)); err != nil {
+ return errors.Wrapf(err, "error creating cgroup path %s", c.config.Path)
+ }
+ }
+ for name, handler := range handlers {
+ created, err := handler.Create(c)
+ if err != nil {
+ return err
+ }
+ if created {
+ createdSoFar[name] = handler
+ }
+ }
+
+ if !c.cgroup2 {
+ // We won't need to do this for cgroup v2
+ for _, ctr := range c.additionalControllers {
+ if ctr.symlink {
+ continue
+ }
+ path := c.getCgroupv1Path(ctr.name)
+ if err := os.MkdirAll(path, 0o755); err != nil {
+ return errors.Wrapf(err, "error creating cgroup path for %s", ctr.name)
+ }
+ }
+ }
+
+ return nil
+}
+
+func readFileAsUint64(path string) (uint64, error) {
+ data, err := ioutil.ReadFile(path)
+ if err != nil {
+ return 0, err
+ }
+ v := cleanString(string(data))
+ if v == "max" {
+ return math.MaxUint64, nil
+ }
+ ret, err := strconv.ParseUint(v, 10, 64)
+ if err != nil {
+ return ret, errors.Wrapf(err, "parse %s from %s", v, path)
+ }
+ return ret, nil
+}
+
+func readFileByKeyAsUint64(path, key string) (uint64, error) {
+ content, err := ioutil.ReadFile(path)
+ if err != nil {
+ return 0, err
+ }
+ for _, line := range strings.Split(string(content), "\n") {
+ fields := strings.SplitN(line, " ", 2)
+ if fields[0] == key {
+ v := cleanString(string(fields[1]))
+ if v == "max" {
+ return math.MaxUint64, nil
+ }
+ ret, err := strconv.ParseUint(v, 10, 64)
+ if err != nil {
+ return ret, errors.Wrapf(err, "parse %s from %s", v, path)
+ }
+ return ret, nil
+ }
+ }
+
+ return 0, fmt.Errorf("no key named %s from %s", key, path)
+}
+
+// New creates a new cgroup control
+func New(path string, resources *configs.Resources) (*CgroupControl, error) {
+ cgroup2, err := IsCgroup2UnifiedMode()
+ if err != nil {
+ return nil, err
+ }
+ control := &CgroupControl{
+ cgroup2: cgroup2,
+ config: &configs.Cgroup{
+ Path: path,
+ Resources: resources,
+ },
+ }
+
+ if !cgroup2 {
+ controllers, err := getAvailableControllers(handlers, false)
+ if err != nil {
+ return nil, err
+ }
+ control.additionalControllers = controllers
+ }
+
+ if err := control.initialize(); err != nil {
+ return nil, err
+ }
+
+ return control, nil
+}
+
+// NewSystemd creates a new cgroup control
+func NewSystemd(path string, resources *configs.Resources) (*CgroupControl, error) {
+ cgroup2, err := IsCgroup2UnifiedMode()
+ if err != nil {
+ return nil, err
+ }
+ control := &CgroupControl{
+ cgroup2: cgroup2,
+ systemd: true,
+ config: &configs.Cgroup{
+ Path: path,
+ Resources: resources,
+ Rootless: unshare.IsRootless(),
+ },
+ }
+
+ return control, nil
+}
+
+// Load loads an existing cgroup control
+func Load(path string) (*CgroupControl, error) {
+ cgroup2, err := IsCgroup2UnifiedMode()
+ if err != nil {
+ return nil, err
+ }
+ control := &CgroupControl{
+ cgroup2: cgroup2,
+ systemd: false,
+ config: &configs.Cgroup{
+ Path: path,
+ },
+ }
+ if !cgroup2 {
+ controllers, err := getAvailableControllers(handlers, false)
+ if err != nil {
+ return nil, err
+ }
+ control.additionalControllers = controllers
+ }
+ if !cgroup2 {
+ oneExists := false
+ // check that the cgroup exists at least under one controller
+ for name := range handlers {
+ p := control.getCgroupv1Path(name)
+ if _, err := os.Stat(p); err == nil {
+ oneExists = true
+ break
+ }
+ }
+
+ // if there is no controller at all, raise an error
+ if !oneExists {
+ if unshare.IsRootless() {
+ return nil, ErrCgroupV1Rootless
+ }
+ // compatible with the error code
+ // used by containerd/cgroups
+ return nil, ErrCgroupDeleted
+ }
+ }
+ return control, nil
+}
+
+// CreateSystemdUnit creates the systemd cgroup
+func (c *CgroupControl) CreateSystemdUnit(path string) error {
+ if !c.systemd {
+ return fmt.Errorf("the cgroup controller is not using systemd")
+ }
+
+ conn, err := systemdDbus.NewWithContext(context.TODO())
+ if err != nil {
+ return err
+ }
+ defer conn.Close()
+
+ return systemdCreate(c.config.Resources, path, conn)
+}
+
+// GetUserConnection returns an user connection to D-BUS
+func GetUserConnection(uid int) (*systemdDbus.Conn, error) {
+ return systemdDbus.NewConnection(func() (*dbus.Conn, error) {
+ return dbusAuthConnection(uid, dbus.SessionBusPrivate)
+ })
+}
+
+// CreateSystemdUserUnit creates the systemd cgroup for the specified user
+func (c *CgroupControl) CreateSystemdUserUnit(path string, uid int) error {
+ if !c.systemd {
+ return fmt.Errorf("the cgroup controller is not using systemd")
+ }
+
+ conn, err := GetUserConnection(uid)
+ if err != nil {
+ return err
+ }
+ defer conn.Close()
+
+ return systemdCreate(c.config.Resources, path, conn)
+}
+
+func dbusAuthConnection(uid int, createBus func(opts ...dbus.ConnOption) (*dbus.Conn, error)) (*dbus.Conn, error) {
+ conn, err := createBus()
+ if err != nil {
+ return nil, err
+ }
+
+ methods := []dbus.Auth{dbus.AuthExternal(strconv.Itoa(uid))}
+
+ err = conn.Auth(methods)
+ if err != nil {
+ conn.Close()
+ return nil, err
+ }
+ if err := conn.Hello(); err != nil {
+ return nil, err
+ }
+
+ return conn, nil
+}
+
+// Delete cleans a cgroup
+func (c *CgroupControl) Delete() error {
+ return c.DeleteByPath(c.config.Path)
+}
+
+// DeleteByPathConn deletes the specified cgroup path using the specified
+// dbus connection if needed.
+func (c *CgroupControl) DeleteByPathConn(path string, conn *systemdDbus.Conn) error {
+ if c.systemd {
+ return systemdDestroyConn(path, conn)
+ }
+ if c.cgroup2 {
+ return rmDirRecursively(filepath.Join(cgroupRoot, c.config.Path))
+ }
+ var lastError error
+ for _, h := range handlers {
+ if err := h.Destroy(c); err != nil {
+ lastError = err
+ }
+ }
+
+ for _, ctr := range c.additionalControllers {
+ if ctr.symlink {
+ continue
+ }
+ p := c.getCgroupv1Path(ctr.name)
+ if err := rmDirRecursively(p); err != nil {
+ lastError = errors.Wrapf(err, "remove %s", p)
+ }
+ }
+ return lastError
+}
+
+// DeleteByPath deletes the specified cgroup path
+func (c *CgroupControl) DeleteByPath(path string) error {
+ if c.systemd {
+ conn, err := systemdDbus.NewWithContext(context.TODO())
+ if err != nil {
+ return err
+ }
+ defer conn.Close()
+ return c.DeleteByPathConn(path, conn)
+ }
+ return c.DeleteByPathConn(path, nil)
+}
+
+// Update updates the cgroups
+func (c *CgroupControl) Update(resources *configs.Resources) error {
+ for _, h := range handlers {
+ if err := h.Apply(c, resources); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+// AddPid moves the specified pid to the cgroup
+func (c *CgroupControl) AddPid(pid int) error {
+ pidString := []byte(fmt.Sprintf("%d\n", pid))
+
+ if c.cgroup2 {
+ path := filepath.Join(cgroupRoot, c.config.Path)
+ return fs2.CreateCgroupPath(path, c.config)
+ }
+
+ names := make([]string, 0, len(handlers))
+ for n := range handlers {
+ names = append(names, n)
+ }
+
+ for _, c := range c.additionalControllers {
+ if !c.symlink {
+ names = append(names, c.name)
+ }
+ }
+
+ for _, n := range names {
+ // If we aren't using cgroup2, we won't write correctly to unified hierarchy
+ if !c.cgroup2 && n == "unified" {
+ continue
+ }
+ p := filepath.Join(c.getCgroupv1Path(n), "tasks")
+ if err := ioutil.WriteFile(p, pidString, 0o644); err != nil {
+ return errors.Wrapf(err, "write %s", p)
+ }
+ }
+ return nil
+}
+
+// Stat returns usage statistics for the cgroup
+func (c *CgroupControl) Stat() (*cgroups.Stats, error) {
+ m := cgroups.Stats{}
+ found := false
+ for _, h := range handlers {
+ if err := h.Stat(c, &m); err != nil {
+ if !os.IsNotExist(errors.Cause(err)) {
+ return nil, err
+ }
+ logrus.Warningf("Failed to retrieve cgroup stats: %v", err)
+ continue
+ }
+ found = true
+ }
+ if !found {
+ return nil, ErrStatCgroup
+ }
+ return &m, nil
+}
+
+func readCgroup2MapPath(path string) (map[string][]string, error) {
+ ret := map[string][]string{}
+ f, err := os.Open(path)
+ if err != nil {
+ if os.IsNotExist(err) {
+ return ret, nil
+ }
+ return nil, errors.Wrapf(err, "open file %s", path)
+ }
+ defer f.Close()
+ scanner := bufio.NewScanner(f)
+ for scanner.Scan() {
+ line := scanner.Text()
+ parts := strings.Fields(line)
+ if len(parts) < 2 {
+ continue
+ }
+ ret[parts[0]] = parts[1:]
+ }
+ if err := scanner.Err(); err != nil {
+ return nil, errors.Wrapf(err, "parsing file %s", path)
+ }
+ return ret, nil
+}
+
+func readCgroup2MapFile(ctr *CgroupControl, name string) (map[string][]string, error) {
+ p := filepath.Join(cgroupRoot, ctr.config.Path, name)
+
+ return readCgroup2MapPath(p)
+}
diff --git a/vendor/github.com/containers/common/pkg/cgroups/cpu.go b/vendor/github.com/containers/common/pkg/cgroups/cpu.go
index c9e94f269..fff76b9e2 100644
--- a/vendor/github.com/containers/common/pkg/cgroups/cpu.go
+++ b/vendor/github.com/containers/common/pkg/cgroups/cpu.go
@@ -1,12 +1,12 @@
+//go:build !linux
+// +build !linux
+
package cgroups
import (
"fmt"
- "io/ioutil"
"os"
- "path/filepath"
"strconv"
- "strings"
spec "github.com/opencontainers/runtime-spec/specs-go"
"github.com/pkg/errors"
@@ -18,36 +18,6 @@ func getCPUHandler() *cpuHandler {
return &cpuHandler{}
}
-func cleanString(s string) string {
- return strings.Trim(s, "\n")
-}
-
-func readAcct(ctr *CgroupControl, name string) (uint64, error) {
- p := filepath.Join(ctr.getCgroupv1Path(CPUAcct), name)
- return readFileAsUint64(p)
-}
-
-func readAcctList(ctr *CgroupControl, name string) ([]uint64, error) {
- p := filepath.Join(ctr.getCgroupv1Path(CPUAcct), name)
- data, err := ioutil.ReadFile(p)
- if err != nil {
- return nil, errors.Wrapf(err, "reading %s", p)
- }
- r := []uint64{}
- for _, s := range strings.Split(string(data), " ") {
- s = cleanString(s)
- if s == "" {
- break
- }
- v, err := strconv.ParseUint(s, 10, 64)
- if err != nil {
- return nil, errors.Wrapf(err, "parsing %s", s)
- }
- r = append(r, v)
- }
- return r, nil
-}
-
// Apply set the specified constraints
func (c *cpuHandler) Apply(ctr *CgroupControl, res *spec.LinuxResources) error {
if res.CPU == nil {
@@ -119,41 +89,3 @@ func (c *cpuHandler) Stat(ctr *CgroupControl, m *Metrics) error {
m.CPU = CPUMetrics{Usage: usage}
return nil
}
-
-// GetSystemCPUUsage returns the system usage for all the cgroups
-func GetSystemCPUUsage() (uint64, error) {
- cgroupv2, err := IsCgroup2UnifiedMode()
- if err != nil {
- return 0, err
- }
- if !cgroupv2 {
- p := filepath.Join(cgroupRoot, CPUAcct, "cpuacct.usage")
- return readFileAsUint64(p)
- }
-
- files, err := ioutil.ReadDir(cgroupRoot)
- if err != nil {
- return 0, err
- }
- var total uint64
- for _, file := range files {
- if !file.IsDir() {
- continue
- }
- p := filepath.Join(cgroupRoot, file.Name(), "cpu.stat")
-
- values, err := readCgroup2MapPath(p)
- if err != nil {
- return 0, err
- }
-
- if val, found := values["usage_usec"]; found {
- v, err := strconv.ParseUint(cleanString(val[0]), 10, 64)
- if err != nil {
- return 0, err
- }
- total += v * 1000
- }
- }
- return total, nil
-}
diff --git a/vendor/github.com/containers/common/pkg/cgroups/cpu_linux.go b/vendor/github.com/containers/common/pkg/cgroups/cpu_linux.go
new file mode 100644
index 000000000..bca55575c
--- /dev/null
+++ b/vendor/github.com/containers/common/pkg/cgroups/cpu_linux.go
@@ -0,0 +1,100 @@
+//go:build linux
+// +build linux
+
+package cgroups
+
+import (
+ "os"
+ "path/filepath"
+ "strconv"
+
+ "github.com/opencontainers/runc/libcontainer/cgroups"
+ "github.com/opencontainers/runc/libcontainer/cgroups/fs"
+ "github.com/opencontainers/runc/libcontainer/cgroups/fs2"
+ "github.com/opencontainers/runc/libcontainer/configs"
+ "github.com/pkg/errors"
+)
+
+type linuxCPUHandler struct {
+ CPU fs.CpuGroup
+}
+
+func getCPUHandler() *linuxCPUHandler {
+ return &linuxCPUHandler{}
+}
+
+// Apply set the specified constraints
+func (c *linuxCPUHandler) Apply(ctr *CgroupControl, res *configs.Resources) error {
+ if ctr.cgroup2 {
+ man, err := fs2.NewManager(ctr.config, filepath.Join(cgroupRoot, ctr.config.Path))
+ if err != nil {
+ return err
+ }
+ return man.Set(res)
+ }
+ path := filepath.Join(cgroupRoot, CPU, ctr.config.Path)
+ return c.CPU.Set(path, res)
+}
+
+// Create the cgroup
+func (c *linuxCPUHandler) Create(ctr *CgroupControl) (bool, error) {
+ if ctr.cgroup2 {
+ return false, nil
+ }
+ return ctr.createCgroupDirectory(CPU)
+}
+
+// Destroy the cgroup
+func (c *linuxCPUHandler) Destroy(ctr *CgroupControl) error {
+ return rmDirRecursively(ctr.getCgroupv1Path(CPU))
+}
+
+// Stat fills a metrics structure with usage stats for the controller
+func (c *linuxCPUHandler) Stat(ctr *CgroupControl, m *cgroups.Stats) error {
+ var err error
+ cpu := cgroups.CpuStats{}
+ if ctr.cgroup2 {
+ values, err := readCgroup2MapFile(ctr, "cpu.stat")
+ if err != nil {
+ return err
+ }
+ if val, found := values["usage_usec"]; found {
+ cpu.CpuUsage.TotalUsage, err = strconv.ParseUint(cleanString(val[0]), 10, 64)
+ if err != nil {
+ return err
+ }
+ cpu.CpuUsage.UsageInKernelmode *= 1000
+ }
+ if val, found := values["system_usec"]; found {
+ cpu.CpuUsage.UsageInKernelmode, err = strconv.ParseUint(cleanString(val[0]), 10, 64)
+ if err != nil {
+ return err
+ }
+ cpu.CpuUsage.TotalUsage *= 1000
+ }
+ } else {
+ cpu.CpuUsage.TotalUsage, err = readAcct(ctr, "cpuacct.usage")
+ if err != nil {
+ if !os.IsNotExist(errors.Cause(err)) {
+ return err
+ }
+ cpu.CpuUsage.TotalUsage = 0
+ }
+ cpu.CpuUsage.UsageInKernelmode, err = readAcct(ctr, "cpuacct.usage_sys")
+ if err != nil {
+ if !os.IsNotExist(errors.Cause(err)) {
+ return err
+ }
+ cpu.CpuUsage.UsageInKernelmode = 0
+ }
+ cpu.CpuUsage.PercpuUsage, err = readAcctList(ctr, "cpuacct.usage_percpu")
+ if err != nil {
+ if !os.IsNotExist(errors.Cause(err)) {
+ return err
+ }
+ cpu.CpuUsage.PercpuUsage = nil
+ }
+ }
+ m.CpuStats = cpu
+ return nil
+}
diff --git a/vendor/github.com/containers/common/pkg/cgroups/cpuset.go b/vendor/github.com/containers/common/pkg/cgroups/cpuset.go
index 2bfeb80db..f7ec9a33b 100644
--- a/vendor/github.com/containers/common/pkg/cgroups/cpuset.go
+++ b/vendor/github.com/containers/common/pkg/cgroups/cpuset.go
@@ -1,52 +1,17 @@
+//go:build !linux
+// +build !linux
+
package cgroups
import (
"fmt"
- "io/ioutil"
"path/filepath"
- "strings"
spec "github.com/opencontainers/runtime-spec/specs-go"
- "github.com/pkg/errors"
)
type cpusetHandler struct{}
-func cpusetCopyFileFromParent(dir, file string, cgroupv2 bool) ([]byte, error) {
- if dir == cgroupRoot {
- return nil, fmt.Errorf("could not find parent to initialize cpuset %s", file)
- }
- path := filepath.Join(dir, file)
- parentPath := path
- if cgroupv2 {
- parentPath = fmt.Sprintf("%s.effective", parentPath)
- }
- data, err := ioutil.ReadFile(parentPath)
- if err != nil {
- return nil, errors.Wrapf(err, "open %s", path)
- }
- if strings.Trim(string(data), "\n") != "" {
- return data, nil
- }
- data, err = cpusetCopyFileFromParent(filepath.Dir(dir), file, cgroupv2)
- if err != nil {
- return nil, err
- }
- if err := ioutil.WriteFile(path, data, 0o644); err != nil {
- return nil, errors.Wrapf(err, "write %s", path)
- }
- return data, nil
-}
-
-func cpusetCopyFromParent(path string, cgroupv2 bool) error {
- for _, file := range []string{"cpuset.cpus", "cpuset.mems"} {
- if _, err := cpusetCopyFileFromParent(path, file, cgroupv2); err != nil {
- return err
- }
- }
- return nil
-}
-
func getCpusetHandler() *cpusetHandler {
return &cpusetHandler{}
}
diff --git a/vendor/github.com/containers/common/pkg/cgroups/cpuset_linux.go b/vendor/github.com/containers/common/pkg/cgroups/cpuset_linux.go
new file mode 100644
index 000000000..a4cc2acaf
--- /dev/null
+++ b/vendor/github.com/containers/common/pkg/cgroups/cpuset_linux.go
@@ -0,0 +1,57 @@
+//go:build linux
+// +build linux
+
+package cgroups
+
+import (
+ "path/filepath"
+
+ "github.com/opencontainers/runc/libcontainer/cgroups"
+ "github.com/opencontainers/runc/libcontainer/cgroups/fs"
+ "github.com/opencontainers/runc/libcontainer/cgroups/fs2"
+ "github.com/opencontainers/runc/libcontainer/configs"
+)
+
+type linuxCpusetHandler struct {
+ CPUSet fs.CpusetGroup
+}
+
+func getCpusetHandler() *linuxCpusetHandler {
+ return &linuxCpusetHandler{}
+}
+
+// Apply set the specified constraints
+func (c *linuxCpusetHandler) Apply(ctr *CgroupControl, res *configs.Resources) error {
+ if ctr.cgroup2 {
+ man, err := fs2.NewManager(ctr.config, filepath.Join(cgroupRoot, ctr.config.Path))
+ if err != nil {
+ return err
+ }
+ return man.Set(res)
+ }
+ path := filepath.Join(cgroupRoot, CPUset, ctr.config.Path)
+ return c.CPUSet.Set(path, res)
+}
+
+// Create the cgroup
+func (c *linuxCpusetHandler) Create(ctr *CgroupControl) (bool, error) {
+ if ctr.cgroup2 {
+ path := filepath.Join(cgroupRoot, ctr.config.Path)
+ return true, cpusetCopyFromParent(path, true)
+ }
+ created, err := ctr.createCgroupDirectory(CPUset)
+ if !created || err != nil {
+ return created, err
+ }
+ return true, cpusetCopyFromParent(ctr.getCgroupv1Path(CPUset), false)
+}
+
+// Destroy the cgroup
+func (c *linuxCpusetHandler) Destroy(ctr *CgroupControl) error {
+ return rmDirRecursively(ctr.getCgroupv1Path(CPUset))
+}
+
+// Stat fills a metrics structure with usage stats for the controller
+func (c *linuxCpusetHandler) Stat(ctr *CgroupControl, m *cgroups.Stats) error {
+ return nil
+}
diff --git a/vendor/github.com/containers/common/pkg/cgroups/memory.go b/vendor/github.com/containers/common/pkg/cgroups/memory.go
index 10d65893c..b597b85bf 100644
--- a/vendor/github.com/containers/common/pkg/cgroups/memory.go
+++ b/vendor/github.com/containers/common/pkg/cgroups/memory.go
@@ -1,3 +1,6 @@
+//go:build !linux
+// +build !linux
+
package cgroups
import (
diff --git a/vendor/github.com/containers/common/pkg/cgroups/memory_linux.go b/vendor/github.com/containers/common/pkg/cgroups/memory_linux.go
new file mode 100644
index 000000000..5d2bf5d0e
--- /dev/null
+++ b/vendor/github.com/containers/common/pkg/cgroups/memory_linux.go
@@ -0,0 +1,78 @@
+//go:build linux
+// +build linux
+
+package cgroups
+
+import (
+ "path/filepath"
+
+ "github.com/opencontainers/runc/libcontainer/cgroups"
+ "github.com/opencontainers/runc/libcontainer/cgroups/fs"
+ "github.com/opencontainers/runc/libcontainer/cgroups/fs2"
+ "github.com/opencontainers/runc/libcontainer/configs"
+)
+
+type linuxMemHandler struct {
+ Mem fs.MemoryGroup
+}
+
+func getMemoryHandler() *linuxMemHandler {
+ return &linuxMemHandler{}
+}
+
+// Apply set the specified constraints
+func (c *linuxMemHandler) Apply(ctr *CgroupControl, res *configs.Resources) error {
+ if ctr.cgroup2 {
+ man, err := fs2.NewManager(ctr.config, filepath.Join(cgroupRoot, ctr.config.Path))
+ if err != nil {
+ return err
+ }
+ return man.Set(res)
+ }
+ path := filepath.Join(cgroupRoot, Memory, ctr.config.Path)
+ return c.Mem.Set(path, res)
+}
+
+// Create the cgroup
+func (c *linuxMemHandler) Create(ctr *CgroupControl) (bool, error) {
+ if ctr.cgroup2 {
+ return false, nil
+ }
+ return ctr.createCgroupDirectory(Memory)
+}
+
+// Destroy the cgroup
+func (c *linuxMemHandler) Destroy(ctr *CgroupControl) error {
+ return rmDirRecursively(ctr.getCgroupv1Path(Memory))
+}
+
+// Stat fills a metrics structure with usage stats for the controller
+func (c *linuxMemHandler) Stat(ctr *CgroupControl, m *cgroups.Stats) error {
+ var err error
+ memUsage := cgroups.MemoryStats{}
+
+ var memoryRoot string
+ var limitFilename string
+
+ if ctr.cgroup2 {
+ memoryRoot = filepath.Join(cgroupRoot, ctr.config.Path)
+ limitFilename = "memory.max"
+ if memUsage.Usage.Usage, err = readFileByKeyAsUint64(filepath.Join(memoryRoot, "memory.stat"), "anon"); err != nil {
+ return err
+ }
+ } else {
+ memoryRoot = ctr.getCgroupv1Path(Memory)
+ limitFilename = "memory.limit_in_bytes"
+ if memUsage.Usage.Usage, err = readFileAsUint64(filepath.Join(memoryRoot, "memory.usage_in_bytes")); err != nil {
+ return err
+ }
+ }
+
+ memUsage.Usage.Limit, err = readFileAsUint64(filepath.Join(memoryRoot, limitFilename))
+ if err != nil {
+ return err
+ }
+
+ m.MemoryStats = memUsage
+ return nil
+}
diff --git a/vendor/github.com/containers/common/pkg/cgroups/pids.go b/vendor/github.com/containers/common/pkg/cgroups/pids.go
index 650120a56..1cb7ced82 100644
--- a/vendor/github.com/containers/common/pkg/cgroups/pids.go
+++ b/vendor/github.com/containers/common/pkg/cgroups/pids.go
@@ -1,3 +1,6 @@
+//go:build !linux
+// +build !linux
+
package cgroups
import (
diff --git a/vendor/github.com/containers/common/pkg/cgroups/pids_linux.go b/vendor/github.com/containers/common/pkg/cgroups/pids_linux.go
new file mode 100644
index 000000000..a8163ce46
--- /dev/null
+++ b/vendor/github.com/containers/common/pkg/cgroups/pids_linux.go
@@ -0,0 +1,71 @@
+//go:build linux
+// +build linux
+
+package cgroups
+
+import (
+ "path/filepath"
+
+ "github.com/opencontainers/runc/libcontainer/cgroups"
+ "github.com/opencontainers/runc/libcontainer/cgroups/fs"
+ "github.com/opencontainers/runc/libcontainer/cgroups/fs2"
+ "github.com/opencontainers/runc/libcontainer/configs"
+)
+
+type linuxPidHandler struct {
+ Pid fs.PidsGroup
+}
+
+func getPidsHandler() *linuxPidHandler {
+ return &linuxPidHandler{}
+}
+
+// Apply set the specified constraints
+func (c *linuxPidHandler) Apply(ctr *CgroupControl, res *configs.Resources) error {
+ if ctr.cgroup2 {
+ man, err := fs2.NewManager(ctr.config, filepath.Join(cgroupRoot, ctr.config.Path))
+ if err != nil {
+ return err
+ }
+ return man.Set(res)
+ }
+
+ path := filepath.Join(cgroupRoot, Pids, ctr.config.Path)
+ return c.Pid.Set(path, res)
+}
+
+// Create the cgroup
+func (c *linuxPidHandler) Create(ctr *CgroupControl) (bool, error) {
+ if ctr.cgroup2 {
+ return false, nil
+ }
+ return ctr.createCgroupDirectory(Pids)
+}
+
+// Destroy the cgroup
+func (c *linuxPidHandler) Destroy(ctr *CgroupControl) error {
+ return rmDirRecursively(ctr.getCgroupv1Path(Pids))
+}
+
+// Stat fills a metrics structure with usage stats for the controller
+func (c *linuxPidHandler) Stat(ctr *CgroupControl, m *cgroups.Stats) error {
+ if ctr.config.Path == "" {
+ // nothing we can do to retrieve the pids.current path
+ return nil
+ }
+
+ var PIDRoot string
+ if ctr.cgroup2 {
+ PIDRoot = filepath.Join(cgroupRoot, ctr.config.Path)
+ } else {
+ PIDRoot = ctr.getCgroupv1Path(Pids)
+ }
+
+ current, err := readFileAsUint64(filepath.Join(PIDRoot, "pids.current"))
+ if err != nil {
+ return err
+ }
+
+ m.PidsStats.Current = current
+ return nil
+}
diff --git a/vendor/github.com/containers/common/pkg/cgroups/systemd.go b/vendor/github.com/containers/common/pkg/cgroups/systemd.go
index 92065a2d7..118fa97a1 100644
--- a/vendor/github.com/containers/common/pkg/cgroups/systemd.go
+++ b/vendor/github.com/containers/common/pkg/cgroups/systemd.go
@@ -1,3 +1,6 @@
+//go:build !linux
+// +build !linux
+
package cgroups
import (
@@ -52,15 +55,11 @@ func systemdCreate(path string, c *systemdDbus.Conn) error {
/*
systemdDestroyConn is copied from containerd/cgroups/systemd.go file, that
has the following license:
-
Copyright The containerd Authors.
-
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
-
https://www.apache.org/licenses/LICENSE-2.0
-
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
diff --git a/vendor/github.com/containers/common/pkg/cgroups/systemd_linux.go b/vendor/github.com/containers/common/pkg/cgroups/systemd_linux.go
new file mode 100644
index 000000000..a45358f9b
--- /dev/null
+++ b/vendor/github.com/containers/common/pkg/cgroups/systemd_linux.go
@@ -0,0 +1,167 @@
+//go:build linux
+// +build linux
+
+package cgroups
+
+import (
+ "context"
+ "fmt"
+ "path/filepath"
+ "strings"
+
+ systemdDbus "github.com/coreos/go-systemd/v22/dbus"
+ "github.com/godbus/dbus/v5"
+ "github.com/opencontainers/runc/libcontainer/configs"
+)
+
+func systemdCreate(resources *configs.Resources, path string, c *systemdDbus.Conn) error {
+ slice, name := filepath.Split(path)
+ slice = strings.TrimSuffix(slice, "/")
+
+ var lastError error
+ for i := 0; i < 2; i++ {
+ properties := []systemdDbus.Property{
+ systemdDbus.PropDescription(fmt.Sprintf("cgroup %s", name)),
+ systemdDbus.PropWants(slice),
+ }
+ pMap := map[string]bool{
+ "DefaultDependencies": false,
+ "MemoryAccounting": true,
+ "CPUAccounting": true,
+ "BlockIOAccounting": true,
+ }
+ if i == 0 {
+ pMap["Delegate"] = true
+ }
+
+ for k, v := range pMap {
+ p := systemdDbus.Property{
+ Name: k,
+ Value: dbus.MakeVariant(v),
+ }
+ properties = append(properties, p)
+ }
+
+ uMap, sMap, bMap, iMap := resourcesToProps(resources)
+ for k, v := range uMap {
+ p := systemdDbus.Property{
+ Name: k,
+ Value: dbus.MakeVariant(v),
+ }
+ properties = append(properties, p)
+ }
+
+ for k, v := range sMap {
+ p := systemdDbus.Property{
+ Name: k,
+ Value: dbus.MakeVariant(v),
+ }
+ properties = append(properties, p)
+ }
+
+ for k, v := range bMap {
+ p := systemdDbus.Property{
+ Name: k,
+ Value: dbus.MakeVariant(v),
+ }
+ properties = append(properties, p)
+ }
+
+ for k, v := range iMap {
+ p := systemdDbus.Property{
+ Name: k,
+ Value: dbus.MakeVariant(v),
+ }
+ properties = append(properties, p)
+ }
+
+ ch := make(chan string)
+ _, err := c.StartTransientUnitContext(context.TODO(), name, "replace", properties, ch)
+ if err != nil {
+ lastError = err
+ continue
+ }
+ <-ch
+ return nil
+ }
+ return lastError
+}
+
+/*
+ systemdDestroyConn is copied from containerd/cgroups/systemd.go file, that
+ has the following license:
+
+ Copyright The containerd Authors.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ https://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+func systemdDestroyConn(path string, c *systemdDbus.Conn) error {
+ name := filepath.Base(path)
+
+ ch := make(chan string)
+ _, err := c.StopUnitContext(context.TODO(), name, "replace", ch)
+ if err != nil {
+ return err
+ }
+ <-ch
+ return nil
+}
+
+func resourcesToProps(res *configs.Resources) (map[string]uint64, map[string]string, map[string][]byte, map[string]int64) {
+ bMap := make(map[string][]byte)
+ // this array is not used but will be once more resource limits are added
+ sMap := make(map[string]string)
+ iMap := make(map[string]int64)
+ uMap := make(map[string]uint64)
+
+ // CPU
+ if res.CpuPeriod != 0 {
+ uMap["CPUQuotaPeriodUSec"] = res.CpuPeriod
+ }
+ if res.CpuQuota != 0 {
+ period := res.CpuPeriod
+ if period == 0 {
+ period = uint64(100000)
+ }
+ cpuQuotaPerSecUSec := uint64(res.CpuQuota*1000000) / period
+ if cpuQuotaPerSecUSec%10000 != 0 {
+ cpuQuotaPerSecUSec = ((cpuQuotaPerSecUSec / 10000) + 1) * 10000
+ }
+ uMap["CPUQuotaPerSecUSec"] = cpuQuotaPerSecUSec
+ }
+
+ // CPUSet
+ if res.CpusetCpus != "" {
+ bits := []byte(res.CpusetCpus)
+ bMap["AllowedCPUs"] = bits
+ }
+ if res.CpusetMems != "" {
+ bits := []byte(res.CpusetMems)
+ bMap["AllowedMemoryNodes"] = bits
+ }
+
+ // Mem
+ if res.Memory != 0 {
+ iMap["MemoryMax"] = res.Memory
+ }
+ if res.MemorySwap != 0 {
+ iMap["MemorySwapMax"] = res.MemorySwap
+ }
+
+ // Blkio
+ if res.BlkioWeight > 0 {
+ uMap["BlockIOWeight"] = uint64(res.BlkioWeight)
+ }
+
+ return uMap, sMap, bMap, iMap
+}
diff --git a/vendor/github.com/containers/common/pkg/cgroups/utils.go b/vendor/github.com/containers/common/pkg/cgroups/utils.go
new file mode 100644
index 000000000..1fd45f40b
--- /dev/null
+++ b/vendor/github.com/containers/common/pkg/cgroups/utils.go
@@ -0,0 +1,176 @@
+package cgroups
+
+import (
+ "bytes"
+ "fmt"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "strconv"
+ "strings"
+
+ "github.com/pkg/errors"
+)
+
+var TestMode bool
+
+func cleanString(s string) string {
+ return strings.Trim(s, "\n")
+}
+
+func readAcct(ctr *CgroupControl, name string) (uint64, error) {
+ p := filepath.Join(ctr.getCgroupv1Path(CPUAcct), name)
+ return readFileAsUint64(p)
+}
+
+func readAcctList(ctr *CgroupControl, name string) ([]uint64, error) {
+ p := filepath.Join(ctr.getCgroupv1Path(CPUAcct), name)
+ data, err := ioutil.ReadFile(p)
+ if err != nil {
+ return nil, errors.Wrapf(err, "reading %s", p)
+ }
+ r := []uint64{}
+ for _, s := range strings.Split(string(data), " ") {
+ s = cleanString(s)
+ if s == "" {
+ break
+ }
+ v, err := strconv.ParseUint(s, 10, 64)
+ if err != nil {
+ return nil, errors.Wrapf(err, "parsing %s", s)
+ }
+ r = append(r, v)
+ }
+ return r, nil
+}
+
+// GetSystemCPUUsage returns the system usage for all the cgroups
+func GetSystemCPUUsage() (uint64, error) {
+ cgroupv2, err := IsCgroup2UnifiedMode()
+ if err != nil {
+ return 0, err
+ }
+ if !cgroupv2 {
+ p := filepath.Join(cgroupRoot, CPUAcct, "cpuacct.usage")
+ return readFileAsUint64(p)
+ }
+
+ files, err := ioutil.ReadDir(cgroupRoot)
+ if err != nil {
+ return 0, err
+ }
+ var total uint64
+ for _, file := range files {
+ if !file.IsDir() {
+ continue
+ }
+ p := filepath.Join(cgroupRoot, file.Name(), "cpu.stat")
+
+ values, err := readCgroup2MapPath(p)
+ if err != nil {
+ return 0, err
+ }
+
+ if val, found := values["usage_usec"]; found {
+ v, err := strconv.ParseUint(cleanString(val[0]), 10, 64)
+ if err != nil {
+ return 0, err
+ }
+ total += v * 1000
+ }
+ }
+ return total, nil
+}
+
+func cpusetCopyFileFromParent(dir, file string, cgroupv2 bool) ([]byte, error) {
+ if dir == cgroupRoot {
+ return nil, fmt.Errorf("could not find parent to initialize cpuset %s", file)
+ }
+ path := filepath.Join(dir, file)
+ parentPath := path
+ if cgroupv2 {
+ parentPath = fmt.Sprintf("%s.effective", parentPath)
+ }
+ data, err := ioutil.ReadFile(parentPath)
+ if err != nil {
+ return nil, errors.Wrapf(err, "open %s", path)
+ }
+ if strings.Trim(string(data), "\n") != "" {
+ return data, nil
+ }
+ data, err = cpusetCopyFileFromParent(filepath.Dir(dir), file, cgroupv2)
+ if err != nil {
+ return nil, err
+ }
+ if err := ioutil.WriteFile(path, data, 0o644); err != nil {
+ return nil, errors.Wrapf(err, "write %s", path)
+ }
+ return data, nil
+}
+
+func cpusetCopyFromParent(path string, cgroupv2 bool) error {
+ for _, file := range []string{"cpuset.cpus", "cpuset.mems"} {
+ if _, err := cpusetCopyFileFromParent(path, file, cgroupv2); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+// createCgroupv2Path creates the cgroupv2 path and enables all the available controllers
+func createCgroupv2Path(path string) (deferredError error) {
+ if !strings.HasPrefix(path, cgroupRoot+"/") {
+ return fmt.Errorf("invalid cgroup path %s", path)
+ }
+ content, err := ioutil.ReadFile(cgroupRoot + "/cgroup.controllers")
+ if err != nil {
+ return err
+ }
+ ctrs := bytes.Fields(content)
+ res := append([]byte("+"), bytes.Join(ctrs, []byte(" +"))...)
+
+ current := "/sys/fs"
+ elements := strings.Split(path, "/")
+ for i, e := range elements[3:] {
+ current = filepath.Join(current, e)
+ if i > 0 {
+ if err := os.Mkdir(current, 0o755); err != nil {
+ if !os.IsExist(err) {
+ return err
+ }
+ } else {
+ // If the directory was created, be sure it is not left around on errors.
+ defer func() {
+ if deferredError != nil {
+ os.Remove(current)
+ }
+ }()
+ }
+ }
+ // We enable the controllers for all the path components except the last one. It is not allowed to add
+ // PIDs if there are already enabled controllers.
+ if i < len(elements[3:])-1 {
+ if err := ioutil.WriteFile(filepath.Join(current, "cgroup.subtree_control"), res, 0o755); err != nil {
+ return err
+ }
+ }
+ }
+ return nil
+}
+
+func (c *CgroupControl) createCgroupDirectory(controller string) (bool, error) {
+ cPath := c.getCgroupv1Path(controller)
+ _, err := os.Stat(cPath)
+ if err == nil {
+ return false, nil
+ }
+
+ if !os.IsNotExist(err) {
+ return false, err
+ }
+
+ if err := os.MkdirAll(cPath, 0o755); err != nil {
+ return false, errors.Wrapf(err, "error creating cgroup for %s", controller)
+ }
+ return true, nil
+}
diff --git a/vendor/github.com/containers/common/pkg/cgroups/utils_linux.go b/vendor/github.com/containers/common/pkg/cgroups/utils_linux.go
new file mode 100644
index 000000000..bd37042cd
--- /dev/null
+++ b/vendor/github.com/containers/common/pkg/cgroups/utils_linux.go
@@ -0,0 +1,146 @@
+//go:build linux
+// +build linux
+
+package cgroups
+
+import (
+ "bytes"
+ "fmt"
+ "os"
+ "path"
+ "path/filepath"
+ "strings"
+
+ "github.com/opencontainers/runc/libcontainer/cgroups"
+ "github.com/opencontainers/runc/libcontainer/configs"
+ "github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
+ "golang.org/x/sys/unix"
+)
+
+// WriteFile writes to a cgroup file
+func WriteFile(dir, file, data string) error {
+ fd, err := OpenFile(dir, file, unix.O_WRONLY)
+ if err != nil {
+ return err
+ }
+ defer fd.Close()
+ for {
+ _, err := fd.Write([]byte(data))
+ if errors.Is(err, unix.EINTR) {
+ logrus.Infof("interrupted while writing %s to %s", data, fd.Name())
+ continue
+ }
+ return err
+ }
+}
+
+// OpenFile opens a cgroup file with the given flags
+func OpenFile(dir, file string, flags int) (*os.File, error) {
+ var resolveFlags uint64
+ mode := os.FileMode(0)
+ if TestMode && flags&os.O_WRONLY != 0 {
+ flags |= os.O_TRUNC | os.O_CREATE
+ mode = 0o600
+ }
+ cgroupPath := path.Join(dir, file)
+ relPath := strings.TrimPrefix(cgroupPath, cgroupRoot+"/")
+
+ var stats unix.Statfs_t
+ fdTest, errOpen := unix.Openat2(-1, cgroupRoot, &unix.OpenHow{
+ Flags: unix.O_DIRECTORY | unix.O_PATH,
+ })
+ errStat := unix.Fstatfs(fdTest, &stats)
+ cgroupFd := fdTest
+
+ resolveFlags = unix.RESOLVE_BENEATH | unix.RESOLVE_NO_MAGICLINKS
+ if stats.Type == unix.CGROUP2_SUPER_MAGIC {
+ // cgroupv2 has a single mountpoint and no "cpu,cpuacct" symlinks
+ resolveFlags |= unix.RESOLVE_NO_XDEV | unix.RESOLVE_NO_SYMLINKS
+ }
+
+ if errOpen != nil || errStat != nil || (len(relPath) == len(cgroupPath)) { // openat2 not available, use os
+ fdTest, err := os.OpenFile(cgroupPath, flags, mode)
+ if err != nil {
+ return nil, err
+ }
+ if TestMode {
+ return fdTest, nil
+ }
+ if err := unix.Fstatfs(int(fdTest.Fd()), &stats); err != nil {
+ _ = fdTest.Close()
+ return nil, &os.PathError{Op: "statfs", Path: cgroupPath, Err: err}
+ }
+ if stats.Type != unix.CGROUP_SUPER_MAGIC && stats.Type != unix.CGROUP2_SUPER_MAGIC {
+ _ = fdTest.Close()
+ return nil, &os.PathError{Op: "open", Path: cgroupPath, Err: errors.New("not a cgroup file")}
+ }
+ return fdTest, nil
+ }
+
+ fd, err := unix.Openat2(cgroupFd, relPath,
+ &unix.OpenHow{
+ Resolve: resolveFlags,
+ Flags: uint64(flags) | unix.O_CLOEXEC,
+ Mode: uint64(mode),
+ })
+ if err != nil {
+ fmt.Println("Error in openat")
+ return nil, err
+ }
+
+ return os.NewFile(uintptr(fd), cgroupPath), nil
+}
+
+// ReadFile reads from a cgroup file, opening it with the read only flag
+func ReadFile(dir, file string) (string, error) {
+ fd, err := OpenFile(dir, file, unix.O_RDONLY)
+ if err != nil {
+ return "", err
+ }
+ defer fd.Close()
+ var buf bytes.Buffer
+
+ _, err = buf.ReadFrom(fd)
+ return buf.String(), err
+}
+
+// GetBlkioFiles gets the proper files for blkio weights
+func GetBlkioFiles(cgroupPath string) (wtFile, wtDevFile string) {
+ var weightFile string
+ var weightDeviceFile string
+ // in this important since runc keeps these variables private, they won't be set
+ if cgroups.PathExists(filepath.Join(cgroupPath, "blkio.weight")) {
+ weightFile = "blkio.weight"
+ weightDeviceFile = "blkio.weight_device"
+ } else {
+ weightFile = "blkio.bfq.weight"
+ weightDeviceFile = "blkio.bfq.weight_device"
+ }
+ return weightFile, weightDeviceFile
+}
+
+// SetBlkioThrottle sets the throttle limits for the cgroup
+func SetBlkioThrottle(res *configs.Resources, cgroupPath string) error {
+ for _, td := range res.BlkioThrottleReadBpsDevice {
+ if err := WriteFile(cgroupPath, "blkio.throttle.read_bps_device", fmt.Sprintf("%d:%d %d", td.Major, td.Minor, td.Rate)); err != nil {
+ return err
+ }
+ }
+ for _, td := range res.BlkioThrottleWriteBpsDevice {
+ if err := WriteFile(cgroupPath, "blkio.throttle.write_bps_device", fmt.Sprintf("%d:%d %d", td.Major, td.Minor, td.Rate)); err != nil {
+ return err
+ }
+ }
+ for _, td := range res.BlkioThrottleReadIOPSDevice {
+ if err := WriteFile(cgroupPath, "blkio.throttle.read_iops_device", td.String()); err != nil {
+ return err
+ }
+ }
+ for _, td := range res.BlkioThrottleWriteIOPSDevice {
+ if err := WriteFile(cgroupPath, "blkio.throttle.write_iops_device", td.String()); err != nil {
+ return err
+ }
+ }
+ return nil
+}
diff --git a/vendor/github.com/containers/common/pkg/parse/parse.go b/vendor/github.com/containers/common/pkg/parse/parse.go
index 6c4958cc2..43b783e0c 100644
--- a/vendor/github.com/containers/common/pkg/parse/parse.go
+++ b/vendor/github.com/containers/common/pkg/parse/parse.go
@@ -14,7 +14,7 @@ import (
// ValidateVolumeOpts validates a volume's options
func ValidateVolumeOpts(options []string) ([]string, error) {
- var foundRootPropagation, foundRWRO, foundLabelChange, bindType, foundExec, foundDev, foundSuid, foundChown, foundUpperDir, foundWorkDir int
+ var foundRootPropagation, foundRWRO, foundLabelChange, bindType, foundExec, foundDev, foundSuid, foundChown, foundUpperDir, foundWorkDir, foundCopy int
finalOpts := make([]string, 0, len(options))
for _, opt := range options {
// support advanced options like upperdir=/path, workdir=/path
@@ -88,6 +88,11 @@ func ValidateVolumeOpts(options []string) ([]string, error) {
// are intended to be always safe to use, even not on OS
// X).
continue
+ case "copy", "nocopy":
+ foundCopy++
+ if foundCopy > 1 {
+ return nil, errors.Errorf("invalid options %q, can only specify 1 'copy' or 'nocopy' option", strings.Join(options, ", "))
+ }
default:
return nil, errors.Errorf("invalid option type %q", opt)
}
diff --git a/vendor/github.com/containers/common/pkg/seccomp/filter.go b/vendor/github.com/containers/common/pkg/seccomp/filter.go
index 609036c82..7f1783efb 100644
--- a/vendor/github.com/containers/common/pkg/seccomp/filter.go
+++ b/vendor/github.com/containers/common/pkg/seccomp/filter.go
@@ -168,7 +168,8 @@ func matchSyscall(filter *libseccomp.ScmpFilter, call *Syscall) error {
func toAction(act Action, errnoRet *uint) (libseccomp.ScmpAction, error) {
switch act {
case ActKill:
- return libseccomp.ActKill, nil
+ // lint was not passing until this was changed from ActKill to ActKilThread.
+ return libseccomp.ActKillThread, nil
case ActKillProcess:
return libseccomp.ActKillProcess, nil
case ActErrno:
diff --git a/vendor/github.com/opencontainers/runc/libcontainer/cgroups/cgroups.go b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/cgroups.go
index ba2b2266c..b9ba889b7 100644
--- a/vendor/github.com/opencontainers/runc/libcontainer/cgroups/cgroups.go
+++ b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/cgroups.go
@@ -1,9 +1,24 @@
package cgroups
import (
+ "errors"
+
"github.com/opencontainers/runc/libcontainer/configs"
)
+var (
+ // ErrDevicesUnsupported is an error returned when a cgroup manager
+ // is not configured to set device rules.
+ ErrDevicesUnsupported = errors.New("cgroup manager is not configured to set device rules")
+
+ // DevicesSetV1 and DevicesSetV2 are functions to set devices for
+ // cgroup v1 and v2, respectively. Unless libcontainer/cgroups/devices
+ // package is imported, it is set to nil, so cgroup managers can't
+ // manage devices.
+ DevicesSetV1 func(path string, r *configs.Resources) error
+ DevicesSetV2 func(path string, r *configs.Resources) error
+)
+
type Manager interface {
// Apply creates a cgroup, if not yet created, and adds a process
// with the specified pid into that cgroup. A special value of -1
diff --git a/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/blkio.go b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/blkio.go
new file mode 100644
index 000000000..c81b6562a
--- /dev/null
+++ b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/blkio.go
@@ -0,0 +1,311 @@
+package fs
+
+import (
+ "bufio"
+ "os"
+ "path/filepath"
+ "strconv"
+ "strings"
+
+ "github.com/opencontainers/runc/libcontainer/cgroups"
+ "github.com/opencontainers/runc/libcontainer/configs"
+)
+
+type BlkioGroup struct {
+ weightFilename string
+ weightDeviceFilename string
+}
+
+func (s *BlkioGroup) Name() string {
+ return "blkio"
+}
+
+func (s *BlkioGroup) Apply(path string, _ *configs.Resources, pid int) error {
+ return apply(path, pid)
+}
+
+func (s *BlkioGroup) Set(path string, r *configs.Resources) error {
+ s.detectWeightFilenames(path)
+ if r.BlkioWeight != 0 {
+ if err := cgroups.WriteFile(path, s.weightFilename, strconv.FormatUint(uint64(r.BlkioWeight), 10)); err != nil {
+ return err
+ }
+ }
+
+ if r.BlkioLeafWeight != 0 {
+ if err := cgroups.WriteFile(path, "blkio.leaf_weight", strconv.FormatUint(uint64(r.BlkioLeafWeight), 10)); err != nil {
+ return err
+ }
+ }
+ for _, wd := range r.BlkioWeightDevice {
+ if wd.Weight != 0 {
+ if err := cgroups.WriteFile(path, s.weightDeviceFilename, wd.WeightString()); err != nil {
+ return err
+ }
+ }
+ if wd.LeafWeight != 0 {
+ if err := cgroups.WriteFile(path, "blkio.leaf_weight_device", wd.LeafWeightString()); err != nil {
+ return err
+ }
+ }
+ }
+ for _, td := range r.BlkioThrottleReadBpsDevice {
+ if err := cgroups.WriteFile(path, "blkio.throttle.read_bps_device", td.String()); err != nil {
+ return err
+ }
+ }
+ for _, td := range r.BlkioThrottleWriteBpsDevice {
+ if err := cgroups.WriteFile(path, "blkio.throttle.write_bps_device", td.String()); err != nil {
+ return err
+ }
+ }
+ for _, td := range r.BlkioThrottleReadIOPSDevice {
+ if err := cgroups.WriteFile(path, "blkio.throttle.read_iops_device", td.String()); err != nil {
+ return err
+ }
+ }
+ for _, td := range r.BlkioThrottleWriteIOPSDevice {
+ if err := cgroups.WriteFile(path, "blkio.throttle.write_iops_device", td.String()); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+/*
+examples:
+
+ blkio.sectors
+ 8:0 6792
+
+ blkio.io_service_bytes
+ 8:0 Read 1282048
+ 8:0 Write 2195456
+ 8:0 Sync 2195456
+ 8:0 Async 1282048
+ 8:0 Total 3477504
+ Total 3477504
+
+ blkio.io_serviced
+ 8:0 Read 124
+ 8:0 Write 104
+ 8:0 Sync 104
+ 8:0 Async 124
+ 8:0 Total 228
+ Total 228
+
+ blkio.io_queued
+ 8:0 Read 0
+ 8:0 Write 0
+ 8:0 Sync 0
+ 8:0 Async 0
+ 8:0 Total 0
+ Total 0
+*/
+
+func splitBlkioStatLine(r rune) bool {
+ return r == ' ' || r == ':'
+}
+
+func getBlkioStat(dir, file string) ([]cgroups.BlkioStatEntry, error) {
+ var blkioStats []cgroups.BlkioStatEntry
+ f, err := cgroups.OpenFile(dir, file, os.O_RDONLY)
+ if err != nil {
+ if os.IsNotExist(err) {
+ return blkioStats, nil
+ }
+ return nil, err
+ }
+ defer f.Close()
+
+ sc := bufio.NewScanner(f)
+ for sc.Scan() {
+ // format: dev type amount
+ fields := strings.FieldsFunc(sc.Text(), splitBlkioStatLine)
+ if len(fields) < 3 {
+ if len(fields) == 2 && fields[0] == "Total" {
+ // skip total line
+ continue
+ } else {
+ return nil, malformedLine(dir, file, sc.Text())
+ }
+ }
+
+ v, err := strconv.ParseUint(fields[0], 10, 64)
+ if err != nil {
+ return nil, &parseError{Path: dir, File: file, Err: err}
+ }
+ major := v
+
+ v, err = strconv.ParseUint(fields[1], 10, 64)
+ if err != nil {
+ return nil, &parseError{Path: dir, File: file, Err: err}
+ }
+ minor := v
+
+ op := ""
+ valueField := 2
+ if len(fields) == 4 {
+ op = fields[2]
+ valueField = 3
+ }
+ v, err = strconv.ParseUint(fields[valueField], 10, 64)
+ if err != nil {
+ return nil, &parseError{Path: dir, File: file, Err: err}
+ }
+ blkioStats = append(blkioStats, cgroups.BlkioStatEntry{Major: major, Minor: minor, Op: op, Value: v})
+ }
+ if err := sc.Err(); err != nil {
+ return nil, &parseError{Path: dir, File: file, Err: err}
+ }
+
+ return blkioStats, nil
+}
+
+func (s *BlkioGroup) GetStats(path string, stats *cgroups.Stats) error {
+ type blkioStatInfo struct {
+ filename string
+ blkioStatEntriesPtr *[]cgroups.BlkioStatEntry
+ }
+ bfqDebugStats := []blkioStatInfo{
+ {
+ filename: "blkio.bfq.sectors_recursive",
+ blkioStatEntriesPtr: &stats.BlkioStats.SectorsRecursive,
+ },
+ {
+ filename: "blkio.bfq.io_service_time_recursive",
+ blkioStatEntriesPtr: &stats.BlkioStats.IoServiceTimeRecursive,
+ },
+ {
+ filename: "blkio.bfq.io_wait_time_recursive",
+ blkioStatEntriesPtr: &stats.BlkioStats.IoWaitTimeRecursive,
+ },
+ {
+ filename: "blkio.bfq.io_merged_recursive",
+ blkioStatEntriesPtr: &stats.BlkioStats.IoMergedRecursive,
+ },
+ {
+ filename: "blkio.bfq.io_queued_recursive",
+ blkioStatEntriesPtr: &stats.BlkioStats.IoQueuedRecursive,
+ },
+ {
+ filename: "blkio.bfq.time_recursive",
+ blkioStatEntriesPtr: &stats.BlkioStats.IoTimeRecursive,
+ },
+ {
+ filename: "blkio.bfq.io_serviced_recursive",
+ blkioStatEntriesPtr: &stats.BlkioStats.IoServicedRecursive,
+ },
+ {
+ filename: "blkio.bfq.io_service_bytes_recursive",
+ blkioStatEntriesPtr: &stats.BlkioStats.IoServiceBytesRecursive,
+ },
+ }
+ bfqStats := []blkioStatInfo{
+ {
+ filename: "blkio.bfq.io_serviced_recursive",
+ blkioStatEntriesPtr: &stats.BlkioStats.IoServicedRecursive,
+ },
+ {
+ filename: "blkio.bfq.io_service_bytes_recursive",
+ blkioStatEntriesPtr: &stats.BlkioStats.IoServiceBytesRecursive,
+ },
+ }
+ cfqStats := []blkioStatInfo{
+ {
+ filename: "blkio.sectors_recursive",
+ blkioStatEntriesPtr: &stats.BlkioStats.SectorsRecursive,
+ },
+ {
+ filename: "blkio.io_service_time_recursive",
+ blkioStatEntriesPtr: &stats.BlkioStats.IoServiceTimeRecursive,
+ },
+ {
+ filename: "blkio.io_wait_time_recursive",
+ blkioStatEntriesPtr: &stats.BlkioStats.IoWaitTimeRecursive,
+ },
+ {
+ filename: "blkio.io_merged_recursive",
+ blkioStatEntriesPtr: &stats.BlkioStats.IoMergedRecursive,
+ },
+ {
+ filename: "blkio.io_queued_recursive",
+ blkioStatEntriesPtr: &stats.BlkioStats.IoQueuedRecursive,
+ },
+ {
+ filename: "blkio.time_recursive",
+ blkioStatEntriesPtr: &stats.BlkioStats.IoTimeRecursive,
+ },
+ {
+ filename: "blkio.io_serviced_recursive",
+ blkioStatEntriesPtr: &stats.BlkioStats.IoServicedRecursive,
+ },
+ {
+ filename: "blkio.io_service_bytes_recursive",
+ blkioStatEntriesPtr: &stats.BlkioStats.IoServiceBytesRecursive,
+ },
+ }
+ throttleRecursiveStats := []blkioStatInfo{
+ {
+ filename: "blkio.throttle.io_serviced_recursive",
+ blkioStatEntriesPtr: &stats.BlkioStats.IoServicedRecursive,
+ },
+ {
+ filename: "blkio.throttle.io_service_bytes_recursive",
+ blkioStatEntriesPtr: &stats.BlkioStats.IoServiceBytesRecursive,
+ },
+ }
+ baseStats := []blkioStatInfo{
+ {
+ filename: "blkio.throttle.io_serviced",
+ blkioStatEntriesPtr: &stats.BlkioStats.IoServicedRecursive,
+ },
+ {
+ filename: "blkio.throttle.io_service_bytes",
+ blkioStatEntriesPtr: &stats.BlkioStats.IoServiceBytesRecursive,
+ },
+ }
+ orderedStats := [][]blkioStatInfo{
+ bfqDebugStats,
+ bfqStats,
+ cfqStats,
+ throttleRecursiveStats,
+ baseStats,
+ }
+
+ var blkioStats []cgroups.BlkioStatEntry
+ var err error
+
+ for _, statGroup := range orderedStats {
+ for i, statInfo := range statGroup {
+ if blkioStats, err = getBlkioStat(path, statInfo.filename); err != nil || blkioStats == nil {
+ // if error occurs on first file, move to next group
+ if i == 0 {
+ break
+ }
+ return err
+ }
+ *statInfo.blkioStatEntriesPtr = blkioStats
+ // finish if all stats are gathered
+ if i == len(statGroup)-1 {
+ return nil
+ }
+ }
+ }
+ return nil
+}
+
+func (s *BlkioGroup) detectWeightFilenames(path string) {
+ if s.weightFilename != "" {
+ // Already detected.
+ return
+ }
+ if cgroups.PathExists(filepath.Join(path, "blkio.weight")) {
+ s.weightFilename = "blkio.weight"
+ s.weightDeviceFilename = "blkio.weight_device"
+ } else {
+ s.weightFilename = "blkio.bfq.weight"
+ s.weightDeviceFilename = "blkio.bfq.weight_device"
+ }
+}
diff --git a/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/cpu.go b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/cpu.go
new file mode 100644
index 000000000..6c79f899b
--- /dev/null
+++ b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/cpu.go
@@ -0,0 +1,129 @@
+package fs
+
+import (
+ "bufio"
+ "errors"
+ "fmt"
+ "os"
+ "strconv"
+
+ "github.com/opencontainers/runc/libcontainer/cgroups"
+ "github.com/opencontainers/runc/libcontainer/cgroups/fscommon"
+ "github.com/opencontainers/runc/libcontainer/configs"
+ "golang.org/x/sys/unix"
+)
+
+type CpuGroup struct{}
+
+func (s *CpuGroup) Name() string {
+ return "cpu"
+}
+
+func (s *CpuGroup) Apply(path string, r *configs.Resources, pid int) error {
+ if err := os.MkdirAll(path, 0o755); err != nil {
+ return err
+ }
+ // We should set the real-Time group scheduling settings before moving
+ // in the process because if the process is already in SCHED_RR mode
+ // and no RT bandwidth is set, adding it will fail.
+ if err := s.SetRtSched(path, r); err != nil {
+ return err
+ }
+ // Since we are not using apply(), we need to place the pid
+ // into the procs file.
+ return cgroups.WriteCgroupProc(path, pid)
+}
+
+func (s *CpuGroup) SetRtSched(path string, r *configs.Resources) error {
+ if r.CpuRtPeriod != 0 {
+ if err := cgroups.WriteFile(path, "cpu.rt_period_us", strconv.FormatUint(r.CpuRtPeriod, 10)); err != nil {
+ return err
+ }
+ }
+ if r.CpuRtRuntime != 0 {
+ if err := cgroups.WriteFile(path, "cpu.rt_runtime_us", strconv.FormatInt(r.CpuRtRuntime, 10)); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func (s *CpuGroup) Set(path string, r *configs.Resources) error {
+ if r.CpuShares != 0 {
+ shares := r.CpuShares
+ if err := cgroups.WriteFile(path, "cpu.shares", strconv.FormatUint(shares, 10)); err != nil {
+ return err
+ }
+ // read it back
+ sharesRead, err := fscommon.GetCgroupParamUint(path, "cpu.shares")
+ if err != nil {
+ return err
+ }
+ // ... and check
+ if shares > sharesRead {
+ return fmt.Errorf("the maximum allowed cpu-shares is %d", sharesRead)
+ } else if shares < sharesRead {
+ return fmt.Errorf("the minimum allowed cpu-shares is %d", sharesRead)
+ }
+ }
+
+ var period string
+ if r.CpuPeriod != 0 {
+ period = strconv.FormatUint(r.CpuPeriod, 10)
+ if err := cgroups.WriteFile(path, "cpu.cfs_period_us", period); err != nil {
+ // Sometimes when the period to be set is smaller
+ // than the current one, it is rejected by the kernel
+ // (EINVAL) as old_quota/new_period exceeds the parent
+ // cgroup quota limit. If this happens and the quota is
+ // going to be set, ignore the error for now and retry
+ // after setting the quota.
+ if !errors.Is(err, unix.EINVAL) || r.CpuQuota == 0 {
+ return err
+ }
+ } else {
+ period = ""
+ }
+ }
+ if r.CpuQuota != 0 {
+ if err := cgroups.WriteFile(path, "cpu.cfs_quota_us", strconv.FormatInt(r.CpuQuota, 10)); err != nil {
+ return err
+ }
+ if period != "" {
+ if err := cgroups.WriteFile(path, "cpu.cfs_period_us", period); err != nil {
+ return err
+ }
+ }
+ }
+ return s.SetRtSched(path, r)
+}
+
+func (s *CpuGroup) GetStats(path string, stats *cgroups.Stats) error {
+ const file = "cpu.stat"
+ f, err := cgroups.OpenFile(path, file, os.O_RDONLY)
+ if err != nil {
+ if os.IsNotExist(err) {
+ return nil
+ }
+ return err
+ }
+ defer f.Close()
+
+ sc := bufio.NewScanner(f)
+ for sc.Scan() {
+ t, v, err := fscommon.ParseKeyValue(sc.Text())
+ if err != nil {
+ return &parseError{Path: path, File: file, Err: err}
+ }
+ switch t {
+ case "nr_periods":
+ stats.CpuStats.ThrottlingData.Periods = v
+
+ case "nr_throttled":
+ stats.CpuStats.ThrottlingData.ThrottledPeriods = v
+
+ case "throttled_time":
+ stats.CpuStats.ThrottlingData.ThrottledTime = v
+ }
+ }
+ return nil
+}
diff --git a/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/cpuacct.go b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/cpuacct.go
new file mode 100644
index 000000000..d3bd7e111
--- /dev/null
+++ b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/cpuacct.go
@@ -0,0 +1,166 @@
+package fs
+
+import (
+ "bufio"
+ "os"
+ "strconv"
+ "strings"
+
+ "github.com/opencontainers/runc/libcontainer/cgroups"
+ "github.com/opencontainers/runc/libcontainer/cgroups/fscommon"
+ "github.com/opencontainers/runc/libcontainer/configs"
+)
+
+const (
+ cgroupCpuacctStat = "cpuacct.stat"
+ cgroupCpuacctUsageAll = "cpuacct.usage_all"
+
+ nanosecondsInSecond = 1000000000
+
+ userModeColumn = 1
+ kernelModeColumn = 2
+ cuacctUsageAllColumnsNumber = 3
+
+ // The value comes from `C.sysconf(C._SC_CLK_TCK)`, and
+ // on Linux it's a constant which is safe to be hard coded,
+ // so we can avoid using cgo here. For details, see:
+ // https://github.com/containerd/cgroups/pull/12
+ clockTicks uint64 = 100
+)
+
+type CpuacctGroup struct{}
+
+func (s *CpuacctGroup) Name() string {
+ return "cpuacct"
+}
+
+func (s *CpuacctGroup) Apply(path string, _ *configs.Resources, pid int) error {
+ return apply(path, pid)
+}
+
+func (s *CpuacctGroup) Set(_ string, _ *configs.Resources) error {
+ return nil
+}
+
+func (s *CpuacctGroup) GetStats(path string, stats *cgroups.Stats) error {
+ if !cgroups.PathExists(path) {
+ return nil
+ }
+ userModeUsage, kernelModeUsage, err := getCpuUsageBreakdown(path)
+ if err != nil {
+ return err
+ }
+
+ totalUsage, err := fscommon.GetCgroupParamUint(path, "cpuacct.usage")
+ if err != nil {
+ return err
+ }
+
+ percpuUsage, err := getPercpuUsage(path)
+ if err != nil {
+ return err
+ }
+
+ percpuUsageInKernelmode, percpuUsageInUsermode, err := getPercpuUsageInModes(path)
+ if err != nil {
+ return err
+ }
+
+ stats.CpuStats.CpuUsage.TotalUsage = totalUsage
+ stats.CpuStats.CpuUsage.PercpuUsage = percpuUsage
+ stats.CpuStats.CpuUsage.PercpuUsageInKernelmode = percpuUsageInKernelmode
+ stats.CpuStats.CpuUsage.PercpuUsageInUsermode = percpuUsageInUsermode
+ stats.CpuStats.CpuUsage.UsageInUsermode = userModeUsage
+ stats.CpuStats.CpuUsage.UsageInKernelmode = kernelModeUsage
+ return nil
+}
+
+// Returns user and kernel usage breakdown in nanoseconds.
+func getCpuUsageBreakdown(path string) (uint64, uint64, error) {
+ var userModeUsage, kernelModeUsage uint64
+ const (
+ userField = "user"
+ systemField = "system"
+ file = cgroupCpuacctStat
+ )
+
+ // Expected format:
+ // user <usage in ticks>
+ // system <usage in ticks>
+ data, err := cgroups.ReadFile(path, file)
+ if err != nil {
+ return 0, 0, err
+ }
+ // TODO: use strings.SplitN instead.
+ fields := strings.Fields(data)
+ if len(fields) < 4 || fields[0] != userField || fields[2] != systemField {
+ return 0, 0, malformedLine(path, file, data)
+ }
+ if userModeUsage, err = strconv.ParseUint(fields[1], 10, 64); err != nil {
+ return 0, 0, &parseError{Path: path, File: file, Err: err}
+ }
+ if kernelModeUsage, err = strconv.ParseUint(fields[3], 10, 64); err != nil {
+ return 0, 0, &parseError{Path: path, File: file, Err: err}
+ }
+
+ return (userModeUsage * nanosecondsInSecond) / clockTicks, (kernelModeUsage * nanosecondsInSecond) / clockTicks, nil
+}
+
+func getPercpuUsage(path string) ([]uint64, error) {
+ const file = "cpuacct.usage_percpu"
+ percpuUsage := []uint64{}
+ data, err := cgroups.ReadFile(path, file)
+ if err != nil {
+ return percpuUsage, err
+ }
+ // TODO: use strings.SplitN instead.
+ for _, value := range strings.Fields(data) {
+ value, err := strconv.ParseUint(value, 10, 64)
+ if err != nil {
+ return percpuUsage, &parseError{Path: path, File: file, Err: err}
+ }
+ percpuUsage = append(percpuUsage, value)
+ }
+ return percpuUsage, nil
+}
+
+func getPercpuUsageInModes(path string) ([]uint64, []uint64, error) {
+ usageKernelMode := []uint64{}
+ usageUserMode := []uint64{}
+ const file = cgroupCpuacctUsageAll
+
+ fd, err := cgroups.OpenFile(path, file, os.O_RDONLY)
+ if os.IsNotExist(err) {
+ return usageKernelMode, usageUserMode, nil
+ } else if err != nil {
+ return nil, nil, err
+ }
+ defer fd.Close()
+
+ scanner := bufio.NewScanner(fd)
+ scanner.Scan() // skipping header line
+
+ for scanner.Scan() {
+ lineFields := strings.SplitN(scanner.Text(), " ", cuacctUsageAllColumnsNumber+1)
+ if len(lineFields) != cuacctUsageAllColumnsNumber {
+ continue
+ }
+
+ usageInKernelMode, err := strconv.ParseUint(lineFields[kernelModeColumn], 10, 64)
+ if err != nil {
+ return nil, nil, &parseError{Path: path, File: file, Err: err}
+ }
+ usageKernelMode = append(usageKernelMode, usageInKernelMode)
+
+ usageInUserMode, err := strconv.ParseUint(lineFields[userModeColumn], 10, 64)
+ if err != nil {
+ return nil, nil, &parseError{Path: path, File: file, Err: err}
+ }
+ usageUserMode = append(usageUserMode, usageInUserMode)
+ }
+ if err := scanner.Err(); err != nil {
+ return nil, nil, &parseError{Path: path, File: file, Err: err}
+ }
+
+ return usageKernelMode, usageUserMode, nil
+}
diff --git a/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/cpuset.go b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/cpuset.go
new file mode 100644
index 000000000..550baa427
--- /dev/null
+++ b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/cpuset.go
@@ -0,0 +1,245 @@
+package fs
+
+import (
+ "errors"
+ "os"
+ "path/filepath"
+ "strconv"
+ "strings"
+
+ "golang.org/x/sys/unix"
+
+ "github.com/opencontainers/runc/libcontainer/cgroups"
+ "github.com/opencontainers/runc/libcontainer/cgroups/fscommon"
+ "github.com/opencontainers/runc/libcontainer/configs"
+)
+
+type CpusetGroup struct{}
+
+func (s *CpusetGroup) Name() string {
+ return "cpuset"
+}
+
+func (s *CpusetGroup) Apply(path string, r *configs.Resources, pid int) error {
+ return s.ApplyDir(path, r, pid)
+}
+
+func (s *CpusetGroup) Set(path string, r *configs.Resources) error {
+ if r.CpusetCpus != "" {
+ if err := cgroups.WriteFile(path, "cpuset.cpus", r.CpusetCpus); err != nil {
+ return err
+ }
+ }
+ if r.CpusetMems != "" {
+ if err := cgroups.WriteFile(path, "cpuset.mems", r.CpusetMems); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func getCpusetStat(path string, file string) ([]uint16, error) {
+ var extracted []uint16
+ fileContent, err := fscommon.GetCgroupParamString(path, file)
+ if err != nil {
+ return extracted, err
+ }
+ if len(fileContent) == 0 {
+ return extracted, &parseError{Path: path, File: file, Err: errors.New("empty file")}
+ }
+
+ for _, s := range strings.Split(fileContent, ",") {
+ sp := strings.SplitN(s, "-", 3)
+ switch len(sp) {
+ case 3:
+ return extracted, &parseError{Path: path, File: file, Err: errors.New("extra dash")}
+ case 2:
+ min, err := strconv.ParseUint(sp[0], 10, 16)
+ if err != nil {
+ return extracted, &parseError{Path: path, File: file, Err: err}
+ }
+ max, err := strconv.ParseUint(sp[1], 10, 16)
+ if err != nil {
+ return extracted, &parseError{Path: path, File: file, Err: err}
+ }
+ if min > max {
+ return extracted, &parseError{Path: path, File: file, Err: errors.New("invalid values, min > max")}
+ }
+ for i := min; i <= max; i++ {
+ extracted = append(extracted, uint16(i))
+ }
+ case 1:
+ value, err := strconv.ParseUint(s, 10, 16)
+ if err != nil {
+ return extracted, &parseError{Path: path, File: file, Err: err}
+ }
+ extracted = append(extracted, uint16(value))
+ }
+ }
+
+ return extracted, nil
+}
+
+func (s *CpusetGroup) GetStats(path string, stats *cgroups.Stats) error {
+ var err error
+
+ stats.CPUSetStats.CPUs, err = getCpusetStat(path, "cpuset.cpus")
+ if err != nil && !errors.Is(err, os.ErrNotExist) {
+ return err
+ }
+
+ stats.CPUSetStats.CPUExclusive, err = fscommon.GetCgroupParamUint(path, "cpuset.cpu_exclusive")
+ if err != nil && !errors.Is(err, os.ErrNotExist) {
+ return err
+ }
+
+ stats.CPUSetStats.Mems, err = getCpusetStat(path, "cpuset.mems")
+ if err != nil && !errors.Is(err, os.ErrNotExist) {
+ return err
+ }
+
+ stats.CPUSetStats.MemHardwall, err = fscommon.GetCgroupParamUint(path, "cpuset.mem_hardwall")
+ if err != nil && !errors.Is(err, os.ErrNotExist) {
+ return err
+ }
+
+ stats.CPUSetStats.MemExclusive, err = fscommon.GetCgroupParamUint(path, "cpuset.mem_exclusive")
+ if err != nil && !errors.Is(err, os.ErrNotExist) {
+ return err
+ }
+
+ stats.CPUSetStats.MemoryMigrate, err = fscommon.GetCgroupParamUint(path, "cpuset.memory_migrate")
+ if err != nil && !errors.Is(err, os.ErrNotExist) {
+ return err
+ }
+
+ stats.CPUSetStats.MemorySpreadPage, err = fscommon.GetCgroupParamUint(path, "cpuset.memory_spread_page")
+ if err != nil && !errors.Is(err, os.ErrNotExist) {
+ return err
+ }
+
+ stats.CPUSetStats.MemorySpreadSlab, err = fscommon.GetCgroupParamUint(path, "cpuset.memory_spread_slab")
+ if err != nil && !errors.Is(err, os.ErrNotExist) {
+ return err
+ }
+
+ stats.CPUSetStats.MemoryPressure, err = fscommon.GetCgroupParamUint(path, "cpuset.memory_pressure")
+ if err != nil && !errors.Is(err, os.ErrNotExist) {
+ return err
+ }
+
+ stats.CPUSetStats.SchedLoadBalance, err = fscommon.GetCgroupParamUint(path, "cpuset.sched_load_balance")
+ if err != nil && !errors.Is(err, os.ErrNotExist) {
+ return err
+ }
+
+ stats.CPUSetStats.SchedRelaxDomainLevel, err = fscommon.GetCgroupParamInt(path, "cpuset.sched_relax_domain_level")
+ if err != nil && !errors.Is(err, os.ErrNotExist) {
+ return err
+ }
+
+ return nil
+}
+
+func (s *CpusetGroup) ApplyDir(dir string, r *configs.Resources, pid int) error {
+ // This might happen if we have no cpuset cgroup mounted.
+ // Just do nothing and don't fail.
+ if dir == "" {
+ return nil
+ }
+ // 'ensureParent' start with parent because we don't want to
+ // explicitly inherit from parent, it could conflict with
+ // 'cpuset.cpu_exclusive'.
+ if err := cpusetEnsureParent(filepath.Dir(dir)); err != nil {
+ return err
+ }
+ if err := os.Mkdir(dir, 0o755); err != nil && !os.IsExist(err) {
+ return err
+ }
+ // We didn't inherit cpuset configs from parent, but we have
+ // to ensure cpuset configs are set before moving task into the
+ // cgroup.
+ // The logic is, if user specified cpuset configs, use these
+ // specified configs, otherwise, inherit from parent. This makes
+ // cpuset configs work correctly with 'cpuset.cpu_exclusive', and
+ // keep backward compatibility.
+ if err := s.ensureCpusAndMems(dir, r); err != nil {
+ return err
+ }
+ // Since we are not using apply(), we need to place the pid
+ // into the procs file.
+ return cgroups.WriteCgroupProc(dir, pid)
+}
+
+func getCpusetSubsystemSettings(parent string) (cpus, mems string, err error) {
+ if cpus, err = cgroups.ReadFile(parent, "cpuset.cpus"); err != nil {
+ return
+ }
+ if mems, err = cgroups.ReadFile(parent, "cpuset.mems"); err != nil {
+ return
+ }
+ return cpus, mems, nil
+}
+
+// cpusetEnsureParent makes sure that the parent directories of current
+// are created and populated with the proper cpus and mems files copied
+// from their respective parent. It does that recursively, starting from
+// the top of the cpuset hierarchy (i.e. cpuset cgroup mount point).
+func cpusetEnsureParent(current string) error {
+ var st unix.Statfs_t
+
+ parent := filepath.Dir(current)
+ err := unix.Statfs(parent, &st)
+ if err == nil && st.Type != unix.CGROUP_SUPER_MAGIC {
+ return nil
+ }
+ // Treat non-existing directory as cgroupfs as it will be created,
+ // and the root cpuset directory obviously exists.
+ if err != nil && err != unix.ENOENT { //nolint:errorlint // unix errors are bare
+ return &os.PathError{Op: "statfs", Path: parent, Err: err}
+ }
+
+ if err := cpusetEnsureParent(parent); err != nil {
+ return err
+ }
+ if err := os.Mkdir(current, 0o755); err != nil && !os.IsExist(err) {
+ return err
+ }
+ return cpusetCopyIfNeeded(current, parent)
+}
+
+// cpusetCopyIfNeeded copies the cpuset.cpus and cpuset.mems from the parent
+// directory to the current directory if the file's contents are 0
+func cpusetCopyIfNeeded(current, parent string) error {
+ currentCpus, currentMems, err := getCpusetSubsystemSettings(current)
+ if err != nil {
+ return err
+ }
+ parentCpus, parentMems, err := getCpusetSubsystemSettings(parent)
+ if err != nil {
+ return err
+ }
+
+ if isEmptyCpuset(currentCpus) {
+ if err := cgroups.WriteFile(current, "cpuset.cpus", parentCpus); err != nil {
+ return err
+ }
+ }
+ if isEmptyCpuset(currentMems) {
+ if err := cgroups.WriteFile(current, "cpuset.mems", parentMems); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func isEmptyCpuset(str string) bool {
+ return str == "" || str == "\n"
+}
+
+func (s *CpusetGroup) ensureCpusAndMems(path string, r *configs.Resources) error {
+ if err := s.Set(path, r); err != nil {
+ return err
+ }
+ return cpusetCopyIfNeeded(path, filepath.Dir(path))
+}
diff --git a/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/devices.go b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/devices.go
new file mode 100644
index 000000000..0bf3d9deb
--- /dev/null
+++ b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/devices.go
@@ -0,0 +1,39 @@
+package fs
+
+import (
+ "github.com/opencontainers/runc/libcontainer/cgroups"
+ "github.com/opencontainers/runc/libcontainer/configs"
+)
+
+type DevicesGroup struct{}
+
+func (s *DevicesGroup) Name() string {
+ return "devices"
+}
+
+func (s *DevicesGroup) Apply(path string, r *configs.Resources, pid int) error {
+ if r.SkipDevices {
+ return nil
+ }
+ if path == "" {
+ // Return error here, since devices cgroup
+ // is a hard requirement for container's security.
+ return errSubsystemDoesNotExist
+ }
+
+ return apply(path, pid)
+}
+
+func (s *DevicesGroup) Set(path string, r *configs.Resources) error {
+ if cgroups.DevicesSetV1 == nil {
+ if len(r.Devices) == 0 {
+ return nil
+ }
+ return cgroups.ErrDevicesUnsupported
+ }
+ return cgroups.DevicesSetV1(path, r)
+}
+
+func (s *DevicesGroup) GetStats(path string, stats *cgroups.Stats) error {
+ return nil
+}
diff --git a/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/error.go b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/error.go
new file mode 100644
index 000000000..f2ab6f130
--- /dev/null
+++ b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/error.go
@@ -0,0 +1,15 @@
+package fs
+
+import (
+ "fmt"
+
+ "github.com/opencontainers/runc/libcontainer/cgroups/fscommon"
+)
+
+type parseError = fscommon.ParseError
+
+// malformedLine is used by all cgroupfs file parsers that expect a line
+// in a particular format but get some garbage instead.
+func malformedLine(path, file, line string) error {
+ return &parseError{Path: path, File: file, Err: fmt.Errorf("malformed line: %s", line)}
+}
diff --git a/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/freezer.go b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/freezer.go
new file mode 100644
index 000000000..987f1bf5e
--- /dev/null
+++ b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/freezer.go
@@ -0,0 +1,158 @@
+package fs
+
+import (
+ "errors"
+ "fmt"
+ "os"
+ "strings"
+ "time"
+
+ "github.com/opencontainers/runc/libcontainer/cgroups"
+ "github.com/opencontainers/runc/libcontainer/configs"
+ "github.com/sirupsen/logrus"
+ "golang.org/x/sys/unix"
+)
+
+type FreezerGroup struct{}
+
+func (s *FreezerGroup) Name() string {
+ return "freezer"
+}
+
+func (s *FreezerGroup) Apply(path string, _ *configs.Resources, pid int) error {
+ return apply(path, pid)
+}
+
+func (s *FreezerGroup) Set(path string, r *configs.Resources) (Err error) {
+ switch r.Freezer {
+ case configs.Frozen:
+ defer func() {
+ if Err != nil {
+ // Freezing failed, and it is bad and dangerous
+ // to leave the cgroup in FROZEN or FREEZING
+ // state, so (try to) thaw it back.
+ _ = cgroups.WriteFile(path, "freezer.state", string(configs.Thawed))
+ }
+ }()
+
+ // As per older kernel docs (freezer-subsystem.txt before
+ // kernel commit ef9fe980c6fcc1821), if FREEZING is seen,
+ // userspace should either retry or thaw. While current
+ // kernel cgroup v1 docs no longer mention a need to retry,
+ // even a recent kernel (v5.4, Ubuntu 20.04) can't reliably
+ // freeze a cgroup v1 while new processes keep appearing in it
+ // (either via fork/clone or by writing new PIDs to
+ // cgroup.procs).
+ //
+ // The numbers below are empirically chosen to have a decent
+ // chance to succeed in various scenarios ("runc pause/unpause
+ // with parallel runc exec" and "bare freeze/unfreeze on a very
+ // slow system"), tested on RHEL7 and Ubuntu 20.04 kernels.
+ //
+ // Adding any amount of sleep in between retries did not
+ // increase the chances of successful freeze in "pause/unpause
+ // with parallel exec" reproducer. OTOH, adding an occasional
+ // sleep helped for the case where the system is extremely slow
+ // (CentOS 7 VM on GHA CI).
+ //
+ // Alas, this is still a game of chances, since the real fix
+ // belong to the kernel (cgroup v2 do not have this bug).
+
+ for i := 0; i < 1000; i++ {
+ if i%50 == 49 {
+ // Occasional thaw and sleep improves
+ // the chances to succeed in freezing
+ // in case new processes keep appearing
+ // in the cgroup.
+ _ = cgroups.WriteFile(path, "freezer.state", string(configs.Thawed))
+ time.Sleep(10 * time.Millisecond)
+ }
+
+ if err := cgroups.WriteFile(path, "freezer.state", string(configs.Frozen)); err != nil {
+ return err
+ }
+
+ if i%25 == 24 {
+ // Occasional short sleep before reading
+ // the state back also improves the chances to
+ // succeed in freezing in case of a very slow
+ // system.
+ time.Sleep(10 * time.Microsecond)
+ }
+ state, err := cgroups.ReadFile(path, "freezer.state")
+ if err != nil {
+ return err
+ }
+ state = strings.TrimSpace(state)
+ switch state {
+ case "FREEZING":
+ continue
+ case string(configs.Frozen):
+ if i > 1 {
+ logrus.Debugf("frozen after %d retries", i)
+ }
+ return nil
+ default:
+ // should never happen
+ return fmt.Errorf("unexpected state %s while freezing", strings.TrimSpace(state))
+ }
+ }
+ // Despite our best efforts, it got stuck in FREEZING.
+ return errors.New("unable to freeze")
+ case configs.Thawed:
+ return cgroups.WriteFile(path, "freezer.state", string(configs.Thawed))
+ case configs.Undefined:
+ return nil
+ default:
+ return fmt.Errorf("Invalid argument '%s' to freezer.state", string(r.Freezer))
+ }
+}
+
+func (s *FreezerGroup) GetStats(path string, stats *cgroups.Stats) error {
+ return nil
+}
+
+func (s *FreezerGroup) GetState(path string) (configs.FreezerState, error) {
+ for {
+ state, err := cgroups.ReadFile(path, "freezer.state")
+ if err != nil {
+ // If the kernel is too old, then we just treat the freezer as
+ // being in an "undefined" state.
+ if os.IsNotExist(err) || errors.Is(err, unix.ENODEV) {
+ err = nil
+ }
+ return configs.Undefined, err
+ }
+ switch strings.TrimSpace(state) {
+ case "THAWED":
+ return configs.Thawed, nil
+ case "FROZEN":
+ // Find out whether the cgroup is frozen directly,
+ // or indirectly via an ancestor.
+ self, err := cgroups.ReadFile(path, "freezer.self_freezing")
+ if err != nil {
+ // If the kernel is too old, then we just treat
+ // it as being frozen.
+ if errors.Is(err, os.ErrNotExist) || errors.Is(err, unix.ENODEV) {
+ err = nil
+ }
+ return configs.Frozen, err
+ }
+ switch self {
+ case "0\n":
+ return configs.Thawed, nil
+ case "1\n":
+ return configs.Frozen, nil
+ default:
+ return configs.Undefined, fmt.Errorf(`unknown "freezer.self_freezing" state: %q`, self)
+ }
+ case "FREEZING":
+ // Make sure we get a stable freezer state, so retry if the cgroup
+ // is still undergoing freezing. This should be a temporary delay.
+ time.Sleep(1 * time.Millisecond)
+ continue
+ default:
+ return configs.Undefined, fmt.Errorf("unknown freezer.state %q", state)
+ }
+ }
+}
diff --git a/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/fs.go b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/fs.go
new file mode 100644
index 000000000..be4dcc341
--- /dev/null
+++ b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/fs.go
@@ -0,0 +1,264 @@
+package fs
+
+import (
+ "errors"
+ "fmt"
+ "os"
+ "sync"
+
+ "golang.org/x/sys/unix"
+
+ "github.com/opencontainers/runc/libcontainer/cgroups"
+ "github.com/opencontainers/runc/libcontainer/cgroups/fscommon"
+ "github.com/opencontainers/runc/libcontainer/configs"
+)
+
+var subsystems = []subsystem{
+ &CpusetGroup{},
+ &DevicesGroup{},
+ &MemoryGroup{},
+ &CpuGroup{},
+ &CpuacctGroup{},
+ &PidsGroup{},
+ &BlkioGroup{},
+ &HugetlbGroup{},
+ &NetClsGroup{},
+ &NetPrioGroup{},
+ &PerfEventGroup{},
+ &FreezerGroup{},
+ &RdmaGroup{},
+ &NameGroup{GroupName: "name=systemd", Join: true},
+}
+
+var errSubsystemDoesNotExist = errors.New("cgroup: subsystem does not exist")
+
+func init() {
+ // If using cgroups-hybrid mode then add a "" controller indicating
+ // it should join the cgroups v2.
+ if cgroups.IsCgroup2HybridMode() {
+ subsystems = append(subsystems, &NameGroup{GroupName: "", Join: true})
+ }
+}
+
+type subsystem interface {
+ // Name returns the name of the subsystem.
+ Name() string
+ // GetStats fills in the stats for the subsystem.
+ GetStats(path string, stats *cgroups.Stats) error
+ // Apply creates and joins a cgroup, adding pid into it. Some
+ // subsystems use resources to pre-configure the cgroup parents
+ // before creating or joining it.
+ Apply(path string, r *configs.Resources, pid int) error
+ // Set sets the cgroup resources.
+ Set(path string, r *configs.Resources) error
+}
+
+type manager struct {
+ mu sync.Mutex
+ cgroups *configs.Cgroup
+ paths map[string]string
+}
+
+func NewManager(cg *configs.Cgroup, paths map[string]string) (cgroups.Manager, error) {
+ // Some v1 controllers (cpu, cpuset, and devices) expect
+ // cgroups.Resources to not be nil in Apply.
+ if cg.Resources == nil {
+ return nil, errors.New("cgroup v1 manager needs configs.Resources to be set during manager creation")
+ }
+ if cg.Resources.Unified != nil {
+ return nil, cgroups.ErrV1NoUnified
+ }
+
+ if paths == nil {
+ var err error
+ paths, err = initPaths(cg)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ return &manager{
+ cgroups: cg,
+ paths: paths,
+ }, nil
+}
+
+// isIgnorableError returns whether err is a permission error (in the loose
+// sense of the word). This includes EROFS (which for an unprivileged user is
+// basically a permission error) and EACCES (for similar reasons) as well as
+// the normal EPERM.
+func isIgnorableError(rootless bool, err error) bool {
+ // We do not ignore errors if we are root.
+ if !rootless {
+ return false
+ }
+ // Is it an ordinary EPERM?
+ if errors.Is(err, os.ErrPermission) {
+ return true
+ }
+ // Handle some specific syscall errors.
+ var errno unix.Errno
+ if errors.As(err, &errno) {
+ return errno == unix.EROFS || errno == unix.EPERM || errno == unix.EACCES
+ }
+ return false
+}
+
+func (m *manager) Apply(pid int) (err error) {
+ m.mu.Lock()
+ defer m.mu.Unlock()
+
+ c := m.cgroups
+
+ for _, sys := range subsystems {
+ name := sys.Name()
+ p, ok := m.paths[name]
+ if !ok {
+ continue
+ }
+
+ if err := sys.Apply(p, c.Resources, pid); err != nil {
+ // In the case of rootless (including euid=0 in userns), where an
+ // explicit cgroup path hasn't been set, we don't bail on error in
+ // case of permission problems here, but do delete the path from
+ // the m.paths map, since it is either non-existent and could not
+ // be created, or the pid could not be added to it.
+ //
+ // Cases where limits for the subsystem have been set are handled
+ // later by Set, which fails with a friendly error (see
+ // if path == "" in Set).
+ if isIgnorableError(c.Rootless, err) && c.Path == "" {
+ delete(m.paths, name)
+ continue
+ }
+ return err
+ }
+
+ }
+ return nil
+}
+
+func (m *manager) Destroy() error {
+ m.mu.Lock()
+ defer m.mu.Unlock()
+ return cgroups.RemovePaths(m.paths)
+}
+
+func (m *manager) Path(subsys string) string {
+ m.mu.Lock()
+ defer m.mu.Unlock()
+ return m.paths[subsys]
+}
+
+func (m *manager) GetStats() (*cgroups.Stats, error) {
+ m.mu.Lock()
+ defer m.mu.Unlock()
+ stats := cgroups.NewStats()
+ for _, sys := range subsystems {
+ path := m.paths[sys.Name()]
+ if path == "" {
+ continue
+ }
+ if err := sys.GetStats(path, stats); err != nil {
+ return nil, err
+ }
+ }
+ return stats, nil
+}
+
+func (m *manager) Set(r *configs.Resources) error {
+ if r == nil {
+ return nil
+ }
+
+ if r.Unified != nil {
+ return cgroups.ErrV1NoUnified
+ }
+
+ m.mu.Lock()
+ defer m.mu.Unlock()
+ for _, sys := range subsystems {
+ path := m.paths[sys.Name()]
+ if err := sys.Set(path, r); err != nil {
+ // When rootless is true, errors from the device subsystem
+ // are ignored, as it is really not expected to work.
+ if m.cgroups.Rootless && sys.Name() == "devices" && !errors.Is(err, cgroups.ErrDevicesUnsupported) {
+ continue
+ }
+ // However, errors from other subsystems are not ignored.
+ // see @test "runc create (rootless + limits + no cgrouppath + no permission) fails with informative error"
+ if path == "" {
+ // We never created a path for this cgroup, so we cannot set
+ // limits for it (though we have already tried at this point).
+ return fmt.Errorf("cannot set %s limit: container could not join or create cgroup", sys.Name())
+ }
+ return err
+ }
+ }
+
+ return nil
+}
+
+// Freeze toggles the container's freezer cgroup depending on the state
+// provided
+func (m *manager) Freeze(state configs.FreezerState) error {
+ path := m.Path("freezer")
+ if path == "" {
+ return errors.New("cannot toggle freezer: cgroups not configured for container")
+ }
+
+ prevState := m.cgroups.Resources.Freezer
+ m.cgroups.Resources.Freezer = state
+ freezer := &FreezerGroup{}
+ if err := freezer.Set(path, m.cgroups.Resources); err != nil {
+ m.cgroups.Resources.Freezer = prevState
+ return err
+ }
+ return nil
+}
+
+func (m *manager) GetPids() ([]int, error) {
+ return cgroups.GetPids(m.Path("devices"))
+}
+
+func (m *manager) GetAllPids() ([]int, error) {
+ return cgroups.GetAllPids(m.Path("devices"))
+}
+
+func (m *manager) GetPaths() map[string]string {
+ m.mu.Lock()
+ defer m.mu.Unlock()
+ return m.paths
+}
+
+func (m *manager) GetCgroups() (*configs.Cgroup, error) {
+ return m.cgroups, nil
+}
+
+func (m *manager) GetFreezerState() (configs.FreezerState, error) {
+ dir := m.Path("freezer")
+ // If the container doesn't have the freezer cgroup, say it's undefined.
+ if dir == "" {
+ return configs.Undefined, nil
+ }
+ freezer := &FreezerGroup{}
+ return freezer.GetState(dir)
+}
+
+func (m *manager) Exists() bool {
+ return cgroups.PathExists(m.Path("devices"))
+}
+
+func OOMKillCount(path string) (uint64, error) {
+ return fscommon.GetValueByKey(path, "memory.oom_control", "oom_kill")
+}
+
+func (m *manager) OOMKillCount() (uint64, error) {
+ c, err := OOMKillCount(m.Path("memory"))
+ // Ignore ENOENT when rootless as it couldn't create cgroup.
+ if err != nil && m.cgroups.Rootless && os.IsNotExist(err) {
+ err = nil
+ }
+
+ return c, err
+}
diff --git a/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/hugetlb.go b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/hugetlb.go
new file mode 100644
index 000000000..8ddd6fdd8
--- /dev/null
+++ b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/hugetlb.go
@@ -0,0 +1,62 @@
+package fs
+
+import (
+ "strconv"
+
+ "github.com/opencontainers/runc/libcontainer/cgroups"
+ "github.com/opencontainers/runc/libcontainer/cgroups/fscommon"
+ "github.com/opencontainers/runc/libcontainer/configs"
+)
+
+type HugetlbGroup struct{}
+
+func (s *HugetlbGroup) Name() string {
+ return "hugetlb"
+}
+
+func (s *HugetlbGroup) Apply(path string, _ *configs.Resources, pid int) error {
+ return apply(path, pid)
+}
+
+func (s *HugetlbGroup) Set(path string, r *configs.Resources) error {
+ for _, hugetlb := range r.HugetlbLimit {
+ if err := cgroups.WriteFile(path, "hugetlb."+hugetlb.Pagesize+".limit_in_bytes", strconv.FormatUint(hugetlb.Limit, 10)); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+func (s *HugetlbGroup) GetStats(path string, stats *cgroups.Stats) error {
+ if !cgroups.PathExists(path) {
+ return nil
+ }
+ hugetlbStats := cgroups.HugetlbStats{}
+ for _, pageSize := range cgroups.HugePageSizes() {
+ usage := "hugetlb." + pageSize + ".usage_in_bytes"
+ value, err := fscommon.GetCgroupParamUint(path, usage)
+ if err != nil {
+ return err
+ }
+ hugetlbStats.Usage = value
+
+ maxUsage := "hugetlb." + pageSize + ".max_usage_in_bytes"
+ value, err = fscommon.GetCgroupParamUint(path, maxUsage)
+ if err != nil {
+ return err
+ }
+ hugetlbStats.MaxUsage = value
+
+ failcnt := "hugetlb." + pageSize + ".failcnt"
+ value, err = fscommon.GetCgroupParamUint(path, failcnt)
+ if err != nil {
+ return err
+ }
+ hugetlbStats.Failcnt = value
+
+ stats.HugetlbStats[pageSize] = hugetlbStats
+ }
+
+ return nil
+}
diff --git a/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/memory.go b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/memory.go
new file mode 100644
index 000000000..b7c75f941
--- /dev/null
+++ b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/memory.go
@@ -0,0 +1,348 @@
+package fs
+
+import (
+ "bufio"
+ "errors"
+ "fmt"
+ "math"
+ "os"
+ "path/filepath"
+ "strconv"
+ "strings"
+
+ "golang.org/x/sys/unix"
+
+ "github.com/opencontainers/runc/libcontainer/cgroups"
+ "github.com/opencontainers/runc/libcontainer/cgroups/fscommon"
+ "github.com/opencontainers/runc/libcontainer/configs"
+)
+
+const (
+ cgroupMemorySwapLimit = "memory.memsw.limit_in_bytes"
+ cgroupMemoryLimit = "memory.limit_in_bytes"
+ cgroupMemoryUsage = "memory.usage_in_bytes"
+ cgroupMemoryMaxUsage = "memory.max_usage_in_bytes"
+)
+
+type MemoryGroup struct{}
+
+func (s *MemoryGroup) Name() string {
+ return "memory"
+}
+
+func (s *MemoryGroup) Apply(path string, _ *configs.Resources, pid int) error {
+ return apply(path, pid)
+}
+
+func setMemory(path string, val int64) error {
+ if val == 0 {
+ return nil
+ }
+
+ err := cgroups.WriteFile(path, cgroupMemoryLimit, strconv.FormatInt(val, 10))
+ if !errors.Is(err, unix.EBUSY) {
+ return err
+ }
+
+ // EBUSY means the kernel can't set new limit as it's too low
+ // (lower than the current usage). Return more specific error.
+ usage, err := fscommon.GetCgroupParamUint(path, cgroupMemoryUsage)
+ if err != nil {
+ return err
+ }
+ max, err := fscommon.GetCgroupParamUint(path, cgroupMemoryMaxUsage)
+ if err != nil {
+ return err
+ }
+
+ return fmt.Errorf("unable to set memory limit to %d (current usage: %d, peak usage: %d)", val, usage, max)
+}
+
+func setSwap(path string, val int64) error {
+ if val == 0 {
+ return nil
+ }
+
+ return cgroups.WriteFile(path, cgroupMemorySwapLimit, strconv.FormatInt(val, 10))
+}
+
+func setMemoryAndSwap(path string, r *configs.Resources) error {
+ // If the memory update is set to -1 and the swap is not explicitly
+ // set, we should also set swap to -1, it means unlimited memory.
+ if r.Memory == -1 && r.MemorySwap == 0 {
+ // Only set swap if it's enabled in kernel
+ if cgroups.PathExists(filepath.Join(path, cgroupMemorySwapLimit)) {
+ r.MemorySwap = -1
+ }
+ }
+
+ // When memory and swap memory are both set, we need to handle the cases
+ // for updating container.
+ if r.Memory != 0 && r.MemorySwap != 0 {
+ curLimit, err := fscommon.GetCgroupParamUint(path, cgroupMemoryLimit)
+ if err != nil {
+ return err
+ }
+
+ // When update memory limit, we should adapt the write sequence
+ // for memory and swap memory, so it won't fail because the new
+ // value and the old value don't fit kernel's validation.
+ if r.MemorySwap == -1 || curLimit < uint64(r.MemorySwap) {
+ if err := setSwap(path, r.MemorySwap); err != nil {
+ return err
+ }
+ if err := setMemory(path, r.Memory); err != nil {
+ return err
+ }
+ return nil
+ }
+ }
+
+ if err := setMemory(path, r.Memory); err != nil {
+ return err
+ }
+ if err := setSwap(path, r.MemorySwap); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func (s *MemoryGroup) Set(path string, r *configs.Resources) error {
+ if err := setMemoryAndSwap(path, r); err != nil {
+ return err
+ }
+
+ // ignore KernelMemory and KernelMemoryTCP
+
+ if r.MemoryReservation != 0 {
+ if err := cgroups.WriteFile(path, "memory.soft_limit_in_bytes", strconv.FormatInt(r.MemoryReservation, 10)); err != nil {
+ return err
+ }
+ }
+
+ if r.OomKillDisable {
+ if err := cgroups.WriteFile(path, "memory.oom_control", "1"); err != nil {
+ return err
+ }
+ }
+ if r.MemorySwappiness == nil || int64(*r.MemorySwappiness) == -1 {
+ return nil
+ } else if *r.MemorySwappiness <= 100 {
+ if err := cgroups.WriteFile(path, "memory.swappiness", strconv.FormatUint(*r.MemorySwappiness, 10)); err != nil {
+ return err
+ }
+ } else {
+ return fmt.Errorf("invalid memory swappiness value: %d (valid range is 0-100)", *r.MemorySwappiness)
+ }
+
+ return nil
+}
+
+func (s *MemoryGroup) GetStats(path string, stats *cgroups.Stats) error {
+ const file = "memory.stat"
+ statsFile, err := cgroups.OpenFile(path, file, os.O_RDONLY)
+ if err != nil {
+ if os.IsNotExist(err) {
+ return nil
+ }
+ return err
+ }
+ defer statsFile.Close()
+
+ sc := bufio.NewScanner(statsFile)
+ for sc.Scan() {
+ t, v, err := fscommon.ParseKeyValue(sc.Text())
+ if err != nil {
+ return &parseError{Path: path, File: file, Err: err}
+ }
+ stats.MemoryStats.Stats[t] = v
+ }
+ stats.MemoryStats.Cache = stats.MemoryStats.Stats["cache"]
+
+ memoryUsage, err := getMemoryData(path, "")
+ if err != nil {
+ return err
+ }
+ stats.MemoryStats.Usage = memoryUsage
+ swapUsage, err := getMemoryData(path, "memsw")
+ if err != nil {
+ return err
+ }
+ stats.MemoryStats.SwapUsage = swapUsage
+ kernelUsage, err := getMemoryData(path, "kmem")
+ if err != nil {
+ return err
+ }
+ stats.MemoryStats.KernelUsage = kernelUsage
+ kernelTCPUsage, err := getMemoryData(path, "kmem.tcp")
+ if err != nil {
+ return err
+ }
+ stats.MemoryStats.KernelTCPUsage = kernelTCPUsage
+
+ value, err := fscommon.GetCgroupParamUint(path, "memory.use_hierarchy")
+ if err != nil {
+ return err
+ }
+ if value == 1 {
+ stats.MemoryStats.UseHierarchy = true
+ }
+
+ pagesByNUMA, err := getPageUsageByNUMA(path)
+ if err != nil {
+ return err
+ }
+ stats.MemoryStats.PageUsageByNUMA = pagesByNUMA
+
+ return nil
+}
+
+func getMemoryData(path, name string) (cgroups.MemoryData, error) {
+ memoryData := cgroups.MemoryData{}
+
+ moduleName := "memory"
+ if name != "" {
+ moduleName = "memory." + name
+ }
+ var (
+ usage = moduleName + ".usage_in_bytes"
+ maxUsage = moduleName + ".max_usage_in_bytes"
+ failcnt = moduleName + ".failcnt"
+ limit = moduleName + ".limit_in_bytes"
+ )
+
+ value, err := fscommon.GetCgroupParamUint(path, usage)
+ if err != nil {
+ if name != "" && os.IsNotExist(err) {
+ // Ignore ENOENT as swap and kmem controllers
+ // are optional in the kernel.
+ return cgroups.MemoryData{}, nil
+ }
+ return cgroups.MemoryData{}, err
+ }
+ memoryData.Usage = value
+ value, err = fscommon.GetCgroupParamUint(path, maxUsage)
+ if err != nil {
+ return cgroups.MemoryData{}, err
+ }
+ memoryData.MaxUsage = value
+ value, err = fscommon.GetCgroupParamUint(path, failcnt)
+ if err != nil {
+ return cgroups.MemoryData{}, err
+ }
+ memoryData.Failcnt = value
+ value, err = fscommon.GetCgroupParamUint(path, limit)
+ if err != nil {
+ return cgroups.MemoryData{}, err
+ }
+ memoryData.Limit = value
+
+ return memoryData, nil
+}
+
+func getPageUsageByNUMA(path string) (cgroups.PageUsageByNUMA, error) {
+ const (
+ maxColumns = math.MaxUint8 + 1
+ file = "memory.numa_stat"
+ )
+ stats := cgroups.PageUsageByNUMA{}
+
+ fd, err := cgroups.OpenFile(path, file, os.O_RDONLY)
+ if os.IsNotExist(err) {
+ return stats, nil
+ } else if err != nil {
+ return stats, err
+ }
+ defer fd.Close()
+
+ // File format is documented in linux/Documentation/cgroup-v1/memory.txt
+ // and it looks like this:
+ //
+ // total=<total pages> N0=<node 0 pages> N1=<node 1 pages> ...
+ // file=<total file pages> N0=<node 0 pages> N1=<node 1 pages> ...
+ // anon=<total anon pages> N0=<node 0 pages> N1=<node 1 pages> ...
+ // unevictable=<total anon pages> N0=<node 0 pages> N1=<node 1 pages> ...
+ // hierarchical_<counter>=<counter pages> N0=<node 0 pages> N1=<node 1 pages> ...
+
+ scanner := bufio.NewScanner(fd)
+ for scanner.Scan() {
+ var field *cgroups.PageStats
+
+ line := scanner.Text()
+ columns := strings.SplitN(line, " ", maxColumns)
+ for i, column := range columns {
+ byNode := strings.SplitN(column, "=", 2)
+ // Some custom kernels have non-standard fields, like
+ // numa_locality 0 0 0 0 0 0 0 0 0 0
+ // numa_exectime 0
+ if len(byNode) < 2 {
+ if i == 0 {
+ // Ignore/skip those.
+ break
+ } else {
+ // The first column was already validated,
+ // so be strict to the rest.
+ return stats, malformedLine(path, file, line)
+ }
+ }
+ key, val := byNode[0], byNode[1]
+ if i == 0 { // First column: key is name, val is total.
+ field = getNUMAField(&stats, key)
+ if field == nil { // unknown field (new kernel?)
+ break
+ }
+ field.Total, err = strconv.ParseUint(val, 0, 64)
+ if err != nil {
+ return stats, &parseError{Path: path, File: file, Err: err}
+ }
+ field.Nodes = map[uint8]uint64{}
+ } else { // Subsequent columns: key is N<id>, val is usage.
+ if len(key) < 2 || key[0] != 'N' {
+ // This is definitely an error.
+ return stats, malformedLine(path, file, line)
+ }
+
+ n, err := strconv.ParseUint(key[1:], 10, 8)
+ if err != nil {
+ return stats, &parseError{Path: path, File: file, Err: err}
+ }
+
+ usage, err := strconv.ParseUint(val, 10, 64)
+ if err != nil {
+ return stats, &parseError{Path: path, File: file, Err: err}
+ }
+
+ field.Nodes[uint8(n)] = usage
+ }
+
+ }
+ }
+ if err := scanner.Err(); err != nil {
+ return cgroups.PageUsageByNUMA{}, &parseError{Path: path, File: file, Err: err}
+ }
+
+ return stats, nil
+}
+
+func getNUMAField(stats *cgroups.PageUsageByNUMA, name string) *cgroups.PageStats {
+ switch name {
+ case "total":
+ return &stats.Total
+ case "file":
+ return &stats.File
+ case "anon":
+ return &stats.Anon
+ case "unevictable":
+ return &stats.Unevictable
+ case "hierarchical_total":
+ return &stats.Hierarchical.Total
+ case "hierarchical_file":
+ return &stats.Hierarchical.File
+ case "hierarchical_anon":
+ return &stats.Hierarchical.Anon
+ case "hierarchical_unevictable":
+ return &stats.Hierarchical.Unevictable
+ }
+ return nil
+}
diff --git a/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/name.go b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/name.go
new file mode 100644
index 000000000..b8d5d849c
--- /dev/null
+++ b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/name.go
@@ -0,0 +1,31 @@
+package fs
+
+import (
+ "github.com/opencontainers/runc/libcontainer/cgroups"
+ "github.com/opencontainers/runc/libcontainer/configs"
+)
+
+type NameGroup struct {
+ GroupName string
+ Join bool
+}
+
+func (s *NameGroup) Name() string {
+ return s.GroupName
+}
+
+func (s *NameGroup) Apply(path string, _ *configs.Resources, pid int) error {
+ if s.Join {
+ // Ignore errors if the named cgroup does not exist.
+ _ = apply(path, pid)
+ }
+ return nil
+}
+
+func (s *NameGroup) Set(_ string, _ *configs.Resources) error {
+ return nil
+}
+
+func (s *NameGroup) GetStats(path string, stats *cgroups.Stats) error {
+ return nil
+}
diff --git a/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/net_cls.go b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/net_cls.go
new file mode 100644
index 000000000..abfd09ce8
--- /dev/null
+++ b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/net_cls.go
@@ -0,0 +1,32 @@
+package fs
+
+import (
+ "strconv"
+
+ "github.com/opencontainers/runc/libcontainer/cgroups"
+ "github.com/opencontainers/runc/libcontainer/configs"
+)
+
+type NetClsGroup struct{}
+
+func (s *NetClsGroup) Name() string {
+ return "net_cls"
+}
+
+func (s *NetClsGroup) Apply(path string, _ *configs.Resources, pid int) error {
+ return apply(path, pid)
+}
+
+func (s *NetClsGroup) Set(path string, r *configs.Resources) error {
+ if r.NetClsClassid != 0 {
+ if err := cgroups.WriteFile(path, "net_cls.classid", strconv.FormatUint(uint64(r.NetClsClassid), 10)); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+func (s *NetClsGroup) GetStats(path string, stats *cgroups.Stats) error {
+ return nil
+}
diff --git a/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/net_prio.go b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/net_prio.go
new file mode 100644
index 000000000..da74d3779
--- /dev/null
+++ b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/net_prio.go
@@ -0,0 +1,30 @@
+package fs
+
+import (
+ "github.com/opencontainers/runc/libcontainer/cgroups"
+ "github.com/opencontainers/runc/libcontainer/configs"
+)
+
+type NetPrioGroup struct{}
+
+func (s *NetPrioGroup) Name() string {
+ return "net_prio"
+}
+
+func (s *NetPrioGroup) Apply(path string, _ *configs.Resources, pid int) error {
+ return apply(path, pid)
+}
+
+func (s *NetPrioGroup) Set(path string, r *configs.Resources) error {
+ for _, prioMap := range r.NetPrioIfpriomap {
+ if err := cgroups.WriteFile(path, "net_prio.ifpriomap", prioMap.CgroupString()); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+func (s *NetPrioGroup) GetStats(path string, stats *cgroups.Stats) error {
+ return nil
+}
diff --git a/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/paths.go b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/paths.go
new file mode 100644
index 000000000..1092331b2
--- /dev/null
+++ b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/paths.go
@@ -0,0 +1,186 @@
+package fs
+
+import (
+ "errors"
+ "os"
+ "path/filepath"
+ "sync"
+
+ "golang.org/x/sys/unix"
+
+ "github.com/opencontainers/runc/libcontainer/cgroups"
+ "github.com/opencontainers/runc/libcontainer/configs"
+ "github.com/opencontainers/runc/libcontainer/utils"
+)
+
+// The absolute path to the root of the cgroup hierarchies.
+var (
+ cgroupRootLock sync.Mutex
+ cgroupRoot string
+)
+
+const defaultCgroupRoot = "/sys/fs/cgroup"
+
+func initPaths(cg *configs.Cgroup) (map[string]string, error) {
+ root, err := rootPath()
+ if err != nil {
+ return nil, err
+ }
+
+ inner, err := innerPath(cg)
+ if err != nil {
+ return nil, err
+ }
+
+ paths := make(map[string]string)
+ for _, sys := range subsystems {
+ name := sys.Name()
+ path, err := subsysPath(root, inner, name)
+ if err != nil {
+ // The non-presence of the devices subsystem
+ // is considered fatal for security reasons.
+ if cgroups.IsNotFound(err) && (cg.SkipDevices || name != "devices") {
+ continue
+ }
+
+ return nil, err
+ }
+ paths[name] = path
+ }
+
+ return paths, nil
+}
+
+func tryDefaultCgroupRoot() string {
+ var st, pst unix.Stat_t
+
+ // (1) it should be a directory...
+ err := unix.Lstat(defaultCgroupRoot, &st)
+ if err != nil || st.Mode&unix.S_IFDIR == 0 {
+ return ""
+ }
+
+ // (2) ... and a mount point ...
+ err = unix.Lstat(filepath.Dir(defaultCgroupRoot), &pst)
+ if err != nil {
+ return ""
+ }
+
+ if st.Dev == pst.Dev {
+ // parent dir has the same dev -- not a mount point
+ return ""
+ }
+
+ // (3) ... of 'tmpfs' fs type.
+ var fst unix.Statfs_t
+ err = unix.Statfs(defaultCgroupRoot, &fst)
+ if err != nil || fst.Type != unix.TMPFS_MAGIC {
+ return ""
+ }
+
+ // (4) it should have at least 1 entry ...
+ dir, err := os.Open(defaultCgroupRoot)
+ if err != nil {
+ return ""
+ }
+ names, err := dir.Readdirnames(1)
+ if err != nil {
+ return ""
+ }
+ if len(names) < 1 {
+ return ""
+ }
+ // ... which is a cgroup mount point.
+ err = unix.Statfs(filepath.Join(defaultCgroupRoot, names[0]), &fst)
+ if err != nil || fst.Type != unix.CGROUP_SUPER_MAGIC {
+ return ""
+ }
+
+ return defaultCgroupRoot
+}
+
+// rootPath finds and returns path to the root of the cgroup hierarchies.
+func rootPath() (string, error) {
+ cgroupRootLock.Lock()
+ defer cgroupRootLock.Unlock()
+
+ if cgroupRoot != "" {
+ return cgroupRoot, nil
+ }
+
+ // fast path
+ cgroupRoot = tryDefaultCgroupRoot()
+ if cgroupRoot != "" {
+ return cgroupRoot, nil
+ }
+
+ // slow path: parse mountinfo
+ mi, err := cgroups.GetCgroupMounts(false)
+ if err != nil {
+ return "", err
+ }
+ if len(mi) < 1 {
+ return "", errors.New("no cgroup mount found in mountinfo")
+ }
+
+ // Get the first cgroup mount (e.g. "/sys/fs/cgroup/memory"),
+ // use its parent directory.
+ root := filepath.Dir(mi[0].Mountpoint)
+
+ if _, err := os.Stat(root); err != nil {
+ return "", err
+ }
+
+ cgroupRoot = root
+ return cgroupRoot, nil
+}
+
+func innerPath(c *configs.Cgroup) (string, error) {
+ if (c.Name != "" || c.Parent != "") && c.Path != "" {
+ return "", errors.New("cgroup: either Path or Name and Parent should be used")
+ }
+
+ // XXX: Do not remove CleanPath. Path safety is important! -- cyphar
+ innerPath := utils.CleanPath(c.Path)
+ if innerPath == "" {
+ cgParent := utils.CleanPath(c.Parent)
+ cgName := utils.CleanPath(c.Name)
+ innerPath = filepath.Join(cgParent, cgName)
+ }
+
+ return innerPath, nil
+}
+
+func subsysPath(root, inner, subsystem string) (string, error) {
+ // If the cgroup name/path is absolute do not look relative to the cgroup of the init process.
+ if filepath.IsAbs(inner) {
+ mnt, err := cgroups.FindCgroupMountpoint(root, subsystem)
+ // If we didn't mount the subsystem, there is no point we make the path.
+ if err != nil {
+ return "", err
+ }
+
+ // Sometimes subsystems can be mounted together as 'cpu,cpuacct'.
+ return filepath.Join(root, filepath.Base(mnt), inner), nil
+ }
+
+ // Use GetOwnCgroupPath instead of GetInitCgroupPath, because the creating
+ // process could in container and shared pid namespace with host, and
+ // /proc/1/cgroup could point to whole other world of cgroups.
+ parentPath, err := cgroups.GetOwnCgroupPath(subsystem)
+ if err != nil {
+ return "", err
+ }
+
+ return filepath.Join(parentPath, inner), nil
+}
+
+func apply(path string, pid int) error {
+ if path == "" {
+ return nil
+ }
+ if err := os.MkdirAll(path, 0o755); err != nil {
+ return err
+ }
+ return cgroups.WriteCgroupProc(path, pid)
+}
diff --git a/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/perf_event.go b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/perf_event.go
new file mode 100644
index 000000000..b86955c8f
--- /dev/null
+++ b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/perf_event.go
@@ -0,0 +1,24 @@
+package fs
+
+import (
+ "github.com/opencontainers/runc/libcontainer/cgroups"
+ "github.com/opencontainers/runc/libcontainer/configs"
+)
+
+type PerfEventGroup struct{}
+
+func (s *PerfEventGroup) Name() string {
+ return "perf_event"
+}
+
+func (s *PerfEventGroup) Apply(path string, _ *configs.Resources, pid int) error {
+ return apply(path, pid)
+}
+
+func (s *PerfEventGroup) Set(_ string, _ *configs.Resources) error {
+ return nil
+}
+
+func (s *PerfEventGroup) GetStats(path string, stats *cgroups.Stats) error {
+ return nil
+}
diff --git a/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/pids.go b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/pids.go
new file mode 100644
index 000000000..1f13532a5
--- /dev/null
+++ b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/pids.go
@@ -0,0 +1,62 @@
+package fs
+
+import (
+ "math"
+ "strconv"
+
+ "github.com/opencontainers/runc/libcontainer/cgroups"
+ "github.com/opencontainers/runc/libcontainer/cgroups/fscommon"
+ "github.com/opencontainers/runc/libcontainer/configs"
+)
+
+type PidsGroup struct{}
+
+func (s *PidsGroup) Name() string {
+ return "pids"
+}
+
+func (s *PidsGroup) Apply(path string, _ *configs.Resources, pid int) error {
+ return apply(path, pid)
+}
+
+func (s *PidsGroup) Set(path string, r *configs.Resources) error {
+ if r.PidsLimit != 0 {
+ // "max" is the fallback value.
+ limit := "max"
+
+ if r.PidsLimit > 0 {
+ limit = strconv.FormatInt(r.PidsLimit, 10)
+ }
+
+ if err := cgroups.WriteFile(path, "pids.max", limit); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+func (s *PidsGroup) GetStats(path string, stats *cgroups.Stats) error {
+ if !cgroups.PathExists(path) {
+ return nil
+ }
+ current, err := fscommon.GetCgroupParamUint(path, "pids.current")
+ if err != nil {
+ return err
+ }
+
+ max, err := fscommon.GetCgroupParamUint(path, "pids.max")
+ if err != nil {
+ return err
+ }
+ // If no limit is set, read from pids.max returns "max", which is
+ // converted to MaxUint64 by GetCgroupParamUint. Historically, we
+ // represent "no limit" for pids as 0, thus this conversion.
+ if max == math.MaxUint64 {
+ max = 0
+ }
+
+ stats.PidsStats.Current = current
+ stats.PidsStats.Limit = max
+ return nil
+}
diff --git a/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/rdma.go b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/rdma.go
new file mode 100644
index 000000000..5bbe0f35f
--- /dev/null
+++ b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/rdma.go
@@ -0,0 +1,25 @@
+package fs
+
+import (
+ "github.com/opencontainers/runc/libcontainer/cgroups"
+ "github.com/opencontainers/runc/libcontainer/cgroups/fscommon"
+ "github.com/opencontainers/runc/libcontainer/configs"
+)
+
+type RdmaGroup struct{}
+
+func (s *RdmaGroup) Name() string {
+ return "rdma"
+}
+
+func (s *RdmaGroup) Apply(path string, _ *configs.Resources, pid int) error {
+ return apply(path, pid)
+}
+
+func (s *RdmaGroup) Set(path string, r *configs.Resources) error {
+ return fscommon.RdmaSet(path, r)
+}
+
+func (s *RdmaGroup) GetStats(path string, stats *cgroups.Stats) error {
+ return fscommon.RdmaGetStats(path, stats)
+}
diff --git a/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs2/cpu.go b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs2/cpu.go
new file mode 100644
index 000000000..bbbae4d58
--- /dev/null
+++ b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs2/cpu.go
@@ -0,0 +1,87 @@
+package fs2
+
+import (
+ "bufio"
+ "os"
+ "strconv"
+
+ "github.com/opencontainers/runc/libcontainer/cgroups"
+ "github.com/opencontainers/runc/libcontainer/cgroups/fscommon"
+ "github.com/opencontainers/runc/libcontainer/configs"
+)
+
+func isCpuSet(r *configs.Resources) bool {
+ return r.CpuWeight != 0 || r.CpuQuota != 0 || r.CpuPeriod != 0
+}
+
+func setCpu(dirPath string, r *configs.Resources) error {
+ if !isCpuSet(r) {
+ return nil
+ }
+
+ // NOTE: .CpuShares is not used here. Conversion is the caller's responsibility.
+ if r.CpuWeight != 0 {
+ if err := cgroups.WriteFile(dirPath, "cpu.weight", strconv.FormatUint(r.CpuWeight, 10)); err != nil {
+ return err
+ }
+ }
+
+ if r.CpuQuota != 0 || r.CpuPeriod != 0 {
+ str := "max"
+ if r.CpuQuota > 0 {
+ str = strconv.FormatInt(r.CpuQuota, 10)
+ }
+ period := r.CpuPeriod
+ if period == 0 {
+ // This default value is documented in
+ // https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v2.html
+ period = 100000
+ }
+ str += " " + strconv.FormatUint(period, 10)
+ if err := cgroups.WriteFile(dirPath, "cpu.max", str); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+func statCpu(dirPath string, stats *cgroups.Stats) error {
+ const file = "cpu.stat"
+ f, err := cgroups.OpenFile(dirPath, file, os.O_RDONLY)
+ if err != nil {
+ return err
+ }
+ defer f.Close()
+
+ sc := bufio.NewScanner(f)
+ for sc.Scan() {
+ t, v, err := fscommon.ParseKeyValue(sc.Text())
+ if err != nil {
+ return &parseError{Path: dirPath, File: file, Err: err}
+ }
+ switch t {
+ case "usage_usec":
+ stats.CpuStats.CpuUsage.TotalUsage = v * 1000
+
+ case "user_usec":
+ stats.CpuStats.CpuUsage.UsageInUsermode = v * 1000
+
+ case "system_usec":
+ stats.CpuStats.CpuUsage.UsageInKernelmode = v * 1000
+
+ case "nr_periods":
+ stats.CpuStats.ThrottlingData.Periods = v
+
+ case "nr_throttled":
+ stats.CpuStats.ThrottlingData.ThrottledPeriods = v
+
+ case "throttled_usec":
+ stats.CpuStats.ThrottlingData.ThrottledTime = v * 1000
+ }
+ }
+ if err := sc.Err(); err != nil {
+ return &parseError{Path: dirPath, File: file, Err: err}
+ }
+ return nil
+}
diff --git a/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs2/cpuset.go b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs2/cpuset.go
new file mode 100644
index 000000000..16c45bad8
--- /dev/null
+++ b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs2/cpuset.go
@@ -0,0 +1,28 @@
+package fs2
+
+import (
+ "github.com/opencontainers/runc/libcontainer/cgroups"
+ "github.com/opencontainers/runc/libcontainer/configs"
+)
+
+func isCpusetSet(r *configs.Resources) bool {
+ return r.CpusetCpus != "" || r.CpusetMems != ""
+}
+
+func setCpuset(dirPath string, r *configs.Resources) error {
+ if !isCpusetSet(r) {
+ return nil
+ }
+
+ if r.CpusetCpus != "" {
+ if err := cgroups.WriteFile(dirPath, "cpuset.cpus", r.CpusetCpus); err != nil {
+ return err
+ }
+ }
+ if r.CpusetMems != "" {
+ if err := cgroups.WriteFile(dirPath, "cpuset.mems", r.CpusetMems); err != nil {
+ return err
+ }
+ }
+ return nil
+}
diff --git a/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs2/create.go b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs2/create.go
new file mode 100644
index 000000000..641123a4d
--- /dev/null
+++ b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs2/create.go
@@ -0,0 +1,152 @@
+package fs2
+
+import (
+ "fmt"
+ "os"
+ "path/filepath"
+ "strings"
+
+ "github.com/opencontainers/runc/libcontainer/cgroups"
+ "github.com/opencontainers/runc/libcontainer/configs"
+)
+
+func supportedControllers() (string, error) {
+ return cgroups.ReadFile(UnifiedMountpoint, "/cgroup.controllers")
+}
+
+// needAnyControllers returns whether we enable some supported controllers or not,
+// based on (1) controllers available and (2) resources that are being set.
+// We don't check "pseudo" controllers such as
+// "freezer" and "devices".
+func needAnyControllers(r *configs.Resources) (bool, error) {
+ if r == nil {
+ return false, nil
+ }
+
+ // list of all available controllers
+ content, err := supportedControllers()
+ if err != nil {
+ return false, err
+ }
+ avail := make(map[string]struct{})
+ for _, ctr := range strings.Fields(content) {
+ avail[ctr] = struct{}{}
+ }
+
+ // check whether the controller if available or not
+ have := func(controller string) bool {
+ _, ok := avail[controller]
+ return ok
+ }
+
+ if isPidsSet(r) && have("pids") {
+ return true, nil
+ }
+ if isMemorySet(r) && have("memory") {
+ return true, nil
+ }
+ if isIoSet(r) && have("io") {
+ return true, nil
+ }
+ if isCpuSet(r) && have("cpu") {
+ return true, nil
+ }
+ if isCpusetSet(r) && have("cpuset") {
+ return true, nil
+ }
+ if isHugeTlbSet(r) && have("hugetlb") {
+ return true, nil
+ }
+
+ return false, nil
+}
+
+// containsDomainController returns whether the current config contains domain controller or not.
+// Refer to: http://man7.org/linux/man-pages/man7/cgroups.7.html
+// As at Linux 4.19, the following controllers are threaded: cpu, perf_event, and pids.
+func containsDomainController(r *configs.Resources) bool {
+ return isMemorySet(r) || isIoSet(r) || isCpuSet(r) || isHugeTlbSet(r)
+}
+
+// CreateCgroupPath creates cgroupv2 path, enabling all the supported controllers.
+func CreateCgroupPath(path string, c *configs.Cgroup) (Err error) {
+ if !strings.HasPrefix(path, UnifiedMountpoint) {
+ return fmt.Errorf("invalid cgroup path %s", path)
+ }
+
+ content, err := supportedControllers()
+ if err != nil {
+ return err
+ }
+
+ const (
+ cgTypeFile = "cgroup.type"
+ cgStCtlFile = "cgroup.subtree_control"
+ )
+ ctrs := strings.Fields(content)
+ res := "+" + strings.Join(ctrs, " +")
+
+ elements := strings.Split(path, "/")
+ elements = elements[3:]
+ current := "/sys/fs"
+ for i, e := range elements {
+ current = filepath.Join(current, e)
+ if i > 0 {
+ if err := os.Mkdir(current, 0o755); err != nil {
+ if !os.IsExist(err) {
+ return err
+ }
+ } else {
+ // If the directory was created, be sure it is not left around on errors.
+ current := current
+ defer func() {
+ if Err != nil {
+ os.Remove(current)
+ }
+ }()
+ }
+ cgType, _ := cgroups.ReadFile(current, cgTypeFile)
+ cgType = strings.TrimSpace(cgType)
+ switch cgType {
+ // If the cgroup is in an invalid mode (usually this means there's an internal
+ // process in the cgroup tree, because we created a cgroup under an
+ // already-populated-by-other-processes cgroup), then we have to error out if
+ // the user requested controllers which are not thread-aware. However, if all
+ // the controllers requested are thread-aware we can simply put the cgroup into
+ // threaded mode.
+ case "domain invalid":
+ if containsDomainController(c.Resources) {
+ return fmt.Errorf("cannot enter cgroupv2 %q with domain controllers -- it is in an invalid state", current)
+ } else {
+ // Not entirely correct (in theory we'd always want to be a domain --
+ // since that means we're a properly delegated cgroup subtree) but in
+ // this case there's not much we can do and it's better than giving an
+ // error.
+ _ = cgroups.WriteFile(current, cgTypeFile, "threaded")
+ }
+ // If the cgroup is in (threaded) or (domain threaded) mode, we can only use thread-aware controllers
+ // (and you cannot usually take a cgroup out of threaded mode).
+ case "domain threaded":
+ fallthrough
+ case "threaded":
+ if containsDomainController(c.Resources) {
+ return fmt.Errorf("cannot enter cgroupv2 %q with domain controllers -- it is in %s mode", current, cgType)
+ }
+ }
+ }
+ // enable all supported controllers
+ if i < len(elements)-1 {
+ if err := cgroups.WriteFile(current, cgStCtlFile, res); err != nil {
+ // try write one by one
+ allCtrs := strings.Split(res, " ")
+ for _, ctr := range allCtrs {
+ _ = cgroups.WriteFile(current, cgStCtlFile, ctr)
+ }
+ }
+ // Some controllers might not be enabled when rootless or containerized,
+ // but we don't catch the error here. (Caught in setXXX() functions.)
+ }
+ }
+
+ return nil
+}
diff --git a/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs2/defaultpath.go b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs2/defaultpath.go
new file mode 100644
index 000000000..9c949c91f
--- /dev/null
+++ b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs2/defaultpath.go
@@ -0,0 +1,99 @@
+/*
+ Copyright The containerd Authors.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+
+package fs2
+
+import (
+ "bufio"
+ "errors"
+ "fmt"
+ "io"
+ "os"
+ "path/filepath"
+ "strings"
+
+ "github.com/opencontainers/runc/libcontainer/configs"
+ "github.com/opencontainers/runc/libcontainer/utils"
+)
+
+const UnifiedMountpoint = "/sys/fs/cgroup"
+
+func defaultDirPath(c *configs.Cgroup) (string, error) {
+ if (c.Name != "" || c.Parent != "") && c.Path != "" {
+ return "", fmt.Errorf("cgroup: either Path or Name and Parent should be used, got %+v", c)
+ }
+
+ return _defaultDirPath(UnifiedMountpoint, c.Path, c.Parent, c.Name)
+}
+
+func _defaultDirPath(root, cgPath, cgParent, cgName string) (string, error) {
+ if (cgName != "" || cgParent != "") && cgPath != "" {
+ return "", errors.New("cgroup: either Path or Name and Parent should be used")
+ }
+
+ // XXX: Do not remove CleanPath. Path safety is important! -- cyphar
+ innerPath := utils.CleanPath(cgPath)
+ if innerPath == "" {
+ cgParent := utils.CleanPath(cgParent)
+ cgName := utils.CleanPath(cgName)
+ innerPath = filepath.Join(cgParent, cgName)
+ }
+ if filepath.IsAbs(innerPath) {
+ return filepath.Join(root, innerPath), nil
+ }
+
+ ownCgroup, err := parseCgroupFile("/proc/self/cgroup")
+ if err != nil {
+ return "", err
+ }
+ // The current user scope most probably has tasks in it already,
+ // making it impossible to enable controllers for its sub-cgroup.
+ // A parent cgroup (with no tasks in it) is what we need.
+ ownCgroup = filepath.Dir(ownCgroup)
+
+ return filepath.Join(root, ownCgroup, innerPath), nil
+}
+
+// parseCgroupFile parses /proc/PID/cgroup file and return string
+func parseCgroupFile(path string) (string, error) {
+ f, err := os.Open(path)
+ if err != nil {
+ return "", err
+ }
+ defer f.Close()
+ return parseCgroupFromReader(f)
+}
+
+func parseCgroupFromReader(r io.Reader) (string, error) {
+ s := bufio.NewScanner(r)
+ for s.Scan() {
+ var (
+ text = s.Text()
+ parts = strings.SplitN(text, ":", 3)
+ )
+ if len(parts) < 3 {
+ return "", fmt.Errorf("invalid cgroup entry: %q", text)
+ }
+ // text is like "0::/user.slice/user-1001.slice/session-1.scope"
+ if parts[0] == "0" && parts[1] == "" {
+ return parts[2], nil
+ }
+ }
+ if err := s.Err(); err != nil {
+ return "", err
+ }
+ return "", errors.New("cgroup path not found")
+}
diff --git a/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs2/freezer.go b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs2/freezer.go
new file mode 100644
index 000000000..8917a6411
--- /dev/null
+++ b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs2/freezer.go
@@ -0,0 +1,127 @@
+package fs2
+
+import (
+ "bufio"
+ "errors"
+ "fmt"
+ "os"
+ "strings"
+ "time"
+
+ "golang.org/x/sys/unix"
+
+ "github.com/opencontainers/runc/libcontainer/cgroups"
+ "github.com/opencontainers/runc/libcontainer/configs"
+)
+
+func setFreezer(dirPath string, state configs.FreezerState) error {
+ var stateStr string
+ switch state {
+ case configs.Undefined:
+ return nil
+ case configs.Frozen:
+ stateStr = "1"
+ case configs.Thawed:
+ stateStr = "0"
+ default:
+ return fmt.Errorf("invalid freezer state %q requested", state)
+ }
+
+ fd, err := cgroups.OpenFile(dirPath, "cgroup.freeze", unix.O_RDWR)
+ if err != nil {
+ // We can ignore this request as long as the user didn't ask us to
+ // freeze the container (since without the freezer cgroup, that's a
+ // no-op).
+ if state != configs.Frozen {
+ return nil
+ }
+ return fmt.Errorf("freezer not supported: %w", err)
+ }
+ defer fd.Close()
+
+ if _, err := fd.WriteString(stateStr); err != nil {
+ return err
+ }
+ // Confirm that the cgroup did actually change states.
+ if actualState, err := readFreezer(dirPath, fd); err != nil {
+ return err
+ } else if actualState != state {
+ return fmt.Errorf(`expected "cgroup.freeze" to be in state %q but was in %q`, state, actualState)
+ }
+ return nil
+}
+
+func getFreezer(dirPath string) (configs.FreezerState, error) {
+ fd, err := cgroups.OpenFile(dirPath, "cgroup.freeze", unix.O_RDONLY)
+ if err != nil {
+ // If the kernel is too old, then we just treat the freezer as being in
+ // an "undefined" state.
+ if os.IsNotExist(err) || errors.Is(err, unix.ENODEV) {
+ err = nil
+ }
+ return configs.Undefined, err
+ }
+ defer fd.Close()
+
+ return readFreezer(dirPath, fd)
+}
+
+func readFreezer(dirPath string, fd *os.File) (configs.FreezerState, error) {
+ if _, err := fd.Seek(0, 0); err != nil {
+ return configs.Undefined, err
+ }
+ state := make([]byte, 2)
+ if _, err := fd.Read(state); err != nil {
+ return configs.Undefined, err
+ }
+ switch string(state) {
+ case "0\n":
+ return configs.Thawed, nil
+ case "1\n":
+ return waitFrozen(dirPath)
+ default:
+ return configs.Undefined, fmt.Errorf(`unknown "cgroup.freeze" state: %q`, state)
+ }
+}
+
+// waitFrozen polls cgroup.events until it sees "frozen 1" in it.
+func waitFrozen(dirPath string) (configs.FreezerState, error) {
+ fd, err := cgroups.OpenFile(dirPath, "cgroup.events", unix.O_RDONLY)
+ if err != nil {
+ return configs.Undefined, err
+ }
+ defer fd.Close()
+
+ // XXX: Simple wait/read/retry is used here. An implementation
+ // based on poll(2) or inotify(7) is possible, but it makes the code
+ // much more complicated. Maybe address this later.
+ const (
+ // Perform maxIter with waitTime in between iterations.
+ waitTime = 10 * time.Millisecond
+ maxIter = 1000
+ )
+ scanner := bufio.NewScanner(fd)
+ for i := 0; scanner.Scan(); {
+ if i == maxIter {
+ return configs.Undefined, fmt.Errorf("timeout of %s reached waiting for the cgroup to freeze", waitTime*maxIter)
+ }
+ line := scanner.Text()
+ val := strings.TrimPrefix(line, "frozen ")
+ if val != line { // got prefix
+ if val[0] == '1' {
+ return configs.Frozen, nil
+ }
+
+ i++
+ // wait, then re-read
+ time.Sleep(waitTime)
+ _, err := fd.Seek(0, 0)
+ if err != nil {
+ return configs.Undefined, err
+ }
+ }
+ }
+ // Should only reach here either on read error,
+ // or if the file does not contain "frozen " line.
+ return configs.Undefined, scanner.Err()
+}
diff --git a/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs2/fs2.go b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs2/fs2.go
new file mode 100644
index 000000000..d5208d778
--- /dev/null
+++ b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs2/fs2.go
@@ -0,0 +1,271 @@
+package fs2
+
+import (
+ "errors"
+ "fmt"
+ "os"
+ "strings"
+
+ "github.com/opencontainers/runc/libcontainer/cgroups"
+ "github.com/opencontainers/runc/libcontainer/cgroups/fscommon"
+ "github.com/opencontainers/runc/libcontainer/configs"
+)
+
+type parseError = fscommon.ParseError
+
+type manager struct {
+ config *configs.Cgroup
+ // dirPath is like "/sys/fs/cgroup/user.slice/user-1001.slice/session-1.scope"
+ dirPath string
+ // controllers is content of "cgroup.controllers" file.
+ // excludes pseudo-controllers ("devices" and "freezer").
+ controllers map[string]struct{}
+}
+
+// NewManager creates a manager for cgroup v2 unified hierarchy.
+// dirPath is like "/sys/fs/cgroup/user.slice/user-1001.slice/session-1.scope".
+// If dirPath is empty, it is automatically set using config.
+func NewManager(config *configs.Cgroup, dirPath string) (cgroups.Manager, error) {
+ if dirPath == "" {
+ var err error
+ dirPath, err = defaultDirPath(config)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ m := &manager{
+ config: config,
+ dirPath: dirPath,
+ }
+ return m, nil
+}
+
+func (m *manager) getControllers() error {
+ if m.controllers != nil {
+ return nil
+ }
+
+ data, err := cgroups.ReadFile(m.dirPath, "cgroup.controllers")
+ if err != nil {
+ if m.config.Rootless && m.config.Path == "" {
+ return nil
+ }
+ return err
+ }
+ fields := strings.Fields(data)
+ m.controllers = make(map[string]struct{}, len(fields))
+ for _, c := range fields {
+ m.controllers[c] = struct{}{}
+ }
+
+ return nil
+}
+
+func (m *manager) Apply(pid int) error {
+ if err := CreateCgroupPath(m.dirPath, m.config); err != nil {
+ // Related tests:
+ // - "runc create (no limits + no cgrouppath + no permission) succeeds"
+ // - "runc create (rootless + no limits + cgrouppath + no permission) fails with permission error"
+ // - "runc create (rootless + limits + no cgrouppath + no permission) fails with informative error"
+ if m.config.Rootless {
+ if m.config.Path == "" {
+ if blNeed, nErr := needAnyControllers(m.config.Resources); nErr == nil && !blNeed {
+ return nil
+ }
+ return fmt.Errorf("rootless needs no limits + no cgrouppath when no permission is granted for cgroups: %w", err)
+ }
+ }
+ return err
+ }
+ if err := cgroups.WriteCgroupProc(m.dirPath, pid); err != nil {
+ return err
+ }
+ return nil
+}
+
+func (m *manager) GetPids() ([]int, error) {
+ return cgroups.GetPids(m.dirPath)
+}
+
+func (m *manager) GetAllPids() ([]int, error) {
+ return cgroups.GetAllPids(m.dirPath)
+}
+
+func (m *manager) GetStats() (*cgroups.Stats, error) {
+ var errs []error
+
+ st := cgroups.NewStats()
+
+ // pids (since kernel 4.5)
+ if err := statPids(m.dirPath, st); err != nil {
+ errs = append(errs, err)
+ }
+ // memory (since kernel 4.5)
+ if err := statMemory(m.dirPath, st); err != nil && !os.IsNotExist(err) {
+ errs = append(errs, err)
+ }
+ // io (since kernel 4.5)
+ if err := statIo(m.dirPath, st); err != nil && !os.IsNotExist(err) {
+ errs = append(errs, err)
+ }
+ // cpu (since kernel 4.15)
+ // Note cpu.stat is available even if the controller is not enabled.
+ if err := statCpu(m.dirPath, st); err != nil && !os.IsNotExist(err) {
+ errs = append(errs, err)
+ }
+ // hugetlb (since kernel 5.6)
+ if err := statHugeTlb(m.dirPath, st); err != nil && !os.IsNotExist(err) {
+ errs = append(errs, err)
+ }
+ // rdma (since kernel 4.11)
+ if err := fscommon.RdmaGetStats(m.dirPath, st); err != nil && !os.IsNotExist(err) {
+ errs = append(errs, err)
+ }
+ if len(errs) > 0 && !m.config.Rootless {
+ return st, fmt.Errorf("error while statting cgroup v2: %+v", errs)
+ }
+ return st, nil
+}
+
+func (m *manager) Freeze(state configs.FreezerState) error {
+ if m.config.Resources == nil {
+ return errors.New("cannot toggle freezer: cgroups not configured for container")
+ }
+ if err := setFreezer(m.dirPath, state); err != nil {
+ return err
+ }
+ m.config.Resources.Freezer = state
+ return nil
+}
+
+func (m *manager) Destroy() error {
+ return cgroups.RemovePath(m.dirPath)
+}
+
+func (m *manager) Path(_ string) string {
+ return m.dirPath
+}
+
+func (m *manager) Set(r *configs.Resources) error {
+ if r == nil {
+ return nil
+ }
+ if err := m.getControllers(); err != nil {
+ return err
+ }
+ // pids (since kernel 4.5)
+ if err := setPids(m.dirPath, r); err != nil {
+ return err
+ }
+ // memory (since kernel 4.5)
+ if err := setMemory(m.dirPath, r); err != nil {
+ return err
+ }
+ // io (since kernel 4.5)
+ if err := setIo(m.dirPath, r); err != nil {
+ return err
+ }
+ // cpu (since kernel 4.15)
+ if err := setCpu(m.dirPath, r); err != nil {
+ return err
+ }
+ // devices (since kernel 4.15, pseudo-controller)
+ //
+ // When rootless is true, errors from the device subsystem are ignored because it is really not expected to work.
+ // However, errors from other subsystems are not ignored.
+ // see @test "runc create (rootless + limits + no cgrouppath + no permission) fails with informative error"
+ if err := setDevices(m.dirPath, r); err != nil {
+ if !m.config.Rootless || errors.Is(err, cgroups.ErrDevicesUnsupported) {
+ return err
+ }
+ }
+ // cpuset (since kernel 5.0)
+ if err := setCpuset(m.dirPath, r); err != nil {
+ return err
+ }
+ // hugetlb (since kernel 5.6)
+ if err := setHugeTlb(m.dirPath, r); err != nil {
+ return err
+ }
+ // rdma (since kernel 4.11)
+ if err := fscommon.RdmaSet(m.dirPath, r); err != nil {
+ return err
+ }
+ // freezer (since kernel 5.2, pseudo-controller)
+ if err := setFreezer(m.dirPath, r.Freezer); err != nil {
+ return err
+ }
+ if err := m.setUnified(r.Unified); err != nil {
+ return err
+ }
+ m.config.Resources = r
+ return nil
+}
+
+func setDevices(dirPath string, r *configs.Resources) error {
+ if cgroups.DevicesSetV2 == nil {
+ if len(r.Devices) > 0 {
+ return cgroups.ErrDevicesUnsupported
+ }
+ return nil
+ }
+ return cgroups.DevicesSetV2(dirPath, r)
+}
+
+func (m *manager) setUnified(res map[string]string) error {
+ for k, v := range res {
+ if strings.Contains(k, "/") {
+ return fmt.Errorf("unified resource %q must be a file name (no slashes)", k)
+ }
+ if err := cgroups.WriteFile(m.dirPath, k, v); err != nil {
+ // Check for both EPERM and ENOENT since O_CREAT is used by WriteFile.
+ if errors.Is(err, os.ErrPermission) || errors.Is(err, os.ErrNotExist) {
+ // Check if a controller is available,
+ // to give more specific error if not.
+ sk := strings.SplitN(k, ".", 2)
+ if len(sk) != 2 {
+ return fmt.Errorf("unified resource %q must be in the form CONTROLLER.PARAMETER", k)
+ }
+ c := sk[0]
+ if _, ok := m.controllers[c]; !ok && c != "cgroup" {
+ return fmt.Errorf("unified resource %q can't be set: controller %q not available", k, c)
+ }
+ }
+ return fmt.Errorf("unable to set unified resource %q: %w", k, err)
+ }
+ }
+
+ return nil
+}
+
+func (m *manager) GetPaths() map[string]string {
+ paths := make(map[string]string, 1)
+ paths[""] = m.dirPath
+ return paths
+}
+
+func (m *manager) GetCgroups() (*configs.Cgroup, error) {
+ return m.config, nil
+}
+
+func (m *manager) GetFreezerState() (configs.FreezerState, error) {
+ return getFreezer(m.dirPath)
+}
+
+func (m *manager) Exists() bool {
+ return cgroups.PathExists(m.dirPath)
+}
+
+func OOMKillCount(path string) (uint64, error) {
+ return fscommon.GetValueByKey(path, "memory.events", "oom_kill")
+}
+
+func (m *manager) OOMKillCount() (uint64, error) {
+ c, err := OOMKillCount(m.dirPath)
+ if err != nil && m.config.Rootless && os.IsNotExist(err) {
+ err = nil
+ }
+
+ return c, err
+}
diff --git a/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs2/hugetlb.go b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs2/hugetlb.go
new file mode 100644
index 000000000..c92a7e64a
--- /dev/null
+++ b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs2/hugetlb.go
@@ -0,0 +1,48 @@
+package fs2
+
+import (
+ "strconv"
+
+ "github.com/opencontainers/runc/libcontainer/cgroups"
+ "github.com/opencontainers/runc/libcontainer/cgroups/fscommon"
+ "github.com/opencontainers/runc/libcontainer/configs"
+)
+
+func isHugeTlbSet(r *configs.Resources) bool {
+ return len(r.HugetlbLimit) > 0
+}
+
+func setHugeTlb(dirPath string, r *configs.Resources) error {
+ if !isHugeTlbSet(r) {
+ return nil
+ }
+ for _, hugetlb := range r.HugetlbLimit {
+ if err := cgroups.WriteFile(dirPath, "hugetlb."+hugetlb.Pagesize+".max", strconv.FormatUint(hugetlb.Limit, 10)); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+func statHugeTlb(dirPath string, stats *cgroups.Stats) error {
+ hugetlbStats := cgroups.HugetlbStats{}
+ for _, pagesize := range cgroups.HugePageSizes() {
+ value, err := fscommon.GetCgroupParamUint(dirPath, "hugetlb."+pagesize+".current")
+ if err != nil {
+ return err
+ }
+ hugetlbStats.Usage = value
+
+ fileName := "hugetlb." + pagesize + ".events"
+ value, err = fscommon.GetValueByKey(dirPath, fileName, "max")
+ if err != nil {
+ return err
+ }
+ hugetlbStats.Failcnt = value
+
+ stats.HugetlbStats[pagesize] = hugetlbStats
+ }
+
+ return nil
+}
diff --git a/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs2/io.go b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs2/io.go
new file mode 100644
index 000000000..b2ff7d340
--- /dev/null
+++ b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs2/io.go
@@ -0,0 +1,193 @@
+package fs2
+
+import (
+ "bufio"
+ "bytes"
+ "fmt"
+ "os"
+ "strconv"
+ "strings"
+
+ "github.com/sirupsen/logrus"
+
+ "github.com/opencontainers/runc/libcontainer/cgroups"
+ "github.com/opencontainers/runc/libcontainer/configs"
+)
+
+func isIoSet(r *configs.Resources) bool {
+ return r.BlkioWeight != 0 ||
+ len(r.BlkioWeightDevice) > 0 ||
+ len(r.BlkioThrottleReadBpsDevice) > 0 ||
+ len(r.BlkioThrottleWriteBpsDevice) > 0 ||
+ len(r.BlkioThrottleReadIOPSDevice) > 0 ||
+ len(r.BlkioThrottleWriteIOPSDevice) > 0
+}
+
+// bfqDeviceWeightSupported checks for per-device BFQ weight support (added
+// in kernel v5.4, commit 795fe54c2a8) by reading from "io.bfq.weight".
+func bfqDeviceWeightSupported(bfq *os.File) bool {
+ if bfq == nil {
+ return false
+ }
+ _, _ = bfq.Seek(0, 0)
+ buf := make([]byte, 32)
+ _, _ = bfq.Read(buf)
+ // If only a single number (default weight) if read back, we have older kernel.
+ _, err := strconv.ParseInt(string(bytes.TrimSpace(buf)), 10, 64)
+ return err != nil
+}
+
+func setIo(dirPath string, r *configs.Resources) error {
+ if !isIoSet(r) {
+ return nil
+ }
+
+ // If BFQ IO scheduler is available, use it.
+ var bfq *os.File
+ if r.BlkioWeight != 0 || len(r.BlkioWeightDevice) > 0 {
+ var err error
+ bfq, err = cgroups.OpenFile(dirPath, "io.bfq.weight", os.O_RDWR)
+ if err == nil {
+ defer bfq.Close()
+ } else if !os.IsNotExist(err) {
+ return err
+ }
+ }
+
+ if r.BlkioWeight != 0 {
+ if bfq != nil { // Use BFQ.
+ if _, err := bfq.WriteString(strconv.FormatUint(uint64(r.BlkioWeight), 10)); err != nil {
+ return err
+ }
+ } else {
+ // Fallback to io.weight with a conversion scheme.
+ v := cgroups.ConvertBlkIOToIOWeightValue(r.BlkioWeight)
+ if err := cgroups.WriteFile(dirPath, "io.weight", strconv.FormatUint(v, 10)); err != nil {
+ return err
+ }
+ }
+ }
+ if bfqDeviceWeightSupported(bfq) {
+ for _, wd := range r.BlkioWeightDevice {
+ if _, err := bfq.WriteString(wd.WeightString() + "\n"); err != nil {
+ return fmt.Errorf("setting device weight %q: %w", wd.WeightString(), err)
+ }
+ }
+ }
+ for _, td := range r.BlkioThrottleReadBpsDevice {
+ if err := cgroups.WriteFile(dirPath, "io.max", td.StringName("rbps")); err != nil {
+ return err
+ }
+ }
+ for _, td := range r.BlkioThrottleWriteBpsDevice {
+ if err := cgroups.WriteFile(dirPath, "io.max", td.StringName("wbps")); err != nil {
+ return err
+ }
+ }
+ for _, td := range r.BlkioThrottleReadIOPSDevice {
+ if err := cgroups.WriteFile(dirPath, "io.max", td.StringName("riops")); err != nil {
+ return err
+ }
+ }
+ for _, td := range r.BlkioThrottleWriteIOPSDevice {
+ if err := cgroups.WriteFile(dirPath, "io.max", td.StringName("wiops")); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+func readCgroup2MapFile(dirPath string, name string) (map[string][]string, error) {
+ ret := map[string][]string{}
+ f, err := cgroups.OpenFile(dirPath, name, os.O_RDONLY)
+ if err != nil {
+ return nil, err
+ }
+ defer f.Close()
+ scanner := bufio.NewScanner(f)
+ for scanner.Scan() {
+ line := scanner.Text()
+ parts := strings.Fields(line)
+ if len(parts) < 2 {
+ continue
+ }
+ ret[parts[0]] = parts[1:]
+ }
+ if err := scanner.Err(); err != nil {
+ return nil, &parseError{Path: dirPath, File: name, Err: err}
+ }
+ return ret, nil
+}
+
+func statIo(dirPath string, stats *cgroups.Stats) error {
+ const file = "io.stat"
+ values, err := readCgroup2MapFile(dirPath, file)
+ if err != nil {
+ return err
+ }
+ // more details on the io.stat file format: https://www.kernel.org/doc/Documentation/cgroup-v2.txt
+ var parsedStats cgroups.BlkioStats
+ for k, v := range values {
+ d := strings.Split(k, ":")
+ if len(d) != 2 {
+ continue
+ }
+ major, err := strconv.ParseUint(d[0], 10, 64)
+ if err != nil {
+ return &parseError{Path: dirPath, File: file, Err: err}
+ }
+ minor, err := strconv.ParseUint(d[1], 10, 64)
+ if err != nil {
+ return &parseError{Path: dirPath, File: file, Err: err}
+ }
+
+ for _, item := range v {
+ d := strings.Split(item, "=")
+ if len(d) != 2 {
+ continue
+ }
+ op := d[0]
+
+ // Map to the cgroupv1 naming and layout (in separate tables).
+ var targetTable *[]cgroups.BlkioStatEntry
+ switch op {
+ // Equivalent to cgroupv1's blkio.io_service_bytes.
+ case "rbytes":
+ op = "Read"
+ targetTable = &parsedStats.IoServiceBytesRecursive
+ case "wbytes":
+ op = "Write"
+ targetTable = &parsedStats.IoServiceBytesRecursive
+ // Equivalent to cgroupv1's blkio.io_serviced.
+ case "rios":
+ op = "Read"
+ targetTable = &parsedStats.IoServicedRecursive
+ case "wios":
+ op = "Write"
+ targetTable = &parsedStats.IoServicedRecursive
+ default:
+ // Skip over entries we cannot map to cgroupv1 stats for now.
+ // In the future we should expand the stats struct to include
+ // them.
+ logrus.Debugf("cgroupv2 io stats: skipping over unmappable %s entry", item)
+ continue
+ }
+
+ value, err := strconv.ParseUint(d[1], 10, 64)
+ if err != nil {
+ return &parseError{Path: dirPath, File: file, Err: err}
+ }
+
+ entry := cgroups.BlkioStatEntry{
+ Op: op,
+ Major: major,
+ Minor: minor,
+ Value: value,
+ }
+ *targetTable = append(*targetTable, entry)
+ }
+ }
+ stats.BlkioStats = parsedStats
+ return nil
+}
diff --git a/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs2/memory.go b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs2/memory.go
new file mode 100644
index 000000000..adbc4b230
--- /dev/null
+++ b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs2/memory.go
@@ -0,0 +1,216 @@
+package fs2
+
+import (
+ "bufio"
+ "errors"
+ "math"
+ "os"
+ "strconv"
+ "strings"
+
+ "golang.org/x/sys/unix"
+
+ "github.com/opencontainers/runc/libcontainer/cgroups"
+ "github.com/opencontainers/runc/libcontainer/cgroups/fscommon"
+ "github.com/opencontainers/runc/libcontainer/configs"
+)
+
+// numToStr converts an int64 value to a string for writing to a
+// cgroupv2 files with .min, .max, .low, or .high suffix.
+// The value of -1 is converted to "max" for cgroupv1 compatibility
+// (which used to write -1 to remove the limit).
+func numToStr(value int64) (ret string) {
+ switch {
+ case value == 0:
+ ret = ""
+ case value == -1:
+ ret = "max"
+ default:
+ ret = strconv.FormatInt(value, 10)
+ }
+
+ return ret
+}
+
+func isMemorySet(r *configs.Resources) bool {
+ return r.MemoryReservation != 0 || r.Memory != 0 || r.MemorySwap != 0
+}
+
+func setMemory(dirPath string, r *configs.Resources) error {
+ if !isMemorySet(r) {
+ return nil
+ }
+ swap, err := cgroups.ConvertMemorySwapToCgroupV2Value(r.MemorySwap, r.Memory)
+ if err != nil {
+ return err
+ }
+ swapStr := numToStr(swap)
+ if swapStr == "" && swap == 0 && r.MemorySwap > 0 {
+ // memory and memorySwap set to the same value -- disable swap
+ swapStr = "0"
+ }
+ // never write empty string to `memory.swap.max`, it means set to 0.
+ if swapStr != "" {
+ if err := cgroups.WriteFile(dirPath, "memory.swap.max", swapStr); err != nil {
+ return err
+ }
+ }
+
+ if val := numToStr(r.Memory); val != "" {
+ if err := cgroups.WriteFile(dirPath, "memory.max", val); err != nil {
+ return err
+ }
+ }
+
+ // cgroup.Resources.KernelMemory is ignored
+
+ if val := numToStr(r.MemoryReservation); val != "" {
+ if err := cgroups.WriteFile(dirPath, "memory.low", val); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+func statMemory(dirPath string, stats *cgroups.Stats) error {
+ const file = "memory.stat"
+ statsFile, err := cgroups.OpenFile(dirPath, file, os.O_RDONLY)
+ if err != nil {
+ return err
+ }
+ defer statsFile.Close()
+
+ sc := bufio.NewScanner(statsFile)
+ for sc.Scan() {
+ t, v, err := fscommon.ParseKeyValue(sc.Text())
+ if err != nil {
+ return &parseError{Path: dirPath, File: file, Err: err}
+ }
+ stats.MemoryStats.Stats[t] = v
+ }
+ if err := sc.Err(); err != nil {
+ return &parseError{Path: dirPath, File: file, Err: err}
+ }
+ stats.MemoryStats.Cache = stats.MemoryStats.Stats["file"]
+ // Unlike cgroup v1 which has memory.use_hierarchy binary knob,
+ // cgroup v2 is always hierarchical.
+ stats.MemoryStats.UseHierarchy = true
+
+ memoryUsage, err := getMemoryDataV2(dirPath, "")
+ if err != nil {
+ if errors.Is(err, unix.ENOENT) && dirPath == UnifiedMountpoint {
+ // The root cgroup does not have memory.{current,max}
+ // so emulate those using data from /proc/meminfo.
+ return statsFromMeminfo(stats)
+ }
+ return err
+ }
+ stats.MemoryStats.Usage = memoryUsage
+ swapUsage, err := getMemoryDataV2(dirPath, "swap")
+ if err != nil {
+ return err
+ }
+ // As cgroup v1 reports SwapUsage values as mem+swap combined,
+ // while in cgroup v2 swap values do not include memory,
+ // report combined mem+swap for v1 compatibility.
+ swapUsage.Usage += memoryUsage.Usage
+ if swapUsage.Limit != math.MaxUint64 {
+ swapUsage.Limit += memoryUsage.Limit
+ }
+ stats.MemoryStats.SwapUsage = swapUsage
+
+ return nil
+}
+
+func getMemoryDataV2(path, name string) (cgroups.MemoryData, error) {
+ memoryData := cgroups.MemoryData{}
+
+ moduleName := "memory"
+ if name != "" {
+ moduleName = "memory." + name
+ }
+ usage := moduleName + ".current"
+ limit := moduleName + ".max"
+
+ value, err := fscommon.GetCgroupParamUint(path, usage)
+ if err != nil {
+ if name != "" && os.IsNotExist(err) {
+ // Ignore EEXIST as there's no swap accounting
+ // if kernel CONFIG_MEMCG_SWAP is not set or
+ // swapaccount=0 kernel boot parameter is given.
+ return cgroups.MemoryData{}, nil
+ }
+ return cgroups.MemoryData{}, err
+ }
+ memoryData.Usage = value
+
+ value, err = fscommon.GetCgroupParamUint(path, limit)
+ if err != nil {
+ return cgroups.MemoryData{}, err
+ }
+ memoryData.Limit = value
+
+ return memoryData, nil
+}
+
+func statsFromMeminfo(stats *cgroups.Stats) error {
+ const file = "/proc/meminfo"
+ f, err := os.Open(file)
+ if err != nil {
+ return err
+ }
+ defer f.Close()
+
+ // Fields we are interested in.
+ var (
+ swap_free uint64
+ swap_total uint64
+ main_total uint64
+ main_free uint64
+ )
+ mem := map[string]*uint64{
+ "SwapFree": &swap_free,
+ "SwapTotal": &swap_total,
+ "MemTotal": &main_total,
+ "MemFree": &main_free,
+ }
+
+ found := 0
+ sc := bufio.NewScanner(f)
+ for sc.Scan() {
+ parts := strings.SplitN(sc.Text(), ":", 3)
+ if len(parts) != 2 {
+ // Should not happen.
+ continue
+ }
+ k := parts[0]
+ p, ok := mem[k]
+ if !ok {
+ // Unknown field -- not interested.
+ continue
+ }
+ vStr := strings.TrimSpace(strings.TrimSuffix(parts[1], " kB"))
+ *p, err = strconv.ParseUint(vStr, 10, 64)
+ if err != nil {
+ return &parseError{File: file, Err: errors.New("bad value for " + k)}
+ }
+
+ found++
+ if found == len(mem) {
+ // Got everything we need -- skip the rest.
+ break
+ }
+ }
+ if err := sc.Err(); err != nil {
+ return &parseError{Path: "", File: file, Err: err}
+ }
+
+ stats.MemoryStats.SwapUsage.Usage = (swap_total - swap_free) * 1024
+ stats.MemoryStats.SwapUsage.Limit = math.MaxUint64
+
+ stats.MemoryStats.Usage.Usage = (main_total - main_free) * 1024
+ stats.MemoryStats.Usage.Limit = math.MaxUint64
+
+ return nil
+}
diff --git a/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs2/pids.go b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs2/pids.go
new file mode 100644
index 000000000..c8c4a3658
--- /dev/null
+++ b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs2/pids.go
@@ -0,0 +1,72 @@
+package fs2
+
+import (
+ "errors"
+ "math"
+ "os"
+ "strings"
+
+ "golang.org/x/sys/unix"
+
+ "github.com/opencontainers/runc/libcontainer/cgroups"
+ "github.com/opencontainers/runc/libcontainer/cgroups/fscommon"
+ "github.com/opencontainers/runc/libcontainer/configs"
+)
+
+func isPidsSet(r *configs.Resources) bool {
+ return r.PidsLimit != 0
+}
+
+func setPids(dirPath string, r *configs.Resources) error {
+ if !isPidsSet(r) {
+ return nil
+ }
+ if val := numToStr(r.PidsLimit); val != "" {
+ if err := cgroups.WriteFile(dirPath, "pids.max", val); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+func statPidsFromCgroupProcs(dirPath string, stats *cgroups.Stats) error {
+ // if the controller is not enabled, let's read PIDS from cgroups.procs
+ // (or threads if cgroup.threads is enabled)
+ contents, err := cgroups.ReadFile(dirPath, "cgroup.procs")
+ if errors.Is(err, unix.ENOTSUP) {
+ contents, err = cgroups.ReadFile(dirPath, "cgroup.threads")
+ }
+ if err != nil {
+ return err
+ }
+ pids := strings.Count(contents, "\n")
+ stats.PidsStats.Current = uint64(pids)
+ stats.PidsStats.Limit = 0
+ return nil
+}
+
+func statPids(dirPath string, stats *cgroups.Stats) error {
+ current, err := fscommon.GetCgroupParamUint(dirPath, "pids.current")
+ if err != nil {
+ if os.IsNotExist(err) {
+ return statPidsFromCgroupProcs(dirPath, stats)
+ }
+ return err
+ }
+
+ max, err := fscommon.GetCgroupParamUint(dirPath, "pids.max")
+ if err != nil {
+ return err
+ }
+ // If no limit is set, read from pids.max returns "max", which is
+ // converted to MaxUint64 by GetCgroupParamUint. Historically, we
+ // represent "no limit" for pids as 0, thus this conversion.
+ if max == math.MaxUint64 {
+ max = 0
+ }
+
+ stats.PidsStats.Current = current
+ stats.PidsStats.Limit = max
+ return nil
+}
diff --git a/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fscommon/rdma.go b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fscommon/rdma.go
new file mode 100644
index 000000000..d463d15ee
--- /dev/null
+++ b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fscommon/rdma.go
@@ -0,0 +1,121 @@
+package fscommon
+
+import (
+ "bufio"
+ "errors"
+ "math"
+ "os"
+ "strconv"
+ "strings"
+
+ "github.com/opencontainers/runc/libcontainer/cgroups"
+ "github.com/opencontainers/runc/libcontainer/configs"
+ "golang.org/x/sys/unix"
+)
+
+// parseRdmaKV parses raw string to RdmaEntry.
+func parseRdmaKV(raw string, entry *cgroups.RdmaEntry) error {
+ var value uint32
+
+ parts := strings.SplitN(raw, "=", 3)
+
+ if len(parts) != 2 {
+ return errors.New("Unable to parse RDMA entry")
+ }
+
+ k, v := parts[0], parts[1]
+
+ if v == "max" {
+ value = math.MaxUint32
+ } else {
+ val64, err := strconv.ParseUint(v, 10, 32)
+ if err != nil {
+ return err
+ }
+ value = uint32(val64)
+ }
+ if k == "hca_handle" {
+ entry.HcaHandles = value
+ } else if k == "hca_object" {
+ entry.HcaObjects = value
+ }
+
+ return nil
+}
+
+// readRdmaEntries reads and converts array of rawstrings to RdmaEntries from file.
+// example entry: mlx4_0 hca_handle=2 hca_object=2000
+func readRdmaEntries(dir, file string) ([]cgroups.RdmaEntry, error) {
+ rdmaEntries := make([]cgroups.RdmaEntry, 0)
+ fd, err := cgroups.OpenFile(dir, file, unix.O_RDONLY)
+ if err != nil {
+ return nil, err
+ }
+ defer fd.Close() //nolint:errorlint
+ scanner := bufio.NewScanner(fd)
+ for scanner.Scan() {
+ parts := strings.SplitN(scanner.Text(), " ", 4)
+ if len(parts) == 3 {
+ entry := new(cgroups.RdmaEntry)
+ entry.Device = parts[0]
+ err = parseRdmaKV(parts[1], entry)
+ if err != nil {
+ continue
+ }
+ err = parseRdmaKV(parts[2], entry)
+ if err != nil {
+ continue
+ }
+
+ rdmaEntries = append(rdmaEntries, *entry)
+ }
+ }
+ return rdmaEntries, scanner.Err()
+}
+
+// RdmaGetStats returns rdma stats such as totalLimit and current entries.
+func RdmaGetStats(path string, stats *cgroups.Stats) error {
+ currentEntries, err := readRdmaEntries(path, "rdma.current")
+ if err != nil {
+ if errors.Is(err, os.ErrNotExist) {
+ err = nil
+ }
+ return err
+ }
+ maxEntries, err := readRdmaEntries(path, "rdma.max")
+ if err != nil {
+ return err
+ }
+ // If device got removed between reading two files, ignore returning stats.
+ if len(currentEntries) != len(maxEntries) {
+ return nil
+ }
+
+ stats.RdmaStats = cgroups.RdmaStats{
+ RdmaLimit: maxEntries,
+ RdmaCurrent: currentEntries,
+ }
+
+ return nil
+}
+
+func createCmdString(device string, limits configs.LinuxRdma) string {
+ cmdString := device
+ if limits.HcaHandles != nil {
+ cmdString += " hca_handle=" + strconv.FormatUint(uint64(*limits.HcaHandles), 10)
+ }
+ if limits.HcaObjects != nil {
+ cmdString += " hca_object=" + strconv.FormatUint(uint64(*limits.HcaObjects), 10)
+ }
+ return cmdString
+}
+
+// RdmaSet sets RDMA resources.
+func RdmaSet(path string, r *configs.Resources) error {
+ for device, limits := range r.Rdma {
+ if err := cgroups.WriteFile(path, "rdma.max", createCmdString(device, limits)); err != nil {
+ return err
+ }
+ }
+ return nil
+}
diff --git a/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fscommon/utils.go b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fscommon/utils.go
new file mode 100644
index 000000000..f4a51c9e5
--- /dev/null
+++ b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fscommon/utils.go
@@ -0,0 +1,145 @@
+package fscommon
+
+import (
+ "errors"
+ "fmt"
+ "math"
+ "path"
+ "strconv"
+ "strings"
+
+ "github.com/opencontainers/runc/libcontainer/cgroups"
+)
+
+var (
+ // Deprecated: use cgroups.OpenFile instead.
+ OpenFile = cgroups.OpenFile
+ // Deprecated: use cgroups.ReadFile instead.
+ ReadFile = cgroups.ReadFile
+ // Deprecated: use cgroups.WriteFile instead.
+ WriteFile = cgroups.WriteFile
+)
+
+// ParseError records a parse error details, including the file path.
+type ParseError struct {
+ Path string
+ File string
+ Err error
+}
+
+func (e *ParseError) Error() string {
+ return "unable to parse " + path.Join(e.Path, e.File) + ": " + e.Err.Error()
+}
+
+func (e *ParseError) Unwrap() error { return e.Err }
+
+// ParseUint converts a string to an uint64 integer.
+// Negative values are returned at zero as, due to kernel bugs,
+// some of the memory cgroup stats can be negative.
+func ParseUint(s string, base, bitSize int) (uint64, error) {
+ value, err := strconv.ParseUint(s, base, bitSize)
+ if err != nil {
+ intValue, intErr := strconv.ParseInt(s, base, bitSize)
+ // 1. Handle negative values greater than MinInt64 (and)
+ // 2. Handle negative values lesser than MinInt64
+ if intErr == nil && intValue < 0 {
+ return 0, nil
+ } else if errors.Is(intErr, strconv.ErrRange) && intValue < 0 {
+ return 0, nil
+ }
+
+ return value, err
+ }
+
+ return value, nil
+}
+
+// ParseKeyValue parses a space-separated "name value" kind of cgroup
+// parameter and returns its key as a string, and its value as uint64
+// (ParseUint is used to convert the value). For example,
+// "io_service_bytes 1234" will be returned as "io_service_bytes", 1234.
+func ParseKeyValue(t string) (string, uint64, error) {
+ parts := strings.SplitN(t, " ", 3)
+ if len(parts) != 2 {
+ return "", 0, fmt.Errorf("line %q is not in key value format", t)
+ }
+
+ value, err := ParseUint(parts[1], 10, 64)
+ if err != nil {
+ return "", 0, err
+ }
+
+ return parts[0], value, nil
+}
+
+// GetValueByKey reads a key-value pairs from the specified cgroup file,
+// and returns a value of the specified key. ParseUint is used for value
+// conversion.
+func GetValueByKey(path, file, key string) (uint64, error) {
+ content, err := cgroups.ReadFile(path, file)
+ if err != nil {
+ return 0, err
+ }
+
+ lines := strings.Split(content, "\n")
+ for _, line := range lines {
+ arr := strings.Split(line, " ")
+ if len(arr) == 2 && arr[0] == key {
+ val, err := ParseUint(arr[1], 10, 64)
+ if err != nil {
+ err = &ParseError{Path: path, File: file, Err: err}
+ }
+ return val, err
+ }
+ }
+
+ return 0, nil
+}
+
+// GetCgroupParamUint reads a single uint64 value from the specified cgroup file.
+// If the value read is "max", the math.MaxUint64 is returned.
+func GetCgroupParamUint(path, file string) (uint64, error) {
+ contents, err := GetCgroupParamString(path, file)
+ if err != nil {
+ return 0, err
+ }
+ contents = strings.TrimSpace(contents)
+ if contents == "max" {
+ return math.MaxUint64, nil
+ }
+
+ res, err := ParseUint(contents, 10, 64)
+ if err != nil {
+ return res, &ParseError{Path: path, File: file, Err: err}
+ }
+ return res, nil
+}
+
+// GetCgroupParamInt reads a single int64 value from specified cgroup file.
+// If the value read is "max", the math.MaxInt64 is returned.
+func GetCgroupParamInt(path, file string) (int64, error) {
+ contents, err := cgroups.ReadFile(path, file)
+ if err != nil {
+ return 0, err
+ }
+ contents = strings.TrimSpace(contents)
+ if contents == "max" {
+ return math.MaxInt64, nil
+ }
+
+ res, err := strconv.ParseInt(contents, 10, 64)
+ if err != nil {
+ return res, &ParseError{Path: path, File: file, Err: err}
+ }
+ return res, nil
+}
+
+// GetCgroupParamString reads a string from the specified cgroup file.
+func GetCgroupParamString(path, file string) (string, error) {
+ contents, err := cgroups.ReadFile(path, file)
+ if err != nil {
+ return "", err
+ }
+
+ return strings.TrimSpace(contents), nil
+}
diff --git a/vendor/github.com/opencontainers/runc/libcontainer/configs/blkio_device.go b/vendor/github.com/opencontainers/runc/libcontainer/configs/blkio_device.go
index fa195bf90..865344f99 100644
--- a/vendor/github.com/opencontainers/runc/libcontainer/configs/blkio_device.go
+++ b/vendor/github.com/opencontainers/runc/libcontainer/configs/blkio_device.go
@@ -2,8 +2,8 @@ package configs
import "fmt"
-// blockIODevice holds major:minor format supported in blkio cgroup
-type blockIODevice struct {
+// BlockIODevice holds major:minor format supported in blkio cgroup.
+type BlockIODevice struct {
// Major is the device's major number
Major int64 `json:"major"`
// Minor is the device's minor number
@@ -12,7 +12,7 @@ type blockIODevice struct {
// WeightDevice struct holds a `major:minor weight`|`major:minor leaf_weight` pair
type WeightDevice struct {
- blockIODevice
+ BlockIODevice
// Weight is the bandwidth rate for the device, range is from 10 to 1000
Weight uint16 `json:"weight"`
// LeafWeight is the bandwidth rate for the device while competing with the cgroup's child cgroups, range is from 10 to 1000, cfq scheduler only
@@ -41,7 +41,7 @@ func (wd *WeightDevice) LeafWeightString() string {
// ThrottleDevice struct holds a `major:minor rate_per_second` pair
type ThrottleDevice struct {
- blockIODevice
+ BlockIODevice
// Rate is the IO rate limit per cgroup per device
Rate uint64 `json:"rate"`
}
diff --git a/vendor/github.com/opencontainers/runc/libcontainer/configs/config.go b/vendor/github.com/opencontainers/runc/libcontainer/configs/config.go
index c1b4a0041..7cf2fb657 100644
--- a/vendor/github.com/opencontainers/runc/libcontainer/configs/config.go
+++ b/vendor/github.com/opencontainers/runc/libcontainer/configs/config.go
@@ -83,9 +83,6 @@ type Syscall struct {
Args []*Arg `json:"args"`
}
-// TODO Windows. Many of these fields should be factored out into those parts
-// which are common across platforms, and those which are platform specific.
-
// Config defines configuration options for executing a process inside a contained environment.
type Config struct {
// NoPivotRoot will use MS_MOVE and a chroot to jail the process into the container's rootfs
diff --git a/vendor/github.com/opencontainers/runc/libcontainer/configs/mount.go b/vendor/github.com/opencontainers/runc/libcontainer/configs/mount.go
index 784c61820..b4c616d55 100644
--- a/vendor/github.com/opencontainers/runc/libcontainer/configs/mount.go
+++ b/vendor/github.com/opencontainers/runc/libcontainer/configs/mount.go
@@ -35,12 +35,6 @@ type Mount struct {
// Extensions are additional flags that are specific to runc.
Extensions int `json:"extensions"`
-
- // Optional Command to be run before Source is mounted.
- PremountCmds []Command `json:"premount_cmds"`
-
- // Optional Command to be run after Source is mounted.
- PostmountCmds []Command `json:"postmount_cmds"`
}
func (m *Mount) IsBind() bool {
diff --git a/vendor/github.com/opencontainers/runc/libcontainer/utils/utils.go b/vendor/github.com/opencontainers/runc/libcontainer/utils/utils.go
index 6b9fc3435..dbd435341 100644
--- a/vendor/github.com/opencontainers/runc/libcontainer/utils/utils.go
+++ b/vendor/github.com/opencontainers/runc/libcontainer/utils/utils.go
@@ -132,19 +132,16 @@ func WithProcfd(root, unsafePath string, fn func(procfd string) error) error {
return fn(procfd)
}
-// SearchLabels searches a list of key-value pairs for the provided key and
-// returns the corresponding value. The pairs must be separated with '='.
-func SearchLabels(labels []string, query string) string {
- for _, l := range labels {
- parts := strings.SplitN(l, "=", 2)
- if len(parts) < 2 {
- continue
- }
- if parts[0] == query {
- return parts[1]
+// SearchLabels searches through a list of key=value pairs for a given key,
+// returning its value, and the binary flag telling whether the key exist.
+func SearchLabels(labels []string, key string) (string, bool) {
+ key += "="
+ for _, s := range labels {
+ if strings.HasPrefix(s, key) {
+ return s[len(key):], true
}
}
- return ""
+ return "", false
}
// Annotations returns the bundle path and user defined annotations from the
diff --git a/vendor/github.com/seccomp/libseccomp-golang/CHANGELOG b/vendor/github.com/seccomp/libseccomp-golang/CHANGELOG
index a01d9a722..905a9b5cd 100644
--- a/vendor/github.com/seccomp/libseccomp-golang/CHANGELOG
+++ b/vendor/github.com/seccomp/libseccomp-golang/CHANGELOG
@@ -2,6 +2,31 @@ libseccomp-golang: Releases
===============================================================================
https://github.com/seccomp/libseccomp-golang
+* Version 0.10.0 - June 9, 2022
+- Minimum supported version of libseccomp bumped to v2.3.1
+- Add seccomp userspace notification API (ActNotify, filter.*Notif*)
+- Add filter.{Get,Set}SSB (to support SCMP_FLTATR_CTL_SSB)
+- Add filter.{Get,Set}Optimize (to support SCMP_FLTATR_CTL_OPTIMIZE)
+- Add filter.{Get,Set}RawRC (to support SCMP_FLTATR_API_SYSRAWRC)
+- Add ArchPARISC, ArchPARISC64, ArchRISCV64
+- Add ActKillProcess and ActKillThread; deprecate ActKill
+- Add go module support
+- Return ErrSyscallDoesNotExist when unable to resolve a syscall
+- Fix some functions to check for both kernel level API and libseccomp version
+- Fix MakeCondition to use sanitizeCompareOp
+- Fix AddRule to handle EACCES (from libseccomp >= 2.5.0)
+- Updated the main docs and converted to README.md
+- Added CONTRIBUTING.md, SECURITY.md, and administrative docs under doc/admin
+- Add GitHub action CI, enable more linters
+- test: test against various libseccomp versions
+- test: fix and simplify execInSubprocess
+- test: fix APILevelIsSupported
+- Refactor the Errno(-1 * retCode) pattern
+- Refactor/unify libseccomp version / API level checks
+- Code cleanups (linter, formatting, spelling fixes)
+- Cleanup: use errors.New instead of fmt.Errorf where appropriate
+- Cleanup: remove duplicated cgo stuff, redundant linux build tag
+
* Version 0.9.1 - May 21, 2019
- Minimum supported version of libseccomp bumped to v2.2.0
- Use Libseccomp's `seccomp_version` API to retrieve library version
diff --git a/vendor/github.com/seccomp/libseccomp-golang/README.md b/vendor/github.com/seccomp/libseccomp-golang/README.md
index 6430f1c9e..312135ee5 100644
--- a/vendor/github.com/seccomp/libseccomp-golang/README.md
+++ b/vendor/github.com/seccomp/libseccomp-golang/README.md
@@ -22,19 +22,37 @@ The library source repository currently lives on GitHub at the following URLs:
* https://github.com/seccomp/libseccomp-golang
* https://github.com/seccomp/libseccomp
-The project mailing list is currently hosted on Google Groups at the URL below,
-please note that a Google account is not required to subscribe to the mailing
-list.
-
-* https://groups.google.com/d/forum/libseccomp
-
Documentation for this package is also available at:
* https://pkg.go.dev/github.com/seccomp/libseccomp-golang
+## Verifying Releases
+
+Starting with libseccomp-golang v0.10.0, the git tag corresponding to each
+release should be signed by one of the libseccomp-golang maintainers. It is
+recommended that before use you verify the release tags using the following
+command:
+
+ % git tag -v <tag>
+
+At present, only the following keys, specified via the fingerprints below, are
+authorized to sign official libseccomp-golang release tags:
+
+ Paul Moore <paul@paul-moore.com>
+ 7100 AADF AE6E 6E94 0D2E 0AD6 55E4 5A5A E8CA 7C8A
+
+ Tom Hromatka <tom.hromatka@oracle.com>
+ 47A6 8FCE 37C7 D702 4FD6 5E11 356C E62C 2B52 4099
+
+ Kir Kolyshkin <kolyshkin@gmail.com>
+ C242 8CD7 5720 FACD CF76 B6EA 17DE 5ECB 75A1 100E
+
+More information on GnuPG and git tag verification can be found at their
+respective websites: https://git-scm.com/docs/git and https://gnupg.org.
+
## Installing the package
- # go get github.com/seccomp/libseccomp-golang
+ % go get github.com/seccomp/libseccomp-golang
## Contributing
diff --git a/vendor/github.com/seccomp/libseccomp-golang/SECURITY.md b/vendor/github.com/seccomp/libseccomp-golang/SECURITY.md
index c448faa8e..f645d4efe 100644
--- a/vendor/github.com/seccomp/libseccomp-golang/SECURITY.md
+++ b/vendor/github.com/seccomp/libseccomp-golang/SECURITY.md
@@ -22,6 +22,7 @@ window.
* Paul Moore, paul@paul-moore.com
* Tom Hromatka, tom.hromatka@oracle.com
+* Kir Kolyshkin, kolyshkin@gmail.com
### Resolving Sensitive Security Issues
diff --git a/vendor/github.com/seccomp/libseccomp-golang/seccomp.go b/vendor/github.com/seccomp/libseccomp-golang/seccomp.go
index 8dad12fdb..c23406754 100644
--- a/vendor/github.com/seccomp/libseccomp-golang/seccomp.go
+++ b/vendor/github.com/seccomp/libseccomp-golang/seccomp.go
@@ -7,6 +7,7 @@
package seccomp
import (
+ "errors"
"fmt"
"os"
"runtime"
@@ -245,8 +246,8 @@ const (
)
// ErrSyscallDoesNotExist represents an error condition where
-// libseccomp is unable to resolve the syscall
-var ErrSyscallDoesNotExist = fmt.Errorf("could not resolve syscall name")
+// libseccomp is unable to resolve the syscall.
+var ErrSyscallDoesNotExist = errors.New("could not resolve syscall name")
const (
// Userspace notification response flags
@@ -556,7 +557,7 @@ func MakeCondition(arg uint, comparison ScmpCompareOp, values ...uint64) (ScmpCo
} else if len(values) > 2 {
return condStruct, fmt.Errorf("conditions can have at most 2 arguments (%d given)", len(values))
} else if len(values) == 0 {
- return condStruct, fmt.Errorf("must provide at least one value to compare against")
+ return condStruct, errors.New("must provide at least one value to compare against")
}
condStruct.Argument = arg
@@ -611,7 +612,7 @@ func NewFilter(defaultAction ScmpAction) (*ScmpFilter, error) {
fPtr := C.seccomp_init(defaultAction.toNative())
if fPtr == nil {
- return nil, fmt.Errorf("could not create filter")
+ return nil, errors.New("could not create filter")
}
filter := new(ScmpFilter)
@@ -623,7 +624,7 @@ func NewFilter(defaultAction ScmpAction) (*ScmpFilter, error) {
// If the kernel does not support TSYNC, allow us to continue without error.
if err := filter.setFilterAttr(filterAttrTsync, 0x1); err != nil && err != syscall.ENOTSUP {
filter.Release()
- return nil, fmt.Errorf("could not create filter - error setting tsync bit: %v", err)
+ return nil, fmt.Errorf("could not create filter: error setting tsync bit: %w", err)
}
return filter, nil
@@ -695,14 +696,14 @@ func (f *ScmpFilter) Merge(src *ScmpFilter) error {
defer src.lock.Unlock()
if !src.valid || !f.valid {
- return fmt.Errorf("one or more of the filter contexts is invalid or uninitialized")
+ return errors.New("one or more of the filter contexts is invalid or uninitialized")
}
// Merge the filters
if retCode := C.seccomp_merge(f.filterCtx, src.filterCtx); retCode != 0 {
e := errRc(retCode)
if e == syscall.EINVAL {
- return fmt.Errorf("filters could not be merged due to a mismatch in attributes or invalid filter")
+ return fmt.Errorf("filters could not be merged due to a mismatch in attributes or invalid filter: %w", e)
}
return e
}
diff --git a/vendor/github.com/seccomp/libseccomp-golang/seccomp_internal.go b/vendor/github.com/seccomp/libseccomp-golang/seccomp_internal.go
index df4dfb7eb..0a7fd34f5 100644
--- a/vendor/github.com/seccomp/libseccomp-golang/seccomp_internal.go
+++ b/vendor/github.com/seccomp/libseccomp-golang/seccomp_internal.go
@@ -340,7 +340,7 @@ func ensureSupportedVersion() error {
func getAPI() (uint, error) {
api := C.seccomp_api_get()
if api == 0 {
- return 0, fmt.Errorf("API level operations are not supported")
+ return 0, errors.New("API level operations are not supported")
}
return uint(api), nil
@@ -349,11 +349,12 @@ func getAPI() (uint, error) {
// Set the API level
func setAPI(api uint) error {
if retCode := C.seccomp_api_set(C.uint(api)); retCode != 0 {
- if errRc(retCode) == syscall.EOPNOTSUPP {
- return fmt.Errorf("API level operations are not supported")
+ e := errRc(retCode)
+ if e == syscall.EOPNOTSUPP {
+ return errors.New("API level operations are not supported")
}
- return fmt.Errorf("could not set API level: %v", retCode)
+ return fmt.Errorf("could not set API level: %w", e)
}
return nil
@@ -411,7 +412,7 @@ func (f *ScmpFilter) setFilterAttr(attr scmpFilterAttr, value C.uint32_t) error
// Wrapper for seccomp_rule_add_... functions
func (f *ScmpFilter) addRuleWrapper(call ScmpSyscall, action ScmpAction, exact bool, length C.uint, cond C.scmp_cast_t) error {
if length != 0 && cond == nil {
- return fmt.Errorf("null conditions list, but length is nonzero")
+ return errors.New("null conditions list, but length is nonzero")
}
var retCode C.int
@@ -430,7 +431,7 @@ func (f *ScmpFilter) addRuleWrapper(call ScmpSyscall, action ScmpAction, exact b
case syscall.EPERM, syscall.EACCES:
return errDefAction
case syscall.EINVAL:
- return fmt.Errorf("two checks on same syscall argument")
+ return errors.New("two checks on same syscall argument")
default:
return e
}
@@ -455,7 +456,7 @@ func (f *ScmpFilter) addRuleGeneric(call ScmpSyscall, action ScmpAction, exact b
} else {
argsArr := C.make_arg_cmp_array(C.uint(len(conds)))
if argsArr == nil {
- return fmt.Errorf("error allocating memory for conditions")
+ return errors.New("error allocating memory for conditions")
}
defer C.free(argsArr)
@@ -495,7 +496,7 @@ func sanitizeAction(in ScmpAction) error {
}
if inTmp != ActTrace && inTmp != ActErrno && (in&0xFFFF0000) != 0 {
- return fmt.Errorf("highest 16 bits must be zeroed except for Trace and Errno")
+ return errors.New("highest 16 bits must be zeroed except for Trace and Errno")
}
return nil
diff --git a/vendor/github.com/stretchr/testify/assert/assertion_compare.go b/vendor/github.com/stretchr/testify/assert/assertion_compare.go
index 3bb22a971..95d8e59da 100644
--- a/vendor/github.com/stretchr/testify/assert/assertion_compare.go
+++ b/vendor/github.com/stretchr/testify/assert/assertion_compare.go
@@ -1,6 +1,7 @@
package assert
import (
+ "bytes"
"fmt"
"reflect"
"time"
@@ -32,7 +33,8 @@ var (
stringType = reflect.TypeOf("")
- timeType = reflect.TypeOf(time.Time{})
+ timeType = reflect.TypeOf(time.Time{})
+ bytesType = reflect.TypeOf([]byte{})
)
func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) {
@@ -323,6 +325,26 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) {
return compare(timeObj1.UnixNano(), timeObj2.UnixNano(), reflect.Int64)
}
+ case reflect.Slice:
+ {
+ // We only care about the []byte type.
+ if !canConvert(obj1Value, bytesType) {
+ break
+ }
+
+ // []byte can be compared!
+ bytesObj1, ok := obj1.([]byte)
+ if !ok {
+ bytesObj1 = obj1Value.Convert(bytesType).Interface().([]byte)
+
+ }
+ bytesObj2, ok := obj2.([]byte)
+ if !ok {
+ bytesObj2 = obj2Value.Convert(bytesType).Interface().([]byte)
+ }
+
+ return CompareType(bytes.Compare(bytesObj1, bytesObj2)), true
+ }
}
return compareEqual, false
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 9b0e22391..7642cbff5 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -111,7 +111,7 @@ github.com/containers/buildah/pkg/rusage
github.com/containers/buildah/pkg/sshagent
github.com/containers/buildah/pkg/util
github.com/containers/buildah/util
-# github.com/containers/common v0.48.1-0.20220608111710-dbecabbe82c9
+# github.com/containers/common v0.48.1-0.20220627112538-97d9656daba8
## explicit
github.com/containers/common/libimage
github.com/containers/common/libimage/define
@@ -555,10 +555,13 @@ github.com/opencontainers/go-digest
## explicit
github.com/opencontainers/image-spec/specs-go
github.com/opencontainers/image-spec/specs-go/v1
-# github.com/opencontainers/runc v1.1.3
+# github.com/opencontainers/runc v1.1.3 => github.com/opencontainers/runc v1.1.1-0.20220617142545-8b9452f75cbc
## explicit
github.com/opencontainers/runc/libcontainer/apparmor
github.com/opencontainers/runc/libcontainer/cgroups
+github.com/opencontainers/runc/libcontainer/cgroups/fs
+github.com/opencontainers/runc/libcontainer/cgroups/fs2
+github.com/opencontainers/runc/libcontainer/cgroups/fscommon
github.com/opencontainers/runc/libcontainer/configs
github.com/opencontainers/runc/libcontainer/devices
github.com/opencontainers/runc/libcontainer/user
@@ -628,7 +631,7 @@ github.com/rootless-containers/rootlesskit/pkg/port/builtin/parent/tcp
github.com/rootless-containers/rootlesskit/pkg/port/builtin/parent/udp
github.com/rootless-containers/rootlesskit/pkg/port/builtin/parent/udp/udpproxy
github.com/rootless-containers/rootlesskit/pkg/port/portutil
-# github.com/seccomp/libseccomp-golang v0.9.2-0.20220502022130-f33da4d89646
+# github.com/seccomp/libseccomp-golang v0.10.0
github.com/seccomp/libseccomp-golang
# github.com/sirupsen/logrus v1.8.1
## explicit
@@ -642,7 +645,7 @@ github.com/spf13/cobra
github.com/spf13/pflag
# github.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980
github.com/stefanberger/go-pkcs11uri
-# github.com/stretchr/testify v1.7.4
+# github.com/stretchr/testify v1.7.5
## explicit
github.com/stretchr/testify/assert
github.com/stretchr/testify/require
@@ -869,3 +872,4 @@ gopkg.in/yaml.v2
gopkg.in/yaml.v3
# sigs.k8s.io/yaml v1.3.0
sigs.k8s.io/yaml
+# github.com/opencontainers/runc => github.com/opencontainers/runc v1.1.1-0.20220617142545-8b9452f75cbc