123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290 |
- package cgroups
- import (
- "errors"
- "fmt"
- "os"
- "path/filepath"
- "strings"
- "sync"
- "syscall"
- securejoin "github.com/cyphar/filepath-securejoin"
- "github.com/moby/sys/mountinfo"
- "golang.org/x/sys/unix"
- )
- // Code in this source file are specific to cgroup v1,
- // and must not be used from any cgroup v2 code.
- const (
- CgroupNamePrefix = "name="
- defaultPrefix = "/sys/fs/cgroup"
- )
- var (
- errUnified = errors.New("not implemented for cgroup v2 unified hierarchy")
- ErrV1NoUnified = errors.New("invalid configuration: cannot use unified on cgroup v1")
- readMountinfoOnce sync.Once
- readMountinfoErr error
- cgroupMountinfo []*mountinfo.Info
- )
- type NotFoundError struct {
- Subsystem string
- }
- func (e *NotFoundError) Error() string {
- return fmt.Sprintf("mountpoint for %s not found", e.Subsystem)
- }
- func NewNotFoundError(sub string) error {
- return &NotFoundError{
- Subsystem: sub,
- }
- }
- func IsNotFound(err error) bool {
- var nfErr *NotFoundError
- return errors.As(err, &nfErr)
- }
- func tryDefaultPath(cgroupPath, subsystem string) string {
- if !strings.HasPrefix(defaultPrefix, cgroupPath) {
- return ""
- }
- // remove possible prefix
- subsystem = strings.TrimPrefix(subsystem, CgroupNamePrefix)
- // Make sure we're still under defaultPrefix, and resolve
- // a possible symlink (like cpu -> cpu,cpuacct).
- path, err := securejoin.SecureJoin(defaultPrefix, subsystem)
- if err != nil {
- return ""
- }
- // (1) path should be a directory.
- st, err := os.Lstat(path)
- if err != nil || !st.IsDir() {
- return ""
- }
- // (2) path should be a mount point.
- pst, err := os.Lstat(filepath.Dir(path))
- if err != nil {
- return ""
- }
- if st.Sys().(*syscall.Stat_t).Dev == pst.Sys().(*syscall.Stat_t).Dev {
- // parent dir has the same dev -- path is not a mount point
- return ""
- }
- // (3) path should have 'cgroup' fs type.
- fst := unix.Statfs_t{}
- err = unix.Statfs(path, &fst)
- if err != nil || fst.Type != unix.CGROUP_SUPER_MAGIC {
- return ""
- }
- return path
- }
- // readCgroupMountinfo returns a list of cgroup v1 mounts (i.e. the ones
- // with fstype of "cgroup") for the current running process.
- //
- // The results are cached (to avoid re-reading mountinfo which is relatively
- // expensive), so it is assumed that cgroup mounts are not being changed.
- func readCgroupMountinfo() ([]*mountinfo.Info, error) {
- readMountinfoOnce.Do(func() {
- cgroupMountinfo, readMountinfoErr = mountinfo.GetMounts(
- mountinfo.FSTypeFilter("cgroup"),
- )
- })
- return cgroupMountinfo, readMountinfoErr
- }
- // https://www.kernel.org/doc/Documentation/cgroup-v1/cgroups.txt
- func FindCgroupMountpoint(cgroupPath, subsystem string) (string, error) {
- if IsCgroup2UnifiedMode() {
- return "", errUnified
- }
- // If subsystem is empty, we look for the cgroupv2 hybrid path.
- if len(subsystem) == 0 {
- return hybridMountpoint, nil
- }
- // Avoid parsing mountinfo by trying the default path first, if possible.
- if path := tryDefaultPath(cgroupPath, subsystem); path != "" {
- return path, nil
- }
- mnt, _, err := FindCgroupMountpointAndRoot(cgroupPath, subsystem)
- return mnt, err
- }
- func FindCgroupMountpointAndRoot(cgroupPath, subsystem string) (string, string, error) {
- if IsCgroup2UnifiedMode() {
- return "", "", errUnified
- }
- mi, err := readCgroupMountinfo()
- if err != nil {
- return "", "", err
- }
- return findCgroupMountpointAndRootFromMI(mi, cgroupPath, subsystem)
- }
- func findCgroupMountpointAndRootFromMI(mounts []*mountinfo.Info, cgroupPath, subsystem string) (string, string, error) {
- for _, mi := range mounts {
- if strings.HasPrefix(mi.Mountpoint, cgroupPath) {
- for _, opt := range strings.Split(mi.VFSOptions, ",") {
- if opt == subsystem {
- return mi.Mountpoint, mi.Root, nil
- }
- }
- }
- }
- return "", "", NewNotFoundError(subsystem)
- }
- func (m Mount) GetOwnCgroup(cgroups map[string]string) (string, error) {
- if len(m.Subsystems) == 0 {
- return "", errors.New("no subsystem for mount")
- }
- return getControllerPath(m.Subsystems[0], cgroups)
- }
- func getCgroupMountsHelper(ss map[string]bool, mounts []*mountinfo.Info, all bool) ([]Mount, error) {
- res := make([]Mount, 0, len(ss))
- numFound := 0
- for _, mi := range mounts {
- m := Mount{
- Mountpoint: mi.Mountpoint,
- Root: mi.Root,
- }
- for _, opt := range strings.Split(mi.VFSOptions, ",") {
- seen, known := ss[opt]
- if !known || (!all && seen) {
- continue
- }
- ss[opt] = true
- opt = strings.TrimPrefix(opt, CgroupNamePrefix)
- m.Subsystems = append(m.Subsystems, opt)
- numFound++
- }
- if len(m.Subsystems) > 0 || all {
- res = append(res, m)
- }
- if !all && numFound >= len(ss) {
- break
- }
- }
- return res, nil
- }
- func getCgroupMountsV1(all bool) ([]Mount, error) {
- mi, err := readCgroupMountinfo()
- if err != nil {
- return nil, err
- }
- allSubsystems, err := ParseCgroupFile("/proc/self/cgroup")
- if err != nil {
- return nil, err
- }
- allMap := make(map[string]bool)
- for s := range allSubsystems {
- allMap[s] = false
- }
- return getCgroupMountsHelper(allMap, mi, all)
- }
- // GetOwnCgroup returns the relative path to the cgroup docker is running in.
- func GetOwnCgroup(subsystem string) (string, error) {
- if IsCgroup2UnifiedMode() {
- return "", errUnified
- }
- cgroups, err := ParseCgroupFile("/proc/self/cgroup")
- if err != nil {
- return "", err
- }
- return getControllerPath(subsystem, cgroups)
- }
- func GetOwnCgroupPath(subsystem string) (string, error) {
- cgroup, err := GetOwnCgroup(subsystem)
- if err != nil {
- return "", err
- }
- // If subsystem is empty, we look for the cgroupv2 hybrid path.
- if len(subsystem) == 0 {
- return hybridMountpoint, nil
- }
- return getCgroupPathHelper(subsystem, cgroup)
- }
- func GetInitCgroup(subsystem string) (string, error) {
- if IsCgroup2UnifiedMode() {
- return "", errUnified
- }
- cgroups, err := ParseCgroupFile("/proc/1/cgroup")
- if err != nil {
- return "", err
- }
- return getControllerPath(subsystem, cgroups)
- }
- func GetInitCgroupPath(subsystem string) (string, error) {
- cgroup, err := GetInitCgroup(subsystem)
- if err != nil {
- return "", err
- }
- return getCgroupPathHelper(subsystem, cgroup)
- }
- func getCgroupPathHelper(subsystem, cgroup string) (string, error) {
- mnt, root, err := FindCgroupMountpointAndRoot("", subsystem)
- if err != nil {
- return "", err
- }
- // This is needed for nested containers, because in /proc/self/cgroup we
- // see paths from host, which don't exist in container.
- relCgroup, err := filepath.Rel(root, cgroup)
- if err != nil {
- return "", err
- }
- return filepath.Join(mnt, relCgroup), nil
- }
- func getControllerPath(subsystem string, cgroups map[string]string) (string, error) {
- if IsCgroup2UnifiedMode() {
- return "", errUnified
- }
- if p, ok := cgroups[subsystem]; ok {
- return p, nil
- }
- if p, ok := cgroups[CgroupNamePrefix+subsystem]; ok {
- return p, nil
- }
- return "", NewNotFoundError(subsystem)
- }
|