From 394610a3967f37aeae12a9b526debb4754c1052b Mon Sep 17 00:00:00 2001 From: Euan Kemp Date: Fri, 10 Jun 2016 12:08:57 -0700 Subject: [PATCH] cgroups: Parse correctly if cgroup path contains : Prior to this change a cgroup with a `:` character in it's path was not parsed correctly (as occurs on some instances of systemd cgroups under some versions of systemd, e.g. 225 with accounting). This fixes that issue and adds a test. Signed-off-by: Euan Kemp --- libcontainer/cgroups/utils.go | 20 +++++++++-- libcontainer/cgroups/utils_test.go | 55 ++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+), 2 deletions(-) diff --git a/libcontainer/cgroups/utils.go b/libcontainer/cgroups/utils.go index 491faf28..1a7c4e1a 100644 --- a/libcontainer/cgroups/utils.go +++ b/libcontainer/cgroups/utils.go @@ -262,6 +262,8 @@ func readProcsFile(dir string) ([]int, error) { return out, nil } +// ParseCgroupFile parses the given cgroup file, typically from +// /proc//cgroup, into a map of subgroups to cgroup names. func ParseCgroupFile(path string) (map[string]string, error) { f, err := os.Open(path) if err != nil { @@ -269,7 +271,12 @@ func ParseCgroupFile(path string) (map[string]string, error) { } defer f.Close() - s := bufio.NewScanner(f) + return parseCgroupFromReader(f) +} + +// helper function for ParseCgroupFile to make testing easier +func parseCgroupFromReader(r io.Reader) (map[string]string, error) { + s := bufio.NewScanner(r) cgroups := make(map[string]string) for s.Scan() { @@ -278,7 +285,16 @@ func ParseCgroupFile(path string) (map[string]string, error) { } text := s.Text() - parts := strings.Split(text, ":") + // from cgroups(7): + // /proc/[pid]/cgroup + // ... + // For each cgroup hierarchy ... there is one entry + // containing three colon-separated fields of the form: + // hierarchy-ID:subsystem-list:cgroup-path + parts := strings.SplitN(text, ":", 3) + if len(parts) < 3 { + return nil, fmt.Errorf("invalid cgroup entry: must contain at least two colons: %v", text) + } for _, subs := range strings.Split(parts[1], ",") { cgroups[subs] = parts[2] diff --git a/libcontainer/cgroups/utils_test.go b/libcontainer/cgroups/utils_test.go index 47bbd5e2..66414079 100644 --- a/libcontainer/cgroups/utils_test.go +++ b/libcontainer/cgroups/utils_test.go @@ -4,6 +4,8 @@ package cgroups import ( "bytes" + "fmt" + "reflect" "strings" "testing" ) @@ -190,3 +192,56 @@ func BenchmarkGetCgroupMounts(b *testing.B) { } } } + +func TestParseCgroupString(t *testing.T) { + testCases := []struct { + input string + expectedError error + expectedOutput map[string]string + }{ + { + // Taken from a CoreOS instance running systemd 225 with CPU/Mem + // accounting enabled in systemd + input: `9:blkio:/ +8:freezer:/ +7:perf_event:/ +6:devices:/system.slice/system-sshd.slice +5:cpuset:/ +4:cpu,cpuacct:/system.slice/system-sshd.slice/sshd@126-10.240.0.15:22-xxx.yyy.zzz.aaa:33678.service +3:net_cls,net_prio:/ +2:memory:/system.slice/system-sshd.slice/sshd@126-10.240.0.15:22-xxx.yyy.zzz.aaa:33678.service +1:name=systemd:/system.slice/system-sshd.slice/sshd@126-10.240.0.15:22-xxx.yyy.zzz.aaa:33678.service`, + expectedOutput: map[string]string{ + "name=systemd": "/system.slice/system-sshd.slice/sshd@126-10.240.0.15:22-xxx.yyy.zzz.aaa:33678.service", + "blkio": "/", + "freezer": "/", + "perf_event": "/", + "devices": "/system.slice/system-sshd.slice", + "cpuset": "/", + "cpu": "/system.slice/system-sshd.slice/sshd@126-10.240.0.15:22-xxx.yyy.zzz.aaa:33678.service", + "cpuacct": "/system.slice/system-sshd.slice/sshd@126-10.240.0.15:22-xxx.yyy.zzz.aaa:33678.service", + "net_cls": "/", + "net_prio": "/", + "memory": "/system.slice/system-sshd.slice/sshd@126-10.240.0.15:22-xxx.yyy.zzz.aaa:33678.service", + }, + }, + { + input: `malformed input`, + expectedError: fmt.Errorf(`invalid cgroup entry: must contain at least two colons: malformed input`), + }, + } + + for ndx, testCase := range testCases { + out, err := parseCgroupFromReader(strings.NewReader(testCase.input)) + if err != nil { + if testCase.expectedError == nil || testCase.expectedError.Error() != err.Error() { + t.Errorf("%v: expected error %v, got error %v", ndx, testCase.expectedError, err) + } + } else { + if !reflect.DeepEqual(testCase.expectedOutput, out) { + t.Errorf("%v: expected output %v, got error %v", ndx, testCase.expectedOutput, out) + } + } + } + +}