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