diff options
Diffstat (limited to 'libpod')
45 files changed, 409 insertions, 107 deletions
diff --git a/libpod/boltdb_state.go b/libpod/boltdb_state.go index 6389431ab..9745121c7 100644 --- a/libpod/boltdb_state.go +++ b/libpod/boltdb_state.go @@ -366,7 +366,7 @@ func (s *BoltState) GetDBConfig() (*DBConfig, error) { err = db.View(func(tx *bolt.Tx) error { configBucket, err := getRuntimeConfigBucket(tx) if err != nil { - return nil + return err } // Some of these may be nil diff --git a/libpod/boltdb_state_linux.go b/libpod/boltdb_state_linux.go index 63ce9784e..8bb10fb63 100644 --- a/libpod/boltdb_state_linux.go +++ b/libpod/boltdb_state_linux.go @@ -1,3 +1,4 @@ +//go:build linux // +build linux package libpod diff --git a/libpod/common/common.go b/libpod/common/common.go index 93a736af2..34cabeadc 100644 --- a/libpod/common/common.go +++ b/libpod/common/common.go @@ -1,16 +1,16 @@ package common -// IsTrue determines whether the given string equals "true" +// IsTrue determines whether the given string equals "true". func IsTrue(str string) bool { return str == "true" } -// IsFalse determines whether the given string equals "false" +// IsFalse determines whether the given string equals "false". func IsFalse(str string) bool { return str == "false" } -// IsValidBool determines whether the given string equals "true" or "false" +// IsValidBool determines whether the given string equals "true" or "false". func IsValidBool(str string) bool { return IsTrue(str) || IsFalse(str) } diff --git a/libpod/container_api.go b/libpod/container_api.go index 03b3dcc04..0b6139335 100644 --- a/libpod/container_api.go +++ b/libpod/container_api.go @@ -921,7 +921,11 @@ func (c *Container) Stat(ctx context.Context, containerPath string) (*define.Fil if err != nil { return nil, err } - defer c.unmount(false) + defer func() { + if err := c.unmount(false); err != nil { + logrus.Errorf("Unmounting container %s: %v", c.ID(), err) + } + }() } info, _, _, err := c.stat(ctx, mountPoint, containerPath) diff --git a/libpod/container_config.go b/libpod/container_config.go index e56f1342a..0d9cd5723 100644 --- a/libpod/container_config.go +++ b/libpod/container_config.go @@ -165,6 +165,10 @@ type ContainerRootFSConfig struct { Volatile bool `json:"volatile,omitempty"` // Passwd allows to user to override podman's passwd/group file setup Passwd *bool `json:"passwd,omitempty"` + // ChrootDirs is an additional set of directories that need to be + // treated as root directories. Standard bind mounts will be mounted + // into paths relative to these directories. + ChrootDirs []string `json:"chroot_directories,omitempty"` } // ContainerSecurityConfig is an embedded sub-config providing security configuration diff --git a/libpod/container_copy_linux.go b/libpod/container_copy_linux.go index d16d635b7..38927d691 100644 --- a/libpod/container_copy_linux.go +++ b/libpod/container_copy_linux.go @@ -1,3 +1,4 @@ +//go:build linux // +build linux package libpod diff --git a/libpod/container_exec.go b/libpod/container_exec.go index d1c190905..140267f28 100644 --- a/libpod/container_exec.go +++ b/libpod/container_exec.go @@ -341,22 +341,60 @@ func (c *Container) ExecStartAndAttach(sessionID string, streams *define.AttachS } lastErr = tmpErr - exitCode, err := c.readExecExitCode(session.ID()) - if err != nil { + exitCode, exitCodeErr := c.readExecExitCode(session.ID()) + + // Lock again. + // Important: we must lock and sync *before* the above error is handled. + // We need info from the database to handle the error. + if !c.batched { + c.lock.Lock() + } + // We can't reuse the old exec session (things may have changed from + // other use, the container was unlocked). + // So re-sync and get a fresh copy. + // If we can't do this, no point in continuing, any attempt to save + // would write garbage to the DB. + if err := c.syncContainer(); err != nil { + if errors.Is(err, define.ErrNoSuchCtr) || errors.Is(err, define.ErrCtrRemoved) { + // We can't save status, but since the container has + // been entirely removed, we don't have to; exit cleanly + return lastErr + } if lastErr != nil { logrus.Errorf("Container %s exec session %s error: %v", c.ID(), session.ID(), lastErr) } - lastErr = err - } + return errors.Wrapf(err, "error syncing container %s state to update exec session %s", c.ID(), sessionID) + } + + // Now handle the error from readExecExitCode above. + if exitCodeErr != nil { + newSess, ok := c.state.ExecSessions[sessionID] + if !ok { + // The exec session was removed entirely, probably by + // the cleanup process. When it did so, it should have + // written an event with the exit code. + // Given that, there's nothing more we can do. + logrus.Infof("Container %s exec session %s already removed", c.ID(), session.ID()) + return lastErr + } - logrus.Debugf("Container %s exec session %s completed with exit code %d", c.ID(), session.ID(), exitCode) + if newSess.State == define.ExecStateStopped { + // Exec session already cleaned up. + // Exit code should be recorded, so it's OK if we were + // not able to read it. + logrus.Infof("Container %s exec session %s already cleaned up", c.ID(), session.ID()) + return lastErr + } - // Lock again - if !c.batched { - c.lock.Lock() + if lastErr != nil { + logrus.Errorf("Container %s exec session %s error: %v", c.ID(), session.ID(), lastErr) + } + lastErr = exitCodeErr } - if err := writeExecExitCode(c, session.ID(), exitCode); err != nil { + logrus.Debugf("Container %s exec session %s completed with exit code %d", c.ID(), session.ID(), exitCode) + + if err := justWriteExecExitCode(c, session.ID(), exitCode); err != nil { if lastErr != nil { logrus.Errorf("Container %s exec session %s error: %v", c.ID(), session.ID(), lastErr) } diff --git a/libpod/container_inspect.go b/libpod/container_inspect.go index 3df6203e3..5fb32bd90 100644 --- a/libpod/container_inspect.go +++ b/libpod/container_inspect.go @@ -411,6 +411,7 @@ func (c *Container) generateInspectContainerConfig(spec *spec.Spec) *define.Insp } ctrConfig.Passwd = c.config.Passwd + ctrConfig.ChrootDirs = append(ctrConfig.ChrootDirs, c.config.ChrootDirs...) return ctrConfig } diff --git a/libpod/container_internal.go b/libpod/container_internal.go index 3c21cade8..b7362e7fb 100644 --- a/libpod/container_internal.go +++ b/libpod/container_internal.go @@ -1087,13 +1087,6 @@ func (c *Container) init(ctx context.Context, retainRetries bool) error { // With the spec complete, do an OCI create if _, err = c.ociRuntime.CreateContainer(c, nil); err != nil { - // Fedora 31 is carrying a patch to display improved error - // messages to better handle the V2 transition. This is NOT - // upstream in any OCI runtime. - // TODO: Remove once runc supports cgroupsv2 - if strings.Contains(err.Error(), "this version of runc doesn't work on cgroups v2") { - logrus.Errorf("Oci runtime %q does not support Cgroups V2: use system migrate to mitigate", c.ociRuntime.Name()) - } return err } @@ -1268,7 +1261,10 @@ func (c *Container) start() error { } } - if c.config.HealthCheckConfig != nil { + // Check if healthcheck is not nil and --no-healthcheck option is not set. + // If --no-healthcheck is set Test will be always set to `[NONE]` so no need + // to update status in such case. + if c.config.HealthCheckConfig != nil && !(len(c.config.HealthCheckConfig.Test) == 1 && c.config.HealthCheckConfig.Test[0] == "NONE") { if err := c.updateHealthStatus(define.HealthCheckStarting); err != nil { logrus.Error(err) } diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go index cef9e2c04..4d6922d73 100644 --- a/libpod/container_internal_linux.go +++ b/libpod/container_internal_linux.go @@ -173,6 +173,57 @@ func (c *Container) prepare() error { return nil } +// isWorkDirSymlink returns true if resolved workdir is symlink or a chain of symlinks, +// and final resolved target is present either on volume, mount or inside of container +// otherwise it returns false. Following function is meant for internal use only and +// can change at any point of time. +func (c *Container) isWorkDirSymlink(resolvedPath string) bool { + // We cannot create workdir since explicit --workdir is + // set in config but workdir could also be a symlink. + // If its a symlink lets check if resolved link is present + // on the container or not. + + // If we can resolve symlink and resolved link is present on the container + // then return nil cause its a valid use-case. + + maxSymLinks := 0 + for { + // Linux only supports a chain of 40 links. + // Reference: https://github.com/torvalds/linux/blob/master/include/linux/namei.h#L13 + if maxSymLinks > 40 { + break + } + resolvedSymlink, err := os.Readlink(resolvedPath) + if err != nil { + // End sym-link resolution loop. + break + } + if resolvedSymlink != "" { + _, resolvedSymlinkWorkdir, err := c.resolvePath(c.state.Mountpoint, resolvedSymlink) + if isPathOnVolume(c, resolvedSymlinkWorkdir) || isPathOnBindMount(c, resolvedSymlinkWorkdir) { + // Resolved symlink exists on external volume or mount + return true + } + if err != nil { + // Could not resolve path so end sym-link resolution loop. + break + } + if resolvedSymlinkWorkdir != "" { + resolvedPath = resolvedSymlinkWorkdir + _, err := os.Stat(resolvedSymlinkWorkdir) + if err == nil { + // Symlink resolved successfully and resolved path exists on container, + // this is a valid use-case so return nil. + logrus.Debugf("Workdir is a symlink with target to %q and resolved symlink exists on container", resolvedSymlink) + return true + } + } + } + maxSymLinks++ + } + return false +} + // resolveWorkDir resolves the container's workdir and, depending on the // configuration, will create it, or error out if it does not exist. // Note that the container must be mounted before. @@ -205,6 +256,11 @@ func (c *Container) resolveWorkDir() error { // the path exists on the container. if err != nil { if os.IsNotExist(err) { + // If resolved Workdir path gets marked as a valid symlink, + // return nil cause this is valid use-case. + if c.isWorkDirSymlink(resolvedWorkdir) { + return nil + } return errors.Errorf("workdir %q does not exist on container %s", workdir, c.ID()) } // This might be a serious error (e.g., permission), so @@ -1755,6 +1811,17 @@ func (c *Container) getRootNetNsDepCtr() (depCtr *Container, err error) { return depCtr, nil } +// Ensure standard bind mounts are mounted into all root directories (including chroot directories) +func (c *Container) mountIntoRootDirs(mountName string, mountPath string) error { + c.state.BindMounts[mountName] = mountPath + + for _, chrootDir := range c.config.ChrootDirs { + c.state.BindMounts[filepath.Join(chrootDir, mountName)] = mountPath + } + + return nil +} + // Make standard bind mounts to include in the container func (c *Container) makeBindMounts() error { if err := os.Chown(c.state.RunDir, c.RootUID(), c.RootGID()); err != nil { @@ -1808,7 +1875,11 @@ func (c *Container) makeBindMounts() error { // If it doesn't, don't copy them resolvPath, exists := bindMounts["/etc/resolv.conf"] if !c.config.UseImageResolvConf && exists { - c.state.BindMounts["/etc/resolv.conf"] = resolvPath + err := c.mountIntoRootDirs("/etc/resolv.conf", resolvPath) + + if err != nil { + return errors.Wrapf(err, "error assigning mounts to container %s", c.ID()) + } } // check if dependency container has an /etc/hosts file. @@ -1828,7 +1899,11 @@ func (c *Container) makeBindMounts() error { depCtr.lock.Unlock() // finally, save it in the new container - c.state.BindMounts["/etc/hosts"] = hostsPath + err := c.mountIntoRootDirs("/etc/hosts", hostsPath) + + if err != nil { + return errors.Wrapf(err, "error assigning mounts to container %s", c.ID()) + } } if !hasCurrentUserMapped(c) { @@ -1845,7 +1920,11 @@ func (c *Container) makeBindMounts() error { if err != nil { return errors.Wrapf(err, "error creating resolv.conf for container %s", c.ID()) } - c.state.BindMounts["/etc/resolv.conf"] = newResolv + err = c.mountIntoRootDirs("/etc/resolv.conf", newResolv) + + if err != nil { + return errors.Wrapf(err, "error assigning mounts to container %s", c.ID()) + } } if !c.config.UseImageHosts { @@ -2273,7 +2352,11 @@ func (c *Container) updateHosts(path string) error { if err != nil { return err } - c.state.BindMounts["/etc/hosts"] = newHosts + + if err = c.mountIntoRootDirs("/etc/hosts", newHosts); err != nil { + return err + } + return nil } @@ -2504,7 +2587,7 @@ func (c *Container) generateUserGroupEntry(addedGID int) (string, int, error) { gid, err := strconv.ParseUint(group, 10, 32) if err != nil { - return "", 0, nil + return "", 0, nil // nolint: nilerr } if addedGID != 0 && addedGID == int(gid) { @@ -2657,7 +2740,7 @@ func (c *Container) generateUserPasswdEntry(addedUID int) (string, int, int, err // If a non numeric User, then don't generate passwd uid, err := strconv.ParseUint(userspec, 10, 32) if err != nil { - return "", 0, 0, nil + return "", 0, 0, nil // nolint: nilerr } if addedUID != 0 && int(uid) == addedUID { diff --git a/libpod/container_linux.go b/libpod/container_linux.go index c445fb8af..8b517e69f 100644 --- a/libpod/container_linux.go +++ b/libpod/container_linux.go @@ -1,3 +1,4 @@ +//go:build linux // +build linux package libpod diff --git a/libpod/container_log_linux.go b/libpod/container_log_linux.go index 6150973ca..8ae8ff2c0 100644 --- a/libpod/container_log_linux.go +++ b/libpod/container_log_linux.go @@ -1,5 +1,5 @@ -//+build linux -//+build systemd +//go:build linux && systemd +// +build linux,systemd package libpod @@ -235,6 +235,9 @@ func (c *Container) readFromJournal(ctx context.Context, options *logs.LogOption logrus.Errorf("Failed parse log line: %v", err) return } + if options.UseName { + logLine.CName = c.Name() + } if doTail { tailQueue = append(tailQueue, logLine) continue diff --git a/libpod/container_log_unsupported.go b/libpod/container_log_unsupported.go index f9ca26966..4f50f9f4c 100644 --- a/libpod/container_log_unsupported.go +++ b/libpod/container_log_unsupported.go @@ -1,4 +1,5 @@ -//+build !linux !systemd +//go:build !linux || !systemd +// +build !linux !systemd package libpod diff --git a/libpod/container_stat_linux.go b/libpod/container_stat_linux.go index d90684197..84ab984e0 100644 --- a/libpod/container_stat_linux.go +++ b/libpod/container_stat_linux.go @@ -1,3 +1,4 @@ +//go:build linux // +build linux package libpod diff --git a/libpod/container_top_linux.go b/libpod/container_top_linux.go index 41300a708..9b3dbc873 100644 --- a/libpod/container_top_linux.go +++ b/libpod/container_top_linux.go @@ -1,3 +1,4 @@ +//go:build linux // +build linux package libpod diff --git a/libpod/define/container_inspect.go b/libpod/define/container_inspect.go index 804b2b143..ae2ce9724 100644 --- a/libpod/define/container_inspect.go +++ b/libpod/define/container_inspect.go @@ -75,6 +75,10 @@ type InspectContainerConfig struct { StopTimeout uint `json:"StopTimeout"` // Passwd determines whether or not podman can add entries to /etc/passwd and /etc/group Passwd *bool `json:"Passwd,omitempty"` + // ChrootDirs is an additional set of directories that need to be + // treated as root directories. Standard bind mounts will be mounted + // into paths relative to these directories. + ChrootDirs []string `json:"ChrootDirs,omitempty"` } // InspectRestartPolicy holds information about the container's restart policy. diff --git a/libpod/define/containerstate.go b/libpod/define/containerstate.go index 23ba1f451..9ad3aec08 100644 --- a/libpod/define/containerstate.go +++ b/libpod/define/containerstate.go @@ -138,7 +138,6 @@ type ContainerStats struct { CPU float64 CPUNano uint64 CPUSystemNano uint64 - DataPoints int64 SystemNano uint64 MemUsage uint64 MemLimit uint64 diff --git a/libpod/events/events_unsupported.go b/libpod/events/events_unsupported.go index 5b32a1b4b..25c175524 100644 --- a/libpod/events/events_unsupported.go +++ b/libpod/events/events_unsupported.go @@ -1,3 +1,4 @@ +//go:build !linux // +build !linux package events diff --git a/libpod/events/journal_linux.go b/libpod/events/journal_linux.go index cc63df120..866042a4c 100644 --- a/libpod/events/journal_linux.go +++ b/libpod/events/journal_linux.go @@ -1,3 +1,4 @@ +//go:build systemd // +build systemd package events diff --git a/libpod/events/journal_unsupported.go b/libpod/events/journal_unsupported.go index 004efdab2..6ed39792b 100644 --- a/libpod/events/journal_unsupported.go +++ b/libpod/events/journal_unsupported.go @@ -1,3 +1,4 @@ +//go:build !systemd // +build !systemd package events diff --git a/libpod/events/logfile.go b/libpod/events/logfile.go index be2aaacca..76173cde9 100644 --- a/libpod/events/logfile.go +++ b/libpod/events/logfile.go @@ -9,6 +9,7 @@ import ( "github.com/containers/podman/v4/pkg/util" "github.com/containers/storage/pkg/lockfile" "github.com/pkg/errors" + "github.com/sirupsen/logrus" ) // EventLogFile is the structure for event writing to a logfile. It contains the eventer @@ -59,7 +60,9 @@ func (e EventLogFile) Read(ctx context.Context, options ReadOptions) error { } go func() { time.Sleep(time.Until(untilTime)) - t.Stop() + if err := t.Stop(); err != nil { + logrus.Errorf("Stopping logger: %v", err) + } }() } funcDone := make(chan bool) diff --git a/libpod/kube.go b/libpod/kube.go index d68d46415..a193df2cb 100644 --- a/libpod/kube.go +++ b/libpod/kube.go @@ -15,6 +15,10 @@ import ( "github.com/containers/common/pkg/config" "github.com/containers/podman/v4/libpod/define" "github.com/containers/podman/v4/pkg/env" + v1 "github.com/containers/podman/v4/pkg/k8s.io/api/core/v1" + "github.com/containers/podman/v4/pkg/k8s.io/apimachinery/pkg/api/resource" + v12 "github.com/containers/podman/v4/pkg/k8s.io/apimachinery/pkg/apis/meta/v1" + "github.com/containers/podman/v4/pkg/k8s.io/apimachinery/pkg/util/intstr" "github.com/containers/podman/v4/pkg/lookup" "github.com/containers/podman/v4/pkg/namespaces" "github.com/containers/podman/v4/pkg/specgen" @@ -23,10 +27,6 @@ import ( "github.com/opencontainers/runtime-tools/generate" "github.com/pkg/errors" "github.com/sirupsen/logrus" - v1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/resource" - v12 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/intstr" ) // GenerateForKube takes a slice of libpod containers and generates diff --git a/libpod/linkmode/linkmode_dynamic.go b/libpod/linkmode/linkmode_dynamic.go index 6d51d60e0..f020fa53e 100644 --- a/libpod/linkmode/linkmode_dynamic.go +++ b/libpod/linkmode/linkmode_dynamic.go @@ -1,3 +1,4 @@ +//go:build !static // +build !static package linkmode diff --git a/libpod/linkmode/linkmode_static.go b/libpod/linkmode/linkmode_static.go index 2db083f4a..b181ad285 100644 --- a/libpod/linkmode/linkmode_static.go +++ b/libpod/linkmode/linkmode_static.go @@ -1,3 +1,4 @@ +//go:build static // +build static package linkmode diff --git a/libpod/lock/shm/shm_lock.go b/libpod/lock/shm/shm_lock.go index fea02a619..c7f4d1bc5 100644 --- a/libpod/lock/shm/shm_lock.go +++ b/libpod/lock/shm/shm_lock.go @@ -1,3 +1,4 @@ +//go:build linux && cgo // +build linux,cgo package shm diff --git a/libpod/lock/shm/shm_lock_nocgo.go b/libpod/lock/shm/shm_lock_nocgo.go index 627344d9c..31fc02223 100644 --- a/libpod/lock/shm/shm_lock_nocgo.go +++ b/libpod/lock/shm/shm_lock_nocgo.go @@ -1,3 +1,4 @@ +//go:build linux && !cgo // +build linux,!cgo package shm diff --git a/libpod/lock/shm/shm_lock_test.go b/libpod/lock/shm/shm_lock_test.go index cb83c7c2c..8dfc849d6 100644 --- a/libpod/lock/shm/shm_lock_test.go +++ b/libpod/lock/shm/shm_lock_test.go @@ -1,3 +1,4 @@ +//go:build linux // +build linux package shm diff --git a/libpod/lock/shm_lock_manager_linux.go b/libpod/lock/shm_lock_manager_linux.go index 8f3b6df7f..3076cd864 100644 --- a/libpod/lock/shm_lock_manager_linux.go +++ b/libpod/lock/shm_lock_manager_linux.go @@ -1,3 +1,4 @@ +//go:build linux // +build linux package lock diff --git a/libpod/lock/shm_lock_manager_unsupported.go b/libpod/lock/shm_lock_manager_unsupported.go index 1d6e3fcbd..d578359ab 100644 --- a/libpod/lock/shm_lock_manager_unsupported.go +++ b/libpod/lock/shm_lock_manager_unsupported.go @@ -1,3 +1,4 @@ +//go:build !linux // +build !linux package lock diff --git a/libpod/mounts_linux.go b/libpod/mounts_linux.go index e6aa09eac..f6945b3a3 100644 --- a/libpod/mounts_linux.go +++ b/libpod/mounts_linux.go @@ -1,3 +1,4 @@ +//go:build linux // +build linux package libpod diff --git a/libpod/networking_linux.go b/libpod/networking_linux.go index 29b9941fe..20c8059a5 100644 --- a/libpod/networking_linux.go +++ b/libpod/networking_linux.go @@ -1,3 +1,4 @@ +//go:build linux // +build linux package libpod @@ -30,6 +31,7 @@ import ( "github.com/containers/podman/v4/pkg/util" "github.com/containers/podman/v4/utils" "github.com/containers/storage/pkg/lockfile" + spec "github.com/opencontainers/runtime-spec/specs-go" "github.com/opencontainers/selinux/go-selinux/label" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -990,8 +992,20 @@ func (c *Container) getContainerNetworkInfo() (*define.InspectNetworkSettings, e return nil, err } - // We can't do more if the network is down. if c.state.NetNS == nil { + if networkNSPath := c.joinedNetworkNSPath(); networkNSPath != "" { + if result, err := c.inspectJoinedNetworkNS(networkNSPath); err == nil { + if basicConfig, err := resultToBasicNetworkConfig(result); err == nil { + // fallback to dummy configuration + settings.InspectBasicNetworkConfig = basicConfig + return settings, nil + } + } + // do not propagate error inspecting a joined network ns + logrus.Errorf("Error inspecting network namespace: %s of container %s: %v", networkNSPath, c.ID(), err) + } + // We can't do more if the network is down. + // We still want to make dummy configurations for each CNI net // the container joined. if len(networks) > 0 { @@ -1065,11 +1079,84 @@ func (c *Container) getContainerNetworkInfo() (*define.InspectNetworkSettings, e return settings, nil } +func (c *Container) joinedNetworkNSPath() string { + for _, namespace := range c.config.Spec.Linux.Namespaces { + if namespace.Type == spec.NetworkNamespace { + return namespace.Path + } + } + return "" +} + +func (c *Container) inspectJoinedNetworkNS(networkns string) (q types.StatusBlock, retErr error) { + var result types.StatusBlock + err := ns.WithNetNSPath(networkns, func(_ ns.NetNS) error { + ifaces, err := net.Interfaces() + if err != nil { + return err + } + routes, err := netlink.RouteList(nil, netlink.FAMILY_ALL) + if err != nil { + return err + } + var gateway net.IP + for _, route := range routes { + // default gateway + if route.Dst == nil { + gateway = route.Gw + } + } + result.Interfaces = make(map[string]types.NetInterface) + for _, iface := range ifaces { + if iface.Flags&net.FlagLoopback != 0 { + continue + } + addrs, err := iface.Addrs() + if err != nil { + continue + } + if len(addrs) == 0 { + continue + } + subnets := make([]types.NetAddress, 0, len(addrs)) + for _, address := range addrs { + if ipnet, ok := address.(*net.IPNet); ok { + if ipnet.IP.IsLinkLocalMulticast() || ipnet.IP.IsLinkLocalUnicast() { + continue + } + subnet := types.NetAddress{ + IPNet: types.IPNet{ + IPNet: *ipnet, + }, + } + if ipnet.Contains(gateway) { + subnet.Gateway = gateway + } + subnets = append(subnets, subnet) + } + } + result.Interfaces[iface.Name] = types.NetInterface{ + Subnets: subnets, + MacAddress: types.HardwareAddr(iface.HardwareAddr), + } + } + return nil + }) + return result, err +} + // resultToBasicNetworkConfig produces an InspectBasicNetworkConfig from a CNI // result func resultToBasicNetworkConfig(result types.StatusBlock) (define.InspectBasicNetworkConfig, error) { config := define.InspectBasicNetworkConfig{} - for _, netInt := range result.Interfaces { + interfaceNames := make([]string, 0, len(result.Interfaces)) + for interfaceName := range result.Interfaces { + interfaceNames = append(interfaceNames, interfaceName) + } + // ensure consistent inspect results by sorting + sort.Strings(interfaceNames) + for _, interfaceName := range interfaceNames { + netInt := result.Interfaces[interfaceName] for _, netAddress := range netInt.Subnets { size, _ := netAddress.IPNet.Mask.Size() if netAddress.IPNet.IP.To4() != nil { diff --git a/libpod/networking_slirp4netns.go b/libpod/networking_slirp4netns.go index 690f0c1fa..a7a002657 100644 --- a/libpod/networking_slirp4netns.go +++ b/libpod/networking_slirp4netns.go @@ -1,3 +1,4 @@ +//go:build linux // +build linux package libpod @@ -13,6 +14,7 @@ import ( "path/filepath" "strconv" "strings" + "sync" "syscall" "time" @@ -302,11 +304,15 @@ func (r *Runtime) setupSlirp4netns(ctr *Container, netns ns.NetNS) error { cmd.Stdout = logFile cmd.Stderr = logFile - var slirpReadyChan (chan struct{}) - + var slirpReadyWg, netnsReadyWg *sync.WaitGroup if netOptions.enableIPv6 { - slirpReadyChan = make(chan struct{}) - defer close(slirpReadyChan) + // use two wait groups to make sure we set the sysctl before + // starting slirp and reset it only after slirp is ready + slirpReadyWg = &sync.WaitGroup{} + netnsReadyWg = &sync.WaitGroup{} + slirpReadyWg.Add(1) + netnsReadyWg.Add(1) + go func() { err := ns.WithNetNSPath(netnsPath, func(_ ns.NetNS) error { // Duplicate Address Detection slows the ipv6 setup down for 1-2 seconds. @@ -318,23 +324,37 @@ func (r *Runtime) setupSlirp4netns(ctr *Container, netns ns.NetNS) error { // is ready in case users rely on this sysctl. orgValue, err := ioutil.ReadFile(ipv6ConfDefaultAcceptDadSysctl) if err != nil { + netnsReadyWg.Done() + // on ipv6 disabled systems the sysctl does not exists + // so we should not error + if errors.Is(err, os.ErrNotExist) { + return nil + } return err } err = ioutil.WriteFile(ipv6ConfDefaultAcceptDadSysctl, []byte("0"), 0644) + netnsReadyWg.Done() if err != nil { return err } - // wait for slirp to finish setup - <-slirpReadyChan + + // wait until slirp4nets is ready before reseting this value + slirpReadyWg.Wait() return ioutil.WriteFile(ipv6ConfDefaultAcceptDadSysctl, orgValue, 0644) }) if err != nil { logrus.Warnf("failed to set net.ipv6.conf.default.accept_dad sysctl: %v", err) } }() + + // wait until we set the sysctl + netnsReadyWg.Wait() } if err := cmd.Start(); err != nil { + if netOptions.enableIPv6 { + slirpReadyWg.Done() + } return errors.Wrapf(err, "failed to start slirp4netns process") } defer func() { @@ -344,11 +364,12 @@ func (r *Runtime) setupSlirp4netns(ctr *Container, netns ns.NetNS) error { } }() - if err := waitForSync(syncR, cmd, logFile, 1*time.Second); err != nil { - return err + err = waitForSync(syncR, cmd, logFile, 1*time.Second) + if netOptions.enableIPv6 { + slirpReadyWg.Done() } - if slirpReadyChan != nil { - slirpReadyChan <- struct{}{} + if err != nil { + return err } // Set a default slirp subnet. Parsing a string with the net helper is easier than building the struct myself diff --git a/libpod/oci_attach_linux.go b/libpod/oci_attach_linux.go index 1ee664e81..b5eabec1f 100644 --- a/libpod/oci_attach_linux.go +++ b/libpod/oci_attach_linux.go @@ -1,4 +1,5 @@ -//+build linux +//go:build linux +// +build linux package libpod diff --git a/libpod/oci_conmon_linux.go b/libpod/oci_conmon_linux.go index a328f7621..ba4079bed 100644 --- a/libpod/oci_conmon_linux.go +++ b/libpod/oci_conmon_linux.go @@ -1,3 +1,4 @@ +//go:build linux // +build linux package libpod @@ -749,7 +750,7 @@ func openControlFile(ctr *Container, parentDir string) (*os.File, error) { for i := 0; i < 600; i++ { controlFile, err := os.OpenFile(controlPath, unix.O_WRONLY|unix.O_NONBLOCK, 0) if err == nil { - return controlFile, err + return controlFile, nil } if !isRetryable(err) { return nil, errors.Wrapf(err, "could not open ctl file for terminal resize for container %s", ctr.ID()) @@ -1014,7 +1015,8 @@ func (r *ConmonOCIRuntime) getLogTag(ctr *Container) (string, error) { } data, err := ctr.inspectLocked(false) if err != nil { - return "", nil + // FIXME: this error should probably be returned + return "", nil // nolint: nilerr } tmpl, err := template.New("container").Parse(logTag) if err != nil { @@ -1199,7 +1201,7 @@ func (r *ConmonOCIRuntime) createOCIContainer(ctr *Container, restoreOptions *Co cmd.ExtraFiles = append(cmd.ExtraFiles, childSyncPipe, childStartPipe) if r.reservePorts && !rootless.IsRootless() && !ctr.config.NetMode.IsSlirp4netns() { - ports, err := bindPorts(ctr.config.PortMappings) + ports, err := bindPorts(ctr.convertPortMappings()) if err != nil { return 0, err } @@ -1370,7 +1372,7 @@ func (r *ConmonOCIRuntime) sharedConmonArgs(ctr *Container, cuuid, bundlePath, p case define.JSONLogging: fallthrough //lint:ignore ST1015 the default case has to be here - default: //nolint-stylecheck + default: //nolint:stylecheck // No case here should happen except JSONLogging, but keep this here in case the options are extended logrus.Errorf("%s logging specified but not supported. Choosing k8s-file logging instead", ctr.LogDriver()) fallthrough @@ -1595,7 +1597,7 @@ func readConmonPipeData(runtimeName string, pipe *os.File, ociLog string) (int, ch <- syncStruct{si: si} }() - data := -1 + data := -1 //nolint: wastedassign select { case ss := <-ch: if ss.err != nil { diff --git a/libpod/options.go b/libpod/options.go index 1ee4e7322..2e5454393 100644 --- a/libpod/options.go +++ b/libpod/options.go @@ -2036,3 +2036,18 @@ func WithVolatile() CtrCreateOption { return nil } } + +// WithChrootDirs is an additional set of directories that need to be +// treated as root directories. Standard bind mounts will be mounted +// into paths relative to these directories. +func WithChrootDirs(dirs []string) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return define.ErrCtrFinalized + } + + ctr.config.ChrootDirs = dirs + + return nil + } +} diff --git a/libpod/pod.go b/libpod/pod.go index 6273ff247..ed2d97b37 100644 --- a/libpod/pod.go +++ b/libpod/pod.go @@ -422,10 +422,6 @@ type PodContainerStats struct { // GetPodStats returns the stats for each of its containers func (p *Pod) GetPodStats(previousContainerStats map[string]*define.ContainerStats) (map[string]*define.ContainerStats, error) { - var ( - ok bool - prevStat *define.ContainerStats - ) p.lock.Lock() defer p.lock.Unlock() @@ -438,10 +434,7 @@ func (p *Pod) GetPodStats(previousContainerStats map[string]*define.ContainerSta } newContainerStats := make(map[string]*define.ContainerStats) for _, c := range containers { - if prevStat, ok = previousContainerStats[c.ID()]; !ok { - prevStat = &define.ContainerStats{} - } - newStats, err := c.GetContainerStats(prevStat) + newStats, err := c.GetContainerStats(previousContainerStats[c.ID()]) // If the container wasn't running, don't include it // but also suppress the error if err != nil && errors.Cause(err) != define.ErrCtrStateInvalid { diff --git a/libpod/pod_top_linux.go b/libpod/pod_top_linux.go index 43823a106..83a070807 100644 --- a/libpod/pod_top_linux.go +++ b/libpod/pod_top_linux.go @@ -1,3 +1,4 @@ +//go:build linux // +build linux package libpod diff --git a/libpod/runtime.go b/libpod/runtime.go index d19997709..07653217a 100644 --- a/libpod/runtime.go +++ b/libpod/runtime.go @@ -210,6 +210,10 @@ func newRuntimeFromConfig(ctx context.Context, conf *config.Config, options ...R } if err := shutdown.Register("libpod", func(sig os.Signal) error { + // For `systemctl stop podman.service` support, exit code should be 0 + if sig == syscall.SIGTERM { + os.Exit(0) + } os.Exit(1) return nil }); err != nil && errors.Cause(err) != shutdown.ErrHandlerExists { diff --git a/libpod/runtime_migrate.go b/libpod/runtime_migrate.go index 32fdc7b5d..fccd5bdee 100644 --- a/libpod/runtime_migrate.go +++ b/libpod/runtime_migrate.go @@ -1,3 +1,4 @@ +//go:build linux // +build linux package libpod diff --git a/libpod/runtime_pod_linux.go b/libpod/runtime_pod_linux.go index 155ad5c2d..2bbccfdf6 100644 --- a/libpod/runtime_pod_linux.go +++ b/libpod/runtime_pod_linux.go @@ -1,3 +1,4 @@ +//go:build linux // +build linux package libpod @@ -5,6 +6,7 @@ package libpod import ( "context" "fmt" + "os" "path" "path/filepath" "strings" @@ -59,50 +61,52 @@ func (r *Runtime) NewPod(ctx context.Context, p specgen.PodSpecGenerator, option pod.valid = true // Check Cgroup parent sanity, and set it if it was not set - switch r.config.Engine.CgroupManager { - case config.CgroupfsCgroupsManager: - canUseCgroup := !rootless.IsRootless() || isRootlessCgroupSet(pod.config.CgroupParent) - if canUseCgroup { + if r.config.Cgroups() != "disabled" { + switch r.config.Engine.CgroupManager { + case config.CgroupfsCgroupsManager: + canUseCgroup := !rootless.IsRootless() || isRootlessCgroupSet(pod.config.CgroupParent) + if canUseCgroup { + if pod.config.CgroupParent == "" { + pod.config.CgroupParent = CgroupfsDefaultCgroupParent + } else if strings.HasSuffix(path.Base(pod.config.CgroupParent), ".slice") { + return nil, errors.Wrapf(define.ErrInvalidArg, "systemd slice received as cgroup parent when using cgroupfs") + } + // 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 + } + } + } + case config.SystemdCgroupsManager: if pod.config.CgroupParent == "" { - pod.config.CgroupParent = CgroupfsDefaultCgroupParent - } else if strings.HasSuffix(path.Base(pod.config.CgroupParent), ".slice") { - return nil, errors.Wrapf(define.ErrInvalidArg, "systemd slice received as cgroup parent when using cgroupfs") + if rootless.IsRootless() { + pod.config.CgroupParent = SystemdDefaultRootlessCgroupParent + } else { + pod.config.CgroupParent = SystemdDefaultCgroupParent + } + } else if len(pod.config.CgroupParent) < 6 || !strings.HasSuffix(path.Base(pod.config.CgroupParent), ".slice") { + return nil, errors.Wrapf(define.ErrInvalidArg, "did not receive systemd slice as cgroup parent when using systemd to manage cgroups") } // 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()) + cgroupPath, err := systemdSliceFromPath(pod.config.CgroupParent, fmt.Sprintf("libpod_pod_%s", pod.ID())) + if err != nil { + return nil, errors.Wrapf(err, "unable to create pod cgroup for pod %s", pod.ID()) + } + pod.state.CgroupPath = cgroupPath if p.InfraContainerSpec != nil { p.InfraContainerSpec.CgroupParent = pod.state.CgroupPath } } + default: + return nil, errors.Wrapf(define.ErrInvalidArg, "unsupported Cgroup manager: %s - cannot validate cgroup parent", r.config.Engine.CgroupManager) } - case config.SystemdCgroupsManager: - if pod.config.CgroupParent == "" { - if rootless.IsRootless() { - pod.config.CgroupParent = SystemdDefaultRootlessCgroupParent - } else { - pod.config.CgroupParent = SystemdDefaultCgroupParent - } - } else if len(pod.config.CgroupParent) < 6 || !strings.HasSuffix(path.Base(pod.config.CgroupParent), ".slice") { - return nil, errors.Wrapf(define.ErrInvalidArg, "did not receive systemd slice as cgroup parent when using systemd to manage cgroups") - } - // 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())) - if err != nil { - return nil, errors.Wrapf(err, "unable to create pod cgroup for pod %s", pod.ID()) - } - pod.state.CgroupPath = cgroupPath - if p.InfraContainerSpec != nil { - p.InfraContainerSpec.CgroupParent = pod.state.CgroupPath - } - } - default: - return nil, errors.Wrapf(define.ErrInvalidArg, "unsupported Cgroup manager: %s - cannot validate cgroup parent", r.config.Engine.CgroupManager) } if pod.config.UsePodCgroup { @@ -236,7 +240,7 @@ func (r *Runtime) removePod(ctx context.Context, p *Pod, removeCtrs, force bool, // Don't try if we failed to retrieve the cgroup if err == nil { - if err := conmonCgroup.Update(resLimits); err != nil { + if err := conmonCgroup.Update(resLimits); err != nil && !os.IsNotExist(err) { logrus.Warnf("Error updating pod %s conmon cgroup PID limit: %v", p.ID(), err) } } diff --git a/libpod/runtime_volume_linux.go b/libpod/runtime_volume_linux.go index c4fe3db90..3d585fa7a 100644 --- a/libpod/runtime_volume_linux.go +++ b/libpod/runtime_volume_linux.go @@ -1,3 +1,4 @@ +//go:build linux // +build linux package libpod diff --git a/libpod/stats.go b/libpod/stats.go index dbb10a27e..25baa378d 100644 --- a/libpod/stats.go +++ b/libpod/stats.go @@ -1,3 +1,4 @@ +//go:build linux // +build linux package libpod @@ -13,7 +14,9 @@ import ( "github.com/pkg/errors" ) -// GetContainerStats gets the running stats for a given container +// GetContainerStats gets the running stats for a given container. +// The previousStats is used to correctly calculate cpu percentages. You +// should pass nil if there is no previous stat for this container. func (c *Container) GetContainerStats(previousStats *define.ContainerStats) (*define.ContainerStats, error) { stats := new(define.ContainerStats) stats.ContainerID = c.ID() @@ -35,6 +38,14 @@ func (c *Container) GetContainerStats(previousStats *define.ContainerStats) (*de return stats, define.ErrCtrStateInvalid } + if previousStats == nil { + previousStats = &define.ContainerStats{ + // if we have no prev stats use the container start time as prev time + // otherwise we cannot correctly calculate the CPU percentage + SystemNano: uint64(c.state.StartedTime.UnixNano()), + } + } + cgroupPath, err := c.cGroupPath() if err != nil { return nil, err @@ -66,8 +77,8 @@ func (c *Container) GetContainerStats(previousStats *define.ContainerStats) (*de stats.Duration = cgroupStats.CPU.Usage.Total stats.UpTime = time.Duration(stats.Duration) stats.CPU = calculateCPUPercent(cgroupStats, previousCPU, now, previousStats.SystemNano) - stats.AvgCPU = calculateAvgCPU(stats.CPU, previousStats.AvgCPU, previousStats.DataPoints) - stats.DataPoints = previousStats.DataPoints + 1 + // 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.MemLimit = c.getMemLimit() stats.MemPerc = (float64(stats.MemUsage) / float64(stats.MemLimit)) * 100 @@ -145,9 +156,3 @@ func calculateBlockIO(stats *cgroups.Metrics) (read uint64, write uint64) { } return } - -// calculateAvgCPU calculates the avg CPU percentage given the previous average and the number of data points. -func calculateAvgCPU(statsCPU float64, prevAvg float64, prevData int64) float64 { - avgPer := ((prevAvg * float64(prevData)) + statsCPU) / (float64(prevData) + 1) - return avgPer -} diff --git a/libpod/util_linux.go b/libpod/util_linux.go index dd115c7fb..fe98056dc 100644 --- a/libpod/util_linux.go +++ b/libpod/util_linux.go @@ -1,3 +1,4 @@ +//go:build linux // +build linux package libpod diff --git a/libpod/volume.go b/libpod/volume.go index d60d978ed..f79ceaa87 100644 --- a/libpod/volume.go +++ b/libpod/volume.go @@ -255,3 +255,16 @@ func (v *Volume) IsDangling() (bool, error) { func (v *Volume) UsesVolumeDriver() bool { return !(v.config.Driver == define.VolumeDriverLocal || v.config.Driver == "") } + +func (v *Volume) Mount() (string, error) { + v.lock.Lock() + defer v.lock.Unlock() + err := v.mount() + return v.config.MountPoint, err +} + +func (v *Volume) Unmount() error { + v.lock.Lock() + defer v.lock.Unlock() + return v.unmount(false) +} diff --git a/libpod/volume_internal_linux.go b/libpod/volume_internal_linux.go index 60d3667a9..7d7dea9d0 100644 --- a/libpod/volume_internal_linux.go +++ b/libpod/volume_internal_linux.go @@ -1,3 +1,4 @@ +//go:build linux // +build linux package libpod |