...

Source file src/github.com/opencontainers/runc/libcontainer/cgroups/v1_utils.go

Documentation: github.com/opencontainers/runc/libcontainer/cgroups

     1  package cgroups
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"os"
     7  	"path/filepath"
     8  	"strings"
     9  	"sync"
    10  	"syscall"
    11  
    12  	securejoin "github.com/cyphar/filepath-securejoin"
    13  	"github.com/moby/sys/mountinfo"
    14  	"golang.org/x/sys/unix"
    15  )
    16  
    17  // Code in this source file are specific to cgroup v1,
    18  // and must not be used from any cgroup v2 code.
    19  
    20  const (
    21  	CgroupNamePrefix = "name="
    22  	defaultPrefix    = "/sys/fs/cgroup"
    23  )
    24  
    25  var (
    26  	errUnified     = errors.New("not implemented for cgroup v2 unified hierarchy")
    27  	ErrV1NoUnified = errors.New("invalid configuration: cannot use unified on cgroup v1")
    28  
    29  	readMountinfoOnce sync.Once
    30  	readMountinfoErr  error
    31  	cgroupMountinfo   []*mountinfo.Info
    32  )
    33  
    34  type NotFoundError struct {
    35  	Subsystem string
    36  }
    37  
    38  func (e *NotFoundError) Error() string {
    39  	return fmt.Sprintf("mountpoint for %s not found", e.Subsystem)
    40  }
    41  
    42  func NewNotFoundError(sub string) error {
    43  	return &NotFoundError{
    44  		Subsystem: sub,
    45  	}
    46  }
    47  
    48  func IsNotFound(err error) bool {
    49  	var nfErr *NotFoundError
    50  	return errors.As(err, &nfErr)
    51  }
    52  
    53  func tryDefaultPath(cgroupPath, subsystem string) string {
    54  	if !strings.HasPrefix(defaultPrefix, cgroupPath) {
    55  		return ""
    56  	}
    57  
    58  	// remove possible prefix
    59  	subsystem = strings.TrimPrefix(subsystem, CgroupNamePrefix)
    60  
    61  	// Make sure we're still under defaultPrefix, and resolve
    62  	// a possible symlink (like cpu -> cpu,cpuacct).
    63  	path, err := securejoin.SecureJoin(defaultPrefix, subsystem)
    64  	if err != nil {
    65  		return ""
    66  	}
    67  
    68  	// (1) path should be a directory.
    69  	st, err := os.Lstat(path)
    70  	if err != nil || !st.IsDir() {
    71  		return ""
    72  	}
    73  
    74  	// (2) path should be a mount point.
    75  	pst, err := os.Lstat(filepath.Dir(path))
    76  	if err != nil {
    77  		return ""
    78  	}
    79  
    80  	if st.Sys().(*syscall.Stat_t).Dev == pst.Sys().(*syscall.Stat_t).Dev {
    81  		// parent dir has the same dev -- path is not a mount point
    82  		return ""
    83  	}
    84  
    85  	// (3) path should have 'cgroup' fs type.
    86  	fst := unix.Statfs_t{}
    87  	err = unix.Statfs(path, &fst)
    88  	if err != nil || fst.Type != unix.CGROUP_SUPER_MAGIC {
    89  		return ""
    90  	}
    91  
    92  	return path
    93  }
    94  
    95  // readCgroupMountinfo returns a list of cgroup v1 mounts (i.e. the ones
    96  // with fstype of "cgroup") for the current running process.
    97  //
    98  // The results are cached (to avoid re-reading mountinfo which is relatively
    99  // expensive), so it is assumed that cgroup mounts are not being changed.
   100  func readCgroupMountinfo() ([]*mountinfo.Info, error) {
   101  	readMountinfoOnce.Do(func() {
   102  		cgroupMountinfo, readMountinfoErr = mountinfo.GetMounts(
   103  			mountinfo.FSTypeFilter("cgroup"),
   104  		)
   105  	})
   106  
   107  	return cgroupMountinfo, readMountinfoErr
   108  }
   109  
   110  // https://www.kernel.org/doc/Documentation/cgroup-v1/cgroups.txt
   111  func FindCgroupMountpoint(cgroupPath, subsystem string) (string, error) {
   112  	if IsCgroup2UnifiedMode() {
   113  		return "", errUnified
   114  	}
   115  
   116  	// If subsystem is empty, we look for the cgroupv2 hybrid path.
   117  	if len(subsystem) == 0 {
   118  		return hybridMountpoint, nil
   119  	}
   120  
   121  	// Avoid parsing mountinfo by trying the default path first, if possible.
   122  	if path := tryDefaultPath(cgroupPath, subsystem); path != "" {
   123  		return path, nil
   124  	}
   125  
   126  	mnt, _, err := FindCgroupMountpointAndRoot(cgroupPath, subsystem)
   127  	return mnt, err
   128  }
   129  
   130  func FindCgroupMountpointAndRoot(cgroupPath, subsystem string) (string, string, error) {
   131  	if IsCgroup2UnifiedMode() {
   132  		return "", "", errUnified
   133  	}
   134  
   135  	mi, err := readCgroupMountinfo()
   136  	if err != nil {
   137  		return "", "", err
   138  	}
   139  
   140  	return findCgroupMountpointAndRootFromMI(mi, cgroupPath, subsystem)
   141  }
   142  
   143  func findCgroupMountpointAndRootFromMI(mounts []*mountinfo.Info, cgroupPath, subsystem string) (string, string, error) {
   144  	for _, mi := range mounts {
   145  		if strings.HasPrefix(mi.Mountpoint, cgroupPath) {
   146  			for _, opt := range strings.Split(mi.VFSOptions, ",") {
   147  				if opt == subsystem {
   148  					return mi.Mountpoint, mi.Root, nil
   149  				}
   150  			}
   151  		}
   152  	}
   153  
   154  	return "", "", NewNotFoundError(subsystem)
   155  }
   156  
   157  func (m Mount) GetOwnCgroup(cgroups map[string]string) (string, error) {
   158  	if len(m.Subsystems) == 0 {
   159  		return "", errors.New("no subsystem for mount")
   160  	}
   161  
   162  	return getControllerPath(m.Subsystems[0], cgroups)
   163  }
   164  
   165  func getCgroupMountsHelper(ss map[string]bool, mounts []*mountinfo.Info, all bool) ([]Mount, error) {
   166  	res := make([]Mount, 0, len(ss))
   167  	numFound := 0
   168  	for _, mi := range mounts {
   169  		m := Mount{
   170  			Mountpoint: mi.Mountpoint,
   171  			Root:       mi.Root,
   172  		}
   173  		for _, opt := range strings.Split(mi.VFSOptions, ",") {
   174  			seen, known := ss[opt]
   175  			if !known || (!all && seen) {
   176  				continue
   177  			}
   178  			ss[opt] = true
   179  			opt = strings.TrimPrefix(opt, CgroupNamePrefix)
   180  			m.Subsystems = append(m.Subsystems, opt)
   181  			numFound++
   182  		}
   183  		if len(m.Subsystems) > 0 || all {
   184  			res = append(res, m)
   185  		}
   186  		if !all && numFound >= len(ss) {
   187  			break
   188  		}
   189  	}
   190  	return res, nil
   191  }
   192  
   193  func getCgroupMountsV1(all bool) ([]Mount, error) {
   194  	mi, err := readCgroupMountinfo()
   195  	if err != nil {
   196  		return nil, err
   197  	}
   198  
   199  	allSubsystems, err := ParseCgroupFile("/proc/self/cgroup")
   200  	if err != nil {
   201  		return nil, err
   202  	}
   203  
   204  	allMap := make(map[string]bool)
   205  	for s := range allSubsystems {
   206  		allMap[s] = false
   207  	}
   208  
   209  	return getCgroupMountsHelper(allMap, mi, all)
   210  }
   211  
   212  // GetOwnCgroup returns the relative path to the cgroup docker is running in.
   213  func GetOwnCgroup(subsystem string) (string, error) {
   214  	if IsCgroup2UnifiedMode() {
   215  		return "", errUnified
   216  	}
   217  	cgroups, err := ParseCgroupFile("/proc/self/cgroup")
   218  	if err != nil {
   219  		return "", err
   220  	}
   221  
   222  	return getControllerPath(subsystem, cgroups)
   223  }
   224  
   225  func GetOwnCgroupPath(subsystem string) (string, error) {
   226  	cgroup, err := GetOwnCgroup(subsystem)
   227  	if err != nil {
   228  		return "", err
   229  	}
   230  
   231  	// If subsystem is empty, we look for the cgroupv2 hybrid path.
   232  	if len(subsystem) == 0 {
   233  		return hybridMountpoint, nil
   234  	}
   235  
   236  	return getCgroupPathHelper(subsystem, cgroup)
   237  }
   238  
   239  func GetInitCgroup(subsystem string) (string, error) {
   240  	if IsCgroup2UnifiedMode() {
   241  		return "", errUnified
   242  	}
   243  	cgroups, err := ParseCgroupFile("/proc/1/cgroup")
   244  	if err != nil {
   245  		return "", err
   246  	}
   247  
   248  	return getControllerPath(subsystem, cgroups)
   249  }
   250  
   251  func GetInitCgroupPath(subsystem string) (string, error) {
   252  	cgroup, err := GetInitCgroup(subsystem)
   253  	if err != nil {
   254  		return "", err
   255  	}
   256  
   257  	return getCgroupPathHelper(subsystem, cgroup)
   258  }
   259  
   260  func getCgroupPathHelper(subsystem, cgroup string) (string, error) {
   261  	mnt, root, err := FindCgroupMountpointAndRoot("", subsystem)
   262  	if err != nil {
   263  		return "", err
   264  	}
   265  
   266  	// This is needed for nested containers, because in /proc/self/cgroup we
   267  	// see paths from host, which don't exist in container.
   268  	relCgroup, err := filepath.Rel(root, cgroup)
   269  	if err != nil {
   270  		return "", err
   271  	}
   272  
   273  	return filepath.Join(mnt, relCgroup), nil
   274  }
   275  
   276  func getControllerPath(subsystem string, cgroups map[string]string) (string, error) {
   277  	if IsCgroup2UnifiedMode() {
   278  		return "", errUnified
   279  	}
   280  
   281  	if p, ok := cgroups[subsystem]; ok {
   282  		return p, nil
   283  	}
   284  
   285  	if p, ok := cgroups[CgroupNamePrefix+subsystem]; ok {
   286  		return p, nil
   287  	}
   288  
   289  	return "", NewNotFoundError(subsystem)
   290  }
   291  

View as plain text