github.com/opencontainers/runc@v1.2.0-rc.1.0.20240520010911-492dc558cdd6/libcontainer/cgroups/fs2/fs2.go (about) 1 package fs2 2 3 import ( 4 "errors" 5 "fmt" 6 "os" 7 "path/filepath" 8 "strings" 9 10 "github.com/opencontainers/runc/libcontainer/cgroups" 11 "github.com/opencontainers/runc/libcontainer/cgroups/fscommon" 12 "github.com/opencontainers/runc/libcontainer/configs" 13 "github.com/opencontainers/runc/libcontainer/utils" 14 ) 15 16 type parseError = fscommon.ParseError 17 18 type Manager struct { 19 config *configs.Cgroup 20 // dirPath is like "/sys/fs/cgroup/user.slice/user-1001.slice/session-1.scope" 21 dirPath string 22 // controllers is content of "cgroup.controllers" file. 23 // excludes pseudo-controllers ("devices" and "freezer"). 24 controllers map[string]struct{} 25 } 26 27 // NewManager creates a manager for cgroup v2 unified hierarchy. 28 // dirPath is like "/sys/fs/cgroup/user.slice/user-1001.slice/session-1.scope". 29 // If dirPath is empty, it is automatically set using config. 30 func NewManager(config *configs.Cgroup, dirPath string) (*Manager, error) { 31 if dirPath == "" { 32 var err error 33 dirPath, err = defaultDirPath(config) 34 if err != nil { 35 return nil, err 36 } 37 } else { 38 // Clean path for safety. 39 dirPath = utils.CleanPath(dirPath) 40 } 41 42 m := &Manager{ 43 config: config, 44 dirPath: dirPath, 45 } 46 return m, nil 47 } 48 49 func (m *Manager) getControllers() error { 50 if m.controllers != nil { 51 return nil 52 } 53 54 data, err := cgroups.ReadFile(m.dirPath, "cgroup.controllers") 55 if err != nil { 56 if m.config.Rootless && m.config.Path == "" { 57 return nil 58 } 59 return err 60 } 61 fields := strings.Fields(data) 62 m.controllers = make(map[string]struct{}, len(fields)) 63 for _, c := range fields { 64 m.controllers[c] = struct{}{} 65 } 66 67 return nil 68 } 69 70 func (m *Manager) Apply(pid int) error { 71 if err := CreateCgroupPath(m.dirPath, m.config); err != nil { 72 // Related tests: 73 // - "runc create (no limits + no cgrouppath + no permission) succeeds" 74 // - "runc create (rootless + no limits + cgrouppath + no permission) fails with permission error" 75 // - "runc create (rootless + limits + no cgrouppath + no permission) fails with informative error" 76 if m.config.Rootless { 77 if m.config.Path == "" { 78 if blNeed, nErr := needAnyControllers(m.config.Resources); nErr == nil && !blNeed { 79 return nil 80 } 81 return fmt.Errorf("rootless needs no limits + no cgrouppath when no permission is granted for cgroups: %w", err) 82 } 83 } 84 return err 85 } 86 if err := cgroups.WriteCgroupProc(m.dirPath, pid); err != nil { 87 return err 88 } 89 return nil 90 } 91 92 func (m *Manager) GetPids() ([]int, error) { 93 return cgroups.GetPids(m.dirPath) 94 } 95 96 func (m *Manager) GetAllPids() ([]int, error) { 97 return cgroups.GetAllPids(m.dirPath) 98 } 99 100 func (m *Manager) GetStats() (*cgroups.Stats, error) { 101 var errs []error 102 103 st := cgroups.NewStats() 104 105 // pids (since kernel 4.5) 106 if err := statPids(m.dirPath, st); err != nil { 107 errs = append(errs, err) 108 } 109 // memory (since kernel 4.5) 110 if err := statMemory(m.dirPath, st); err != nil && !os.IsNotExist(err) { 111 errs = append(errs, err) 112 } 113 // io (since kernel 4.5) 114 if err := statIo(m.dirPath, st); err != nil && !os.IsNotExist(err) { 115 errs = append(errs, err) 116 } 117 // cpu (since kernel 4.15) 118 // Note cpu.stat is available even if the controller is not enabled. 119 if err := statCpu(m.dirPath, st); err != nil && !os.IsNotExist(err) { 120 errs = append(errs, err) 121 } 122 // PSI (since kernel 4.20). 123 var err error 124 if st.CpuStats.PSI, err = statPSI(m.dirPath, "cpu.pressure"); err != nil { 125 errs = append(errs, err) 126 } 127 if st.MemoryStats.PSI, err = statPSI(m.dirPath, "memory.pressure"); err != nil { 128 errs = append(errs, err) 129 } 130 if st.BlkioStats.PSI, err = statPSI(m.dirPath, "io.pressure"); err != nil { 131 errs = append(errs, err) 132 } 133 // hugetlb (since kernel 5.6) 134 if err := statHugeTlb(m.dirPath, st); err != nil && !os.IsNotExist(err) { 135 errs = append(errs, err) 136 } 137 // rdma (since kernel 4.11) 138 if err := fscommon.RdmaGetStats(m.dirPath, st); err != nil && !os.IsNotExist(err) { 139 errs = append(errs, err) 140 } 141 // misc (since kernel 5.13) 142 if err := statMisc(m.dirPath, st); err != nil && !os.IsNotExist(err) { 143 errs = append(errs, err) 144 } 145 if len(errs) > 0 && !m.config.Rootless { 146 return st, fmt.Errorf("error while statting cgroup v2: %+v", errs) 147 } 148 return st, nil 149 } 150 151 func (m *Manager) Freeze(state configs.FreezerState) error { 152 if m.config.Resources == nil { 153 return errors.New("cannot toggle freezer: cgroups not configured for container") 154 } 155 if err := setFreezer(m.dirPath, state); err != nil { 156 return err 157 } 158 m.config.Resources.Freezer = state 159 return nil 160 } 161 162 func (m *Manager) Destroy() error { 163 return cgroups.RemovePath(m.dirPath) 164 } 165 166 func (m *Manager) Path(_ string) string { 167 return m.dirPath 168 } 169 170 func (m *Manager) Set(r *configs.Resources) error { 171 if r == nil { 172 return nil 173 } 174 if err := m.getControllers(); err != nil { 175 return err 176 } 177 // pids (since kernel 4.5) 178 if err := setPids(m.dirPath, r); err != nil { 179 return err 180 } 181 // memory (since kernel 4.5) 182 if err := setMemory(m.dirPath, r); err != nil { 183 return err 184 } 185 // io (since kernel 4.5) 186 if err := setIo(m.dirPath, r); err != nil { 187 return err 188 } 189 // cpu (since kernel 4.15) 190 if err := setCpu(m.dirPath, r); err != nil { 191 return err 192 } 193 // devices (since kernel 4.15, pseudo-controller) 194 // 195 // When rootless is true, errors from the device subsystem are ignored because it is really not expected to work. 196 // However, errors from other subsystems are not ignored. 197 // see @test "runc create (rootless + limits + no cgrouppath + no permission) fails with informative error" 198 if err := setDevices(m.dirPath, r); err != nil { 199 if !m.config.Rootless || errors.Is(err, cgroups.ErrDevicesUnsupported) { 200 return err 201 } 202 } 203 // cpuset (since kernel 5.0) 204 if err := setCpuset(m.dirPath, r); err != nil { 205 return err 206 } 207 // hugetlb (since kernel 5.6) 208 if err := setHugeTlb(m.dirPath, r); err != nil { 209 return err 210 } 211 // rdma (since kernel 4.11) 212 if err := fscommon.RdmaSet(m.dirPath, r); err != nil { 213 return err 214 } 215 // freezer (since kernel 5.2, pseudo-controller) 216 if err := setFreezer(m.dirPath, r.Freezer); err != nil { 217 return err 218 } 219 if err := m.setUnified(r.Unified); err != nil { 220 return err 221 } 222 m.config.Resources = r 223 return nil 224 } 225 226 func setDevices(dirPath string, r *configs.Resources) error { 227 if cgroups.DevicesSetV2 == nil { 228 if len(r.Devices) > 0 { 229 return cgroups.ErrDevicesUnsupported 230 } 231 return nil 232 } 233 return cgroups.DevicesSetV2(dirPath, r) 234 } 235 236 func (m *Manager) setUnified(res map[string]string) error { 237 for k, v := range res { 238 if strings.Contains(k, "/") { 239 return fmt.Errorf("unified resource %q must be a file name (no slashes)", k) 240 } 241 if err := cgroups.WriteFile(m.dirPath, k, v); err != nil { 242 // Check for both EPERM and ENOENT since O_CREAT is used by WriteFile. 243 if errors.Is(err, os.ErrPermission) || errors.Is(err, os.ErrNotExist) { 244 // Check if a controller is available, 245 // to give more specific error if not. 246 sk := strings.SplitN(k, ".", 2) 247 if len(sk) != 2 { 248 return fmt.Errorf("unified resource %q must be in the form CONTROLLER.PARAMETER", k) 249 } 250 c := sk[0] 251 if _, ok := m.controllers[c]; !ok && c != "cgroup" { 252 return fmt.Errorf("unified resource %q can't be set: controller %q not available", k, c) 253 } 254 } 255 return fmt.Errorf("unable to set unified resource %q: %w", k, err) 256 } 257 } 258 259 return nil 260 } 261 262 func (m *Manager) GetPaths() map[string]string { 263 paths := make(map[string]string, 1) 264 paths[""] = m.dirPath 265 return paths 266 } 267 268 func (m *Manager) GetCgroups() (*configs.Cgroup, error) { 269 return m.config, nil 270 } 271 272 func (m *Manager) GetFreezerState() (configs.FreezerState, error) { 273 return getFreezer(m.dirPath) 274 } 275 276 func (m *Manager) Exists() bool { 277 return cgroups.PathExists(m.dirPath) 278 } 279 280 func OOMKillCount(path string) (uint64, error) { 281 return fscommon.GetValueByKey(path, "memory.events", "oom_kill") 282 } 283 284 func (m *Manager) OOMKillCount() (uint64, error) { 285 c, err := OOMKillCount(m.dirPath) 286 if err != nil && m.config.Rootless && os.IsNotExist(err) { 287 err = nil 288 } 289 290 return c, err 291 } 292 293 func CheckMemoryUsage(dirPath string, r *configs.Resources) error { 294 if !r.MemoryCheckBeforeUpdate { 295 return nil 296 } 297 298 if r.Memory <= 0 && r.MemorySwap <= 0 { 299 return nil 300 } 301 302 usage, err := fscommon.GetCgroupParamUint(dirPath, "memory.current") 303 if err != nil { 304 // This check is on best-effort basis, so if we can't read the 305 // current usage (cgroup not yet created, or any other error), 306 // we should not fail. 307 return nil 308 } 309 310 if r.MemorySwap > 0 { 311 if uint64(r.MemorySwap) <= usage { 312 return fmt.Errorf("rejecting memory+swap limit %d <= usage %d", r.MemorySwap, usage) 313 } 314 } 315 316 if r.Memory > 0 { 317 if uint64(r.Memory) <= usage { 318 return fmt.Errorf("rejecting memory limit %d <= usage %d", r.Memory, usage) 319 } 320 } 321 322 return nil 323 } 324 325 func (m *Manager) GetEffectiveCPUs() string { 326 // Fast path. 327 if m.config.CpusetCpus != "" { 328 return m.config.CpusetCpus 329 } else if !strings.HasPrefix(m.dirPath, UnifiedMountpoint) { 330 return "" 331 } 332 333 // Iterates until it goes outside of the cgroup root path. 334 // It's required for containers in which cpuset controller 335 // is not enabled, in this case a parent cgroup is used. 336 outsidePath := filepath.Dir(UnifiedMountpoint) 337 338 for path := m.dirPath; path != outsidePath; path = filepath.Dir(path) { 339 cpus, err := fscommon.GetCgroupParamString(path, "cpuset.cpus.effective") 340 if err == nil { 341 return cpus 342 } 343 } 344 345 return "" 346 }