123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204 |
- //go:build !windows
- package daemon // import "github.com/docker/docker/daemon"
- import (
- "bytes"
- "context"
- "fmt"
- "os/exec"
- "regexp"
- "strconv"
- "strings"
- "github.com/docker/docker/api/types/container"
- "github.com/docker/docker/api/types/events"
- "github.com/docker/docker/errdefs"
- libcontainerdtypes "github.com/docker/docker/libcontainerd/types"
- "github.com/pkg/errors"
- )
- func validatePSArgs(psArgs string) error {
- // NOTE: \\s does not detect unicode whitespaces.
- // So we use fieldsASCII instead of strings.Fields in parsePSOutput.
- // See https://github.com/docker/docker/pull/24358
- //nolint: gosimple
- re := regexp.MustCompile("\\s+([^\\s]*)=\\s*(PID[^\\s]*)")
- for _, group := range re.FindAllStringSubmatch(psArgs, -1) {
- if len(group) >= 3 {
- k := group[1]
- v := group[2]
- if k != "pid" {
- return fmt.Errorf(`specifying "%s=%s" is not allowed`, k, v)
- }
- }
- }
- return nil
- }
- // fieldsASCII is similar to strings.Fields but only allows ASCII whitespaces
- func fieldsASCII(s string) []string {
- fn := func(r rune) bool {
- switch r {
- case '\t', '\n', '\f', '\r', ' ':
- return true
- }
- return false
- }
- return strings.FieldsFunc(s, fn)
- }
- func appendProcess2ProcList(procList *container.ContainerTopOKBody, fields []string) {
- // Make sure number of fields equals number of header titles
- // merging "overhanging" fields
- process := fields[:len(procList.Titles)-1]
- process = append(process, strings.Join(fields[len(procList.Titles)-1:], " "))
- procList.Processes = append(procList.Processes, process)
- }
- func hasPid(procs []uint32, pid int) bool {
- for _, p := range procs {
- if int(p) == pid {
- return true
- }
- }
- return false
- }
- func parsePSOutput(output []byte, procs []uint32) (*container.ContainerTopOKBody, error) {
- procList := &container.ContainerTopOKBody{}
- lines := strings.Split(string(output), "\n")
- procList.Titles = fieldsASCII(lines[0])
- pidIndex := -1
- for i, name := range procList.Titles {
- if name == "PID" {
- pidIndex = i
- break
- }
- }
- if pidIndex == -1 {
- return nil, fmt.Errorf("Couldn't find PID field in ps output")
- }
- // loop through the output and extract the PID from each line
- // fixing #30580, be able to display thread line also when "m" option used
- // in "docker top" client command
- preContainedPidFlag := false
- for _, line := range lines[1:] {
- if len(line) == 0 {
- continue
- }
- fields := fieldsASCII(line)
- var (
- p int
- err error
- )
- if fields[pidIndex] == "-" {
- if preContainedPidFlag {
- appendProcess2ProcList(procList, fields)
- }
- continue
- }
- p, err = strconv.Atoi(fields[pidIndex])
- if err != nil {
- return nil, fmt.Errorf("Unexpected pid '%s': %s", fields[pidIndex], err)
- }
- if hasPid(procs, p) {
- preContainedPidFlag = true
- appendProcess2ProcList(procList, fields)
- continue
- }
- preContainedPidFlag = false
- }
- return procList, nil
- }
- // psPidsArg converts a slice of PIDs to a string consisting
- // of comma-separated list of PIDs prepended by "-q".
- // For example, psPidsArg([]uint32{1,2,3}) returns "-q1,2,3".
- func psPidsArg(pids []uint32) string {
- b := []byte{'-', 'q'}
- for i, p := range pids {
- b = strconv.AppendUint(b, uint64(p), 10)
- if i < len(pids)-1 {
- b = append(b, ',')
- }
- }
- return string(b)
- }
- // ContainerTop lists the processes running inside of the given
- // container by calling ps with the given args, or with the flags
- // "-ef" if no args are given. An error is returned if the container
- // is not found, or is not running, or if there are any problems
- // running ps, or parsing the output.
- func (daemon *Daemon) ContainerTop(name string, psArgs string) (*container.ContainerTopOKBody, error) {
- if psArgs == "" {
- psArgs = "-ef"
- }
- if err := validatePSArgs(psArgs); err != nil {
- return nil, err
- }
- ctr, err := daemon.GetContainer(name)
- if err != nil {
- return nil, err
- }
- tsk, err := func() (libcontainerdtypes.Task, error) {
- ctr.Lock()
- defer ctr.Unlock()
- tsk, err := ctr.GetRunningTask()
- if err != nil {
- return nil, err
- }
- if ctr.Restarting {
- return nil, errContainerIsRestarting(ctr.ID)
- }
- return tsk, nil
- }()
- if err != nil {
- return nil, err
- }
- infos, err := tsk.Pids(context.Background())
- if err != nil {
- return nil, err
- }
- procs := make([]uint32, len(infos))
- for i, p := range infos {
- procs[i] = p.Pid
- }
- args := strings.Split(psArgs, " ")
- pids := psPidsArg(procs)
- output, err := exec.Command("ps", append(args, pids)...).Output()
- if err != nil {
- // some ps options (such as f) can't be used together with q,
- // so retry without it
- output, err = exec.Command("ps", args...).Output()
- if err != nil {
- if ee, ok := err.(*exec.ExitError); ok {
- // first line of stderr shows why ps failed
- line := bytes.SplitN(ee.Stderr, []byte{'\n'}, 2)
- if len(line) > 0 && len(line[0]) > 0 {
- err = errors.New(string(line[0]))
- }
- }
- return nil, errdefs.System(errors.Wrap(err, "ps"))
- }
- }
- procList, err := parsePSOutput(output, procs)
- if err != nil {
- return nil, err
- }
- daemon.LogContainerEvent(ctr, events.ActionTop)
- return procList, nil
- }
|