github.com/gravitational/moby@v1.13.1/daemon/top_unix.go (about) 1 //+build !windows 2 3 package daemon 4 5 import ( 6 "fmt" 7 "os/exec" 8 "regexp" 9 "strconv" 10 "strings" 11 12 "github.com/docker/docker/api/types" 13 ) 14 15 func validatePSArgs(psArgs string) error { 16 // NOTE: \\s does not detect unicode whitespaces. 17 // So we use fieldsASCII instead of strings.Fields in parsePSOutput. 18 // See https://github.com/docker/docker/pull/24358 19 re := regexp.MustCompile("\\s+([^\\s]*)=\\s*(PID[^\\s]*)") 20 for _, group := range re.FindAllStringSubmatch(psArgs, -1) { 21 if len(group) >= 3 { 22 k := group[1] 23 v := group[2] 24 if k != "pid" { 25 return fmt.Errorf("specifying \"%s=%s\" is not allowed", k, v) 26 } 27 } 28 } 29 return nil 30 } 31 32 // fieldsASCII is similar to strings.Fields but only allows ASCII whitespaces 33 func fieldsASCII(s string) []string { 34 fn := func(r rune) bool { 35 switch r { 36 case '\t', '\n', '\f', '\r', ' ': 37 return true 38 } 39 return false 40 } 41 return strings.FieldsFunc(s, fn) 42 } 43 44 func parsePSOutput(output []byte, pids []int) (*types.ContainerProcessList, error) { 45 procList := &types.ContainerProcessList{} 46 47 lines := strings.Split(string(output), "\n") 48 procList.Titles = fieldsASCII(lines[0]) 49 50 pidIndex := -1 51 for i, name := range procList.Titles { 52 if name == "PID" { 53 pidIndex = i 54 } 55 } 56 if pidIndex == -1 { 57 return nil, fmt.Errorf("Couldn't find PID field in ps output") 58 } 59 60 // loop through the output and extract the PID from each line 61 for _, line := range lines[1:] { 62 if len(line) == 0 { 63 continue 64 } 65 fields := fieldsASCII(line) 66 p, err := strconv.Atoi(fields[pidIndex]) 67 if err != nil { 68 return nil, fmt.Errorf("Unexpected pid '%s': %s", fields[pidIndex], err) 69 } 70 71 for _, pid := range pids { 72 if pid == p { 73 // Make sure number of fields equals number of header titles 74 // merging "overhanging" fields 75 process := fields[:len(procList.Titles)-1] 76 process = append(process, strings.Join(fields[len(procList.Titles)-1:], " ")) 77 procList.Processes = append(procList.Processes, process) 78 } 79 } 80 } 81 return procList, nil 82 } 83 84 // ContainerTop lists the processes running inside of the given 85 // container by calling ps with the given args, or with the flags 86 // "-ef" if no args are given. An error is returned if the container 87 // is not found, or is not running, or if there are any problems 88 // running ps, or parsing the output. 89 func (daemon *Daemon) ContainerTop(name string, psArgs string) (*types.ContainerProcessList, error) { 90 if psArgs == "" { 91 psArgs = "-ef" 92 } 93 94 if err := validatePSArgs(psArgs); err != nil { 95 return nil, err 96 } 97 98 container, err := daemon.GetContainer(name) 99 if err != nil { 100 return nil, err 101 } 102 103 if !container.IsRunning() { 104 return nil, errNotRunning{container.ID} 105 } 106 107 if container.IsRestarting() { 108 return nil, errContainerIsRestarting(container.ID) 109 } 110 111 pids, err := daemon.containerd.GetPidsForContainer(container.ID) 112 if err != nil { 113 return nil, err 114 } 115 116 output, err := exec.Command("ps", strings.Split(psArgs, " ")...).Output() 117 if err != nil { 118 return nil, fmt.Errorf("Error running ps: %v", err) 119 } 120 procList, err := parsePSOutput(output, pids) 121 if err != nil { 122 return nil, err 123 } 124 daemon.LogContainerEvent(container, "top") 125 return procList, nil 126 }