github.com/elastic/gosigar@v0.14.3/cgroup/util.go (about) 1 package cgroup 2 3 import ( 4 "bufio" 5 "bytes" 6 "errors" 7 "fmt" 8 "io/ioutil" 9 "os" 10 "path/filepath" 11 "strconv" 12 "strings" 13 ) 14 15 var ( 16 // ErrCgroupsMissing indicates the /proc/cgroups was not found. This means 17 // that cgroups were disabled at compile time (CONFIG_CGROUPS=n) or that 18 // an invalid rootfs path was given. 19 ErrCgroupsMissing = errors.New("cgroups not found or unsupported by OS") 20 21 // ErrInvalidFormat indicates a malformed key/value pair on a line. 22 ErrInvalidFormat = errors.New("error invalid key/value format") 23 ) 24 25 // mountinfo represents a subset of the fields containing /proc/[pid]/mountinfo. 26 type mountinfo struct { 27 mountpoint string 28 filesystemType string 29 superOptions []string 30 } 31 32 // Parses a cgroup param and returns the key name and value. 33 func parseCgroupParamKeyValue(t string) (string, uint64, error) { 34 parts := strings.Fields(t) 35 if len(parts) != 2 { 36 return "", 0, ErrInvalidFormat 37 } 38 39 value, err := parseUint([]byte(parts[1])) 40 if err != nil { 41 return "", 0, fmt.Errorf("unable to convert param value (%q) to uint64: %v", parts[1], err) 42 } 43 44 return parts[0], value, nil 45 } 46 47 // parseUintFromFile reads a single uint value from a file. 48 func parseUintFromFile(path ...string) (uint64, error) { 49 value, err := ioutil.ReadFile(filepath.Join(path...)) 50 if err != nil { 51 // Not all features are implemented/enabled by each OS. 52 if os.IsNotExist(err) { 53 return 0, nil 54 } 55 return 0, err 56 } 57 58 return parseUint(value) 59 } 60 61 // parseUint reads a single uint value. It will trip any whitespace before 62 // attempting to parse string. If the value is negative it will return 0. 63 func parseUint(value []byte) (uint64, error) { 64 strValue := string(bytes.TrimSpace(value)) 65 uintValue, err := strconv.ParseUint(strValue, 10, 64) 66 if err != nil { 67 // Munge negative values to 0. 68 intValue, intErr := strconv.ParseInt(strValue, 10, 64) 69 if intErr == nil && intValue < 0 { 70 return 0, nil 71 } else if intErr != nil && intErr.(*strconv.NumError).Err == strconv.ErrRange && intValue < 0 { 72 return 0, nil 73 } 74 75 return 0, err 76 } 77 78 return uintValue, nil 79 } 80 81 // parseMountinfoLine parses a line from the /proc/[pid]/mountinfo file on 82 // Linux. The format of the line is specified in section 3.5 of 83 // https://www.kernel.org/doc/Documentation/filesystems/proc.txt. 84 func parseMountinfoLine(line string) (mountinfo, error) { 85 mount := mountinfo{} 86 87 fields := strings.Fields(line) 88 if len(fields) < 10 { 89 return mount, fmt.Errorf("invalid mountinfo line, expected at least "+ 90 "10 fields but got %d from line='%s'", len(fields), line) 91 } 92 93 mount.mountpoint = fields[4] 94 95 var seperatorIndex int 96 for i, value := range fields { 97 if value == "-" { 98 seperatorIndex = i 99 break 100 } 101 } 102 if fields[seperatorIndex] != "-" { 103 return mount, fmt.Errorf("invalid mountinfo line, separator ('-') not "+ 104 "found in line='%s'", line) 105 } 106 107 if len(fields)-seperatorIndex-1 < 3 { 108 return mount, fmt.Errorf("invalid mountinfo line, expected at least "+ 109 "3 fields after seperator but got %d from line='%s'", 110 len(fields)-seperatorIndex-1, line) 111 } 112 113 fields = fields[seperatorIndex+1:] 114 mount.filesystemType = fields[0] 115 mount.superOptions = strings.Split(fields[2], ",") 116 return mount, nil 117 } 118 119 // SupportedSubsystems returns the subsystems that are supported by the 120 // kernel. The returned map contains a entry for each subsystem. 121 func SupportedSubsystems(rootfsMountpoint string) (map[string]struct{}, error) { 122 if rootfsMountpoint == "" { 123 rootfsMountpoint = "/" 124 } 125 126 cgroups, err := os.Open(filepath.Join(rootfsMountpoint, "proc", "cgroups")) 127 if err != nil { 128 if os.IsNotExist(err) { 129 return nil, ErrCgroupsMissing 130 } 131 return nil, err 132 } 133 defer cgroups.Close() 134 135 subsystemSet := map[string]struct{}{} 136 sc := bufio.NewScanner(cgroups) 137 for sc.Scan() { 138 line := sc.Text() 139 140 // Ignore the header. 141 if len(line) > 0 && line[0] == '#' { 142 continue 143 } 144 145 // Parse the cgroup subsystems. 146 // Format: subsys_name hierarchy num_cgroups enabled 147 // Example: cpuset 4 1 1 148 fields := strings.Fields(line) 149 if len(fields) == 0 { 150 continue 151 } 152 153 // Check the enabled flag. 154 if len(fields) > 3 { 155 enabled := fields[3] 156 if enabled == "0" { 157 // Ignore cgroup subsystems that are disabled (via the 158 // cgroup_disable kernel command-line boot parameter). 159 continue 160 } 161 } 162 163 subsystem := fields[0] 164 subsystemSet[subsystem] = struct{}{} 165 } 166 167 return subsystemSet, sc.Err() 168 } 169 170 // SubsystemMountpoints returns the mountpoints for each of the given subsystems. 171 // The returned map contains the subsystem name as a key and the value is the 172 // mountpoint. 173 func SubsystemMountpoints(rootfsMountpoint string, subsystems map[string]struct{}) (map[string]string, error) { 174 if rootfsMountpoint == "" { 175 rootfsMountpoint = "/" 176 } 177 178 mountinfo, err := os.Open(filepath.Join(rootfsMountpoint, "proc", "self", "mountinfo")) 179 if err != nil { 180 return nil, err 181 } 182 defer mountinfo.Close() 183 184 mounts := map[string]string{} 185 sc := bufio.NewScanner(mountinfo) 186 for sc.Scan() { 187 // https://www.kernel.org/doc/Documentation/filesystems/proc.txt 188 // Example: 189 // 25 21 0:20 / /cgroup/cpu rw,relatime - cgroup cgroup rw,cpu 190 line := strings.TrimSpace(sc.Text()) 191 if line == "" { 192 continue 193 } 194 195 mount, err := parseMountinfoLine(line) 196 if err != nil { 197 return nil, err 198 } 199 200 if mount.filesystemType != "cgroup" { 201 continue 202 } 203 204 if !strings.HasPrefix(mount.mountpoint, rootfsMountpoint) { 205 continue 206 } 207 208 for _, opt := range mount.superOptions { 209 // Sometimes the subsystem name is written like "name=blkio". 210 fields := strings.SplitN(opt, "=", 2) 211 if len(fields) > 1 { 212 opt = fields[1] 213 } 214 215 // Test if option is a subsystem name. 216 if _, found := subsystems[opt]; found { 217 // Add the subsystem mount if it does not already exist. 218 if _, exists := mounts[opt]; !exists { 219 mounts[opt] = mount.mountpoint 220 } 221 } 222 } 223 } 224 225 return mounts, sc.Err() 226 } 227 228 // ProcessCgroupPaths returns the cgroups to which a process belongs and the 229 // pathname of the cgroup relative to the mountpoint of the subsystem. 230 func ProcessCgroupPaths(rootfsMountpoint string, pid int) (map[string]string, error) { 231 if rootfsMountpoint == "" { 232 rootfsMountpoint = "/" 233 } 234 235 cgroup, err := os.Open(filepath.Join(rootfsMountpoint, "proc", strconv.Itoa(pid), "cgroup")) 236 if err != nil { 237 return nil, err 238 } 239 defer cgroup.Close() 240 241 paths := map[string]string{} 242 sc := bufio.NewScanner(cgroup) 243 for sc.Scan() { 244 // http://man7.org/linux/man-pages/man7/cgroups.7.html 245 // Format: hierarchy-ID:subsystem-list:cgroup-path 246 // Example: 247 // 2:cpu:/docker/b29faf21b7eff959f64b4192c34d5d67a707fe8561e9eaa608cb27693fba4242 248 line := sc.Text() 249 250 fields := strings.Split(line, ":") 251 if len(fields) != 3 { 252 continue 253 } 254 255 path := fields[2] 256 subsystems := strings.Split(fields[1], ",") 257 for _, subsystem := range subsystems { 258 paths[subsystem] = path 259 } 260 } 261 262 return paths, sc.Err() 263 }