github.com/rootless-containers/rootlesskit/v2@v2.3.4/pkg/parent/cgrouputil/cgrouputil.go (about) 1 package cgrouputil 2 3 import ( 4 "errors" 5 "fmt" 6 "os" 7 "path/filepath" 8 "strings" 9 10 "github.com/moby/sys/mountinfo" 11 12 "github.com/sirupsen/logrus" 13 ) 14 15 // EvacuateCgroup2 evacuates cgroup2. Must be called in the parent PID namespace. 16 // 17 // When the current process belongs to "/foo" group (visible under "/sys/fs/cgroup/foo") and evac is like "bar", 18 // - All processes in the "/foo" group are moved to "/foo/bar" group, by writing PIDs into "/sys/fs/cgroup/foo/bar/cgroup.procs" 19 // - As many controllers as possible are enabled for "/foo/*" groups, by writing "/sys/fs/cgroup/foo/cgroup.subtree_control" 20 // 21 // Returns nil when cgroup2 is not enabled. 22 // Ported from https://github.com/rootless-containers/usernetes/commit/46ad812db7489914897ff8b1774f2fab0efda62b 23 func EvacuateCgroup2(evac string) error { 24 if evac == "" { 25 return errors.New("got empty evacuation group name") 26 } 27 if strings.Contains(evac, "/") { 28 return fmt.Errorf("unexpected evacuation group name %q: must not contain \"/\"", evac) 29 } 30 31 mountpoint := findCgroup2Mountpoint() 32 if mountpoint == "" { 33 logrus.Warn("cgroup2 is not mounted. cgroup2 evacuation is discarded.") 34 return nil 35 } 36 37 oldGroup := getCgroup2(os.Getpid()) 38 if mountpoint == "" { 39 logrus.Warn("process is not running with cgroup2. cgroup2 evacuation is discarded.") 40 return nil 41 } 42 43 newGroup := filepath.Join(oldGroup, evac) 44 45 oldPath := filepath.Join(mountpoint, oldGroup) 46 newPath := filepath.Join(mountpoint, newGroup) 47 48 if err := os.MkdirAll(newPath, 0755); err != nil { 49 return err 50 } 51 52 // evacuate existing procs from oldGroup to newGroup, so that we can enable all controllers including threaded ones 53 cgroupProcsBytes, err := os.ReadFile(filepath.Join(oldPath, "cgroup.procs")) 54 if err != nil { 55 return err 56 } 57 for _, pidStr := range strings.Split(string(cgroupProcsBytes), "\n") { 58 if pidStr == "" || pidStr == "0" { 59 continue 60 } 61 if err := os.WriteFile(filepath.Join(newPath, "cgroup.procs"), []byte(pidStr), 0644); err != nil { 62 logrus.WithError(err).Warnf("failed to move process %s to cgroup %q", pidStr, newGroup) 63 } 64 } 65 66 // enable controllers for all subgroups under the oldGroup 67 controllerBytes, err := os.ReadFile(filepath.Join(oldPath, "cgroup.controllers")) 68 if err != nil { 69 return err 70 } 71 for _, controller := range strings.Fields(string(controllerBytes)) { 72 logrus.Debugf("enabling controller %q", controller) 73 if err := os.WriteFile(filepath.Join(oldPath, "cgroup.subtree_control"), []byte("+"+controller), 0644); err != nil { 74 logrus.WithError(err).Warnf("failed to enable controller %q", controller) 75 } 76 } 77 78 return nil 79 } 80 81 func findCgroup2Mountpoint() string { 82 f := mountinfoFSTypeFilter("cgroup2") 83 mounts, err := mountinfo.GetMounts(f) 84 if err != nil { 85 logrus.WithError(err).Warn("failed to find mountpoint for cgroup2") 86 return "" 87 } 88 if len(mounts) == 0 { 89 return "" 90 } 91 if len(mounts) != 1 { 92 logrus.Warnf("expected single mountpoint for cgroup2, got %d", len(mounts)) 93 } 94 return mounts[0].Mountpoint 95 } 96 97 func getCgroup2(pid int) string { 98 p := fmt.Sprintf("/proc/%d/cgroup", pid) 99 b, err := os.ReadFile(p) 100 if err != nil { 101 logrus.WithError(err).Warnf("failed to read %q", p) 102 return "" 103 } 104 return getCgroup2FromProcPidCgroup(b) 105 } 106 107 func getCgroup2FromProcPidCgroup(b []byte) string { 108 for _, l := range strings.Split(string(b), "\n") { 109 if strings.HasPrefix(l, "0::") { 110 return strings.TrimPrefix(l, "0::") 111 } 112 } 113 return "" 114 }