github.com/containers/podman/v4@v4.9.4/libpod/container_top_freebsd.go (about)

     1  //go:build !remote
     2  // +build !remote
     3  
     4  package libpod
     5  
     6  import (
     7  	"bufio"
     8  	"errors"
     9  	"fmt"
    10  	"os/exec"
    11  	"strings"
    12  	"sync"
    13  
    14  	"github.com/containers/podman/v4/libpod/define"
    15  	"github.com/containers/podman/v4/pkg/util"
    16  	"github.com/google/shlex"
    17  	"github.com/sirupsen/logrus"
    18  )
    19  
    20  var isDescriptor = map[string]bool{}
    21  
    22  func init() {
    23  	allDescriptors, err := util.GetContainerPidInformationDescriptors()
    24  	if err != nil {
    25  		// Should never happen
    26  		logrus.Debugf("failed call to util.GetContainerPidInformationDescriptors()")
    27  		return
    28  	}
    29  	for _, d := range allDescriptors {
    30  		isDescriptor[d] = true
    31  	}
    32  }
    33  
    34  // Top gathers statistics about the running processes in a container. It returns a
    35  // []string for output
    36  func (c *Container) Top(descriptors []string) ([]string, error) {
    37  	conStat, err := c.State()
    38  	if err != nil {
    39  		return nil, fmt.Errorf("unable to look up state for %s: %w", c.ID(), err)
    40  	}
    41  	if conStat != define.ContainerStateRunning {
    42  		return nil, errors.New("top can only be used on running containers")
    43  	}
    44  
    45  	// Default to 'ps -ef' compatible descriptors
    46  	if len(strings.Join(descriptors, "")) == 0 {
    47  		descriptors = []string{"user", "pid", "ppid", "pcpu", "etime", "tty", "time", "args"}
    48  	}
    49  
    50  	// If everything in descriptors is a supported AIX format
    51  	// descriptor, we use 'ps -ao <descriptors>', otherwise we pass
    52  	// everything straight through to ps.
    53  	supportedDescriptors := true
    54  	for _, d := range descriptors {
    55  		if _, ok := isDescriptor[d]; !ok {
    56  			supportedDescriptors = false
    57  			break
    58  		}
    59  	}
    60  	if supportedDescriptors {
    61  		descriptors = []string{"-ao", strings.Join(descriptors, ",")}
    62  	}
    63  
    64  	// Note that the descriptors to ps(1) must be shlexed (see #12452).
    65  	psDescriptors := []string{}
    66  	for _, d := range descriptors {
    67  		shSplit, err := shlex.Split(d)
    68  		if err != nil {
    69  			return nil, fmt.Errorf("parsing ps args: %w", err)
    70  		}
    71  		for _, s := range shSplit {
    72  			if s != "" {
    73  				psDescriptors = append(psDescriptors, s)
    74  			}
    75  		}
    76  	}
    77  
    78  	jailName, err := c.jailName()
    79  	if err != nil {
    80  		return nil, fmt.Errorf("getting jail name: %w", err)
    81  	}
    82  
    83  	args := []string{
    84  		"-J",
    85  		jailName,
    86  	}
    87  	args = append(args, psDescriptors...)
    88  
    89  	output, err := execPS(args)
    90  	if err != nil {
    91  		return nil, fmt.Errorf("executing ps(1): %w", err)
    92  	}
    93  
    94  	return output, nil
    95  }
    96  
    97  func execPS(args []string) ([]string, error) {
    98  	cmd := exec.Command("ps", args...)
    99  	stdoutPipe, err := cmd.StdoutPipe()
   100  	if err != nil {
   101  		return nil, err
   102  	}
   103  	stderrPipe, err := cmd.StderrPipe()
   104  	if err != nil {
   105  		return nil, err
   106  	}
   107  
   108  	var wg sync.WaitGroup
   109  	wg.Add(2)
   110  	stdout := []string{}
   111  	go func() {
   112  		scanner := bufio.NewScanner(stdoutPipe)
   113  		for scanner.Scan() {
   114  			stdout = append(stdout, scanner.Text())
   115  		}
   116  		wg.Done()
   117  	}()
   118  	stderr := []string{}
   119  	go func() {
   120  		scanner := bufio.NewScanner(stderrPipe)
   121  		for scanner.Scan() {
   122  			stderr = append(stderr, scanner.Text())
   123  		}
   124  		wg.Done()
   125  	}()
   126  
   127  	if err := cmd.Start(); err != nil {
   128  		return nil, err
   129  	}
   130  	wg.Wait()
   131  	if err := cmd.Wait(); err != nil {
   132  		return nil, fmt.Errorf("ps(1) command failed: %w, output: %s", err, strings.Join(stderr, " "))
   133  	}
   134  
   135  	return stdout, nil
   136  }