gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/pkg/sentry/hostmm/cgroup.go (about) 1 // Copyright 2019 The gVisor Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package hostmm 16 17 import ( 18 "bufio" 19 "fmt" 20 "os" 21 "path" 22 "strings" 23 ) 24 25 // currentCgroupDirectory returns the directory for the cgroup for the given 26 // controller in which the calling process resides. 27 func currentCgroupDirectory(ctrl string) (string, error) { 28 root, err := cgroupRootDirectory(ctrl) 29 if err != nil { 30 return "", err 31 } 32 cg, err := currentCgroup(ctrl) 33 if err != nil { 34 return "", err 35 } 36 return path.Join(root, cg), nil 37 } 38 39 // cgroupRootDirectory returns the root directory for the cgroup hierarchy in 40 // which the given cgroup controller is mounted in the calling process' mount 41 // namespace. 42 func cgroupRootDirectory(ctrl string) (string, error) { 43 const path = "/proc/self/mounts" 44 file, err := os.Open(path) 45 if err != nil { 46 return "", err 47 } 48 defer file.Close() 49 50 // Per proc(5) -> fstab(5): 51 // Each line of /proc/self/mounts describes a mount. 52 scanner := bufio.NewScanner(file) 53 for scanner.Scan() { 54 // Each line consists of 6 space-separated fields. Find the line for 55 // which the third field (fs_vfstype) is cgroup, and the fourth field 56 // (fs_mntops, a comma-separated list of mount options) contains 57 // ctrl. 58 var spec, file, vfstype, mntopts, freq, passno string 59 const nrfields = 6 60 line := scanner.Text() 61 n, err := fmt.Sscan(line, &spec, &file, &vfstype, &mntopts, &freq, &passno) 62 if err != nil { 63 return "", fmt.Errorf("failed to parse %s: %v", path, err) 64 } 65 if n != nrfields { 66 return "", fmt.Errorf("failed to parse %s: line %q: got %d fields, wanted %d", path, line, n, nrfields) 67 } 68 if vfstype != "cgroup" { 69 continue 70 } 71 for _, mntopt := range strings.Split(mntopts, ",") { 72 if mntopt == ctrl { 73 return file, nil 74 } 75 } 76 } 77 return "", fmt.Errorf("no cgroup hierarchy mounted for controller %s", ctrl) 78 } 79 80 // currentCgroup returns the cgroup for the given controller in which the 81 // calling process resides. The returned string is a path that should be 82 // interpreted as relative to cgroupRootDirectory(ctrl). 83 func currentCgroup(ctrl string) (string, error) { 84 const path = "/proc/self/cgroup" 85 file, err := os.Open(path) 86 if err != nil { 87 return "", err 88 } 89 defer file.Close() 90 91 // Per proc(5) -> cgroups(7): 92 // Each line of /proc/self/cgroups describes a cgroup hierarchy. 93 scanner := bufio.NewScanner(file) 94 for scanner.Scan() { 95 // Each line consists of 3 colon-separated fields. Find the line for 96 // which the second field (controller-list, a comma-separated list of 97 // cgroup controllers) contains ctrl. 98 line := scanner.Text() 99 const nrfields = 3 100 fields := strings.Split(line, ":") 101 if len(fields) != nrfields { 102 return "", fmt.Errorf("failed to parse %s: line %q: got %d fields, wanted %d", path, line, len(fields), nrfields) 103 } 104 for _, controller := range strings.Split(fields[1], ",") { 105 if controller == ctrl { 106 return fields[2], nil 107 } 108 } 109 } 110 return "", fmt.Errorf("not a member of a cgroup hierarchy for controller %s", ctrl) 111 }