top_unix.go 3.7 KB

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