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