github.com/jiasir/docker@v1.3.3-0.20170609024000-252e610103e7/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/container" 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 appendProcess2ProcList(procList *container.ContainerTopOKBody, fields []string) { 45 // Make sure number of fields equals number of header titles 46 // merging "overhanging" fields 47 process := fields[:len(procList.Titles)-1] 48 process = append(process, strings.Join(fields[len(procList.Titles)-1:], " ")) 49 procList.Processes = append(procList.Processes, process) 50 } 51 52 func hasPid(pids []int, pid int) bool { 53 for _, i := range pids { 54 if i == pid { 55 return true 56 } 57 } 58 return false 59 } 60 61 func parsePSOutput(output []byte, pids []int) (*container.ContainerTopOKBody, error) { 62 procList := &container.ContainerTopOKBody{} 63 64 lines := strings.Split(string(output), "\n") 65 procList.Titles = fieldsASCII(lines[0]) 66 67 pidIndex := -1 68 for i, name := range procList.Titles { 69 if name == "PID" { 70 pidIndex = i 71 } 72 } 73 if pidIndex == -1 { 74 return nil, fmt.Errorf("Couldn't find PID field in ps output") 75 } 76 77 // loop through the output and extract the PID from each line 78 // fixing #30580, be able to display thread line also when "m" option used 79 // in "docker top" client command 80 preContainedPidFlag := false 81 for _, line := range lines[1:] { 82 if len(line) == 0 { 83 continue 84 } 85 fields := fieldsASCII(line) 86 87 var ( 88 p int 89 err error 90 ) 91 92 if fields[pidIndex] == "-" { 93 if preContainedPidFlag { 94 appendProcess2ProcList(procList, fields) 95 } 96 continue 97 } 98 p, err = strconv.Atoi(fields[pidIndex]) 99 if err != nil { 100 return nil, fmt.Errorf("Unexpected pid '%s': %s", fields[pidIndex], err) 101 } 102 103 if hasPid(pids, p) { 104 preContainedPidFlag = true 105 appendProcess2ProcList(procList, fields) 106 continue 107 } 108 preContainedPidFlag = false 109 } 110 return procList, nil 111 } 112 113 // ContainerTop lists the processes running inside of the given 114 // container by calling ps with the given args, or with the flags 115 // "-ef" if no args are given. An error is returned if the container 116 // is not found, or is not running, or if there are any problems 117 // running ps, or parsing the output. 118 func (daemon *Daemon) ContainerTop(name string, psArgs string) (*container.ContainerTopOKBody, error) { 119 if psArgs == "" { 120 psArgs = "-ef" 121 } 122 123 if err := validatePSArgs(psArgs); err != nil { 124 return nil, err 125 } 126 127 container, err := daemon.GetContainer(name) 128 if err != nil { 129 return nil, err 130 } 131 132 if !container.IsRunning() { 133 return nil, errNotRunning{container.ID} 134 } 135 136 if container.IsRestarting() { 137 return nil, errContainerIsRestarting(container.ID) 138 } 139 140 pids, err := daemon.containerd.GetPidsForContainer(container.ID) 141 if err != nil { 142 return nil, err 143 } 144 145 output, err := exec.Command("ps", strings.Split(psArgs, " ")...).Output() 146 if err != nil { 147 return nil, fmt.Errorf("Error running ps: %v", err) 148 } 149 procList, err := parsePSOutput(output, pids) 150 if err != nil { 151 return nil, err 152 } 153 daemon.LogContainerEvent(container, "top") 154 return procList, nil 155 }