2020-05-04 20:46:48 +00:00
|
|
|
// +build linux
|
|
|
|
|
|
|
|
package fs2
|
|
|
|
|
|
|
|
import (
|
|
|
|
"io/ioutil"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
"github.com/opencontainers/runc/libcontainer/cgroups"
|
|
|
|
"github.com/opencontainers/runc/libcontainer/configs"
|
|
|
|
"github.com/pkg/errors"
|
2020-08-10 17:43:49 +00:00
|
|
|
"golang.org/x/sys/unix"
|
2020-05-04 20:46:48 +00:00
|
|
|
)
|
|
|
|
|
2020-08-10 17:43:49 +00:00
|
|
|
type manager struct {
|
|
|
|
config *configs.Cgroup
|
|
|
|
// dirPath is like "/sys/fs/cgroup/user.slice/user-1001.slice/session-1.scope"
|
|
|
|
dirPath string
|
|
|
|
// controllers is content of "cgroup.controllers" file.
|
|
|
|
// excludes pseudo-controllers ("devices" and "freezer").
|
|
|
|
controllers map[string]struct{}
|
|
|
|
rootless bool
|
|
|
|
}
|
|
|
|
|
2020-05-04 20:46:48 +00:00
|
|
|
// NewManager creates a manager for cgroup v2 unified hierarchy.
|
|
|
|
// dirPath is like "/sys/fs/cgroup/user.slice/user-1001.slice/session-1.scope".
|
|
|
|
// If dirPath is empty, it is automatically set using config.
|
|
|
|
func NewManager(config *configs.Cgroup, dirPath string, rootless bool) (cgroups.Manager, error) {
|
|
|
|
if config == nil {
|
|
|
|
config = &configs.Cgroup{}
|
|
|
|
}
|
2020-08-10 17:43:49 +00:00
|
|
|
if dirPath == "" {
|
2020-05-04 20:46:48 +00:00
|
|
|
var err error
|
|
|
|
dirPath, err = defaultDirPath(config)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
m := &manager{
|
2020-08-10 17:43:49 +00:00
|
|
|
config: config,
|
|
|
|
dirPath: dirPath,
|
|
|
|
rootless: rootless,
|
2020-05-04 20:46:48 +00:00
|
|
|
}
|
|
|
|
return m, nil
|
|
|
|
}
|
|
|
|
|
2020-08-10 17:43:49 +00:00
|
|
|
func (m *manager) getControllers() error {
|
|
|
|
if m.controllers != nil {
|
|
|
|
return nil
|
2020-05-04 20:46:48 +00:00
|
|
|
}
|
2020-08-10 17:43:49 +00:00
|
|
|
|
|
|
|
file := filepath.Join(m.dirPath, "cgroup.controllers")
|
|
|
|
data, err := ioutil.ReadFile(file)
|
2020-05-04 20:46:48 +00:00
|
|
|
if err != nil {
|
2020-08-10 17:43:49 +00:00
|
|
|
if m.rootless && m.config.Path == "" {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return err
|
2020-05-04 20:46:48 +00:00
|
|
|
}
|
2020-08-10 17:43:49 +00:00
|
|
|
fields := strings.Fields(string(data))
|
|
|
|
m.controllers = make(map[string]struct{}, len(fields))
|
|
|
|
for _, c := range fields {
|
|
|
|
m.controllers[c] = struct{}{}
|
2020-05-04 20:46:48 +00:00
|
|
|
}
|
|
|
|
|
2020-08-10 17:43:49 +00:00
|
|
|
return nil
|
2020-05-04 20:46:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (m *manager) Apply(pid int) error {
|
2020-08-10 17:43:49 +00:00
|
|
|
if err := CreateCgroupPath(m.dirPath, m.config); err != nil {
|
|
|
|
// Related tests:
|
|
|
|
// - "runc create (no limits + no cgrouppath + no permission) succeeds"
|
|
|
|
// - "runc create (rootless + no limits + cgrouppath + no permission) fails with permission error"
|
|
|
|
// - "runc create (rootless + limits + no cgrouppath + no permission) fails with informative error"
|
|
|
|
if m.rootless {
|
|
|
|
if m.config.Path == "" {
|
|
|
|
if blNeed, nErr := needAnyControllers(m.config); nErr == nil && !blNeed {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return errors.Wrap(err, "rootless needs no limits + no cgrouppath when no permission is granted for cgroups")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if err := cgroups.WriteCgroupProc(m.dirPath, pid); err != nil {
|
2020-05-04 20:46:48 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *manager) GetPids() ([]int, error) {
|
|
|
|
return cgroups.GetPids(m.dirPath)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *manager) GetAllPids() ([]int, error) {
|
|
|
|
return cgroups.GetAllPids(m.dirPath)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *manager) GetStats() (*cgroups.Stats, error) {
|
|
|
|
var (
|
|
|
|
errs []error
|
|
|
|
)
|
2020-08-10 17:43:49 +00:00
|
|
|
|
|
|
|
st := cgroups.NewStats()
|
|
|
|
if err := m.getControllers(); err != nil {
|
|
|
|
return st, err
|
|
|
|
}
|
|
|
|
|
2020-05-04 20:46:48 +00:00
|
|
|
// pids (since kernel 4.5)
|
|
|
|
if _, ok := m.controllers["pids"]; ok {
|
2020-08-10 17:43:49 +00:00
|
|
|
if err := statPids(m.dirPath, st); err != nil {
|
2020-05-04 20:46:48 +00:00
|
|
|
errs = append(errs, err)
|
|
|
|
}
|
|
|
|
} else {
|
2020-08-10 17:43:49 +00:00
|
|
|
if err := statPidsWithoutController(m.dirPath, st); err != nil {
|
2020-05-04 20:46:48 +00:00
|
|
|
errs = append(errs, err)
|
|
|
|
}
|
|
|
|
}
|
2020-08-10 17:43:49 +00:00
|
|
|
// memory (since kernel 4.5)
|
2020-05-04 20:46:48 +00:00
|
|
|
if _, ok := m.controllers["memory"]; ok {
|
2020-08-10 17:43:49 +00:00
|
|
|
if err := statMemory(m.dirPath, st); err != nil {
|
2020-05-04 20:46:48 +00:00
|
|
|
errs = append(errs, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// io (since kernel 4.5)
|
|
|
|
if _, ok := m.controllers["io"]; ok {
|
2020-08-10 17:43:49 +00:00
|
|
|
if err := statIo(m.dirPath, st); err != nil {
|
2020-05-04 20:46:48 +00:00
|
|
|
errs = append(errs, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// cpu (since kernel 4.15)
|
|
|
|
if _, ok := m.controllers["cpu"]; ok {
|
2020-08-10 17:43:49 +00:00
|
|
|
if err := statCpu(m.dirPath, st); err != nil {
|
|
|
|
errs = append(errs, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// hugetlb (since kernel 5.6)
|
|
|
|
if _, ok := m.controllers["hugetlb"]; ok {
|
|
|
|
if err := statHugeTlb(m.dirPath, st); err != nil {
|
2020-05-04 20:46:48 +00:00
|
|
|
errs = append(errs, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if len(errs) > 0 && !m.rootless {
|
2020-08-10 17:43:49 +00:00
|
|
|
return st, errors.Errorf("error while statting cgroup v2: %+v", errs)
|
2020-05-04 20:46:48 +00:00
|
|
|
}
|
2020-08-10 17:43:49 +00:00
|
|
|
return st, nil
|
2020-05-04 20:46:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (m *manager) Freeze(state configs.FreezerState) error {
|
|
|
|
if err := setFreezer(m.dirPath, state); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
m.config.Resources.Freezer = state
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-08-10 17:43:49 +00:00
|
|
|
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}
|
2020-05-04 20:46:48 +00:00
|
|
|
}
|
|
|
|
|
2020-08-10 17:43:49 +00:00
|
|
|
// removeCgroupPath aims to remove cgroup path recursively
|
|
|
|
// Because there may be subcgroups in it.
|
|
|
|
func removeCgroupPath(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
|
2020-05-04 20:46:48 +00:00
|
|
|
}
|
2020-08-10 17:43:49 +00:00
|
|
|
for _, info := range infos {
|
|
|
|
if info.IsDir() {
|
|
|
|
// We should remove subcgroups dir first
|
|
|
|
if err = removeCgroupPath(filepath.Join(path, info.Name())); err != nil {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
2020-05-04 20:46:48 +00:00
|
|
|
}
|
2020-08-10 17:43:49 +00:00
|
|
|
if err == nil {
|
|
|
|
err = rmdir(path)
|
|
|
|
}
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *manager) Destroy() error {
|
|
|
|
return removeCgroupPath(m.dirPath)
|
2020-05-04 20:46:48 +00:00
|
|
|
}
|
|
|
|
|
2020-08-10 17:43:49 +00:00
|
|
|
func (m *manager) Path(_ string) string {
|
|
|
|
return m.dirPath
|
2020-05-04 20:46:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (m *manager) Set(container *configs.Config) error {
|
|
|
|
if container == nil || container.Cgroups == nil {
|
|
|
|
return nil
|
|
|
|
}
|
2020-08-10 17:43:49 +00:00
|
|
|
if err := m.getControllers(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2020-05-04 20:46:48 +00:00
|
|
|
// pids (since kernel 4.5)
|
2020-08-10 17:43:49 +00:00
|
|
|
if err := setPids(m.dirPath, container.Cgroups); err != nil {
|
|
|
|
return err
|
2020-05-04 20:46:48 +00:00
|
|
|
}
|
|
|
|
// memory (since kernel 4.5)
|
2020-08-10 17:43:49 +00:00
|
|
|
if err := setMemory(m.dirPath, container.Cgroups); err != nil {
|
|
|
|
return err
|
2020-05-04 20:46:48 +00:00
|
|
|
}
|
|
|
|
// io (since kernel 4.5)
|
2020-08-10 17:43:49 +00:00
|
|
|
if err := setIo(m.dirPath, container.Cgroups); err != nil {
|
|
|
|
return err
|
2020-05-04 20:46:48 +00:00
|
|
|
}
|
|
|
|
// cpu (since kernel 4.15)
|
2020-08-10 17:43:49 +00:00
|
|
|
if err := setCpu(m.dirPath, container.Cgroups); err != nil {
|
|
|
|
return err
|
2020-05-04 20:46:48 +00:00
|
|
|
}
|
|
|
|
// devices (since kernel 4.15, pseudo-controller)
|
2020-08-10 17:43:49 +00:00
|
|
|
//
|
|
|
|
// When m.Rootless is true, errors from the device subsystem are ignored because it is really not expected to work.
|
|
|
|
// However, errors from other subsystems are not ignored.
|
|
|
|
// see @test "runc create (rootless + limits + no cgrouppath + no permission) fails with informative error"
|
|
|
|
if err := setDevices(m.dirPath, container.Cgroups); err != nil && !m.rootless {
|
|
|
|
return err
|
2020-05-04 20:46:48 +00:00
|
|
|
}
|
|
|
|
// cpuset (since kernel 5.0)
|
2020-08-10 17:43:49 +00:00
|
|
|
if err := setCpuset(m.dirPath, container.Cgroups); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
// hugetlb (since kernel 5.6)
|
|
|
|
if err := setHugeTlb(m.dirPath, container.Cgroups); err != nil {
|
|
|
|
return err
|
2020-05-04 20:46:48 +00:00
|
|
|
}
|
|
|
|
// freezer (since kernel 5.2, pseudo-controller)
|
|
|
|
if err := setFreezer(m.dirPath, container.Cgroups.Freezer); err != nil {
|
2020-08-10 17:43:49 +00:00
|
|
|
return err
|
2020-05-04 20:46:48 +00:00
|
|
|
}
|
|
|
|
m.config = container.Cgroups
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-08-10 17:43:49 +00:00
|
|
|
func (m *manager) GetPaths() map[string]string {
|
|
|
|
paths := make(map[string]string, 1)
|
|
|
|
paths[""] = m.dirPath
|
|
|
|
return paths
|
|
|
|
}
|
|
|
|
|
2020-05-04 20:46:48 +00:00
|
|
|
func (m *manager) GetCgroups() (*configs.Cgroup, error) {
|
|
|
|
return m.config, nil
|
|
|
|
}
|
2020-08-10 17:43:49 +00:00
|
|
|
|
|
|
|
func (m *manager) GetFreezerState() (configs.FreezerState, error) {
|
|
|
|
return getFreezer(m.dirPath)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *manager) Exists() bool {
|
|
|
|
return cgroups.PathExists(m.dirPath)
|
|
|
|
}
|