aboutsummaryrefslogtreecommitdiff
path: root/vendor/github.com/opencontainers/runc/libcontainer/cgroups
diff options
context:
space:
mode:
authorGiuseppe Scrivano <gscrivan@redhat.com>2021-03-17 14:43:10 +0100
committerGiuseppe Scrivano <gscrivan@redhat.com>2021-03-18 20:27:25 +0100
commitec1651fbf11c4d3d1c792e7f46139ebd96f7ffb2 (patch)
tree61606abbce5e8efbf0c1f0c0ff6fe1c785c8a203 /vendor/github.com/opencontainers/runc/libcontainer/cgroups
parent77b3a2df645f2548f7bd2da85bbdb17e4de98310 (diff)
downloadpodman-ec1651fbf11c4d3d1c792e7f46139ebd96f7ffb2.tar.gz
podman-ec1651fbf11c4d3d1c792e7f46139ebd96f7ffb2.tar.bz2
podman-ec1651fbf11c4d3d1c792e7f46139ebd96f7ffb2.zip
Bump github.com/containers/storage from 1.25.0 to 1.28.0
Bumps [github.com/containers/storage](https://github.com/containers/storage) from 1.25.0 to 1.28.0. - [Release notes](https://github.com/containers/storage/releases) - [Changelog](https://github.com/containers/storage/blob/master/docs/containers-storage-changes.md) - [Commits](https://github.com/containers/storage/compare/v1.25.0...v1.28.0) Signed-off-by: dependabot-preview[bot] <support@dependabot.com> Signed-off-by: Giuseppe Scrivano <gscrivan@redhat.com>
Diffstat (limited to 'vendor/github.com/opencontainers/runc/libcontainer/cgroups')
-rw-r--r--vendor/github.com/opencontainers/runc/libcontainer/cgroups/fscommon/fscommon.go51
-rw-r--r--vendor/github.com/opencontainers/runc/libcontainer/cgroups/fscommon/open.go103
-rw-r--r--vendor/github.com/opencontainers/runc/libcontainer/cgroups/fscommon/utils.go102
-rw-r--r--vendor/github.com/opencontainers/runc/libcontainer/cgroups/stats.go28
-rw-r--r--vendor/github.com/opencontainers/runc/libcontainer/cgroups/utils.go135
-rw-r--r--vendor/github.com/opencontainers/runc/libcontainer/cgroups/v1_utils.go155
6 files changed, 482 insertions, 92 deletions
diff --git a/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fscommon/fscommon.go b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fscommon/fscommon.go
new file mode 100644
index 000000000..ae2613cdb
--- /dev/null
+++ b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fscommon/fscommon.go
@@ -0,0 +1,51 @@
+// +build linux
+
+package fscommon
+
+import (
+ "bytes"
+ "os"
+
+ "github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
+ "golang.org/x/sys/unix"
+)
+
+// WriteFile writes data to a cgroup file in dir.
+// It is supposed to be used for cgroup files only.
+func WriteFile(dir, file, data string) error {
+ fd, err := OpenFile(dir, file, unix.O_WRONLY)
+ if err != nil {
+ return err
+ }
+ defer fd.Close()
+ if err := retryingWriteFile(fd, data); err != nil {
+ return errors.Wrapf(err, "failed to write %q", data)
+ }
+ return nil
+}
+
+// ReadFile reads data from a cgroup file in dir.
+// It is supposed to be used for cgroup files only.
+func ReadFile(dir, file string) (string, error) {
+ fd, err := OpenFile(dir, file, unix.O_RDONLY)
+ if err != nil {
+ return "", err
+ }
+ defer fd.Close()
+ var buf bytes.Buffer
+
+ _, err = buf.ReadFrom(fd)
+ return buf.String(), err
+}
+
+func retryingWriteFile(fd *os.File, data string) error {
+ for {
+ _, err := fd.Write([]byte(data))
+ if errors.Is(err, unix.EINTR) {
+ logrus.Infof("interrupted while writing %s to %s", data, fd.Name())
+ continue
+ }
+ return err
+ }
+}
diff --git a/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fscommon/open.go b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fscommon/open.go
new file mode 100644
index 000000000..0a7e3d952
--- /dev/null
+++ b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fscommon/open.go
@@ -0,0 +1,103 @@
+package fscommon
+
+import (
+ "os"
+ "strings"
+ "sync"
+
+ securejoin "github.com/cyphar/filepath-securejoin"
+ "github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
+ "golang.org/x/sys/unix"
+)
+
+const (
+ cgroupfsDir = "/sys/fs/cgroup"
+ cgroupfsPrefix = cgroupfsDir + "/"
+)
+
+var (
+ // Set to true by fs unit tests
+ TestMode bool
+
+ cgroupFd int = -1
+ prepOnce sync.Once
+ prepErr error
+ resolveFlags uint64
+)
+
+func prepareOpenat2() error {
+ prepOnce.Do(func() {
+ fd, err := unix.Openat2(-1, cgroupfsDir, &unix.OpenHow{
+ Flags: unix.O_DIRECTORY | unix.O_PATH})
+ if err != nil {
+ prepErr = &os.PathError{Op: "openat2", Path: cgroupfsDir, Err: err}
+ if err != unix.ENOSYS {
+ logrus.Warnf("falling back to securejoin: %s", prepErr)
+ } else {
+ logrus.Debug("openat2 not available, falling back to securejoin")
+ }
+ return
+ }
+ var st unix.Statfs_t
+ if err = unix.Fstatfs(fd, &st); err != nil {
+ prepErr = &os.PathError{Op: "statfs", Path: cgroupfsDir, Err: err}
+ logrus.Warnf("falling back to securejoin: %s", prepErr)
+ return
+ }
+
+ cgroupFd = fd
+
+ resolveFlags = unix.RESOLVE_BENEATH | unix.RESOLVE_NO_MAGICLINKS
+ if st.Type == unix.CGROUP2_SUPER_MAGIC {
+ // cgroupv2 has a single mountpoint and no "cpu,cpuacct" symlinks
+ resolveFlags |= unix.RESOLVE_NO_XDEV | unix.RESOLVE_NO_SYMLINKS
+ }
+
+ })
+
+ return prepErr
+}
+
+// OpenFile opens a cgroup file in a given dir with given flags.
+// It is supposed to be used for cgroup files only.
+func OpenFile(dir, file string, flags int) (*os.File, error) {
+ if dir == "" {
+ return nil, errors.Errorf("no directory specified for %s", file)
+ }
+ mode := os.FileMode(0)
+ if TestMode && flags&os.O_WRONLY != 0 {
+ // "emulate" cgroup fs for unit tests
+ flags |= os.O_TRUNC | os.O_CREATE
+ mode = 0o600
+ }
+ reldir := strings.TrimPrefix(dir, cgroupfsPrefix)
+ if len(reldir) == len(dir) { // non-standard path, old system?
+ return openWithSecureJoin(dir, file, flags, mode)
+ }
+ if prepareOpenat2() != nil {
+ return openWithSecureJoin(dir, file, flags, mode)
+ }
+
+ relname := reldir + "/" + file
+ fd, err := unix.Openat2(cgroupFd, relname,
+ &unix.OpenHow{
+ Resolve: resolveFlags,
+ Flags: uint64(flags) | unix.O_CLOEXEC,
+ Mode: uint64(mode),
+ })
+ if err != nil {
+ return nil, &os.PathError{Op: "openat2", Path: dir + "/" + file, Err: err}
+ }
+
+ return os.NewFile(uintptr(fd), cgroupfsPrefix+relname), nil
+}
+
+func openWithSecureJoin(dir, file string, flags int, mode os.FileMode) (*os.File, error) {
+ path, err := securejoin.SecureJoin(dir, file)
+ if err != nil {
+ return nil, err
+ }
+
+ return os.OpenFile(path, flags, mode)
+}
diff --git a/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fscommon/utils.go b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fscommon/utils.go
new file mode 100644
index 000000000..2e4e837f2
--- /dev/null
+++ b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fscommon/utils.go
@@ -0,0 +1,102 @@
+// +build linux
+
+package fscommon
+
+import (
+ "errors"
+ "fmt"
+ "math"
+ "strconv"
+ "strings"
+)
+
+var (
+ ErrNotValidFormat = errors.New("line is not a valid key value format")
+)
+
+// ParseUint converts a string to an uint64 integer.
+// Negative values are returned at zero as, due to kernel bugs,
+// some of the memory cgroup stats can be negative.
+func ParseUint(s string, base, bitSize int) (uint64, error) {
+ value, err := strconv.ParseUint(s, base, bitSize)
+ if err != nil {
+ intValue, intErr := strconv.ParseInt(s, base, bitSize)
+ // 1. Handle negative values greater than MinInt64 (and)
+ // 2. Handle negative values lesser than MinInt64
+ if intErr == nil && intValue < 0 {
+ return 0, nil
+ } else if intErr != nil && intErr.(*strconv.NumError).Err == strconv.ErrRange && intValue < 0 {
+ return 0, nil
+ }
+
+ return value, err
+ }
+
+ return value, nil
+}
+
+// GetCgroupParamKeyValue parses a space-separated "name value" kind of cgroup
+// parameter and returns its components. For example, "io_service_bytes 1234"
+// will return as "io_service_bytes", 1234.
+func GetCgroupParamKeyValue(t string) (string, uint64, error) {
+ parts := strings.Fields(t)
+ switch len(parts) {
+ case 2:
+ value, err := ParseUint(parts[1], 10, 64)
+ if err != nil {
+ return "", 0, fmt.Errorf("unable to convert to uint64: %v", err)
+ }
+
+ return parts[0], value, nil
+ default:
+ return "", 0, ErrNotValidFormat
+ }
+}
+
+// GetCgroupParamUint reads a single uint64 value from the specified cgroup file.
+// If the value read is "max", the math.MaxUint64 is returned.
+func GetCgroupParamUint(path, file string) (uint64, error) {
+ contents, err := GetCgroupParamString(path, file)
+ if err != nil {
+ return 0, err
+ }
+ contents = strings.TrimSpace(contents)
+ if contents == "max" {
+ return math.MaxUint64, nil
+ }
+
+ res, err := ParseUint(contents, 10, 64)
+ if err != nil {
+ return res, fmt.Errorf("unable to parse file %q", path+"/"+file)
+ }
+ return res, nil
+}
+
+// GetCgroupParamInt reads a single int64 value from specified cgroup file.
+// If the value read is "max", the math.MaxInt64 is returned.
+func GetCgroupParamInt(path, file string) (int64, error) {
+ contents, err := ReadFile(path, file)
+ if err != nil {
+ return 0, err
+ }
+ contents = strings.TrimSpace(contents)
+ if contents == "max" {
+ return math.MaxInt64, nil
+ }
+
+ res, err := strconv.ParseInt(contents, 10, 64)
+ if err != nil {
+ return res, fmt.Errorf("unable to parse %q as a int from Cgroup file %q", contents, path+"/"+file)
+ }
+ return res, nil
+}
+
+// GetCgroupParamString reads a string from the specified cgroup file.
+func GetCgroupParamString(path, file string) (string, error) {
+ contents, err := ReadFile(path, file)
+ if err != nil {
+ return "", err
+ }
+
+ return strings.TrimSpace(contents), nil
+}
diff --git a/vendor/github.com/opencontainers/runc/libcontainer/cgroups/stats.go b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/stats.go
index 7ac816605..e7f9c4626 100644
--- a/vendor/github.com/opencontainers/runc/libcontainer/cgroups/stats.go
+++ b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/stats.go
@@ -39,6 +39,33 @@ type CpuStats struct {
ThrottlingData ThrottlingData `json:"throttling_data,omitempty"`
}
+type CPUSetStats struct {
+ // List of the physical numbers of the CPUs on which processes
+ // in that cpuset are allowed to execute
+ CPUs []uint16 `json:"cpus,omitempty"`
+ // cpu_exclusive flag
+ CPUExclusive uint64 `json:"cpu_exclusive"`
+ // List of memory nodes on which processes in that cpuset
+ // are allowed to allocate memory
+ Mems []uint16 `json:"mems,omitempty"`
+ // mem_hardwall flag
+ MemHardwall uint64 `json:"mem_hardwall"`
+ // mem_exclusive flag
+ MemExclusive uint64 `json:"mem_exclusive"`
+ // memory_migrate flag
+ MemoryMigrate uint64 `json:"memory_migrate"`
+ // memory_spread page flag
+ MemorySpreadPage uint64 `json:"memory_spread_page"`
+ // memory_spread slab flag
+ MemorySpreadSlab uint64 `json:"memory_spread_slab"`
+ // memory_pressure
+ MemoryPressure uint64 `json:"memory_pressure"`
+ // sched_load balance flag
+ SchedLoadBalance uint64 `json:"sched_load_balance"`
+ // sched_relax_domain_level
+ SchedRelaxDomainLevel int64 `json:"sched_relax_domain_level"`
+}
+
type MemoryData struct {
Usage uint64 `json:"usage,omitempty"`
MaxUsage uint64 `json:"max_usage,omitempty"`
@@ -121,6 +148,7 @@ type HugetlbStats struct {
type Stats struct {
CpuStats CpuStats `json:"cpu_stats,omitempty"`
+ CPUSetStats CPUSetStats `json:"cpuset_stats,omitempty"`
MemoryStats MemoryStats `json:"memory_stats,omitempty"`
PidsStats PidsStats `json:"pids_stats,omitempty"`
BlkioStats BlkioStats `json:"blkio_stats,omitempty"`
diff --git a/vendor/github.com/opencontainers/runc/libcontainer/cgroups/utils.go b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/utils.go
index 6e88b5dff..840817e39 100644
--- a/vendor/github.com/opencontainers/runc/libcontainer/cgroups/utils.go
+++ b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/utils.go
@@ -15,7 +15,9 @@ import (
"sync"
"time"
- units "github.com/docker/go-units"
+ "github.com/opencontainers/runc/libcontainer/cgroups/fscommon"
+ "github.com/opencontainers/runc/libcontainer/system"
+ "github.com/sirupsen/logrus"
"golang.org/x/sys/unix"
)
@@ -29,19 +31,19 @@ var (
isUnified bool
)
-// HugePageSizeUnitList is a list of the units used by the linux kernel when
-// naming the HugePage control files.
-// https://www.kernel.org/doc/Documentation/cgroup-v1/hugetlb.txt
-// TODO Since the kernel only use KB, MB and GB; TB and PB should be removed,
-// depends on https://github.com/docker/go-units/commit/a09cd47f892041a4fac473133d181f5aea6fa393
-var HugePageSizeUnitList = []string{"B", "KB", "MB", "GB", "TB", "PB"}
-
// IsCgroup2UnifiedMode returns whether we are running in cgroup v2 unified mode.
func IsCgroup2UnifiedMode() bool {
isUnifiedOnce.Do(func() {
var st unix.Statfs_t
- if err := unix.Statfs(unifiedMountpoint, &st); err != nil {
- panic("cannot statfs cgroup root")
+ err := unix.Statfs(unifiedMountpoint, &st)
+ if err != nil {
+ if os.IsNotExist(err) && system.RunningInUserNS() {
+ // ignore the "not found" error if running in userns
+ logrus.WithError(err).Debugf("%s missing, assuming cgroup v1", unifiedMountpoint)
+ isUnified = false
+ return
+ }
+ panic(fmt.Sprintf("cannot statfs cgroup root: %s", err))
}
isUnified = st.Type == unix.CGROUP2_SUPER_MAGIC
})
@@ -86,11 +88,11 @@ func GetAllSubsystems() ([]string, error) {
// - freezer: implemented in kernel 5.2
// We assume these are always available, as it is hard to detect availability.
pseudo := []string{"devices", "freezer"}
- data, err := ioutil.ReadFile("/sys/fs/cgroup/cgroup.controllers")
+ data, err := fscommon.ReadFile("/sys/fs/cgroup", "cgroup.controllers")
if err != nil {
return nil, err
}
- subsystems := append(pseudo, strings.Fields(string(data))...)
+ subsystems := append(pseudo, strings.Fields(data)...)
return subsystems, nil
}
f, err := os.Open("/proc/cgroups")
@@ -207,20 +209,66 @@ func EnterPid(cgroupPaths map[string]string, pid int) error {
return nil
}
+func rmdir(path string) error {
+ err := unix.Rmdir(path)
+ if err == nil || err == unix.ENOENT {
+ return nil
+ }
+ return &os.PathError{Op: "rmdir", Path: path, Err: err}
+}
+
+// RemovePath aims to remove cgroup path. It does so recursively,
+// by removing any subdirectories (sub-cgroups) first.
+func RemovePath(path string) error {
+ // try the fast path first
+ if err := rmdir(path); err == nil {
+ return nil
+ }
+
+ infos, err := ioutil.ReadDir(path)
+ if err != nil {
+ if os.IsNotExist(err) {
+ err = nil
+ }
+ return err
+ }
+ for _, info := range infos {
+ if info.IsDir() {
+ // We should remove subcgroups dir first
+ if err = RemovePath(filepath.Join(path, info.Name())); err != nil {
+ break
+ }
+ }
+ }
+ if err == nil {
+ err = rmdir(path)
+ }
+ return err
+}
+
// RemovePaths iterates over the provided paths removing them.
// We trying to remove all paths five times with increasing delay between tries.
// If after all there are not removed cgroups - appropriate error will be
// returned.
func RemovePaths(paths map[string]string) (err error) {
+ const retries = 5
delay := 10 * time.Millisecond
- for i := 0; i < 5; i++ {
+ for i := 0; i < retries; i++ {
if i != 0 {
time.Sleep(delay)
delay *= 2
}
for s, p := range paths {
- os.RemoveAll(p)
- // TODO: here probably should be logging
+ if err := RemovePath(p); err != nil {
+ // do not log intermediate iterations
+ switch i {
+ case 0:
+ logrus.WithError(err).Warnf("Failed to remove cgroup (will retry)")
+ case retries - 1:
+ logrus.WithError(err).Error("Failed to remove cgroup")
+ }
+
+ }
_, err := os.Stat(p)
// We need this strange way of checking cgroups existence because
// RemoveAll almost always returns error, even on already removed
@@ -230,6 +278,8 @@ func RemovePaths(paths map[string]string) (err error) {
}
}
if len(paths) == 0 {
+ //nolint:ineffassign,staticcheck // done to help garbage collecting: opencontainers/runc#2506
+ paths = make(map[string]string)
return nil
}
}
@@ -237,27 +287,50 @@ func RemovePaths(paths map[string]string) (err error) {
}
func GetHugePageSize() ([]string, error) {
- files, err := ioutil.ReadDir("/sys/kernel/mm/hugepages")
+ dir, err := os.OpenFile("/sys/kernel/mm/hugepages", unix.O_DIRECTORY|unix.O_RDONLY, 0)
if err != nil {
- return []string{}, err
+ return nil, err
}
- var fileNames []string
- for _, st := range files {
- fileNames = append(fileNames, st.Name())
+ files, err := dir.Readdirnames(0)
+ dir.Close()
+ if err != nil {
+ return nil, err
}
- return getHugePageSizeFromFilenames(fileNames)
+
+ return getHugePageSizeFromFilenames(files)
}
func getHugePageSizeFromFilenames(fileNames []string) ([]string, error) {
- var pageSizes []string
- for _, fileName := range fileNames {
- nameArray := strings.Split(fileName, "-")
- pageSize, err := units.RAMInBytes(nameArray[1])
+ pageSizes := make([]string, 0, len(fileNames))
+
+ for _, file := range fileNames {
+ // example: hugepages-1048576kB
+ val := strings.TrimPrefix(file, "hugepages-")
+ if len(val) == len(file) {
+ // unexpected file name: no prefix found
+ continue
+ }
+ // The suffix is always "kB" (as of Linux 5.9)
+ eLen := len(val) - 2
+ val = strings.TrimSuffix(val, "kB")
+ if len(val) != eLen {
+ logrus.Warnf("GetHugePageSize: %s: invalid filename suffix (expected \"kB\")", file)
+ continue
+ }
+ size, err := strconv.Atoi(val)
if err != nil {
- return []string{}, err
+ return nil, err
+ }
+ // Model after https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/mm/hugetlb_cgroup.c?id=eff48ddeab782e35e58ccc8853f7386bbae9dec4#n574
+ // but in our case the size is in KB already.
+ if size >= (1 << 20) {
+ val = strconv.Itoa(size>>20) + "GB"
+ } else if size >= (1 << 10) {
+ val = strconv.Itoa(size>>10) + "MB"
+ } else {
+ val += "KB"
}
- sizeString := units.CustomSize("%g%s", float64(pageSize), 1024.0, HugePageSizeUnitList)
- pageSizes = append(pageSizes, sizeString)
+ pageSizes = append(pageSizes, val)
}
return pageSizes, nil
@@ -303,14 +376,14 @@ func WriteCgroupProc(dir string, pid int) error {
return nil
}
- cgroupProcessesFile, err := os.OpenFile(filepath.Join(dir, CgroupProcesses), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0700)
+ file, err := fscommon.OpenFile(dir, CgroupProcesses, os.O_WRONLY)
if err != nil {
return fmt.Errorf("failed to write %v to %v: %v", pid, CgroupProcesses, err)
}
- defer cgroupProcessesFile.Close()
+ defer file.Close()
for i := 0; i < 5; i++ {
- _, err = cgroupProcessesFile.WriteString(strconv.Itoa(pid))
+ _, err = file.WriteString(strconv.Itoa(pid))
if err == nil {
return nil
}
diff --git a/vendor/github.com/opencontainers/runc/libcontainer/cgroups/v1_utils.go b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/v1_utils.go
index f8487b0a9..95ec9dff0 100644
--- a/vendor/github.com/opencontainers/runc/libcontainer/cgroups/v1_utils.go
+++ b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/v1_utils.go
@@ -1,13 +1,17 @@
package cgroups
import (
- "bufio"
"errors"
"fmt"
- "io"
"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,
@@ -15,10 +19,16 @@ import (
const (
CgroupNamePrefix = "name="
+ defaultPrefix = "/sys/fs/cgroup"
)
var (
- errUnified = errors.New("not implemented for cgroup v2 unified hierarchy")
+ 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 {
@@ -43,11 +53,74 @@ func IsNotFound(err error) bool {
return ok
}
+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
}
+
+ // 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
}
@@ -57,58 +130,28 @@ func FindCgroupMountpointAndRoot(cgroupPath, subsystem string) (string, string,
return "", "", errUnified
}
- // We are not using mount.GetMounts() because it's super-inefficient,
- // parsing it directly sped up x10 times because of not using Sscanf.
- // It was one of two major performance drawbacks in container start.
- if !isSubsystemAvailable(subsystem) {
- return "", "", NewNotFoundError(subsystem)
- }
-
- f, err := os.Open("/proc/self/mountinfo")
+ mi, err := readCgroupMountinfo()
if err != nil {
return "", "", err
}
- defer f.Close()
- return findCgroupMountpointAndRootFromReader(f, cgroupPath, subsystem)
+ return findCgroupMountpointAndRootFromMI(mi, cgroupPath, subsystem)
}
-func findCgroupMountpointAndRootFromReader(reader io.Reader, cgroupPath, subsystem string) (string, string, error) {
- scanner := bufio.NewScanner(reader)
- for scanner.Scan() {
- txt := scanner.Text()
- fields := strings.Fields(txt)
- if len(fields) < 9 {
- continue
- }
- if strings.HasPrefix(fields[4], cgroupPath) {
- for _, opt := range strings.Split(fields[len(fields)-1], ",") {
+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 fields[4], fields[3], nil
+ return mi.Mountpoint, mi.Root, nil
}
}
}
}
- if err := scanner.Err(); err != nil {
- return "", "", err
- }
return "", "", NewNotFoundError(subsystem)
}
-func isSubsystemAvailable(subsystem string) bool {
- if IsCgroup2UnifiedMode() {
- panic("don't call isSubsystemAvailable from cgroupv2 code")
- }
-
- cgroups, err := ParseCgroupFile("/proc/self/cgroup")
- if err != nil {
- return false
- }
- _, avail := cgroups[subsystem]
- return avail
-}
-
func (m Mount) GetOwnCgroup(cgroups map[string]string) (string, error) {
if len(m.Subsystems) == 0 {
return "", fmt.Errorf("no subsystem for mount")
@@ -117,25 +160,15 @@ func (m Mount) GetOwnCgroup(cgroups map[string]string) (string, error) {
return getControllerPath(m.Subsystems[0], cgroups)
}
-func getCgroupMountsHelper(ss map[string]bool, mi io.Reader, all bool) ([]Mount, error) {
+func getCgroupMountsHelper(ss map[string]bool, mounts []*mountinfo.Info, all bool) ([]Mount, error) {
res := make([]Mount, 0, len(ss))
- scanner := bufio.NewScanner(mi)
numFound := 0
- for scanner.Scan() && numFound < len(ss) {
- txt := scanner.Text()
- sepIdx := strings.Index(txt, " - ")
- if sepIdx == -1 {
- return nil, fmt.Errorf("invalid mountinfo format")
- }
- if txt[sepIdx+3:sepIdx+10] == "cgroup2" || txt[sepIdx+3:sepIdx+9] != "cgroup" {
- continue
- }
- fields := strings.Split(txt, " ")
+ for _, mi := range mounts {
m := Mount{
- Mountpoint: fields[4],
- Root: fields[3],
+ Mountpoint: mi.Mountpoint,
+ Root: mi.Root,
}
- for _, opt := range strings.Split(fields[len(fields)-1], ",") {
+ for _, opt := range strings.Split(mi.VFSOptions, ",") {
seen, known := ss[opt]
if !known || (!all && seen) {
continue
@@ -148,19 +181,18 @@ func getCgroupMountsHelper(ss map[string]bool, mi io.Reader, all bool) ([]Mount,
if len(m.Subsystems) > 0 || all {
res = append(res, m)
}
- }
- if err := scanner.Err(); err != nil {
- return nil, err
+ if !all && numFound >= len(ss) {
+ break
+ }
}
return res, nil
}
func getCgroupMountsV1(all bool) ([]Mount, error) {
- f, err := os.Open("/proc/self/mountinfo")
+ mi, err := readCgroupMountinfo()
if err != nil {
return nil, err
}
- defer f.Close()
allSubsystems, err := ParseCgroupFile("/proc/self/cgroup")
if err != nil {
@@ -171,7 +203,8 @@ func getCgroupMountsV1(all bool) ([]Mount, error) {
for s := range allSubsystems {
allMap[s] = false
}
- return getCgroupMountsHelper(allMap, f, all)
+
+ return getCgroupMountsHelper(allMap, mi, all)
}
// GetOwnCgroup returns the relative path to the cgroup docker is running in.