top_unix.go 4.9 KB

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