github.com/opencontainers/runc@v1.2.0-rc.1.0.20240520010911-492dc558cdd6/libcontainer/cgroups/fs/paths.go (about) 1 package fs 2 3 import ( 4 "errors" 5 "os" 6 "path/filepath" 7 "sync" 8 9 "golang.org/x/sys/unix" 10 11 "github.com/opencontainers/runc/libcontainer/cgroups" 12 "github.com/opencontainers/runc/libcontainer/configs" 13 "github.com/opencontainers/runc/libcontainer/utils" 14 ) 15 16 // The absolute path to the root of the cgroup hierarchies. 17 var ( 18 cgroupRootLock sync.Mutex 19 cgroupRoot string 20 ) 21 22 const defaultCgroupRoot = "/sys/fs/cgroup" 23 24 func initPaths(cg *configs.Cgroup) (map[string]string, error) { 25 root, err := rootPath() 26 if err != nil { 27 return nil, err 28 } 29 30 inner, err := innerPath(cg) 31 if err != nil { 32 return nil, err 33 } 34 35 paths := make(map[string]string) 36 for _, sys := range subsystems { 37 name := sys.Name() 38 path, err := subsysPath(root, inner, name) 39 if err != nil { 40 // The non-presence of the devices subsystem 41 // is considered fatal for security reasons. 42 if cgroups.IsNotFound(err) && (cg.SkipDevices || name != "devices") { 43 continue 44 } 45 46 return nil, err 47 } 48 paths[name] = path 49 } 50 51 return paths, nil 52 } 53 54 func tryDefaultCgroupRoot() string { 55 var st, pst unix.Stat_t 56 57 // (1) it should be a directory... 58 err := unix.Lstat(defaultCgroupRoot, &st) 59 if err != nil || st.Mode&unix.S_IFDIR == 0 { 60 return "" 61 } 62 63 // (2) ... and a mount point ... 64 err = unix.Lstat(filepath.Dir(defaultCgroupRoot), &pst) 65 if err != nil { 66 return "" 67 } 68 69 if st.Dev == pst.Dev { 70 // parent dir has the same dev -- not a mount point 71 return "" 72 } 73 74 // (3) ... of 'tmpfs' fs type. 75 var fst unix.Statfs_t 76 err = unix.Statfs(defaultCgroupRoot, &fst) 77 if err != nil || fst.Type != unix.TMPFS_MAGIC { 78 return "" 79 } 80 81 // (4) it should have at least 1 entry ... 82 dir, err := os.Open(defaultCgroupRoot) 83 if err != nil { 84 return "" 85 } 86 defer dir.Close() 87 names, err := dir.Readdirnames(1) 88 if err != nil { 89 return "" 90 } 91 if len(names) < 1 { 92 return "" 93 } 94 // ... which is a cgroup mount point. 95 err = unix.Statfs(filepath.Join(defaultCgroupRoot, names[0]), &fst) 96 if err != nil || fst.Type != unix.CGROUP_SUPER_MAGIC { 97 return "" 98 } 99 100 return defaultCgroupRoot 101 } 102 103 // rootPath finds and returns path to the root of the cgroup hierarchies. 104 func rootPath() (string, error) { 105 cgroupRootLock.Lock() 106 defer cgroupRootLock.Unlock() 107 108 if cgroupRoot != "" { 109 return cgroupRoot, nil 110 } 111 112 // fast path 113 cgroupRoot = tryDefaultCgroupRoot() 114 if cgroupRoot != "" { 115 return cgroupRoot, nil 116 } 117 118 // slow path: parse mountinfo 119 mi, err := cgroups.GetCgroupMounts(false) 120 if err != nil { 121 return "", err 122 } 123 if len(mi) < 1 { 124 return "", errors.New("no cgroup mount found in mountinfo") 125 } 126 127 // Get the first cgroup mount (e.g. "/sys/fs/cgroup/memory"), 128 // use its parent directory. 129 root := filepath.Dir(mi[0].Mountpoint) 130 131 if _, err := os.Stat(root); err != nil { 132 return "", err 133 } 134 135 cgroupRoot = root 136 return cgroupRoot, nil 137 } 138 139 func innerPath(c *configs.Cgroup) (string, error) { 140 if (c.Name != "" || c.Parent != "") && c.Path != "" { 141 return "", errors.New("cgroup: either Path or Name and Parent should be used") 142 } 143 144 // XXX: Do not remove CleanPath. Path safety is important! -- cyphar 145 innerPath := utils.CleanPath(c.Path) 146 if innerPath == "" { 147 cgParent := utils.CleanPath(c.Parent) 148 cgName := utils.CleanPath(c.Name) 149 innerPath = filepath.Join(cgParent, cgName) 150 } 151 152 return innerPath, nil 153 } 154 155 func subsysPath(root, inner, subsystem string) (string, error) { 156 // If the cgroup name/path is absolute do not look relative to the cgroup of the init process. 157 if filepath.IsAbs(inner) { 158 mnt, err := cgroups.FindCgroupMountpoint(root, subsystem) 159 // If we didn't mount the subsystem, there is no point we make the path. 160 if err != nil { 161 return "", err 162 } 163 164 // Sometimes subsystems can be mounted together as 'cpu,cpuacct'. 165 return filepath.Join(root, filepath.Base(mnt), inner), nil 166 } 167 168 // Use GetOwnCgroupPath for dind-like cases, when cgroupns is not 169 // available. This is ugly. 170 parentPath, err := cgroups.GetOwnCgroupPath(subsystem) 171 if err != nil { 172 return "", err 173 } 174 175 return filepath.Join(parentPath, inner), nil 176 } 177 178 func apply(path string, pid int) error { 179 if path == "" { 180 return nil 181 } 182 if err := os.MkdirAll(path, 0o755); err != nil { 183 return err 184 } 185 return cgroups.WriteCgroupProc(path, pid) 186 }