top_unix.go 3.7 KB

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