package fs import ( "errors" "os" "path/filepath" "sync" "golang.org/x/sys/unix" "github.com/opencontainers/runc/libcontainer/cgroups" "github.com/opencontainers/runc/libcontainer/configs" "github.com/opencontainers/runc/libcontainer/utils" ) // The absolute path to the root of the cgroup hierarchies. var ( cgroupRootLock sync.Mutex cgroupRoot string ) const defaultCgroupRoot = "/sys/fs/cgroup" func initPaths(cg *configs.Cgroup) (map[string]string, error) { root, err := rootPath() if err != nil { return nil, err } inner, err := innerPath(cg) if err != nil { return nil, err } paths := make(map[string]string) for _, sys := range subsystems { name := sys.Name() path, err := subsysPath(root, inner, name) if err != nil { // The non-presence of the devices subsystem // is considered fatal for security reasons. if cgroups.IsNotFound(err) && (cg.SkipDevices || name != "devices") { continue } return nil, err } paths[name] = path } return paths, nil } func tryDefaultCgroupRoot() string { var st, pst unix.Stat_t // (1) it should be a directory... err := unix.Lstat(defaultCgroupRoot, &st) if err != nil || st.Mode&unix.S_IFDIR == 0 { return "" } // (2) ... and a mount point ... err = unix.Lstat(filepath.Dir(defaultCgroupRoot), &pst) if err != nil { return "" } if st.Dev == pst.Dev { // parent dir has the same dev -- not a mount point return "" } // (3) ... of 'tmpfs' fs type. var fst unix.Statfs_t err = unix.Statfs(defaultCgroupRoot, &fst) if err != nil || fst.Type != unix.TMPFS_MAGIC { return "" } // (4) it should have at least 1 entry ... dir, err := os.Open(defaultCgroupRoot) if err != nil { return "" } names, err := dir.Readdirnames(1) if err != nil { return "" } if len(names) < 1 { return "" } // ... which is a cgroup mount point. err = unix.Statfs(filepath.Join(defaultCgroupRoot, names[0]), &fst) if err != nil || fst.Type != unix.CGROUP_SUPER_MAGIC { return "" } return defaultCgroupRoot } // rootPath finds and returns path to the root of the cgroup hierarchies. func rootPath() (string, error) { cgroupRootLock.Lock() defer cgroupRootLock.Unlock() if cgroupRoot != "" { return cgroupRoot, nil } // fast path cgroupRoot = tryDefaultCgroupRoot() if cgroupRoot != "" { return cgroupRoot, nil } // slow path: parse mountinfo mi, err := cgroups.GetCgroupMounts(false) if err != nil { return "", err } if len(mi) < 1 { return "", errors.New("no cgroup mount found in mountinfo") } // Get the first cgroup mount (e.g. "/sys/fs/cgroup/memory"), // use its parent directory. root := filepath.Dir(mi[0].Mountpoint) if _, err := os.Stat(root); err != nil { return "", err } cgroupRoot = root return cgroupRoot, nil } func innerPath(c *configs.Cgroup) (string, error) { if (c.Name != "" || c.Parent != "") && c.Path != "" { return "", errors.New("cgroup: either Path or Name and Parent should be used") } // XXX: Do not remove CleanPath. Path safety is important! -- cyphar innerPath := utils.CleanPath(c.Path) if innerPath == "" { cgParent := utils.CleanPath(c.Parent) cgName := utils.CleanPath(c.Name) innerPath = filepath.Join(cgParent, cgName) } return innerPath, nil } func subsysPath(root, inner, subsystem string) (string, error) { // If the cgroup name/path is absolute do not look relative to the cgroup of the init process. if filepath.IsAbs(inner) { mnt, err := cgroups.FindCgroupMountpoint(root, subsystem) // If we didn't mount the subsystem, there is no point we make the path. if err != nil { return "", err } // Sometimes subsystems can be mounted together as 'cpu,cpuacct'. return filepath.Join(root, filepath.Base(mnt), inner), nil } // Use GetOwnCgroupPath instead of GetInitCgroupPath, because the creating // process could in container and shared pid namespace with host, and // /proc/1/cgroup could point to whole other world of cgroups. parentPath, err := cgroups.GetOwnCgroupPath(subsystem) if err != nil { return "", err } return filepath.Join(parentPath, inner), nil } func apply(path string, pid int) error { if path == "" { return nil } if err := os.MkdirAll(path, 0o755); err != nil { return err } return cgroups.WriteCgroupProc(path, pid) }