1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: AGPL-3.0
19 // Return the current process's cgroup for the given subsystem.
21 // If the host has cgroups v2 and not v1 (i.e., unified mode), return
22 // the current process's cgroup.
23 func findCgroup(fsys fs.FS, subsystem string) (string, error) {
24 subsys := []byte(subsystem)
25 cgroups, err := fs.ReadFile(fsys, "proc/self/cgroup")
29 for _, line := range bytes.Split(cgroups, []byte("\n")) {
30 toks := bytes.SplitN(line, []byte(":"), 4)
34 if len(toks[1]) == 0 && string(toks[0]) == "0" {
35 // cgroups v2: "0::$PATH"
37 // In "hybrid" mode, this entry is last, so we
38 // use it when the specified subsystem doesn't
39 // match a cgroups v1 entry.
41 // In "unified" mode, this is the only entry,
42 // so we use it regardless of which subsystem
44 return string(toks[2]), nil
46 for _, s := range bytes.Split(toks[1], []byte(",")) {
47 // cgroups v1: "7:cpu,cpuacct:/user.slice"
48 if bytes.Compare(s, subsys) == 0 {
49 return string(toks[2]), nil
53 return "", fmt.Errorf("subsystem %q not found in /proc/self/cgroup", subsystem)
57 // After calling checkCgroupSupport, cgroupSupport indicates
58 // support for singularity resource limits.
60 // E.g., cgroupSupport["memory"]==true if systemd is installed
61 // and configured such that singularity can use the "memory"
62 // cgroup controller to set resource limits.
63 cgroupSupport map[string]bool
64 cgroupSupportLock sync.Mutex
67 // checkCgroupSupport should be called before looking up strings like
68 // "memory" and "cpu" in cgroupSupport.
69 func checkCgroupSupport(logf func(string, ...interface{})) {
70 cgroupSupportLock.Lock()
71 defer cgroupSupportLock.Unlock()
72 if cgroupSupport != nil {
75 cgroupSupport = make(map[string]bool)
77 xrd := os.Getenv("XDG_RUNTIME_DIR")
78 if xrd == "" || os.Getenv("DBUS_SESSION_BUS_ADDRESS") == "" {
79 logf("not running as root, and empty XDG_RUNTIME_DIR or DBUS_SESSION_BUS_ADDRESS -- singularity resource limits are not supported")
82 if fi, err := os.Stat(xrd + "/systemd"); err != nil || !fi.IsDir() {
83 logf("not running as root, and %s/systemd is not a directory -- singularity resource limits are not supported", xrd)
86 version, err := exec.Command("systemd-run", "--version").CombinedOutput()
87 if match := regexp.MustCompile(`^systemd (\d+)`).FindSubmatch(version); err != nil || match == nil {
88 logf("not running as root, and could not get systemd version -- singularity resource limits are not supported")
90 } else if v, _ := strconv.ParseInt(string(match[1]), 10, 64); v < 224 {
91 logf("not running as root, and systemd version %s < minimum 224 -- singularity resource limits are not supported", match[1])
95 mount, err := cgroupMount()
97 logf("no cgroup support: %s", err)
100 cgroup, err := findCgroup(os.DirFS("/"), "")
102 logf("cannot find cgroup: %s", err)
105 controllers, err := os.ReadFile(mount + cgroup + "/cgroup.controllers")
107 logf("cannot read cgroup.controllers file: %s", err)
110 for _, controller := range bytes.Split(bytes.TrimRight(controllers, "\n"), []byte{' '}) {
111 cgroupSupport[string(controller)] = true
115 // Return the cgroup2 mount point, typically "/sys/fs/cgroup".
116 func cgroupMount() (string, error) {
117 mounts, err := os.ReadFile("/proc/mounts")
121 for _, mount := range bytes.Split(mounts, []byte{'\n'}) {
122 toks := bytes.Split(mount, []byte{' '})
123 if len(toks) > 2 && bytes.Equal(toks[0], []byte("cgroup2")) {
124 return string(toks[1]), nil
127 return "", errors.New("cgroup2 mount not found")