top_unix.go 4.4 KB

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