utils.go 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
  1. /*
  2. Copyright The containerd Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package cgroup1
  14. import (
  15. "bufio"
  16. "bytes"
  17. "fmt"
  18. "os"
  19. "path/filepath"
  20. "strconv"
  21. "strings"
  22. "time"
  23. "github.com/containerd/cgroups/v3"
  24. units "github.com/docker/go-units"
  25. specs "github.com/opencontainers/runtime-spec/specs-go"
  26. )
  27. // defaults returns all known groups
  28. func defaults(root string) ([]Subsystem, error) {
  29. h, err := NewHugetlb(root)
  30. if err != nil && !os.IsNotExist(err) {
  31. return nil, err
  32. }
  33. s := []Subsystem{
  34. NewNamed(root, "systemd"),
  35. NewFreezer(root),
  36. NewPids(root),
  37. NewNetCls(root),
  38. NewNetPrio(root),
  39. NewPerfEvent(root),
  40. NewCpuset(root),
  41. NewCpu(root),
  42. NewCpuacct(root),
  43. NewMemory(root),
  44. NewBlkio(root),
  45. NewRdma(root),
  46. }
  47. // only add the devices cgroup if we are not in a user namespace
  48. // because modifications are not allowed
  49. if !cgroups.RunningInUserNS() {
  50. s = append(s, NewDevices(root))
  51. }
  52. // add the hugetlb cgroup if error wasn't due to missing hugetlb
  53. // cgroup support on the host
  54. if err == nil {
  55. s = append(s, h)
  56. }
  57. return s, nil
  58. }
  59. // remove will remove a cgroup path handling EAGAIN and EBUSY errors and
  60. // retrying the remove after a exp timeout
  61. func remove(path string) error {
  62. delay := 10 * time.Millisecond
  63. for i := 0; i < 5; i++ {
  64. if i != 0 {
  65. time.Sleep(delay)
  66. delay *= 2
  67. }
  68. if err := os.RemoveAll(path); err == nil {
  69. return nil
  70. }
  71. }
  72. return fmt.Errorf("cgroups: unable to remove path %q", path)
  73. }
  74. // readPids will read all the pids of processes or tasks in a cgroup by the provided path
  75. func readPids(path string, subsystem Name, pType procType) ([]Process, error) {
  76. f, err := os.Open(filepath.Join(path, pType))
  77. if err != nil {
  78. return nil, err
  79. }
  80. defer f.Close()
  81. var (
  82. out []Process
  83. s = bufio.NewScanner(f)
  84. )
  85. for s.Scan() {
  86. if t := s.Text(); t != "" {
  87. pid, err := strconv.Atoi(t)
  88. if err != nil {
  89. return nil, err
  90. }
  91. out = append(out, Process{
  92. Pid: pid,
  93. Subsystem: subsystem,
  94. Path: path,
  95. })
  96. }
  97. }
  98. if err := s.Err(); err != nil {
  99. // failed to read all pids?
  100. return nil, err
  101. }
  102. return out, nil
  103. }
  104. func hugePageSizes() ([]string, error) {
  105. var (
  106. pageSizes []string
  107. sizeList = []string{"B", "KB", "MB", "GB", "TB", "PB"}
  108. )
  109. files, err := os.ReadDir("/sys/kernel/mm/hugepages")
  110. if err != nil {
  111. return nil, err
  112. }
  113. for _, st := range files {
  114. nameArray := strings.Split(st.Name(), "-")
  115. pageSize, err := units.RAMInBytes(nameArray[1])
  116. if err != nil {
  117. return nil, err
  118. }
  119. pageSizes = append(pageSizes, units.CustomSize("%g%s", float64(pageSize), 1024.0, sizeList))
  120. }
  121. return pageSizes, nil
  122. }
  123. func readUint(path string) (uint64, error) {
  124. f, err := os.Open(path)
  125. if err != nil {
  126. return 0, err
  127. }
  128. defer f.Close()
  129. // We should only need 20 bytes for the max uint64, but for a nice power of 2
  130. // lets use 32.
  131. b := make([]byte, 32)
  132. n, err := f.Read(b)
  133. if err != nil {
  134. return 0, err
  135. }
  136. s := string(bytes.TrimSpace(b[:n]))
  137. if s == "max" {
  138. // Return 0 for the max value to maintain backward compatibility.
  139. return 0, nil
  140. }
  141. return parseUint(s, 10, 64)
  142. }
  143. func parseUint(s string, base, bitSize int) (uint64, error) {
  144. v, err := strconv.ParseUint(s, base, bitSize)
  145. if err != nil {
  146. intValue, intErr := strconv.ParseInt(s, base, bitSize)
  147. // 1. Handle negative values greater than MinInt64 (and)
  148. // 2. Handle negative values lesser than MinInt64
  149. if intErr == nil && intValue < 0 {
  150. return 0, nil
  151. } else if intErr != nil &&
  152. intErr.(*strconv.NumError).Err == strconv.ErrRange &&
  153. intValue < 0 {
  154. return 0, nil
  155. }
  156. return 0, err
  157. }
  158. return v, nil
  159. }
  160. func parseKV(raw string) (string, uint64, error) {
  161. parts := strings.Fields(raw)
  162. switch len(parts) {
  163. case 2:
  164. v, err := parseUint(parts[1], 10, 64)
  165. if err != nil {
  166. return "", 0, err
  167. }
  168. return parts[0], v, nil
  169. default:
  170. return "", 0, ErrInvalidFormat
  171. }
  172. }
  173. // ParseCgroupFile parses the given cgroup file, typically /proc/self/cgroup
  174. // or /proc/<pid>/cgroup, into a map of subsystems to cgroup paths, e.g.
  175. //
  176. // "cpu": "/user.slice/user-1000.slice"
  177. // "pids": "/user.slice/user-1000.slice"
  178. //
  179. // etc.
  180. //
  181. // The resulting map does not have an element for cgroup v2 unified hierarchy.
  182. // Use [cgroups.ParseCgroupFileUnified] to get the unified path.
  183. func ParseCgroupFile(path string) (map[string]string, error) {
  184. x, _, err := ParseCgroupFileUnified(path)
  185. return x, err
  186. }
  187. // ParseCgroupFileUnified returns legacy subsystem paths as the first value,
  188. // and returns the unified path as the second value.
  189. //
  190. // Deprecated: use [cgroups.ParseCgroupFileUnified] instead .
  191. func ParseCgroupFileUnified(path string) (map[string]string, string, error) {
  192. return cgroups.ParseCgroupFileUnified(path)
  193. }
  194. func getCgroupDestination(subsystem string) (string, error) {
  195. f, err := os.Open("/proc/self/mountinfo")
  196. if err != nil {
  197. return "", err
  198. }
  199. defer f.Close()
  200. s := bufio.NewScanner(f)
  201. for s.Scan() {
  202. fields := strings.Split(s.Text(), " ")
  203. if len(fields) < 10 {
  204. // broken mountinfo?
  205. continue
  206. }
  207. if fields[len(fields)-3] != "cgroup" {
  208. continue
  209. }
  210. for _, opt := range strings.Split(fields[len(fields)-1], ",") {
  211. if opt == subsystem {
  212. return fields[3], nil
  213. }
  214. }
  215. }
  216. if err := s.Err(); err != nil {
  217. return "", err
  218. }
  219. return "", ErrNoCgroupMountDestination
  220. }
  221. func pathers(subystems []Subsystem) []pather {
  222. var out []pather
  223. for _, s := range subystems {
  224. if p, ok := s.(pather); ok {
  225. out = append(out, p)
  226. }
  227. }
  228. return out
  229. }
  230. func initializeSubsystem(s Subsystem, path Path, resources *specs.LinuxResources) error {
  231. if c, ok := s.(creator); ok {
  232. p, err := path(s.Name())
  233. if err != nil {
  234. return err
  235. }
  236. if err := c.Create(p, resources); err != nil {
  237. return err
  238. }
  239. } else if c, ok := s.(pather); ok {
  240. p, err := path(s.Name())
  241. if err != nil {
  242. return err
  243. }
  244. // do the default create if the group does not have a custom one
  245. if err := os.MkdirAll(c.Path(p), defaultDirPerm); err != nil {
  246. return err
  247. }
  248. }
  249. return nil
  250. }
  251. func cleanPath(path string) string {
  252. if path == "" {
  253. return ""
  254. }
  255. path = filepath.Clean(path)
  256. if !filepath.IsAbs(path) {
  257. path, _ = filepath.Rel(string(os.PathSeparator), filepath.Clean(string(os.PathSeparator)+path))
  258. }
  259. return path
  260. }