top_unix.go 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. //+build !windows
  2. package daemon // import "github.com/docker/docker/daemon"
  3. import (
  4. "bytes"
  5. "context"
  6. "fmt"
  7. "os/exec"
  8. "regexp"
  9. "strconv"
  10. "strings"
  11. "github.com/docker/docker/api/types/container"
  12. "github.com/docker/docker/errdefs"
  13. "github.com/pkg/errors"
  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. // nolint: gosimple
  20. re := regexp.MustCompile("\\s+([^\\s]*)=\\s*(PID[^\\s]*)")
  21. for _, group := range re.FindAllStringSubmatch(psArgs, -1) {
  22. if len(group) >= 3 {
  23. k := group[1]
  24. v := group[2]
  25. if k != "pid" {
  26. return fmt.Errorf("specifying \"%s=%s\" is not allowed", k, v)
  27. }
  28. }
  29. }
  30. return nil
  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. func appendProcess2ProcList(procList *container.ContainerTopOKBody, fields []string) {
  44. // Make sure number of fields equals number of header titles
  45. // merging "overhanging" fields
  46. process := fields[:len(procList.Titles)-1]
  47. process = append(process, strings.Join(fields[len(procList.Titles)-1:], " "))
  48. procList.Processes = append(procList.Processes, process)
  49. }
  50. func hasPid(procs []uint32, pid int) bool {
  51. for _, p := range procs {
  52. if int(p) == pid {
  53. return true
  54. }
  55. }
  56. return false
  57. }
  58. func parsePSOutput(output []byte, procs []uint32) (*container.ContainerTopOKBody, error) {
  59. procList := &container.ContainerTopOKBody{}
  60. lines := strings.Split(string(output), "\n")
  61. procList.Titles = fieldsASCII(lines[0])
  62. pidIndex := -1
  63. for i, name := range procList.Titles {
  64. if name == "PID" {
  65. pidIndex = i
  66. break
  67. }
  68. }
  69. if pidIndex == -1 {
  70. return nil, fmt.Errorf("Couldn't find PID field in ps output")
  71. }
  72. // loop through the output and extract the PID from each line
  73. // fixing #30580, be able to display thread line also when "m" option used
  74. // in "docker top" client command
  75. preContainedPidFlag := false
  76. for _, line := range lines[1:] {
  77. if len(line) == 0 {
  78. continue
  79. }
  80. fields := fieldsASCII(line)
  81. var (
  82. p int
  83. err error
  84. )
  85. if fields[pidIndex] == "-" {
  86. if preContainedPidFlag {
  87. appendProcess2ProcList(procList, fields)
  88. }
  89. continue
  90. }
  91. p, err = strconv.Atoi(fields[pidIndex])
  92. if err != nil {
  93. return nil, fmt.Errorf("Unexpected pid '%s': %s", fields[pidIndex], err)
  94. }
  95. if hasPid(procs, p) {
  96. preContainedPidFlag = true
  97. appendProcess2ProcList(procList, fields)
  98. continue
  99. }
  100. preContainedPidFlag = false
  101. }
  102. return procList, nil
  103. }
  104. // psPidsArg converts a slice of PIDs to a string consisting
  105. // of comma-separated list of PIDs prepended by "-q".
  106. // For example, psPidsArg([]uint32{1,2,3}) returns "-q1,2,3".
  107. func psPidsArg(pids []uint32) string {
  108. b := []byte{'-', 'q'}
  109. for i, p := range pids {
  110. b = strconv.AppendUint(b, uint64(p), 10)
  111. if i < len(pids)-1 {
  112. b = append(b, ',')
  113. }
  114. }
  115. return string(b)
  116. }
  117. // ContainerTop lists the processes running inside of the given
  118. // container by calling ps with the given args, or with the flags
  119. // "-ef" if no args are given. An error is returned if the container
  120. // is not found, or is not running, or if there are any problems
  121. // running ps, or parsing the output.
  122. func (daemon *Daemon) ContainerTop(name string, psArgs string) (*container.ContainerTopOKBody, error) {
  123. if psArgs == "" {
  124. psArgs = "-ef"
  125. }
  126. if err := validatePSArgs(psArgs); err != nil {
  127. return nil, err
  128. }
  129. container, err := daemon.GetContainer(name)
  130. if err != nil {
  131. return nil, err
  132. }
  133. if !container.IsRunning() {
  134. return nil, errNotRunning(container.ID)
  135. }
  136. if container.IsRestarting() {
  137. return nil, errContainerIsRestarting(container.ID)
  138. }
  139. procs, err := daemon.containerd.ListPids(context.Background(), container.ID)
  140. if err != nil {
  141. return nil, err
  142. }
  143. args := strings.Split(psArgs, " ")
  144. pids := psPidsArg(procs)
  145. output, err := exec.Command("ps", append(args, pids)...).Output()
  146. if err != nil {
  147. // some ps options (such as f) can't be used together with q,
  148. // so retry without it
  149. output, err = exec.Command("ps", args...).Output()
  150. if err != nil {
  151. if ee, ok := err.(*exec.ExitError); ok {
  152. // first line of stderr shows why ps failed
  153. line := bytes.SplitN(ee.Stderr, []byte{'\n'}, 2)
  154. if len(line) > 0 && len(line[0]) > 0 {
  155. err = errors.New(string(line[0]))
  156. }
  157. }
  158. return nil, errdefs.System(errors.Wrap(err, "ps"))
  159. }
  160. }
  161. procList, err := parsePSOutput(output, procs)
  162. if err != nil {
  163. return nil, err
  164. }
  165. daemon.LogContainerEvent(container, "top")
  166. return procList, nil
  167. }