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