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  }