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 }