Tizen_4.0 base
[platform/upstream/docker-engine.git] / daemon / top_unix.go
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 }