github.com/opencontainers/runc@v1.2.0-rc.1.0.20240520010911-492dc558cdd6/libcontainer/cgroups/fs2/create.go (about) 1 package fs2 2 3 import ( 4 "fmt" 5 "os" 6 "path/filepath" 7 "strings" 8 9 "github.com/opencontainers/runc/libcontainer/cgroups" 10 "github.com/opencontainers/runc/libcontainer/configs" 11 ) 12 13 func supportedControllers() (string, error) { 14 return cgroups.ReadFile(UnifiedMountpoint, "/cgroup.controllers") 15 } 16 17 // needAnyControllers returns whether we enable some supported controllers or not, 18 // based on (1) controllers available and (2) resources that are being set. 19 // We don't check "pseudo" controllers such as 20 // "freezer" and "devices". 21 func needAnyControllers(r *configs.Resources) (bool, error) { 22 if r == nil { 23 return false, nil 24 } 25 26 // list of all available controllers 27 content, err := supportedControllers() 28 if err != nil { 29 return false, err 30 } 31 avail := make(map[string]struct{}) 32 for _, ctr := range strings.Fields(content) { 33 avail[ctr] = struct{}{} 34 } 35 36 // check whether the controller if available or not 37 have := func(controller string) bool { 38 _, ok := avail[controller] 39 return ok 40 } 41 42 if isPidsSet(r) && have("pids") { 43 return true, nil 44 } 45 if isMemorySet(r) && have("memory") { 46 return true, nil 47 } 48 if isIoSet(r) && have("io") { 49 return true, nil 50 } 51 if isCpuSet(r) && have("cpu") { 52 return true, nil 53 } 54 if isCpusetSet(r) && have("cpuset") { 55 return true, nil 56 } 57 if isHugeTlbSet(r) && have("hugetlb") { 58 return true, nil 59 } 60 61 return false, nil 62 } 63 64 // containsDomainController returns whether the current config contains domain controller or not. 65 // Refer to: http://man7.org/linux/man-pages/man7/cgroups.7.html 66 // As at Linux 4.19, the following controllers are threaded: cpu, perf_event, and pids. 67 func containsDomainController(r *configs.Resources) bool { 68 return isMemorySet(r) || isIoSet(r) || isCpuSet(r) || isHugeTlbSet(r) 69 } 70 71 // CreateCgroupPath creates cgroupv2 path, enabling all the supported controllers. 72 func CreateCgroupPath(path string, c *configs.Cgroup) (Err error) { 73 if !strings.HasPrefix(path, UnifiedMountpoint) { 74 return fmt.Errorf("invalid cgroup path %s", path) 75 } 76 77 content, err := supportedControllers() 78 if err != nil { 79 return err 80 } 81 82 const ( 83 cgTypeFile = "cgroup.type" 84 cgStCtlFile = "cgroup.subtree_control" 85 ) 86 ctrs := strings.Fields(content) 87 res := "+" + strings.Join(ctrs, " +") 88 89 elements := strings.Split(path, "/") 90 elements = elements[3:] 91 current := "/sys/fs" 92 for i, e := range elements { 93 current = filepath.Join(current, e) 94 if i > 0 { 95 if err := os.Mkdir(current, 0o755); err != nil { 96 if !os.IsExist(err) { 97 return err 98 } 99 } else { 100 // If the directory was created, be sure it is not left around on errors. 101 current := current 102 defer func() { 103 if Err != nil { 104 os.Remove(current) 105 } 106 }() 107 } 108 cgType, _ := cgroups.ReadFile(current, cgTypeFile) 109 cgType = strings.TrimSpace(cgType) 110 switch cgType { 111 // If the cgroup is in an invalid mode (usually this means there's an internal 112 // process in the cgroup tree, because we created a cgroup under an 113 // already-populated-by-other-processes cgroup), then we have to error out if 114 // the user requested controllers which are not thread-aware. However, if all 115 // the controllers requested are thread-aware we can simply put the cgroup into 116 // threaded mode. 117 case "domain invalid": 118 if containsDomainController(c.Resources) { 119 return fmt.Errorf("cannot enter cgroupv2 %q with domain controllers -- it is in an invalid state", current) 120 } else { 121 // Not entirely correct (in theory we'd always want to be a domain -- 122 // since that means we're a properly delegated cgroup subtree) but in 123 // this case there's not much we can do and it's better than giving an 124 // error. 125 _ = cgroups.WriteFile(current, cgTypeFile, "threaded") 126 } 127 // If the cgroup is in (threaded) or (domain threaded) mode, we can only use thread-aware controllers 128 // (and you cannot usually take a cgroup out of threaded mode). 129 case "domain threaded": 130 fallthrough 131 case "threaded": 132 if containsDomainController(c.Resources) { 133 return fmt.Errorf("cannot enter cgroupv2 %q with domain controllers -- it is in %s mode", current, cgType) 134 } 135 } 136 } 137 // enable all supported controllers 138 if i < len(elements)-1 { 139 if err := cgroups.WriteFile(current, cgStCtlFile, res); err != nil { 140 // try write one by one 141 allCtrs := strings.Split(res, " ") 142 for _, ctr := range allCtrs { 143 _ = cgroups.WriteFile(current, cgStCtlFile, ctr) 144 } 145 } 146 // Some controllers might not be enabled when rootless or containerized, 147 // but we don't catch the error here. (Caught in setXXX() functions.) 148 } 149 } 150 151 return nil 152 }