create.go 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. package fs2
  2. import (
  3. "fmt"
  4. "os"
  5. "path/filepath"
  6. "strings"
  7. "github.com/opencontainers/runc/libcontainer/cgroups"
  8. "github.com/opencontainers/runc/libcontainer/configs"
  9. )
  10. func supportedControllers() (string, error) {
  11. return cgroups.ReadFile(UnifiedMountpoint, "/cgroup.controllers")
  12. }
  13. // needAnyControllers returns whether we enable some supported controllers or not,
  14. // based on (1) controllers available and (2) resources that are being set.
  15. // We don't check "pseudo" controllers such as
  16. // "freezer" and "devices".
  17. func needAnyControllers(r *configs.Resources) (bool, error) {
  18. if r == nil {
  19. return false, nil
  20. }
  21. // list of all available controllers
  22. content, err := supportedControllers()
  23. if err != nil {
  24. return false, err
  25. }
  26. avail := make(map[string]struct{})
  27. for _, ctr := range strings.Fields(content) {
  28. avail[ctr] = struct{}{}
  29. }
  30. // check whether the controller if available or not
  31. have := func(controller string) bool {
  32. _, ok := avail[controller]
  33. return ok
  34. }
  35. if isPidsSet(r) && have("pids") {
  36. return true, nil
  37. }
  38. if isMemorySet(r) && have("memory") {
  39. return true, nil
  40. }
  41. if isIoSet(r) && have("io") {
  42. return true, nil
  43. }
  44. if isCpuSet(r) && have("cpu") {
  45. return true, nil
  46. }
  47. if isCpusetSet(r) && have("cpuset") {
  48. return true, nil
  49. }
  50. if isHugeTlbSet(r) && have("hugetlb") {
  51. return true, nil
  52. }
  53. return false, nil
  54. }
  55. // containsDomainController returns whether the current config contains domain controller or not.
  56. // Refer to: http://man7.org/linux/man-pages/man7/cgroups.7.html
  57. // As at Linux 4.19, the following controllers are threaded: cpu, perf_event, and pids.
  58. func containsDomainController(r *configs.Resources) bool {
  59. return isMemorySet(r) || isIoSet(r) || isCpuSet(r) || isHugeTlbSet(r)
  60. }
  61. // CreateCgroupPath creates cgroupv2 path, enabling all the supported controllers.
  62. func CreateCgroupPath(path string, c *configs.Cgroup) (Err error) {
  63. if !strings.HasPrefix(path, UnifiedMountpoint) {
  64. return fmt.Errorf("invalid cgroup path %s", path)
  65. }
  66. content, err := supportedControllers()
  67. if err != nil {
  68. return err
  69. }
  70. const (
  71. cgTypeFile = "cgroup.type"
  72. cgStCtlFile = "cgroup.subtree_control"
  73. )
  74. ctrs := strings.Fields(content)
  75. res := "+" + strings.Join(ctrs, " +")
  76. elements := strings.Split(path, "/")
  77. elements = elements[3:]
  78. current := "/sys/fs"
  79. for i, e := range elements {
  80. current = filepath.Join(current, e)
  81. if i > 0 {
  82. if err := os.Mkdir(current, 0o755); err != nil {
  83. if !os.IsExist(err) {
  84. return err
  85. }
  86. } else {
  87. // If the directory was created, be sure it is not left around on errors.
  88. current := current
  89. defer func() {
  90. if Err != nil {
  91. os.Remove(current)
  92. }
  93. }()
  94. }
  95. cgType, _ := cgroups.ReadFile(current, cgTypeFile)
  96. cgType = strings.TrimSpace(cgType)
  97. switch cgType {
  98. // If the cgroup is in an invalid mode (usually this means there's an internal
  99. // process in the cgroup tree, because we created a cgroup under an
  100. // already-populated-by-other-processes cgroup), then we have to error out if
  101. // the user requested controllers which are not thread-aware. However, if all
  102. // the controllers requested are thread-aware we can simply put the cgroup into
  103. // threaded mode.
  104. case "domain invalid":
  105. if containsDomainController(c.Resources) {
  106. return fmt.Errorf("cannot enter cgroupv2 %q with domain controllers -- it is in an invalid state", current)
  107. } else {
  108. // Not entirely correct (in theory we'd always want to be a domain --
  109. // since that means we're a properly delegated cgroup subtree) but in
  110. // this case there's not much we can do and it's better than giving an
  111. // error.
  112. _ = cgroups.WriteFile(current, cgTypeFile, "threaded")
  113. }
  114. // If the cgroup is in (threaded) or (domain threaded) mode, we can only use thread-aware controllers
  115. // (and you cannot usually take a cgroup out of threaded mode).
  116. case "domain threaded":
  117. fallthrough
  118. case "threaded":
  119. if containsDomainController(c.Resources) {
  120. return fmt.Errorf("cannot enter cgroupv2 %q with domain controllers -- it is in %s mode", current, cgType)
  121. }
  122. }
  123. }
  124. // enable all supported controllers
  125. if i < len(elements)-1 {
  126. if err := cgroups.WriteFile(current, cgStCtlFile, res); err != nil {
  127. // try write one by one
  128. allCtrs := strings.Split(res, " ")
  129. for _, ctr := range allCtrs {
  130. _ = cgroups.WriteFile(current, cgStCtlFile, ctr)
  131. }
  132. }
  133. // Some controllers might not be enabled when rootless or containerized,
  134. // but we don't catch the error here. (Caught in setXXX() functions.)
  135. }
  136. }
  137. return nil
  138. }