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  }