Skip to content
Closed
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 9 additions & 10 deletions libcontainer/cgroups/fs/fs.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,26 +179,25 @@ func (m *manager) Apply(pid int) (err error) {

var c = m.cgroups

d, err := getCgroupData(m.cgroups, pid)
if err != nil {
return err
}

m.paths = make(map[string]string)
if c.Paths != nil {
for name, path := range c.Paths {
_, err := d.path(name)
cgMap, err := cgroups.ParseCgroupFile("/proc/self/cgroup")
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a check to see whether the subsystem is available. I am not quite sure in which scenario such a check is required. Maybe we need to just drop it.

if err != nil {
if cgroups.IsNotFound(err) {
continue
}
return err
}
m.paths[name] = path
if _, ok := cgMap[name]; ok {
m.paths[name] = path
}
}
return cgroups.EnterPid(m.paths, pid)
}

d, err := getCgroupData(m.cgroups, pid)
if err != nil {
return err
}

for _, sys := range m.getSubsystems() {
p, err := d.path(sys.Name())
if err != nil {
Expand Down
70 changes: 26 additions & 44 deletions libcontainer/cgroups/systemd/v1.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,16 +116,14 @@ func (m *legacyManager) Apply(pid int) error {
defer m.mu.Unlock()
if c.Paths != nil {
paths := make(map[string]string)
cgMap, err := cgroups.ParseCgroupFile("/proc/self/cgroup")
if err != nil {
return err
}
for name, path := range c.Paths {
_, err := getSubsystemPath(m.cgroups, name)
if err != nil {
// Don't fail if a cgroup hierarchy was not found, just skip this subsystem
if cgroups.IsNotFound(err) {
continue
}
return err
if _, ok := cgMap[name]; ok {
paths[name] = path
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as above

}
paths[name] = path
}
m.paths = paths
return cgroups.EnterPid(m.paths, pid)
Expand Down Expand Up @@ -190,14 +188,16 @@ func (m *legacyManager) Apply(pid int) error {
return err
}

if err := joinCgroups(c, pid); err != nil {
return err
}

paths := make(map[string]string)
for _, s := range legacySubsystems {
subsystemPath, err := getSubsystemPath(m.cgroups, s.Name())
if err != nil {
// Even if it's `not found` error, we'll return err
// because devices cgroup is hard requirement for
// container security.
if s.Name() == "devices" {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this was moved here from joinCgroups() (see below) so we bail out as early as in the old code in case of error.

return err
}
// Don't fail if a cgroup hierarchy was not found, just skip this subsystem
if cgroups.IsNotFound(err) {
continue
Expand All @@ -207,6 +207,11 @@ func (m *legacyManager) Apply(pid int) error {
paths[s.Name()] = subsystemPath
}
m.paths = paths

if err := m.joinCgroups(pid); err != nil {
return err
}

return nil
}

Expand Down Expand Up @@ -235,48 +240,25 @@ func (m *legacyManager) Path(subsys string) string {
return m.paths[subsys]
}

func join(c *configs.Cgroup, subsystem string, pid int) (string, error) {
Copy link
Contributor Author

@kolyshkin kolyshkin Jun 17, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the body of this function (modulo getSubsystemPath) was inlined into joinCgroups (see below)

path, err := getSubsystemPath(c, subsystem)
if err != nil {
return "", err
}

if err := os.MkdirAll(path, 0755); err != nil {
return "", err
}
if err := cgroups.WriteCgroupProc(path, pid); err != nil {
return "", err
}
return path, nil
}

func joinCgroups(c *configs.Cgroup, pid int) error {
func (m *legacyManager) joinCgroups(pid int) error {
for _, sys := range legacySubsystems {
name := sys.Name()
switch name {
case "name=systemd":
// let systemd handle this
case "cpuset":
path, err := getSubsystemPath(c, name)
if err != nil && !cgroups.IsNotFound(err) {
return err
}
s := &fs.CpusetGroup{}
if err := s.ApplyDir(path, c, pid); err != nil {
return err
if path, ok := m.paths[name]; ok {
s := &fs.CpusetGroup{}
if err := s.ApplyDir(path, m.cgroups, pid); err != nil {
return err
}
}
default:
_, err := join(c, name, pid)
if err != nil {
// Even if it's `not found` error, we'll return err
// because devices cgroup is hard requirement for
// container security.
if name == "devices" {
if path, ok := m.paths[name]; ok {
if err := os.MkdirAll(path, 0755); err != nil {
return err
}
// For other subsystems, omit the `not found` error
// because they are optional.
if !cgroups.IsNotFound(err) {
if err := cgroups.WriteCgroupProc(path, pid); err != nil {
return err
}
}
Expand Down
58 changes: 55 additions & 3 deletions libcontainer/cgroups/v1_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ import (
"os"
"path/filepath"
"strings"
"syscall"

securejoin "github.com/cyphar/filepath-securejoin"
"golang.org/x/sys/unix"
)

// Code in this source file are specific to cgroup v1,
Expand All @@ -19,6 +23,8 @@ const (

var (
errUnified = errors.New("not implemented for cgroup v2 unified hierarchy")

defaultPrefix = "/sys/fs/cgroup"
)

type NotFoundError struct {
Expand All @@ -43,11 +49,59 @@ 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
}

// 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
}
Expand All @@ -57,9 +111,7 @@ 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.
// Avoid parsing mountinfo by checking if subsystem is valid/available
if !isSubsystemAvailable(subsystem) {
return "", "", NewNotFoundError(subsystem)
}
Expand Down