Skip to content

Commit 394610a

Browse files
committed
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 <[email protected]>
1 parent e40e71f commit 394610a

File tree

2 files changed

+73
-2
lines changed

2 files changed

+73
-2
lines changed

libcontainer/cgroups/utils.go

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -262,14 +262,21 @@ func readProcsFile(dir string) ([]int, error) {
262262
return out, nil
263263
}
264264

265+
// ParseCgroupFile parses the given cgroup file, typically from
266+
// /proc/<pid>/cgroup, into a map of subgroups to cgroup names.
265267
func ParseCgroupFile(path string) (map[string]string, error) {
266268
f, err := os.Open(path)
267269
if err != nil {
268270
return nil, err
269271
}
270272
defer f.Close()
271273

272-
s := bufio.NewScanner(f)
274+
return parseCgroupFromReader(f)
275+
}
276+
277+
// helper function for ParseCgroupFile to make testing easier
278+
func parseCgroupFromReader(r io.Reader) (map[string]string, error) {
279+
s := bufio.NewScanner(r)
273280
cgroups := make(map[string]string)
274281

275282
for s.Scan() {
@@ -278,7 +285,16 @@ func ParseCgroupFile(path string) (map[string]string, error) {
278285
}
279286

280287
text := s.Text()
281-
parts := strings.Split(text, ":")
288+
// from cgroups(7):
289+
// /proc/[pid]/cgroup
290+
// ...
291+
// For each cgroup hierarchy ... there is one entry
292+
// containing three colon-separated fields of the form:
293+
// hierarchy-ID:subsystem-list:cgroup-path
294+
parts := strings.SplitN(text, ":", 3)
295+
if len(parts) < 3 {
296+
return nil, fmt.Errorf("invalid cgroup entry: must contain at least two colons: %v", text)
297+
}
282298

283299
for _, subs := range strings.Split(parts[1], ",") {
284300
cgroups[subs] = parts[2]

libcontainer/cgroups/utils_test.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ package cgroups
44

55
import (
66
"bytes"
7+
"fmt"
8+
"reflect"
79
"strings"
810
"testing"
911
)
@@ -190,3 +192,56 @@ func BenchmarkGetCgroupMounts(b *testing.B) {
190192
}
191193
}
192194
}
195+
196+
func TestParseCgroupString(t *testing.T) {
197+
testCases := []struct {
198+
input string
199+
expectedError error
200+
expectedOutput map[string]string
201+
}{
202+
{
203+
// Taken from a CoreOS instance running systemd 225 with CPU/Mem
204+
// accounting enabled in systemd
205+
input: `9:blkio:/
206+
8:freezer:/
207+
7:perf_event:/
208+
6:devices:/system.slice/system-sshd.slice
209+
5:cpuset:/
210+
4:cpu,cpuacct:/system.slice/system-sshd.slice/[email protected]:22-xxx.yyy.zzz.aaa:33678.service
211+
3:net_cls,net_prio:/
212+
2:memory:/system.slice/system-sshd.slice/[email protected]:22-xxx.yyy.zzz.aaa:33678.service
213+
1:name=systemd:/system.slice/system-sshd.slice/[email protected]:22-xxx.yyy.zzz.aaa:33678.service`,
214+
expectedOutput: map[string]string{
215+
"name=systemd": "/system.slice/system-sshd.slice/[email protected]:22-xxx.yyy.zzz.aaa:33678.service",
216+
"blkio": "/",
217+
"freezer": "/",
218+
"perf_event": "/",
219+
"devices": "/system.slice/system-sshd.slice",
220+
"cpuset": "/",
221+
"cpu": "/system.slice/system-sshd.slice/[email protected]:22-xxx.yyy.zzz.aaa:33678.service",
222+
"cpuacct": "/system.slice/system-sshd.slice/[email protected]:22-xxx.yyy.zzz.aaa:33678.service",
223+
"net_cls": "/",
224+
"net_prio": "/",
225+
"memory": "/system.slice/system-sshd.slice/[email protected]:22-xxx.yyy.zzz.aaa:33678.service",
226+
},
227+
},
228+
{
229+
input: `malformed input`,
230+
expectedError: fmt.Errorf(`invalid cgroup entry: must contain at least two colons: malformed input`),
231+
},
232+
}
233+
234+
for ndx, testCase := range testCases {
235+
out, err := parseCgroupFromReader(strings.NewReader(testCase.input))
236+
if err != nil {
237+
if testCase.expectedError == nil || testCase.expectedError.Error() != err.Error() {
238+
t.Errorf("%v: expected error %v, got error %v", ndx, testCase.expectedError, err)
239+
}
240+
} else {
241+
if !reflect.DeepEqual(testCase.expectedOutput, out) {
242+
t.Errorf("%v: expected output %v, got error %v", ndx, testCase.expectedOutput, out)
243+
}
244+
}
245+
}
246+
247+
}

0 commit comments

Comments
 (0)