github.com/docker/docker@v299999999.0.0-20200612211812-aaf470eca7b5+incompatible/daemon/top_unix.go (about)

     1  //+build !windows
     2  
     3  package daemon // import "github.com/docker/docker/daemon"
     4  
     5  import (
     6  	"bytes"
     7  	"context"
     8  	"fmt"
     9  	"os/exec"
    10  	"regexp"
    11  	"strconv"
    12  	"strings"
    13  
    14  	"github.com/docker/docker/api/types/container"
    15  	"github.com/docker/docker/errdefs"
    16  	"github.com/pkg/errors"
    17  )
    18  
    19  func validatePSArgs(psArgs string) error {
    20  	// NOTE: \\s does not detect unicode whitespaces.
    21  	// So we use fieldsASCII instead of strings.Fields in parsePSOutput.
    22  	// See https://github.com/docker/docker/pull/24358
    23  	// nolint: gosimple
    24  	re := regexp.MustCompile("\\s+([^\\s]*)=\\s*(PID[^\\s]*)")
    25  	for _, group := range re.FindAllStringSubmatch(psArgs, -1) {
    26  		if len(group) >= 3 {
    27  			k := group[1]
    28  			v := group[2]
    29  			if k != "pid" {
    30  				return fmt.Errorf("specifying \"%s=%s\" is not allowed", k, v)
    31  			}
    32  		}
    33  	}
    34  	return nil
    35  }
    36  
    37  // fieldsASCII is similar to strings.Fields but only allows ASCII whitespaces
    38  func fieldsASCII(s string) []string {
    39  	fn := func(r rune) bool {
    40  		switch r {
    41  		case '\t', '\n', '\f', '\r', ' ':
    42  			return true
    43  		}
    44  		return false
    45  	}
    46  	return strings.FieldsFunc(s, fn)
    47  }
    48  
    49  func appendProcess2ProcList(procList *container.ContainerTopOKBody, fields []string) {
    50  	// Make sure number of fields equals number of header titles
    51  	// merging "overhanging" fields
    52  	process := fields[:len(procList.Titles)-1]
    53  	process = append(process, strings.Join(fields[len(procList.Titles)-1:], " "))
    54  	procList.Processes = append(procList.Processes, process)
    55  }
    56  
    57  func hasPid(procs []uint32, pid int) bool {
    58  	for _, p := range procs {
    59  		if int(p) == pid {
    60  			return true
    61  		}
    62  	}
    63  	return false
    64  }
    65  
    66  func parsePSOutput(output []byte, procs []uint32) (*container.ContainerTopOKBody, error) {
    67  	procList := &container.ContainerTopOKBody{}
    68  
    69  	lines := strings.Split(string(output), "\n")
    70  	procList.Titles = fieldsASCII(lines[0])
    71  
    72  	pidIndex := -1
    73  	for i, name := range procList.Titles {
    74  		if name == "PID" {
    75  			pidIndex = i
    76  			break
    77  		}
    78  	}
    79  	if pidIndex == -1 {
    80  		return nil, fmt.Errorf("Couldn't find PID field in ps output")
    81  	}
    82  
    83  	// loop through the output and extract the PID from each line
    84  	// fixing #30580, be able to display thread line also when "m" option used
    85  	// in "docker top" client command
    86  	preContainedPidFlag := false
    87  	for _, line := range lines[1:] {
    88  		if len(line) == 0 {
    89  			continue
    90  		}
    91  		fields := fieldsASCII(line)
    92  
    93  		var (
    94  			p   int
    95  			err error
    96  		)
    97  
    98  		if fields[pidIndex] == "-" {
    99  			if preContainedPidFlag {
   100  				appendProcess2ProcList(procList, fields)
   101  			}
   102  			continue
   103  		}
   104  		p, err = strconv.Atoi(fields[pidIndex])
   105  		if err != nil {
   106  			return nil, fmt.Errorf("Unexpected pid '%s': %s", fields[pidIndex], err)
   107  		}
   108  
   109  		if hasPid(procs, p) {
   110  			preContainedPidFlag = true
   111  			appendProcess2ProcList(procList, fields)
   112  			continue
   113  		}
   114  		preContainedPidFlag = false
   115  	}
   116  	return procList, nil
   117  }
   118  
   119  // psPidsArg converts a slice of PIDs to a string consisting
   120  // of comma-separated list of PIDs prepended by "-q".
   121  // For example, psPidsArg([]uint32{1,2,3}) returns "-q1,2,3".
   122  func psPidsArg(pids []uint32) string {
   123  	b := []byte{'-', 'q'}
   124  	for i, p := range pids {
   125  		b = strconv.AppendUint(b, uint64(p), 10)
   126  		if i < len(pids)-1 {
   127  			b = append(b, ',')
   128  		}
   129  	}
   130  	return string(b)
   131  }
   132  
   133  // ContainerTop lists the processes running inside of the given
   134  // container by calling ps with the given args, or with the flags
   135  // "-ef" if no args are given.  An error is returned if the container
   136  // is not found, or is not running, or if there are any problems
   137  // running ps, or parsing the output.
   138  func (daemon *Daemon) ContainerTop(name string, psArgs string) (*container.ContainerTopOKBody, error) {
   139  	if psArgs == "" {
   140  		psArgs = "-ef"
   141  	}
   142  
   143  	if err := validatePSArgs(psArgs); err != nil {
   144  		return nil, err
   145  	}
   146  
   147  	ctr, err := daemon.GetContainer(name)
   148  	if err != nil {
   149  		return nil, err
   150  	}
   151  
   152  	if !ctr.IsRunning() {
   153  		return nil, errNotRunning(ctr.ID)
   154  	}
   155  
   156  	if ctr.IsRestarting() {
   157  		return nil, errContainerIsRestarting(ctr.ID)
   158  	}
   159  
   160  	procs, err := daemon.containerd.ListPids(context.Background(), ctr.ID)
   161  	if err != nil {
   162  		return nil, err
   163  	}
   164  
   165  	args := strings.Split(psArgs, " ")
   166  	pids := psPidsArg(procs)
   167  	output, err := exec.Command("ps", append(args, pids)...).Output()
   168  	if err != nil {
   169  		// some ps options (such as f) can't be used together with q,
   170  		// so retry without it
   171  		output, err = exec.Command("ps", args...).Output()
   172  		if err != nil {
   173  			if ee, ok := err.(*exec.ExitError); ok {
   174  				// first line of stderr shows why ps failed
   175  				line := bytes.SplitN(ee.Stderr, []byte{'\n'}, 2)
   176  				if len(line) > 0 && len(line[0]) > 0 {
   177  					err = errors.New(string(line[0]))
   178  				}
   179  			}
   180  			return nil, errdefs.System(errors.Wrap(err, "ps"))
   181  		}
   182  	}
   183  	procList, err := parsePSOutput(output, procs)
   184  	if err != nil {
   185  		return nil, err
   186  	}
   187  	daemon.LogContainerEvent(ctr, "top")
   188  	return procList, nil
   189  }