utils.go (6459B)
1 /* 2 Copyright The containerd Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package cgroup1 18 19 import ( 20 "bufio" 21 "fmt" 22 "os" 23 "path/filepath" 24 "strconv" 25 "strings" 26 "time" 27 28 "github.com/containerd/cgroups/v3" 29 units "github.com/docker/go-units" 30 specs "github.com/opencontainers/runtime-spec/specs-go" 31 ) 32 33 // defaults returns all known groups 34 func defaults(root string) ([]Subsystem, error) { 35 h, err := NewHugetlb(root) 36 if err != nil && !os.IsNotExist(err) { 37 return nil, err 38 } 39 s := []Subsystem{ 40 NewNamed(root, "systemd"), 41 NewFreezer(root), 42 NewPids(root), 43 NewNetCls(root), 44 NewNetPrio(root), 45 NewPerfEvent(root), 46 NewCpuset(root), 47 NewCpu(root), 48 NewCpuacct(root), 49 NewMemory(root), 50 NewBlkio(root), 51 NewRdma(root), 52 } 53 // only add the devices cgroup if we are not in a user namespace 54 // because modifications are not allowed 55 if !cgroups.RunningInUserNS() { 56 s = append(s, NewDevices(root)) 57 } 58 // add the hugetlb cgroup if error wasn't due to missing hugetlb 59 // cgroup support on the host 60 if err == nil { 61 s = append(s, h) 62 } 63 return s, nil 64 } 65 66 // remove will remove a cgroup path handling EAGAIN and EBUSY errors and 67 // retrying the remove after a exp timeout 68 func remove(path string) error { 69 delay := 10 * time.Millisecond 70 for i := 0; i < 5; i++ { 71 if i != 0 { 72 time.Sleep(delay) 73 delay *= 2 74 } 75 if err := os.RemoveAll(path); err == nil { 76 return nil 77 } 78 } 79 return fmt.Errorf("cgroups: unable to remove path %q", path) 80 } 81 82 // readPids will read all the pids of processes or tasks in a cgroup by the provided path 83 func readPids(path string, subsystem Name, pType procType) ([]Process, error) { 84 f, err := os.Open(filepath.Join(path, pType)) 85 if err != nil { 86 return nil, err 87 } 88 defer f.Close() 89 var ( 90 out []Process 91 s = bufio.NewScanner(f) 92 ) 93 for s.Scan() { 94 if t := s.Text(); t != "" { 95 pid, err := strconv.Atoi(t) 96 if err != nil { 97 return nil, err 98 } 99 out = append(out, Process{ 100 Pid: pid, 101 Subsystem: subsystem, 102 Path: path, 103 }) 104 } 105 } 106 if err := s.Err(); err != nil { 107 // failed to read all pids? 108 return nil, err 109 } 110 return out, nil 111 } 112 113 func hugePageSizes() ([]string, error) { 114 var ( 115 pageSizes []string 116 sizeList = []string{"B", "KB", "MB", "GB", "TB", "PB"} 117 ) 118 files, err := os.ReadDir("/sys/kernel/mm/hugepages") 119 if err != nil { 120 return nil, err 121 } 122 for _, st := range files { 123 nameArray := strings.Split(st.Name(), "-") 124 pageSize, err := units.RAMInBytes(nameArray[1]) 125 if err != nil { 126 return nil, err 127 } 128 pageSizes = append(pageSizes, units.CustomSize("%g%s", float64(pageSize), 1024.0, sizeList)) 129 } 130 return pageSizes, nil 131 } 132 133 func readUint(path string) (uint64, error) { 134 v, err := os.ReadFile(path) 135 if err != nil { 136 return 0, err 137 } 138 return parseUint(strings.TrimSpace(string(v)), 10, 64) 139 } 140 141 func parseUint(s string, base, bitSize int) (uint64, error) { 142 v, err := strconv.ParseUint(s, base, bitSize) 143 if err != nil { 144 intValue, intErr := strconv.ParseInt(s, base, bitSize) 145 // 1. Handle negative values greater than MinInt64 (and) 146 // 2. Handle negative values lesser than MinInt64 147 if intErr == nil && intValue < 0 { 148 return 0, nil 149 } else if intErr != nil && 150 intErr.(*strconv.NumError).Err == strconv.ErrRange && 151 intValue < 0 { 152 return 0, nil 153 } 154 return 0, err 155 } 156 return v, nil 157 } 158 159 func parseKV(raw string) (string, uint64, error) { 160 parts := strings.Fields(raw) 161 switch len(parts) { 162 case 2: 163 v, err := parseUint(parts[1], 10, 64) 164 if err != nil { 165 return "", 0, err 166 } 167 return parts[0], v, nil 168 default: 169 return "", 0, ErrInvalidFormat 170 } 171 } 172 173 // ParseCgroupFile parses the given cgroup file, typically /proc/self/cgroup 174 // or /proc/<pid>/cgroup, into a map of subsystems to cgroup paths, e.g. 175 // 176 // "cpu": "/user.slice/user-1000.slice" 177 // "pids": "/user.slice/user-1000.slice" 178 // 179 // etc. 180 // 181 // The resulting map does not have an element for cgroup v2 unified hierarchy. 182 // Use [cgroups.ParseCgroupFileUnified] to get the unified path. 183 func ParseCgroupFile(path string) (map[string]string, error) { 184 x, _, err := ParseCgroupFileUnified(path) 185 return x, err 186 } 187 188 // ParseCgroupFileUnified returns legacy subsystem paths as the first value, 189 // and returns the unified path as the second value. 190 // 191 // Deprecated: use [cgroups.ParseCgroupFileUnified] instead . 192 func ParseCgroupFileUnified(path string) (map[string]string, string, error) { 193 return cgroups.ParseCgroupFileUnified(path) 194 } 195 196 func getCgroupDestination(subsystem string) (string, error) { 197 f, err := os.Open("/proc/self/mountinfo") 198 if err != nil { 199 return "", err 200 } 201 defer f.Close() 202 s := bufio.NewScanner(f) 203 for s.Scan() { 204 fields := strings.Split(s.Text(), " ") 205 if len(fields) < 10 { 206 // broken mountinfo? 207 continue 208 } 209 if fields[len(fields)-3] != "cgroup" { 210 continue 211 } 212 for _, opt := range strings.Split(fields[len(fields)-1], ",") { 213 if opt == subsystem { 214 return fields[3], nil 215 } 216 } 217 } 218 if err := s.Err(); err != nil { 219 return "", err 220 } 221 return "", ErrNoCgroupMountDestination 222 } 223 224 func pathers(subystems []Subsystem) []pather { 225 var out []pather 226 for _, s := range subystems { 227 if p, ok := s.(pather); ok { 228 out = append(out, p) 229 } 230 } 231 return out 232 } 233 234 func initializeSubsystem(s Subsystem, path Path, resources *specs.LinuxResources) error { 235 if c, ok := s.(creator); ok { 236 p, err := path(s.Name()) 237 if err != nil { 238 return err 239 } 240 if err := c.Create(p, resources); err != nil { 241 return err 242 } 243 } else if c, ok := s.(pather); ok { 244 p, err := path(s.Name()) 245 if err != nil { 246 return err 247 } 248 // do the default create if the group does not have a custom one 249 if err := os.MkdirAll(c.Path(p), defaultDirPerm); err != nil { 250 return err 251 } 252 } 253 return nil 254 } 255 256 func cleanPath(path string) string { 257 if path == "" { 258 return "" 259 } 260 path = filepath.Clean(path) 261 if !filepath.IsAbs(path) { 262 path, _ = filepath.Rel(string(os.PathSeparator), filepath.Clean(string(os.PathSeparator)+path)) 263 } 264 return path 265 }