diff options
Diffstat (limited to 'libpod')
55 files changed, 405 insertions, 181 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..ea644764c 100644 --- a/libpod/container_config.go +++ b/libpod/container_config.go @@ -8,6 +8,7 @@ import ( "github.com/containers/common/pkg/secrets" "github.com/containers/image/v5/manifest" "github.com/containers/podman/v4/pkg/namespaces" + "github.com/containers/podman/v4/pkg/specgen" "github.com/containers/storage" spec "github.com/opencontainers/runtime-spec/specs-go" ) @@ -165,6 +166,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 @@ -401,13 +406,19 @@ type ContainerMiscConfig struct { InitContainerType string `json:"init_container_type,omitempty"` } +// InfraInherit contains the compatible options inheritable from the infra container type InfraInherit struct { - InfraSecurity ContainerSecurityConfig - InfraLabels []string `json:"labelopts,omitempty"` - InfraVolumes []*ContainerNamedVolume `json:"namedVolumes,omitempty"` - InfraOverlay []*ContainerOverlayVolume `json:"overlayVolumes,omitempty"` - InfraImageVolumes []*ContainerImageVolume `json:"ctrImageVolumes,omitempty"` - InfraUserVolumes []string `json:"userVolumes,omitempty"` - InfraResources *spec.LinuxResources `json:"resources,omitempty"` - InfraDevices []spec.LinuxDevice `json:"device_host_src,omitempty"` + ApparmorProfile string `json:"apparmor_profile,omitempty"` + CapAdd []string `json:"cap_add,omitempty"` + 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"` + SeccompPolicy string `json:"seccomp_policy,omitempty"` + SeccompProfilePath string `json:"seccomp_profile_path,omitempty"` + SelinuxOpts []string `json:"selinux_opts,omitempty"` + Volumes []*specgen.NamedVolume `json:"volumes,omitempty"` } 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..c9d0b8a6c 100644 --- a/libpod/container_inspect.go +++ b/libpod/container_inspect.go @@ -103,8 +103,8 @@ func (c *Container) getContainerInspectData(size bool, driverData *define.Driver } } - namedVolumes, mounts := c.sortUserVolumes(ctrSpec) - inspectMounts, err := c.GetInspectMounts(namedVolumes, c.config.ImageVolumes, mounts) + namedVolumes, mounts := c.SortUserVolumes(ctrSpec) + inspectMounts, err := c.GetMounts(namedVolumes, c.config.ImageVolumes, mounts) if err != nil { return nil, err } @@ -222,7 +222,7 @@ func (c *Container) getContainerInspectData(size bool, driverData *define.Driver // Get inspect-formatted mounts list. // Only includes user-specified mounts. Only includes bind mounts and named // volumes, not tmpfs volumes. -func (c *Container) GetInspectMounts(namedVolumes []*ContainerNamedVolume, imageVolumes []*ContainerImageVolume, mounts []spec.Mount) ([]define.InspectMount, error) { +func (c *Container) GetMounts(namedVolumes []*ContainerNamedVolume, imageVolumes []*ContainerImageVolume, mounts []spec.Mount) ([]define.InspectMount, error) { inspectMounts := []define.InspectMount{} // No mounts, return early @@ -367,7 +367,7 @@ func (c *Container) generateInspectContainerConfig(spec *spec.Spec) *define.Insp // Leave empty if not explicitly overwritten by user if len(c.config.Entrypoint) != 0 { - ctrConfig.Entrypoint = strings.Join(c.config.Entrypoint, " ") + ctrConfig.Entrypoint = c.config.Entrypoint } if len(c.config.Labels) != 0 { @@ -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..f1f467879 100644 --- a/libpod/container_internal.go +++ b/libpod/container_internal.go @@ -99,15 +99,8 @@ func (c *Container) rootFsSize() (int64, error) { // rwSize gets the size of the mutable top layer of the container. func (c *Container) rwSize() (int64, error) { if c.config.Rootfs != "" { - var size int64 - err := filepath.Walk(c.config.Rootfs, func(path string, info os.FileInfo, err error) error { - if err != nil { - return err - } - size += info.Size() - return nil - }) - return size, err + size, err := util.SizeOfPath(c.config.Rootfs) + return int64(size), err } container, err := c.runtime.store.Container(c.ID()) @@ -1087,13 +1080,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 +1254,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) } @@ -2246,9 +2235,9 @@ func (c *Container) prepareCheckpointExport() error { return nil } -// sortUserVolumes sorts the volumes specified for a container +// SortUserVolumes sorts the volumes specified for a container // between named and normal volumes -func (c *Container) sortUserVolumes(ctrSpec *spec.Spec) ([]*ContainerNamedVolume, []spec.Mount) { +func (c *Container) SortUserVolumes(ctrSpec *spec.Spec) ([]*ContainerNamedVolume, []spec.Mount) { namedUserVolumes := []*ContainerNamedVolume{} userMounts := []spec.Mount{} diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go index 1517a7df7..11ca169ca 100644 --- a/libpod/container_internal_linux.go +++ b/libpod/container_internal_linux.go @@ -968,6 +968,16 @@ func (c *Container) mountNotifySocket(g generate.Generator) error { // systemd expects to have /run, /run/lock and /tmp on tmpfs // It also expects to be able to write to /sys/fs/cgroup/systemd and /var/log/journal func (c *Container) setupSystemd(mounts []spec.Mount, g generate.Generator) error { + var containerUUIDSet bool + for _, s := range c.config.Spec.Process.Env { + if strings.HasPrefix(s, "container_uuid=") { + containerUUIDSet = true + break + } + } + if !containerUUIDSet { + g.AddProcessEnv("container_uuid", c.ID()[:32]) + } options := []string{"rw", "rprivate", "nosuid", "nodev"} for _, dest := range []string{"/run", "/run/lock"} { if MountExists(mounts, dest) { @@ -1811,6 +1821,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 { @@ -1864,7 +1885,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. @@ -1884,7 +1909,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) { @@ -1901,7 +1930,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 { @@ -2329,7 +2362,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 } @@ -2560,7 +2597,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) { @@ -2713,7 +2750,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.go b/libpod/container_log.go index 47877951d..7a9eb2dbf 100644 --- a/libpod/container_log.go +++ b/libpod/container_log.go @@ -9,7 +9,7 @@ import ( "github.com/containers/podman/v4/libpod/define" "github.com/containers/podman/v4/libpod/events" "github.com/containers/podman/v4/libpod/logs" - "github.com/hpcloud/tail/watch" + "github.com/nxadm/tail/watch" "github.com/pkg/errors" "github.com/sirupsen/logrus" ) @@ -23,8 +23,8 @@ func init() { // Log is a runtime function that can read one or more container logs. func (r *Runtime) Log(ctx context.Context, containers []*Container, options *logs.LogOptions, logChannel chan *logs.LogLine) error { - for _, ctr := range containers { - if err := ctr.ReadLog(ctx, options, logChannel); err != nil { + for c, ctr := range containers { + if err := ctr.ReadLog(ctx, options, logChannel, int64(c)); err != nil { return err } } @@ -32,26 +32,26 @@ func (r *Runtime) Log(ctx context.Context, containers []*Container, options *log } // ReadLog reads a containers log based on the input options and returns log lines over a channel. -func (c *Container) ReadLog(ctx context.Context, options *logs.LogOptions, logChannel chan *logs.LogLine) error { +func (c *Container) ReadLog(ctx context.Context, options *logs.LogOptions, logChannel chan *logs.LogLine, colorID int64) error { switch c.LogDriver() { case define.PassthroughLogging: return errors.Wrapf(define.ErrNoLogs, "this container is using the 'passthrough' log driver, cannot read logs") case define.NoLogging: return errors.Wrapf(define.ErrNoLogs, "this container is using the 'none' log driver, cannot read logs") case define.JournaldLogging: - return c.readFromJournal(ctx, options, logChannel) + return c.readFromJournal(ctx, options, logChannel, colorID) case define.JSONLogging: // TODO provide a separate implementation of this when Conmon // has support. fallthrough case define.KubernetesLogging, "": - return c.readFromLogFile(ctx, options, logChannel) + return c.readFromLogFile(ctx, options, logChannel, colorID) default: return errors.Wrapf(define.ErrInternal, "unrecognized log driver %q, cannot read logs", c.LogDriver()) } } -func (c *Container) readFromLogFile(ctx context.Context, options *logs.LogOptions, logChannel chan *logs.LogLine) error { +func (c *Container) readFromLogFile(ctx context.Context, options *logs.LogOptions, logChannel chan *logs.LogLine, colorID int64) error { t, tailLog, err := logs.GetLogFile(c.LogPath(), options) if err != nil { // If the log file does not exist, this is not fatal. @@ -65,6 +65,7 @@ func (c *Container) readFromLogFile(ctx context.Context, options *logs.LogOption for _, nll := range tailLog { nll.CID = c.ID() nll.CName = c.Name() + nll.ColorID = colorID if nll.Since(options.Since) && nll.Until(options.Until) { logChannel <- nll } @@ -97,6 +98,7 @@ func (c *Container) readFromLogFile(ctx context.Context, options *logs.LogOption } nll.CID = c.ID() nll.CName = c.Name() + nll.ColorID = colorID if nll.Since(options.Since) && nll.Until(options.Until) { logChannel <- nll } diff --git a/libpod/container_log_linux.go b/libpod/container_log_linux.go index 8ae8ff2c0..d96647e51 100644 --- a/libpod/container_log_linux.go +++ b/libpod/container_log_linux.go @@ -45,7 +45,7 @@ func (c *Container) initializeJournal(ctx context.Context) error { return journal.Send("", journal.PriInfo, m) } -func (c *Container) readFromJournal(ctx context.Context, options *logs.LogOptions, logChannel chan *logs.LogLine) error { +func (c *Container) readFromJournal(ctx context.Context, options *logs.LogOptions, logChannel chan *logs.LogLine, colorID int64) error { // We need the container's events in the same journal to guarantee // consistency, see #10323. if options.Follow && c.runtime.config.Engine.EventsLogger != "journald" { @@ -231,6 +231,7 @@ func (c *Container) readFromJournal(ctx context.Context, options *logs.LogOption } logLine, err := logs.NewJournaldLogLine(message, options.Multi) + logLine.ColorID = colorID if err != nil { logrus.Errorf("Failed parse log line: %v", err) return diff --git a/libpod/container_log_unsupported.go b/libpod/container_log_unsupported.go index f9ca26966..c84a578cc 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 @@ -10,7 +11,7 @@ import ( "github.com/pkg/errors" ) -func (c *Container) readFromJournal(_ context.Context, _ *logs.LogOptions, _ chan *logs.LogLine) error { +func (c *Container) readFromJournal(_ context.Context, _ *logs.LogOptions, _ chan *logs.LogLine, colorID int64) error { return errors.Wrapf(define.ErrOSNotSupported, "Journald logging only enabled with systemd on linux") } diff --git a/libpod/container_path_resolution.go b/libpod/container_path_resolution.go index 7db23b783..80a3749f5 100644 --- a/libpod/container_path_resolution.go +++ b/libpod/container_path_resolution.go @@ -1,4 +1,3 @@ -// +linux package libpod import ( 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..444fbff62 100644 --- a/libpod/define/container_inspect.go +++ b/libpod/define/container_inspect.go @@ -44,7 +44,7 @@ type InspectContainerConfig struct { // Container working directory WorkingDir string `json:"WorkingDir"` // Container entrypoint - Entrypoint string `json:"Entrypoint"` + Entrypoint []string `json:"Entrypoint"` // On-build arguments - presently unused. More of Buildah's domain. OnBuild *string `json:"OnBuild"` // Container labels @@ -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/define/version.go b/libpod/define/version.go index 039b0ff27..2c17e6e92 100644 --- a/libpod/define/version.go +++ b/libpod/define/version.go @@ -27,6 +27,7 @@ type Version struct { BuiltTime string Built int64 OsArch string + Os string } // GetVersion returns a VersionOutput struct for API and podman @@ -49,5 +50,6 @@ func GetVersion() (Version, error) { BuiltTime: time.Unix(buildTime, 0).Format(time.ANSIC), Built: buildTime, OsArch: runtime.GOOS + "/" + runtime.GOARCH, + Os: runtime.GOOS, }, nil } diff --git a/libpod/doc.go b/libpod/doc.go new file mode 100644 index 000000000..948153181 --- /dev/null +++ b/libpod/doc.go @@ -0,0 +1,11 @@ +// The libpod library is not stable and we do not support use cases outside of +// this repository. The API can change at any time even with patch releases. +// +// If you need a stable interface Podman provides a HTTP API which follows semver, +// please see https://docs.podman.io/en/latest/markdown/podman-system-service.1.html +// to start the api service and https://docs.podman.io/en/latest/_static/api.html +// for the API reference. +// +// We also provide stable go bindings to talk to the api service from another go +// program, see the pkg/bindings directory. +package libpod diff --git a/libpod/events/config.go b/libpod/events/config.go index d88d7b6e3..188d15578 100644 --- a/libpod/events/config.go +++ b/libpod/events/config.go @@ -162,6 +162,8 @@ const ( Refresh Status = "refresh" // Remove ... Remove Status = "remove" + // Rename indicates that a container was renamed + Rename Status = "rename" // Renumber indicates that lock numbers were reallocated at user // request. Renumber Status = "renumber" diff --git a/libpod/events/events.go b/libpod/events/events.go index 16dd6424e..2cdd2ab67 100644 --- a/libpod/events/events.go +++ b/libpod/events/events.go @@ -7,7 +7,7 @@ import ( "time" "github.com/containers/storage/pkg/stringid" - "github.com/hpcloud/tail" + "github.com/nxadm/tail" "github.com/pkg/errors" ) @@ -188,6 +188,8 @@ func StringToStatus(name string) (Status, error) { return Refresh, nil case Remove.String(): return Remove, nil + case Rename.String(): + return Rename, nil case Renumber.String(): return Renumber, nil case Restart.String(): 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..22fbb5f9f 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 @@ -773,7 +773,7 @@ func libpodEnvVarsToKubeEnvVars(envs []string, imageEnvs []string) ([]v1.EnvVar, // libpodMountsToKubeVolumeMounts converts the containers mounts to a struct kube understands func libpodMountsToKubeVolumeMounts(c *Container) ([]v1.VolumeMount, []v1.Volume, map[string]string, error) { - namedVolumes, mounts := c.sortUserVolumes(c.config.Spec) + namedVolumes, mounts := c.SortUserVolumes(c.config.Spec) vms := make([]v1.VolumeMount, 0, len(mounts)) vos := make([]v1.Volume, 0, len(mounts)) annotations := make(map[string]string) 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/logs/log.go b/libpod/logs/log.go index 886911f2d..0eb3bb922 100644 --- a/libpod/logs/log.go +++ b/libpod/logs/log.go @@ -9,7 +9,7 @@ import ( "time" "github.com/containers/podman/v4/libpod/logs/reversereader" - "github.com/hpcloud/tail" + "github.com/nxadm/tail" "github.com/pkg/errors" "github.com/sirupsen/logrus" ) @@ -27,6 +27,9 @@ const ( // FullLogType signifies a log line is full FullLogType = "F" + + //ANSIEscapeResetCode is a code that resets all colors and text effects + ANSIEscapeResetCode = "\033[0m" ) // LogOptions is the options you can use for logs @@ -37,6 +40,7 @@ type LogOptions struct { Until time.Time Tail int64 Timestamps bool + Colors bool Multi bool WaitGroup *sync.WaitGroup UseName bool @@ -50,6 +54,7 @@ type LogLine struct { Msg string CID string CName string + ColorID int64 } // GetLogFile returns an hp tail for a container given options @@ -162,6 +167,24 @@ func getTailLog(path string, tail int) ([]*LogLine, error) { return tailLog, nil } +//getColor returns a ANSI escape code for color based on the colorID +func getColor(colorID int64) string { + colors := map[int64]string{ + 0: "\033[37m", // Light Gray + 1: "\033[31m", // Red + 2: "\033[33m", // Yellow + 3: "\033[34m", // Blue + 4: "\033[35m", // Magenta + 5: "\033[36m", // Cyan + 6: "\033[32m", // Green + } + return colors[colorID%int64(len(colors))] +} + +func (l *LogLine) colorize(prefix string) string { + return getColor(l.ColorID) + prefix + l.Msg + ANSIEscapeResetCode +} + // String converts a log line to a string for output given whether a detail // bool is specified. func (l *LogLine) String(options *LogOptions) string { @@ -177,10 +200,18 @@ func (l *LogLine) String(options *LogOptions) string { out = fmt.Sprintf("%s ", cid) } } + if options.Timestamps { out += fmt.Sprintf("%s ", l.Time.Format(LogTimeFormat)) } - return out + l.Msg + + if options.Colors { + out = l.colorize(out) + } else { + out += l.Msg + } + + return out } // Since returns a bool as to whether a log line occurred after a given time 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 7fd80927b..71e29f18f 100644 --- a/libpod/networking_linux.go +++ b/libpod/networking_linux.go @@ -1,3 +1,4 @@ +//go:build linux // +build linux package libpod @@ -497,10 +498,13 @@ func (r *Runtime) GetRootlessNetNs(new bool) (*RootlessNetNS, error) { return nil, err } - // move to systemd scope to prevent systemd from killing it - err = utils.MoveRootlessNetnsSlirpProcessToUserSlice(cmd.Process.Pid) - if err != nil { - logrus.Errorf("failed to move the rootless netns slirp4netns process to the systemd user.slice: %v", err) + if utils.RunsOnSystemd() { + // move to systemd scope to prevent systemd from killing it + err = utils.MoveRootlessNetnsSlirpProcessToUserSlice(cmd.Process.Pid) + if err != nil { + // only log this, it is not fatal but can lead to issues when running podman inside systemd units + logrus.Errorf("failed to move the rootless netns slirp4netns process to the systemd user.slice: %v", err) + } } // build a new resolv.conf file which uses the slirp4netns dns server address @@ -1001,7 +1005,7 @@ func (c *Container) getContainerNetworkInfo() (*define.InspectNetworkSettings, e } } // do not propagate error inspecting a joined network ns - logrus.Errorf("Error inspecting network namespace: %s of container %s: %v", networkNSPath, c.ID(), err) + logrus.Errorf("Inspecting network namespace: %s of container %s: %v", networkNSPath, c.ID(), err) } // We can't do more if the network is down. @@ -1148,7 +1152,7 @@ func (c *Container) inspectJoinedNetworkNS(networkns string) (q types.StatusBloc // result func resultToBasicNetworkConfig(result types.StatusBlock) (define.InspectBasicNetworkConfig, error) { config := define.InspectBasicNetworkConfig{} - interfaceNames := make([]string, len(result.Interfaces)) + interfaceNames := make([]string, 0, len(result.Interfaces)) for interfaceName := range result.Interfaces { interfaceNames = append(interfaceNames, interfaceName) } diff --git a/libpod/networking_machine.go b/libpod/networking_machine.go index ca759b893..d2a6b7cfa 100644 --- a/libpod/networking_machine.go +++ b/libpod/networking_machine.go @@ -11,6 +11,7 @@ import ( "net/http" "strconv" "strings" + "time" "github.com/containers/common/libnetwork/types" "github.com/sirupsen/logrus" @@ -36,7 +37,18 @@ func requestMachinePorts(expose bool, ports []types.PortMapping) error { url = url + "unexpose" } ctx := context.Background() - client := &http.Client{} + client := &http.Client{ + Transport: &http.Transport{ + // make sure to not set a proxy here so explicitly ignore the proxy + // since we want to talk directly to gvproxy + // https://github.com/containers/podman/issues/13628 + Proxy: nil, + MaxIdleConns: 50, + IdleConnTimeout: 30 * time.Second, + TLSHandshakeTimeout: 10 * time.Second, + ExpectContinueTimeout: 1 * time.Second, + }, + } buf := new(bytes.Buffer) for num, port := range ports { protocols := strings.Split(port.Protocol, ",") @@ -78,7 +90,6 @@ func requestMachinePorts(expose bool, ports []types.PortMapping) error { } func makeMachineRequest(ctx context.Context, client *http.Client, url string, buf io.Reader) error { - //var buf io.ReadWriter req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, buf) if err != nil { return err diff --git a/libpod/networking_slirp4netns.go b/libpod/networking_slirp4netns.go index 690f0c1fa..3f2842d4c 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" @@ -214,8 +216,7 @@ func (r *Runtime) setupSlirp4netns(ctr *Container, netns ns.NetNS) error { var err error path, err = exec.LookPath("slirp4netns") if err != nil { - logrus.Errorf("Could not find slirp4netns, the network namespace won't be configured: %v", err) - return nil + return fmt.Errorf("could not find slirp4netns, the network namespace can't be configured: %w", err) } } @@ -302,11 +303,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 +323,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 resetting 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 +363,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 @@ -594,60 +614,73 @@ func (r *Runtime) setupRootlessPortMappingViaSlirp(ctr *Container, cmd *exec.Cmd // for each port we want to add we need to open a connection to the slirp4netns control socket // and send the add_hostfwd command. - for _, i := range ctr.convertPortMappings() { - conn, err := net.Dial("unix", apiSocket) - if err != nil { - return errors.Wrapf(err, "cannot open connection to %s", apiSocket) - } - defer func() { - if err := conn.Close(); err != nil { - logrus.Errorf("Unable to close connection: %q", err) + for _, port := range ctr.convertPortMappings() { + protocols := strings.Split(port.Protocol, ",") + for _, protocol := range protocols { + hostIP := port.HostIP + if hostIP == "" { + hostIP = "0.0.0.0" + } + for i := uint16(0); i < port.Range; i++ { + if err := openSlirp4netnsPort(apiSocket, protocol, hostIP, port.HostPort+i, port.ContainerPort+i); err != nil { + return err + } } - }() - hostIP := i.HostIP - if hostIP == "" { - hostIP = "0.0.0.0" - } - apiCmd := slirp4netnsCmd{ - Execute: "add_hostfwd", - Args: slirp4netnsCmdArg{ - Proto: i.Protocol, - HostAddr: hostIP, - HostPort: i.HostPort, - GuestPort: i.ContainerPort, - }, - } - // create the JSON payload and send it. Mark the end of request shutting down writes - // to the socket, as requested by slirp4netns. - data, err := json.Marshal(&apiCmd) - if err != nil { - return errors.Wrapf(err, "cannot marshal JSON for slirp4netns") - } - if _, err := conn.Write([]byte(fmt.Sprintf("%s\n", data))); err != nil { - return errors.Wrapf(err, "cannot write to control socket %s", apiSocket) - } - if err := conn.(*net.UnixConn).CloseWrite(); err != nil { - return errors.Wrapf(err, "cannot shutdown the socket %s", apiSocket) - } - buf := make([]byte, 2048) - readLength, err := conn.Read(buf) - if err != nil { - return errors.Wrapf(err, "cannot read from control socket %s", apiSocket) - } - // if there is no 'error' key in the received JSON data, then the operation was - // successful. - var y map[string]interface{} - if err := json.Unmarshal(buf[0:readLength], &y); err != nil { - return errors.Wrapf(err, "error parsing error status from slirp4netns") - } - if e, found := y["error"]; found { - return errors.Errorf("error from slirp4netns while setting up port redirection: %v", e) } } logrus.Debug("slirp4netns port-forwarding setup via add_hostfwd is ready") return nil } +// openSlirp4netnsPort sends the slirp4netns pai quey to the given socket +func openSlirp4netnsPort(apiSocket, proto, hostip string, hostport, guestport uint16) error { + conn, err := net.Dial("unix", apiSocket) + if err != nil { + return errors.Wrapf(err, "cannot open connection to %s", apiSocket) + } + defer func() { + if err := conn.Close(); err != nil { + logrus.Errorf("Unable to close slirp4netns connection: %q", err) + } + }() + apiCmd := slirp4netnsCmd{ + Execute: "add_hostfwd", + Args: slirp4netnsCmdArg{ + Proto: proto, + HostAddr: hostip, + HostPort: hostport, + GuestPort: guestport, + }, + } + // create the JSON payload and send it. Mark the end of request shutting down writes + // to the socket, as requested by slirp4netns. + data, err := json.Marshal(&apiCmd) + if err != nil { + return errors.Wrapf(err, "cannot marshal JSON for slirp4netns") + } + if _, err := conn.Write([]byte(fmt.Sprintf("%s\n", data))); err != nil { + return errors.Wrapf(err, "cannot write to control socket %s", apiSocket) + } + if err := conn.(*net.UnixConn).CloseWrite(); err != nil { + return errors.Wrapf(err, "cannot shutdown the socket %s", apiSocket) + } + buf := make([]byte, 2048) + readLength, err := conn.Read(buf) + if err != nil { + return errors.Wrapf(err, "cannot read from control socket %s", apiSocket) + } + // if there is no 'error' key in the received JSON data, then the operation was + // successful. + var y map[string]interface{} + if err := json.Unmarshal(buf[0:readLength], &y); err != nil { + return errors.Wrapf(err, "error parsing error status from slirp4netns") + } + if e, found := y["error"]; found { + return errors.Errorf("from slirp4netns while setting up port redirection: %v", e) + } + return nil +} + func getRootlessPortChildIP(c *Container, netStatus map[string]types.StatusBlock) string { if c.config.NetMode.IsSlirp4netns() { slirp4netnsIP, err := GetSlirp4netnsIP(c.slirp4netnsSubnet) 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_exec_linux.go b/libpod/oci_conmon_exec_linux.go index c88ef2c67..1005d18da 100644 --- a/libpod/oci_conmon_exec_linux.go +++ b/libpod/oci_conmon_exec_linux.go @@ -758,11 +758,14 @@ func prepareProcessExec(c *Container, options *ExecOptions, env []string, sessio } else { pspec.Capabilities.Bounding = ctrSpec.Process.Capabilities.Bounding } + + // Always unset the inheritable capabilities similarly to what the Linux kernel does + // They are used only when using capabilities with uid != 0. + pspec.Capabilities.Inheritable = []string{} + if execUser.Uid == 0 { pspec.Capabilities.Effective = pspec.Capabilities.Bounding - pspec.Capabilities.Inheritable = pspec.Capabilities.Bounding pspec.Capabilities.Permitted = pspec.Capabilities.Bounding - pspec.Capabilities.Ambient = pspec.Capabilities.Bounding } else { if user == c.config.User { pspec.Capabilities.Effective = ctrSpec.Process.Capabilities.Effective diff --git a/libpod/oci_conmon_linux.go b/libpod/oci_conmon_linux.go index cf439cd33..06ba8a03f 100644 --- a/libpod/oci_conmon_linux.go +++ b/libpod/oci_conmon_linux.go @@ -1,3 +1,4 @@ +//go:build linux // +build linux package libpod @@ -659,7 +660,7 @@ func (r *ConmonOCIRuntime) HTTPAttach(ctr *Container, req *http.Request, w http. } errChan <- err }() - if err := ctr.ReadLog(context.Background(), logOpts, logChan); err != nil { + if err := ctr.ReadLog(context.Background(), logOpts, logChan, 0); err != nil { return err } go func() { @@ -748,7 +749,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()) @@ -1013,7 +1014,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 { @@ -1198,7 +1200,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 } @@ -1369,7 +1371,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 @@ -1542,17 +1544,19 @@ func readConmonPipeData(runtimeName string, pipe *os.File, ociLog string) (int, var si *syncInfo rdr := bufio.NewReader(pipe) b, err := rdr.ReadBytes('\n') - if err != nil { + // ignore EOF here, error is returned even when data was read + // if it is no valid json unmarshal will fail below + if err != nil && !errors.Is(err, io.EOF) { ch <- syncStruct{err: err} } if err := json.Unmarshal(b, &si); err != nil { - ch <- syncStruct{err: err} + ch <- syncStruct{err: fmt.Errorf("conmon bytes %q: %w", string(b), err)} return } 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_api.go b/libpod/pod_api.go index be726d8d1..48049798b 100644 --- a/libpod/pod_api.go +++ b/libpod/pod_api.go @@ -602,8 +602,8 @@ func (p *Pod) Inspect() (*define.InspectPodData, error) { infraConfig.CPUSetCPUs = p.ResourceLim().CPU.Cpus infraConfig.PidNS = p.PidMode() infraConfig.UserNS = p.UserNSMode() - namedVolumes, mounts := infra.sortUserVolumes(infra.config.Spec) - inspectMounts, err = infra.GetInspectMounts(namedVolumes, infra.config.ImageVolumes, mounts) + namedVolumes, mounts := infra.SortUserVolumes(infra.config.Spec) + inspectMounts, err = infra.GetMounts(namedVolumes, infra.config.ImageVolumes, mounts) infraSecurity = infra.GetSecurityOptions() if err != nil { return nil, err 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_ctr.go b/libpod/runtime_ctr.go index fc1a688fb..8c3d283a5 100644 --- a/libpod/runtime_ctr.go +++ b/libpod/runtime_ctr.go @@ -155,6 +155,7 @@ func (r *Runtime) RenameContainer(ctx context.Context, ctr *Container, newName s return nil, err } + ctr.newContainerEvent(events.Rename) return ctr, nil } 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 230491c1a..2bbccfdf6 100644 --- a/libpod/runtime_pod_linux.go +++ b/libpod/runtime_pod_linux.go @@ -6,6 +6,7 @@ package libpod import ( "context" "fmt" + "os" "path" "path/filepath" "strings" @@ -239,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 f79ceaa87..bffafdc15 100644 --- a/libpod/volume.go +++ b/libpod/volume.go @@ -1,13 +1,12 @@ package libpod import ( - "os" - "path/filepath" "time" "github.com/containers/podman/v4/libpod/define" "github.com/containers/podman/v4/libpod/lock" "github.com/containers/podman/v4/libpod/plugin" + "github.com/containers/podman/v4/pkg/util" ) // Volume is a libpod named volume. @@ -93,14 +92,7 @@ func (v *Volume) Name() string { // Returns the size on disk of volume func (v *Volume) Size() (uint64, error) { - var size uint64 - err := filepath.Walk(v.config.MountPoint, func(path string, info os.FileInfo, err error) error { - if err == nil && !info.IsDir() { - size += (uint64)(info.Size()) - } - return err - }) - return size, err + return util.SizeOfPath(v.config.MountPoint) } // Driver retrieves the volume's driver. |