github.com/criyle/go-sandbox@v0.10.3/pkg/cgroup/utils_linux.go (about) 1 package cgroup 2 3 import ( 4 "errors" 5 "fmt" 6 "io/fs" 7 "math/rand/v2" 8 "os" 9 "path" 10 "strconv" 11 "strings" 12 "syscall" 13 14 "golang.org/x/sys/unix" 15 ) 16 17 // EnsureDirExists creates directories if the path not exists 18 func EnsureDirExists(path string) error { 19 if _, err := os.Stat(path); os.IsNotExist(err) { 20 return os.MkdirAll(path, dirPerm) 21 } 22 return os.ErrExist 23 } 24 25 // CreateV1ControllerPath create path for controller with given group, prefix 26 func CreateV1ControllerPath(controller, prefix string) (string, error) { 27 p := path.Join(basePath, controller, prefix) 28 return p, EnsureDirExists(p) 29 } 30 31 const initPath = "init" 32 33 // EnableV2Nesting migrates all process in the container to nested /init path 34 // and enables all available controllers in the root cgroup 35 func EnableV2Nesting() error { 36 if DetectType() != TypeV2 { 37 return nil 38 } 39 40 p, err := readFile(path.Join(basePath, cgroupProcs)) 41 if err != nil { 42 return err 43 } 44 procs := strings.Split(string(p), "\n") 45 if len(procs) == 0 { 46 return nil 47 } 48 49 // mkdir init 50 if err := os.Mkdir(path.Join(basePath, initPath), dirPerm); err != nil && !errors.Is(err, os.ErrExist) { 51 return err 52 } 53 // move all process into init cgroup 54 procFile, err := os.OpenFile(path.Join(basePath, initPath, cgroupProcs), os.O_RDWR, filePerm) 55 if err != nil { 56 return err 57 } 58 for _, v := range procs { 59 if _, err := procFile.WriteString(v); err != nil { 60 continue 61 //return err 62 } 63 } 64 procFile.Close() 65 return nil 66 } 67 68 // ReadProcesses reads cgroup.procs file and return pids individually 69 func ReadProcesses(path string) ([]int, error) { 70 content, err := readFile(path) 71 if err != nil { 72 return nil, err 73 } 74 procs := strings.Split(string(content), "\n") 75 rt := make([]int, len(procs)) 76 for i, x := range procs { 77 if len(x) == 0 { 78 continue 79 } 80 rt[i], err = strconv.Atoi(x) 81 if err != nil { 82 return nil, err 83 } 84 } 85 return rt, nil 86 } 87 88 // AddProcesses add processes into cgroup.procs file 89 func AddProcesses(path string, procs []int) error { 90 f, err := os.OpenFile(path, os.O_RDWR, filePerm) 91 if err != nil { 92 return err 93 } 94 defer f.Close() 95 for _, p := range procs { 96 if _, err := f.WriteString(strconv.Itoa(p)); err != nil { 97 return err 98 } 99 } 100 return nil 101 } 102 103 // DetectType detects current mounted cgroup type in systemd default path 104 func DetectType() Type { 105 // if /sys/fs/cgroup is mounted as CGROUPV2 or TMPFS (V1) 106 var st unix.Statfs_t 107 if err := unix.Statfs(basePath, &st); err != nil { 108 // ignore errors, defaulting to CgroupV1 109 return TypeV1 110 } 111 if st.Type == unix.CGROUP2_SUPER_MAGIC { 112 return TypeV2 113 } 114 return TypeV1 115 } 116 117 func remove(name string) error { 118 if name != "" { 119 return os.Remove(name) 120 } 121 return nil 122 } 123 124 var errPatternHasSeparator = errors.New("pattern contains path separator") 125 126 // prefixAndSuffix splits pattern by the last wildcard "*", if applicable, 127 // returning prefix as the part before "*" and suffix as the part after "*". 128 func prefixAndSuffix(pattern string) (prefix, suffix string, err error) { 129 for i := 0; i < len(pattern); i++ { 130 if os.IsPathSeparator(pattern[i]) { 131 return "", "", errPatternHasSeparator 132 } 133 } 134 if pos := strings.LastIndexByte(pattern, '*'); pos != -1 { 135 prefix, suffix = pattern[:pos], pattern[pos+1:] 136 } else { 137 prefix = pattern 138 } 139 return prefix, suffix, nil 140 } 141 142 func readFile(p string) ([]byte, error) { 143 data, err := os.ReadFile(p) 144 for err != nil && errors.Is(err, syscall.EINTR) { 145 data, err = os.ReadFile(p) 146 } 147 return data, err 148 } 149 150 func writeFile(p string, content []byte, perm fs.FileMode) error { 151 err := os.WriteFile(p, content, perm) 152 for err != nil && errors.Is(err, syscall.EINTR) { 153 err = os.WriteFile(p, content, perm) 154 } 155 return err 156 } 157 158 func nextRandom() string { 159 return strconv.Itoa(int(rand.Int32())) 160 } 161 162 // randomBuild creates a cgroup with random directory, similar to os.MkdirTemp 163 func randomBuild(pattern string, build func(string) (Cgroup, error)) (Cgroup, error) { 164 prefix, suffix, err := prefixAndSuffix(pattern) 165 if err != nil { 166 return nil, fmt.Errorf("cgroup.builder: random %v", err) 167 } 168 169 try := 0 170 for { 171 name := prefix + nextRandom() + suffix 172 cg, err := build(name) 173 if err == nil { 174 return cg, nil 175 } 176 if errors.Is(err, os.ErrExist) || cg.Existing() { 177 if try++; try < 10000 { 178 continue 179 } 180 return nil, fmt.Errorf("cgroup.builder: tried 10000 times but failed") 181 } 182 return nil, fmt.Errorf("cgroup.builder: random %v", err) 183 } 184 }