]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/test/test-cgroup-util.c
Merge pull request #8237 from sourcejedi/timer_suspend
[thirdparty/systemd.git] / src / test / test-cgroup-util.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
2 /***
3 This file is part of systemd.
4
5 Copyright 2013 Zbigniew Jędrzejewski-Szmek
6
7 systemd is free software; you can redistribute it and/or modify it
8 under the terms of the GNU Lesser General Public License as published by
9 the Free Software Foundation; either version 2.1 of the License, or
10 (at your option) any later version.
11
12 systemd is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
16
17 You should have received a copy of the GNU Lesser General Public License
18 along with systemd; If not, see <http://www.gnu.org/licenses/>.
19 ***/
20
21 #include "alloc-util.h"
22 #include "build.h"
23 #include "cgroup-util.h"
24 #include "dirent-util.h"
25 #include "fd-util.h"
26 #include "format-util.h"
27 #include "parse-util.h"
28 #include "proc-cmdline.h"
29 #include "process-util.h"
30 #include "special.h"
31 #include "stat-util.h"
32 #include "string-util.h"
33 #include "strv.h"
34 #include "test-helper.h"
35 #include "user-util.h"
36 #include "util.h"
37
38 static void check_p_d_u(const char *path, int code, const char *result) {
39 _cleanup_free_ char *unit = NULL;
40 int r;
41
42 r = cg_path_decode_unit(path, &unit);
43 printf("%s: %s → %s %d expected %s %d\n", __func__, path, unit, r, result, code);
44 assert_se(r == code);
45 assert_se(streq_ptr(unit, result));
46 }
47
48 static void test_path_decode_unit(void) {
49 check_p_d_u("getty@tty2.service", 0, "getty@tty2.service");
50 check_p_d_u("getty@tty2.service/", 0, "getty@tty2.service");
51 check_p_d_u("getty@tty2.service/xxx", 0, "getty@tty2.service");
52 check_p_d_u("getty@.service/", -ENXIO, NULL);
53 check_p_d_u("getty@.service", -ENXIO, NULL);
54 check_p_d_u("getty.service", 0, "getty.service");
55 check_p_d_u("getty", -ENXIO, NULL);
56 check_p_d_u("getty/waldo", -ENXIO, NULL);
57 check_p_d_u("_cpu.service", 0, "cpu.service");
58 }
59
60 static void check_p_g_u(const char *path, int code, const char *result) {
61 _cleanup_free_ char *unit = NULL;
62 int r;
63
64 r = cg_path_get_unit(path, &unit);
65 printf("%s: %s → %s %d expected %s %d\n", __func__, path, unit, r, result, code);
66 assert_se(r == code);
67 assert_se(streq_ptr(unit, result));
68 }
69
70 static void test_path_get_unit(void) {
71 check_p_g_u("/system.slice/foobar.service/sdfdsaf", 0, "foobar.service");
72 check_p_g_u("/system.slice/getty@tty5.service", 0, "getty@tty5.service");
73 check_p_g_u("/system.slice/getty@tty5.service/aaa/bbb", 0, "getty@tty5.service");
74 check_p_g_u("/system.slice/getty@tty5.service/", 0, "getty@tty5.service");
75 check_p_g_u("/system.slice/getty@tty6.service/tty5", 0, "getty@tty6.service");
76 check_p_g_u("sadfdsafsda", -ENXIO, NULL);
77 check_p_g_u("/system.slice/getty####@tty6.service/xxx", -ENXIO, NULL);
78 check_p_g_u("/system.slice/system-waldo.slice/foobar.service/sdfdsaf", 0, "foobar.service");
79 check_p_g_u("/system.slice/system-waldo.slice/_cpu.service/sdfdsaf", 0, "cpu.service");
80 check_p_g_u("/user.slice/user-1000.slice/user@1000.service/server.service", 0, "user@1000.service");
81 check_p_g_u("/user.slice/user-1000.slice/user@.service/server.service", -ENXIO, NULL);
82 }
83
84 static void check_p_g_u_u(const char *path, int code, const char *result) {
85 _cleanup_free_ char *unit = NULL;
86 int r;
87
88 r = cg_path_get_user_unit(path, &unit);
89 printf("%s: %s → %s %d expected %s %d\n", __func__, path, unit, r, result, code);
90 assert_se(r == code);
91 assert_se(streq_ptr(unit, result));
92 }
93
94 static void test_path_get_user_unit(void) {
95 check_p_g_u_u("/user.slice/user-1000.slice/session-2.scope/foobar.service", 0, "foobar.service");
96 check_p_g_u_u("/user.slice/user-1000.slice/session-2.scope/waldo.slice/foobar.service", 0, "foobar.service");
97 check_p_g_u_u("/user.slice/user-1002.slice/session-2.scope/foobar.service/waldo", 0, "foobar.service");
98 check_p_g_u_u("/user.slice/user-1000.slice/session-2.scope/foobar.service/waldo/uuuux", 0, "foobar.service");
99 check_p_g_u_u("/user.slice/user-1000.slice/session-2.scope/waldo/waldo/uuuux", -ENXIO, NULL);
100 check_p_g_u_u("/user.slice/user-1000.slice/session-2.scope/foobar@pie.service/pa/po", 0, "foobar@pie.service");
101 check_p_g_u_u("/session-2.scope/foobar@pie.service/pa/po", 0, "foobar@pie.service");
102 check_p_g_u_u("/xyz.slice/xyz-waldo.slice/session-77.scope/foobar@pie.service/pa/po", 0, "foobar@pie.service");
103 check_p_g_u_u("/meh.service", -ENXIO, NULL);
104 check_p_g_u_u("/session-3.scope/_cpu.service", 0, "cpu.service");
105 check_p_g_u_u("/user.slice/user-1000.slice/user@1000.service/server.service", 0, "server.service");
106 check_p_g_u_u("/user.slice/user-1000.slice/user@1000.service/foobar.slice/foobar@pie.service", 0, "foobar@pie.service");
107 check_p_g_u_u("/user.slice/user-1000.slice/user@.service/server.service", -ENXIO, NULL);
108 }
109
110 static void check_p_g_s(const char *path, int code, const char *result) {
111 _cleanup_free_ char *s = NULL;
112
113 assert_se(cg_path_get_session(path, &s) == code);
114 assert_se(streq_ptr(s, result));
115 }
116
117 static void test_path_get_session(void) {
118 check_p_g_s("/user.slice/user-1000.slice/session-2.scope/foobar.service", 0, "2");
119 check_p_g_s("/session-3.scope", 0, "3");
120 check_p_g_s("/session-.scope", -ENXIO, NULL);
121 check_p_g_s("", -ENXIO, NULL);
122 }
123
124 static void check_p_g_o_u(const char *path, int code, uid_t result) {
125 uid_t uid = 0;
126
127 assert_se(cg_path_get_owner_uid(path, &uid) == code);
128 assert_se(uid == result);
129 }
130
131 static void test_path_get_owner_uid(void) {
132 check_p_g_o_u("/user.slice/user-1000.slice/session-2.scope/foobar.service", 0, 1000);
133 check_p_g_o_u("/user.slice/user-1006.slice", 0, 1006);
134 check_p_g_o_u("", -ENXIO, 0);
135 }
136
137 static void check_p_g_slice(const char *path, int code, const char *result) {
138 _cleanup_free_ char *s = NULL;
139
140 assert_se(cg_path_get_slice(path, &s) == code);
141 assert_se(streq_ptr(s, result));
142 }
143
144 static void test_path_get_slice(void) {
145 check_p_g_slice("/user.slice", 0, "user.slice");
146 check_p_g_slice("/foobar", 0, SPECIAL_ROOT_SLICE);
147 check_p_g_slice("/user.slice/user-waldo.slice", 0, "user-waldo.slice");
148 check_p_g_slice("", 0, SPECIAL_ROOT_SLICE);
149 check_p_g_slice("foobar", 0, SPECIAL_ROOT_SLICE);
150 check_p_g_slice("foobar.slice", 0, "foobar.slice");
151 check_p_g_slice("foo.slice/foo-bar.slice/waldo.service", 0, "foo-bar.slice");
152 }
153
154 static void check_p_g_u_slice(const char *path, int code, const char *result) {
155 _cleanup_free_ char *s = NULL;
156
157 assert_se(cg_path_get_user_slice(path, &s) == code);
158 assert_se(streq_ptr(s, result));
159 }
160
161 static void test_path_get_user_slice(void) {
162 check_p_g_u_slice("/user.slice", -ENXIO, NULL);
163 check_p_g_u_slice("/foobar", -ENXIO, NULL);
164 check_p_g_u_slice("/user.slice/user-waldo.slice", -ENXIO, NULL);
165 check_p_g_u_slice("", -ENXIO, NULL);
166 check_p_g_u_slice("foobar", -ENXIO, NULL);
167 check_p_g_u_slice("foobar.slice", -ENXIO, NULL);
168 check_p_g_u_slice("foo.slice/foo-bar.slice/waldo.service", -ENXIO, NULL);
169
170 check_p_g_u_slice("foo.slice/foo-bar.slice/user@1000.service", 0, SPECIAL_ROOT_SLICE);
171 check_p_g_u_slice("foo.slice/foo-bar.slice/user@1000.service/", 0, SPECIAL_ROOT_SLICE);
172 check_p_g_u_slice("foo.slice/foo-bar.slice/user@1000.service///", 0, SPECIAL_ROOT_SLICE);
173 check_p_g_u_slice("foo.slice/foo-bar.slice/user@1000.service/waldo.service", 0, SPECIAL_ROOT_SLICE);
174 check_p_g_u_slice("foo.slice/foo-bar.slice/user@1000.service/piep.slice/foo.service", 0, "piep.slice");
175 check_p_g_u_slice("/foo.slice//foo-bar.slice/user@1000.service/piep.slice//piep-pap.slice//foo.service", 0, "piep-pap.slice");
176 }
177
178 static void test_get_paths(void) {
179 _cleanup_free_ char *a = NULL;
180
181 assert_se(cg_get_root_path(&a) >= 0);
182 log_info("Root = %s", a);
183 }
184
185 static void test_proc(void) {
186 _cleanup_closedir_ DIR *d = NULL;
187 struct dirent *de;
188 int r;
189
190 d = opendir("/proc");
191 assert_se(d);
192
193 FOREACH_DIRENT(de, d, break) {
194 _cleanup_free_ char *path = NULL, *path_shifted = NULL, *session = NULL, *unit = NULL, *user_unit = NULL, *machine = NULL, *slice = NULL;
195 pid_t pid;
196 uid_t uid = UID_INVALID;
197
198 if (!IN_SET(de->d_type, DT_DIR, DT_UNKNOWN))
199 continue;
200
201 r = parse_pid(de->d_name, &pid);
202 if (r < 0)
203 continue;
204
205 if (is_kernel_thread(pid))
206 continue;
207
208 cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER, pid, &path);
209 cg_pid_get_path_shifted(pid, NULL, &path_shifted);
210 cg_pid_get_owner_uid(pid, &uid);
211 cg_pid_get_session(pid, &session);
212 cg_pid_get_unit(pid, &unit);
213 cg_pid_get_user_unit(pid, &user_unit);
214 cg_pid_get_machine_name(pid, &machine);
215 cg_pid_get_slice(pid, &slice);
216
217 printf(PID_FMT"\t%s\t%s\t"UID_FMT"\t%s\t%s\t%s\t%s\t%s\n",
218 pid,
219 path,
220 path_shifted,
221 uid,
222 session,
223 unit,
224 user_unit,
225 machine,
226 slice);
227 }
228 }
229
230 static void test_escape_one(const char *s, const char *r) {
231 _cleanup_free_ char *b;
232
233 b = cg_escape(s);
234 assert_se(b);
235 assert_se(streq(b, r));
236
237 assert_se(streq(cg_unescape(b), s));
238 }
239
240 static void test_escape(void) {
241 test_escape_one("foobar", "foobar");
242 test_escape_one(".foobar", "_.foobar");
243 test_escape_one("foobar.service", "foobar.service");
244 test_escape_one("cgroup.service", "_cgroup.service");
245 test_escape_one("tasks", "_tasks");
246 if (access("/sys/fs/cgroup/cpu", F_OK) == 0)
247 test_escape_one("cpu.service", "_cpu.service");
248 test_escape_one("_foobar", "__foobar");
249 test_escape_one("", "_");
250 test_escape_one("_", "__");
251 test_escape_one(".", "_.");
252 }
253
254 static void test_controller_is_valid(void) {
255 assert_se(cg_controller_is_valid("foobar"));
256 assert_se(cg_controller_is_valid("foo_bar"));
257 assert_se(cg_controller_is_valid("name=foo"));
258 assert_se(!cg_controller_is_valid(""));
259 assert_se(!cg_controller_is_valid("name="));
260 assert_se(!cg_controller_is_valid("="));
261 assert_se(!cg_controller_is_valid("cpu,cpuacct"));
262 assert_se(!cg_controller_is_valid("_"));
263 assert_se(!cg_controller_is_valid("_foobar"));
264 assert_se(!cg_controller_is_valid("tatü"));
265 }
266
267 static void test_slice_to_path_one(const char *unit, const char *path, int error) {
268 _cleanup_free_ char *ret = NULL;
269
270 assert_se(cg_slice_to_path(unit, &ret) == error);
271 assert_se(streq_ptr(ret, path));
272 }
273
274 static void test_slice_to_path(void) {
275
276 test_slice_to_path_one("foobar.slice", "foobar.slice", 0);
277 test_slice_to_path_one("foobar-waldo.slice", "foobar.slice/foobar-waldo.slice", 0);
278 test_slice_to_path_one("foobar-waldo.service", NULL, -EINVAL);
279 test_slice_to_path_one(SPECIAL_ROOT_SLICE, "", 0);
280 test_slice_to_path_one("--.slice", NULL, -EINVAL);
281 test_slice_to_path_one("-", NULL, -EINVAL);
282 test_slice_to_path_one("-foo-.slice", NULL, -EINVAL);
283 test_slice_to_path_one("-foo.slice", NULL, -EINVAL);
284 test_slice_to_path_one("foo-.slice", NULL, -EINVAL);
285 test_slice_to_path_one("foo--bar.slice", NULL, -EINVAL);
286 test_slice_to_path_one("foo.slice/foo--bar.slice", NULL, -EINVAL);
287 test_slice_to_path_one("a-b.slice", "a.slice/a-b.slice", 0);
288 test_slice_to_path_one("a-b-c-d-e.slice", "a.slice/a-b.slice/a-b-c.slice/a-b-c-d.slice/a-b-c-d-e.slice", 0);
289 }
290
291 static void test_shift_path_one(const char *raw, const char *root, const char *shifted) {
292 const char *s = NULL;
293
294 assert_se(cg_shift_path(raw, root, &s) >= 0);
295 assert_se(streq(s, shifted));
296 }
297
298 static void test_shift_path(void) {
299
300 test_shift_path_one("/foobar/waldo", "/", "/foobar/waldo");
301 test_shift_path_one("/foobar/waldo", "", "/foobar/waldo");
302 test_shift_path_one("/foobar/waldo", "/foobar", "/waldo");
303 test_shift_path_one("/foobar/waldo", "/fuckfuck", "/foobar/waldo");
304 }
305
306 static void test_mask_supported(void) {
307
308 CGroupMask m;
309 CGroupController c;
310
311 assert_se(cg_mask_supported(&m) >= 0);
312
313 for (c = 0; c < _CGROUP_CONTROLLER_MAX; c++)
314 printf("'%s' is supported: %s\n", cgroup_controller_to_string(c), yes_no(m & CGROUP_CONTROLLER_TO_MASK(c)));
315 }
316
317 static void test_is_cgroup_fs(void) {
318 struct statfs sfs;
319 assert_se(statfs("/sys/fs/cgroup", &sfs) == 0);
320 if (is_temporary_fs(&sfs))
321 assert_se(statfs("/sys/fs/cgroup/systemd", &sfs) == 0);
322 assert_se(is_cgroup_fs(&sfs));
323 }
324
325 static void test_fd_is_cgroup_fs(void) {
326 int fd;
327
328 fd = open("/sys/fs/cgroup", O_RDONLY|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW);
329 assert_se(fd >= 0);
330 if (fd_is_temporary_fs(fd)) {
331 fd = safe_close(fd);
332 fd = open("/sys/fs/cgroup/systemd", O_RDONLY|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW);
333 assert_se(fd >= 0);
334 }
335 assert_se(fd_is_cgroup_fs(fd));
336 fd = safe_close(fd);
337 }
338
339 static void test_is_wanted_print(bool header) {
340 _cleanup_free_ char *cmdline = NULL;
341
342 log_info("-- %s --", __func__);
343 assert_se(proc_cmdline(&cmdline) >= 0);
344 log_info("cmdline: %s", cmdline);
345 if (header) {
346
347 log_info(_CGROUP_HIEARCHY_);
348 (void) system("findmnt -n /sys/fs/cgroup");
349 }
350
351 log_info("is_unified_wanted() → %s", yes_no(cg_is_unified_wanted()));
352 log_info("is_hybrid_wanted() → %s", yes_no(cg_is_hybrid_wanted()));
353 log_info("is_legacy_wanted() → %s", yes_no(cg_is_legacy_wanted()));
354 log_info(" ");
355 }
356
357 static void test_is_wanted(void) {
358 assert_se(setenv("SYSTEMD_PROC_CMDLINE",
359 "systemd.unified_cgroup_hierarchy", 1) >= 0);
360 test_is_wanted_print(false);
361
362 assert_se(setenv("SYSTEMD_PROC_CMDLINE",
363 "systemd.unified_cgroup_hierarchy=0", 1) >= 0);
364 test_is_wanted_print(false);
365
366 assert_se(setenv("SYSTEMD_PROC_CMDLINE",
367 "systemd.unified_cgroup_hierarchy=0 "
368 "systemd.legacy_systemd_cgroup_controller", 1) >= 0);
369 test_is_wanted_print(false);
370
371 assert_se(setenv("SYSTEMD_PROC_CMDLINE",
372 "systemd.unified_cgroup_hierarchy=0 "
373 "systemd.legacy_systemd_cgroup_controller=0", 1) >= 0);
374 test_is_wanted_print(false);
375 }
376
377 static void test_cg_tests(void) {
378 int all, hybrid, systemd, r;
379
380 r = cg_unified_flush();
381 if (r == -ENOMEDIUM) {
382 log_notice_errno(r, "Skipping cg hierarchy tests: %m");
383 return;
384 }
385 assert_se(r == 0);
386
387 all = cg_all_unified();
388 assert_se(IN_SET(all, 0, 1));
389
390 hybrid = cg_hybrid_unified();
391 assert_se(IN_SET(hybrid, 0, 1));
392
393 systemd = cg_unified_controller(SYSTEMD_CGROUP_CONTROLLER);
394 assert_se(IN_SET(systemd, 0, 1));
395
396 if (all) {
397 assert_se(systemd);
398 assert_se(!hybrid);
399
400 } else if (hybrid) {
401 assert_se(systemd);
402 assert_se(!all);
403
404 } else
405 assert_se(!systemd);
406 }
407
408 static void test_cg_get_keyed_attribute(void) {
409 _cleanup_free_ char *val = NULL;
410 char *vals3[3] = {}, *vals3a[3] = {};
411 int i;
412
413 assert_se(cg_get_keyed_attribute("cpu", "/init.scope", "no_such_file", STRV_MAKE("no_such_attr"), &val) == -ENOENT);
414 assert_se(val == NULL);
415
416 if (access("/sys/fs/cgroup/init.scope/cpu.stat", R_OK) < 0) {
417 log_info_errno(errno, "Skipping most of %s, /init.scope/cpu.stat not accessible: %m", __func__);
418 return;
419 }
420
421 assert_se(cg_get_keyed_attribute("cpu", "/init.scope", "cpu.stat", STRV_MAKE("no_such_attr"), &val) == -ENXIO);
422 assert_se(val == NULL);
423
424 assert_se(cg_get_keyed_attribute("cpu", "/init.scope", "cpu.stat", STRV_MAKE("usage_usec"), &val) == 0);
425 log_info("cpu /init.scope cpu.stat [usage_usec] → \"%s\"", val);
426
427 assert_se(cg_get_keyed_attribute("cpu", "/init.scope", "cpu.stat", STRV_MAKE("usage_usec", "no_such_attr"), vals3) == -ENXIO);
428
429 assert_se(cg_get_keyed_attribute("cpu", "/init.scope", "cpu.stat", STRV_MAKE("usage_usec", "usage_usec"), vals3) == -ENXIO);
430
431 assert_se(cg_get_keyed_attribute("cpu", "/init.scope", "cpu.stat",
432 STRV_MAKE("usage_usec", "user_usec", "system_usec"), vals3) == 0);
433 log_info("cpu /init.scope cpu.stat [usage_usec user_usec system_usec] → \"%s\", \"%s\", \"%s\"",
434 vals3[0], vals3[1], vals3[2]);
435
436 assert_se(cg_get_keyed_attribute("cpu", "/init.scope", "cpu.stat",
437 STRV_MAKE("system_usec", "user_usec", "usage_usec"), vals3a) == 0);
438 log_info("cpu /init.scope cpu.stat [system_usec user_usec usage_usec] → \"%s\", \"%s\", \"%s\"",
439 vals3a[0], vals3a[1], vals3a[2]);
440
441 for (i = 0; i < 3; i++) {
442 free(vals3[i]);
443 free(vals3a[i]);
444 }
445 }
446
447 int main(void) {
448 log_set_max_level(LOG_DEBUG);
449 log_parse_environment();
450 log_open();
451
452 test_path_decode_unit();
453 test_path_get_unit();
454 test_path_get_user_unit();
455 test_path_get_session();
456 test_path_get_owner_uid();
457 test_path_get_slice();
458 test_path_get_user_slice();
459 TEST_REQ_RUNNING_SYSTEMD(test_get_paths());
460 test_proc();
461 TEST_REQ_RUNNING_SYSTEMD(test_escape());
462 test_controller_is_valid();
463 test_slice_to_path();
464 test_shift_path();
465 TEST_REQ_RUNNING_SYSTEMD(test_mask_supported());
466 TEST_REQ_RUNNING_SYSTEMD(test_is_cgroup_fs());
467 TEST_REQ_RUNNING_SYSTEMD(test_fd_is_cgroup_fs());
468 test_is_wanted_print(true);
469 test_is_wanted_print(false); /* run twice to test caching */
470 test_is_wanted();
471 test_cg_tests();
472 test_cg_get_keyed_attribute();
473
474 return 0;
475 }