diff --git a/libpod/container_internal_freebsd.go b/libpod/container_internal_freebsd.go index 468f5725c7..cae11cd2ba 100644 --- a/libpod/container_internal_freebsd.go +++ b/libpod/container_internal_freebsd.go @@ -279,3 +279,11 @@ func (c *Container) getConmonPidFd() int { // keeping things simple for now. return -1 } + +func (c *Container) jailName() string { + if c.state.NetNS != nil { + return c.state.NetNS.Name + "." + c.ID() + } else { + return c.ID() + } +} diff --git a/libpod/container_top_freebsd.go b/libpod/container_top_freebsd.go new file mode 100644 index 0000000000..1a59a5d68f --- /dev/null +++ b/libpod/container_top_freebsd.go @@ -0,0 +1,131 @@ +//go:build freebsd +// +build freebsd + +package libpod + +import ( + "bufio" + "errors" + "fmt" + "os/exec" + "strings" + "sync" + + "github.com/containers/podman/v4/libpod/define" + "github.com/containers/podman/v4/pkg/util" + "github.com/google/shlex" + "github.com/sirupsen/logrus" +) + +var isDescriptor = map[string]bool{} + +func init() { + allDescriptors, err := util.GetContainerPidInformationDescriptors() + if err != nil { + // Should never happen + logrus.Debugf("failed call to util.GetContainerPidInformationDescriptors()") + return + } + for _, d := range allDescriptors { + isDescriptor[d] = true + } +} + +// Top gathers statistics about the running processes in a container. It returns a +// []string for output +func (c *Container) Top(descriptors []string) ([]string, error) { + conStat, err := c.State() + if err != nil { + return nil, fmt.Errorf("unable to look up state for %s: %w", c.ID(), err) + } + if conStat != define.ContainerStateRunning { + return nil, errors.New("top can only be used on running containers") + } + + // Default to 'ps -ef' compatible descriptors + if len(descriptors) == 0 { + descriptors = []string{"user", "pid", "ppid", "pcpu", "etime", "tty", "time", "args"} + } + + // If everything in descriptors is a supported AIX format + // descriptor, we use 'ps -ao ', otherwise we pass + // everything straight through to ps. + supportedDescriptors := true + for _, d := range descriptors { + if _, ok := isDescriptor[d]; !ok { + supportedDescriptors = false + break + } + } + if supportedDescriptors { + descriptors = []string{"-ao", strings.Join(descriptors, ",")} + } + + // Note that the descriptors to ps(1) must be shlexed (see #12452). + psDescriptors := []string{} + for _, d := range descriptors { + shSplit, err := shlex.Split(d) + if err != nil { + return nil, fmt.Errorf("parsing ps args: %w", err) + } + for _, s := range shSplit { + if s != "" { + psDescriptors = append(psDescriptors, s) + } + } + } + + args := []string{ + "-J", + c.jailName(), + } + args = append(args, psDescriptors...) + + output, err := c.execPS(args) + if err != nil { + return nil, fmt.Errorf("executing ps(1): %w", err) + } + + return output, nil +} + +func (c *Container) execPS(args []string) ([]string, error) { + cmd := exec.Command("ps", args...) + stdoutPipe, err := cmd.StdoutPipe() + if err != nil { + return nil, err + } + stderrPipe, err := cmd.StderrPipe() + if err != nil { + return nil, err + } + + var wg sync.WaitGroup + wg.Add(2) + stdout := []string{} + go func() { + scanner := bufio.NewScanner(stdoutPipe) + for scanner.Scan() { + stdout = append(stdout, scanner.Text()) + } + wg.Done() + }() + stderr := []string{} + go func() { + scanner := bufio.NewScanner(stderrPipe) + for scanner.Scan() { + stderr = append(stderr, scanner.Text()) + } + wg.Done() + }() + + if err := cmd.Start(); err != nil { + return nil, err + } + wg.Wait() + if err := cmd.Wait(); err != nil { + return nil, fmt.Errorf("ps(1) command failed: %w, output: %s", err, strings.Join(stderr, " ")) + } + + return stdout, nil +} diff --git a/libpod/container_top_unsupported.go b/libpod/container_top_unsupported.go index a8d9b970b7..444f4e9c62 100644 --- a/libpod/container_top_unsupported.go +++ b/libpod/container_top_unsupported.go @@ -1,5 +1,5 @@ -//go:build !linux -// +build !linux +//go:build !linux && !freebsd +// +build !linux,!freebsd package libpod diff --git a/libpod/stats_freebsd.go b/libpod/stats_freebsd.go index be22e86184..fcc0985597 100644 --- a/libpod/stats_freebsd.go +++ b/libpod/stats_freebsd.go @@ -20,13 +20,9 @@ import ( func (c *Container) getPlatformContainerStats(stats *define.ContainerStats, previousStats *define.ContainerStats) error { now := uint64(time.Now().UnixNano()) - jailName := c.ID() - if c.state.NetNS != nil { - jailName = c.state.NetNS.Name + "." + jailName - } - entries, err := rctl.GetRacct("jail:" + jailName) + entries, err := rctl.GetRacct("jail:" + c.jailName()) if err != nil { - return fmt.Errorf("unable to read accounting for %s: %w", jailName, err) + return fmt.Errorf("unable to read accounting for %s: %w", c.jailName(), err) } // If the current total usage is less than what was previously diff --git a/pkg/util/utils_freebsd.go b/pkg/util/utils_freebsd.go index ba91308aff..621bb436ee 100644 --- a/pkg/util/utils_freebsd.go +++ b/pkg/util/utils_freebsd.go @@ -4,13 +4,29 @@ package util import ( - "errors" - "github.com/opencontainers/runtime-tools/generate" ) func GetContainerPidInformationDescriptors() ([]string, error) { - return []string{}, errors.New("this function is not supported on freebsd") + // These are chosen to match the set of AIX format descriptors + // supported in Linux - FreeBSD ps does support (many) others. + return []string{ + "args", + "comm", + "etime", + "group", + "nice", + "pcpu", + "pgid", + "pid", + "ppid", + "rgroup", + "ruser", + "time", + "tty", + "user", + "vsz", + }, nil } func AddPrivilegedDevices(g *generate.Generator, systemdMode bool) error {