| package fs2 |
| |
| import ( |
| "fmt" |
| "os" |
| "path/filepath" |
| "strings" |
| |
| "github.com/opencontainers/runc/libcontainer/cgroups" |
| "github.com/opencontainers/runc/libcontainer/configs" |
| ) |
| |
| func supportedControllers() (string, error) { |
| return cgroups.ReadFile(UnifiedMountpoint, "/cgroup.controllers") |
| } |
| |
| // needAnyControllers returns whether we enable some supported controllers or not, |
| // based on (1) controllers available and (2) resources that are being set. |
| // We don't check "pseudo" controllers such as |
| // "freezer" and "devices". |
| func needAnyControllers(r *configs.Resources) (bool, error) { |
| if r == nil { |
| return false, nil |
| } |
| |
| // list of all available controllers |
| content, err := supportedControllers() |
| if err != nil { |
| return false, err |
| } |
| avail := make(map[string]struct{}) |
| for _, ctr := range strings.Fields(content) { |
| avail[ctr] = struct{}{} |
| } |
| |
| // check whether the controller if available or not |
| have := func(controller string) bool { |
| _, ok := avail[controller] |
| return ok |
| } |
| |
| if isPidsSet(r) && have("pids") { |
| return true, nil |
| } |
| if isMemorySet(r) && have("memory") { |
| return true, nil |
| } |
| if isIoSet(r) && have("io") { |
| return true, nil |
| } |
| if isCpuSet(r) && have("cpu") { |
| return true, nil |
| } |
| if isCpusetSet(r) && have("cpuset") { |
| return true, nil |
| } |
| if isHugeTlbSet(r) && have("hugetlb") { |
| return true, nil |
| } |
| |
| return false, nil |
| } |
| |
| // containsDomainController returns whether the current config contains domain controller or not. |
| // Refer to: http://man7.org/linux/man-pages/man7/cgroups.7.html |
| // As at Linux 4.19, the following controllers are threaded: cpu, perf_event, and pids. |
| func containsDomainController(r *configs.Resources) bool { |
| return isMemorySet(r) || isIoSet(r) || isCpuSet(r) || isHugeTlbSet(r) |
| } |
| |
| // CreateCgroupPath creates cgroupv2 path, enabling all the supported controllers. |
| func CreateCgroupPath(path string, c *configs.Cgroup) (Err error) { |
| if !strings.HasPrefix(path, UnifiedMountpoint) { |
| return fmt.Errorf("invalid cgroup path %s", path) |
| } |
| |
| content, err := supportedControllers() |
| if err != nil { |
| return err |
| } |
| |
| const ( |
| cgTypeFile = "cgroup.type" |
| cgStCtlFile = "cgroup.subtree_control" |
| ) |
| ctrs := strings.Fields(content) |
| res := "+" + strings.Join(ctrs, " +") |
| |
| elements := strings.Split(path, "/") |
| elements = elements[3:] |
| current := "/sys/fs" |
| for i, e := range elements { |
| current = filepath.Join(current, e) |
| if i > 0 { |
| if err := os.Mkdir(current, 0o755); err != nil { |
| if !os.IsExist(err) { |
| return err |
| } |
| } else { |
| // If the directory was created, be sure it is not left around on errors. |
| current := current |
| defer func() { |
| if Err != nil { |
| os.Remove(current) |
| } |
| }() |
| } |
| cgType, _ := cgroups.ReadFile(current, cgTypeFile) |
| cgType = strings.TrimSpace(cgType) |
| switch cgType { |
| // If the cgroup is in an invalid mode (usually this means there's an internal |
| // process in the cgroup tree, because we created a cgroup under an |
| // already-populated-by-other-processes cgroup), then we have to error out if |
| // the user requested controllers which are not thread-aware. However, if all |
| // the controllers requested are thread-aware we can simply put the cgroup into |
| // threaded mode. |
| case "domain invalid": |
| if containsDomainController(c.Resources) { |
| return fmt.Errorf("cannot enter cgroupv2 %q with domain controllers -- it is in an invalid state", current) |
| } else { |
| // Not entirely correct (in theory we'd always want to be a domain -- |
| // since that means we're a properly delegated cgroup subtree) but in |
| // this case there's not much we can do and it's better than giving an |
| // error. |
| _ = cgroups.WriteFile(current, cgTypeFile, "threaded") |
| } |
| // If the cgroup is in (threaded) or (domain threaded) mode, we can only use thread-aware controllers |
| // (and you cannot usually take a cgroup out of threaded mode). |
| case "domain threaded": |
| fallthrough |
| case "threaded": |
| if containsDomainController(c.Resources) { |
| return fmt.Errorf("cannot enter cgroupv2 %q with domain controllers -- it is in %s mode", current, cgType) |
| } |
| } |
| } |
| // enable all supported controllers |
| if i < len(elements)-1 { |
| if err := cgroups.WriteFile(current, cgStCtlFile, res); err != nil { |
| // try write one by one |
| allCtrs := strings.Split(res, " ") |
| for _, ctr := range allCtrs { |
| _ = cgroups.WriteFile(current, cgStCtlFile, ctr) |
| } |
| } |
| // Some controllers might not be enabled when rootless or containerized, |
| // but we don't catch the error here. (Caught in setXXX() functions.) |
| } |
| } |
| |
| return nil |
| } |