top_unix.go 5.0 KB

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