1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3 * Copyright (c) 2020 Red Hat, Inc.
4 * Copyright (c) 2020 Li Wang <liwang@redhat.com>
5 * Copyright (c) 2020-2021 SUSE LLC <rpalethorpe@suse.com>
6 */
7
8 #define TST_NO_DEFAULT_MAIN
9
10 #include <stdio.h>
11 #include <stddef.h>
12 #include <stdlib.h>
13 #include <mntent.h>
14 #include <sys/mount.h>
15
16 #include "tst_test.h"
17 #include "lapi/fcntl.h"
18 #include "lapi/mount.h"
19 #include "lapi/mkdirat.h"
20 #include "tst_safe_file_at.h"
21 #include "tst_cgroup.h"
22
23 struct cgroup_root;
24
25 /* A node in a single CGroup hierarchy. It exists mainly for
26 * convenience so that we do not have to traverse the CGroup structure
27 * for frequent operations.
28 *
29 * This is actually a single-linked list not a tree. We only need to
30 * traverse from leaf towards root.
31 */
32 struct cgroup_dir {
33 const char *dir_name;
34 const struct cgroup_dir *dir_parent;
35
36 /* Shortcut to root */
37 const struct cgroup_root *dir_root;
38
39 /* Subsystems (controllers) bit field. Only controllers which
40 * were required and configured for this node are added to
41 * this field. So it may be different from root->css_field.
42 */
43 uint32_t ctrl_field;
44
45 /* In general we avoid having sprintfs everywhere and use
46 * openat, linkat, etc.
47 */
48 int dir_fd;
49
50 int we_created_it:1;
51 };
52
53 /* The root of a CGroup hierarchy/tree */
54 struct cgroup_root {
55 enum tst_cgroup_ver ver;
56 /* A mount path */
57 char mnt_path[PATH_MAX];
58 /* Subsystems (controllers) bit field. Includes all
59 * controllers found while scanning this root.
60 */
61 uint32_t ctrl_field;
62
63 /* CGroup hierarchy: mnt -> ltp -> {drain, test -> ??? } We
64 * keep a flat reference to ltp, drain and test for
65 * convenience.
66 */
67
68 /* Mount directory */
69 struct cgroup_dir mnt_dir;
70 /* LTP CGroup directory, contains drain and test dirs */
71 struct cgroup_dir ltp_dir;
72 /* Drain CGroup, see cgroup_cleanup */
73 struct cgroup_dir drain_dir;
74 /* CGroup for current test. Which may have children. */
75 struct cgroup_dir test_dir;
76
77 int we_mounted_it:1;
78 /* cpuset is in compatability mode */
79 int no_cpuset_prefix:1;
80 };
81
82 /* Controller sub-systems */
83 enum cgroup_ctrl_indx {
84 CTRL_MEMORY = 1,
85 CTRL_CPUSET = 2,
86 };
87 #define CTRLS_MAX CTRL_CPUSET
88
89 /* At most we can have one cgroup V1 tree for each controller and one
90 * (empty) v2 tree.
91 */
92 #define ROOTS_MAX (CTRLS_MAX + 1)
93
94 /* Describes a controller file or knob
95 *
96 * The primary purpose of this is to map V2 names to V1
97 * names.
98 */
99 struct cgroup_file {
100 /* Canonical name. Is the V2 name unless an item is V1 only */
101 const char *const file_name;
102 /* V1 name or NULL if item is V2 only */
103 const char *const file_name_v1;
104
105 /* The controller this item belongs to or zero for
106 * 'cgroup.<item>'.
107 */
108 const enum cgroup_ctrl_indx ctrl_indx;
109 };
110
111 /* Describes a Controller or subsystem
112 *
113 * Internally the kernel seems to call controllers subsystems and uses
114 * the abbreviations subsys and css.
115 */
116 struct cgroup_ctrl {
117 /* Userland name of the controller (e.g. 'memory' not 'memcg') */
118 const char *const ctrl_name;
119 /* List of files belonging to this controller */
120 const struct cgroup_file *const files;
121 /* Our index for the controller */
122 const enum cgroup_ctrl_indx ctrl_indx;
123
124 /* Runtime; hierarchy the controller is attached to */
125 struct cgroup_root *ctrl_root;
126 /* Runtime; whether we required the controller */
127 int we_require_it:1;
128 };
129
130 struct tst_cgroup_group {
131 char group_name[NAME_MAX + 1];
132 /* Maps controller ID to the tree which contains it. The V2
133 * tree is at zero even if it contains no controllers.
134 */
135 struct cgroup_dir *dirs_by_ctrl[ROOTS_MAX];
136 /* NULL terminated list of trees */
137 struct cgroup_dir *dirs[ROOTS_MAX + 1];
138 };
139
140 /* Always use first item for unified hierarchy */
141 static struct cgroup_root roots[ROOTS_MAX + 1];
142
143 /* Lookup tree for item names. */
144 typedef struct cgroup_file files_t[];
145
146 static const files_t cgroup_ctrl_files = {
147 /* procs exists on V1, however it was read-only until kernel v3.0. */
148 { "cgroup.procs", "tasks", 0 },
149 { "cgroup.subtree_control", NULL, 0 },
150 { "cgroup.clone_children", "cgroup.clone_children", 0 },
151 { }
152 };
153
154 static const files_t memory_ctrl_files = {
155 { "memory.current", "memory.usage_in_bytes", CTRL_MEMORY },
156 { "memory.max", "memory.limit_in_bytes", CTRL_MEMORY },
157 { "memory.swappiness", "memory.swappiness", CTRL_MEMORY },
158 { "memory.swap.current", "memory.memsw.usage_in_bytes", CTRL_MEMORY },
159 { "memory.swap.max", "memory.memsw.limit_in_bytes", CTRL_MEMORY },
160 { "memory.kmem.usage_in_bytes", "memory.kmem.usage_in_bytes", CTRL_MEMORY },
161 { "memory.kmem.limit_in_bytes", "memory.kmem.limit_in_bytes", CTRL_MEMORY },
162 { }
163 };
164
165 static const files_t cpuset_ctrl_files = {
166 { "cpuset.cpus", "cpuset.cpus", CTRL_CPUSET },
167 { "cpuset.mems", "cpuset.mems", CTRL_CPUSET },
168 { "cpuset.memory_migrate", "cpuset.memory_migrate", CTRL_CPUSET },
169 { }
170 };
171
172 static struct cgroup_ctrl controllers[] = {
173 [0] = { "cgroup", cgroup_ctrl_files, 0, NULL, 0 },
174 [CTRL_MEMORY] = {
175 "memory", memory_ctrl_files, CTRL_MEMORY, NULL, 0
176 },
177 [CTRL_CPUSET] = {
178 "cpuset", cpuset_ctrl_files, CTRL_CPUSET, NULL, 0
179 },
180 { }
181 };
182
183 static const struct tst_cgroup_opts default_opts = { 0 };
184
185 /* We should probably allow these to be set in environment
186 * variables */
187 static const char *ltp_cgroup_dir = "ltp";
188 static const char *ltp_cgroup_drain_dir = "drain";
189 static char test_cgroup_dir[NAME_MAX + 1];
190 static const char *ltp_mount_prefix = "/tmp/cgroup_";
191 static const char *ltp_v2_mount = "unified";
192
193 #define first_root \
194 (roots[0].ver ? roots : roots + 1)
195 #define for_each_root(r) \
196 for ((r) = first_root; (r)->ver; (r)++)
197 #define for_each_v1_root(r) \
198 for ((r) = roots + 1; (r)->ver; (r)++)
199 #define for_each_ctrl(ctrl) \
200 for ((ctrl) = controllers + 1; (ctrl)->ctrl_name; (ctrl)++)
201
202 /* In all cases except one, this only loops once.
203 *
204 * If (ctrl) == 0 and multiple V1 (and a V2) hierarchies are mounted,
205 * then we need to loop over multiple directories. For example if we
206 * need to write to "tasks"/"cgroup.procs" which exists for each
207 * hierarchy.
208 */
209 #define for_each_dir(cg, ctrl, t) \
210 for ((t) = (ctrl) ? (cg)->dirs_by_ctrl + (ctrl) : (cg)->dirs; \
211 *(t); \
212 (t) = (ctrl) ? (cg)->dirs + ROOTS_MAX : (t) + 1)
213
214 __attribute__ ((nonnull))
has_ctrl(const uint32_t ctrl_field,const struct cgroup_ctrl * const ctrl)215 static int has_ctrl(const uint32_t ctrl_field,
216 const struct cgroup_ctrl *const ctrl)
217 {
218 return !!(ctrl_field & (1 << ctrl->ctrl_indx));
219 }
220
221 __attribute__ ((nonnull))
add_ctrl(uint32_t * const ctrl_field,const struct cgroup_ctrl * const ctrl)222 static void add_ctrl(uint32_t *const ctrl_field,
223 const struct cgroup_ctrl *const ctrl)
224 {
225 *ctrl_field |= 1 << ctrl->ctrl_indx;
226 }
227
228 __attribute__ ((warn_unused_result))
tst_cgroup_root_get(void)229 struct cgroup_root *tst_cgroup_root_get(void)
230 {
231 return roots[0].ver ? roots : roots + 1;
232 }
233
cgroup_v2_mounted(void)234 static int cgroup_v2_mounted(void)
235 {
236 return !!roots[0].ver;
237 }
238
cgroup_v1_mounted(void)239 static int cgroup_v1_mounted(void)
240 {
241 return !!roots[1].ver;
242 }
243
cgroup_mounted(void)244 static int cgroup_mounted(void)
245 {
246 return cgroup_v2_mounted() || cgroup_v1_mounted();
247 }
248
249 __attribute__ ((nonnull, warn_unused_result))
cgroup_ctrl_on_v2(const struct cgroup_ctrl * const ctrl)250 static int cgroup_ctrl_on_v2(const struct cgroup_ctrl *const ctrl)
251 {
252 return ctrl->ctrl_root && ctrl->ctrl_root->ver == TST_CGROUP_V2;
253 }
254
255 __attribute__ ((nonnull))
cgroup_dir_mk(const struct cgroup_dir * const parent,const char * const dir_name,struct cgroup_dir * const new)256 static void cgroup_dir_mk(const struct cgroup_dir *const parent,
257 const char *const dir_name,
258 struct cgroup_dir *const new)
259 {
260 const char *dpath;
261
262 new->dir_root = parent->dir_root;
263 new->dir_name = dir_name;
264 new->dir_parent = parent;
265 new->ctrl_field = parent->ctrl_field;
266 new->we_created_it = 0;
267
268 if (!mkdirat(parent->dir_fd, dir_name, 0777)) {
269 new->we_created_it = 1;
270 goto opendir;
271 }
272
273 if (errno == EEXIST)
274 goto opendir;
275
276 dpath = tst_decode_fd(parent->dir_fd);
277
278 if (errno == EACCES) {
279 tst_brk(TCONF | TERRNO,
280 "Lack permission to make '%s/%s'; premake it or run as root",
281 dpath, dir_name);
282 } else {
283 tst_brk(TBROK | TERRNO,
284 "mkdirat(%d<%s>, '%s', 0777)",
285 parent->dir_fd, dpath, dir_name);
286 }
287
288 opendir:
289 new->dir_fd = SAFE_OPENAT(parent->dir_fd, dir_name,
290 O_PATH | O_DIRECTORY);
291 }
292
tst_cgroup_print_config(void)293 void tst_cgroup_print_config(void)
294 {
295 struct cgroup_root *root;
296 const struct cgroup_ctrl *ctrl;
297
298 tst_res(TINFO, "Detected Controllers:");
299
300 for_each_ctrl(ctrl) {
301 root = ctrl->ctrl_root;
302
303 if (!root)
304 continue;
305
306 tst_res(TINFO, "\t%.10s %s @ %s:%s",
307 ctrl->ctrl_name,
308 root->no_cpuset_prefix ? "[noprefix]" : "",
309 root->ver == TST_CGROUP_V1 ? "V1" : "V2",
310 root->mnt_path);
311 }
312 }
313
314 __attribute__ ((nonnull, warn_unused_result))
cgroup_find_ctrl(const char * const ctrl_name)315 static struct cgroup_ctrl *cgroup_find_ctrl(const char *const ctrl_name)
316 {
317 struct cgroup_ctrl *ctrl = controllers;
318
319 while (ctrl->ctrl_name && strcmp(ctrl_name, ctrl->ctrl_name))
320 ctrl++;
321
322 if (!ctrl->ctrl_name)
323 ctrl = NULL;
324
325 return ctrl;
326 }
327
328 /* Determine if a mounted cgroup hierarchy is unique and record it if so.
329 *
330 * For CGroups V2 this is very simple as there is only one
331 * hierarchy. We just record which controllers are available and check
332 * if this matches what we read from any previous mount points.
333 *
334 * For V1 the set of controllers S is partitioned into sets {P_1, P_2,
335 * ..., P_n} with one or more controllers in each partion. Each
336 * partition P_n can be mounted multiple times, but the same
337 * controller can not appear in more than one partition. Usually each
338 * partition contains a single controller, but, for example, cpu and
339 * cpuacct are often mounted together in the same partiion.
340 *
341 * Each controller partition has its own hierarchy (root) which we
342 * must track and update independently.
343 */
344 __attribute__ ((nonnull))
cgroup_root_scan(const char * const mnt_type,const char * const mnt_dir,char * const mnt_opts)345 static void cgroup_root_scan(const char *const mnt_type,
346 const char *const mnt_dir,
347 char *const mnt_opts)
348 {
349 struct cgroup_root *root = roots;
350 const struct cgroup_ctrl *const_ctrl;
351 struct cgroup_ctrl *ctrl;
352 uint32_t ctrl_field = 0;
353 int no_prefix = 0;
354 char buf[BUFSIZ];
355 char *tok;
356 const int mnt_dfd = SAFE_OPEN(mnt_dir, O_PATH | O_DIRECTORY);
357
358 if (!strcmp(mnt_type, "cgroup"))
359 goto v1;
360
361 SAFE_FILE_READAT(mnt_dfd, "cgroup.controllers", buf, sizeof(buf));
362
363 for (tok = strtok(buf, " "); tok; tok = strtok(NULL, " ")) {
364 if ((const_ctrl = cgroup_find_ctrl(tok)))
365 add_ctrl(&ctrl_field, const_ctrl);
366 }
367
368 if (root->ver && ctrl_field == root->ctrl_field)
369 goto discard;
370
371 if (root->ctrl_field)
372 tst_brk(TBROK, "Available V2 controllers are changing between scans?");
373
374 root->ver = TST_CGROUP_V2;
375
376 goto backref;
377
378 v1:
379 for (tok = strtok(mnt_opts, ","); tok; tok = strtok(NULL, ",")) {
380 if ((const_ctrl = cgroup_find_ctrl(tok)))
381 add_ctrl(&ctrl_field, const_ctrl);
382
383 no_prefix |= !strcmp("noprefix", tok);
384 }
385
386 if (!ctrl_field)
387 goto discard;
388
389 for_each_v1_root(root) {
390 if (!(ctrl_field & root->ctrl_field))
391 continue;
392
393 if (ctrl_field == root->ctrl_field)
394 goto discard;
395
396 tst_brk(TBROK,
397 "The intersection of two distinct sets of mounted controllers should be null?"
398 "Check '%s' and '%s'", root->mnt_path, mnt_dir);
399 }
400
401 if (root >= roots + ROOTS_MAX) {
402 tst_brk(TBROK,
403 "Unique controller mounts have exceeded our limit %d?",
404 ROOTS_MAX);
405 }
406
407 root->ver = TST_CGROUP_V1;
408
409 backref:
410 strcpy(root->mnt_path, mnt_dir);
411 root->mnt_dir.dir_root = root;
412 root->mnt_dir.dir_name = root->mnt_path;
413 root->mnt_dir.dir_fd = mnt_dfd;
414 root->ctrl_field = ctrl_field;
415 root->no_cpuset_prefix = no_prefix;
416
417 for_each_ctrl(ctrl) {
418 if (has_ctrl(root->ctrl_field, ctrl))
419 ctrl->ctrl_root = root;
420 }
421
422 return;
423
424 discard:
425 close(mnt_dfd);
426 }
427
tst_cgroup_scan(void)428 void tst_cgroup_scan(void)
429 {
430 struct mntent *mnt;
431 FILE *f = setmntent("/proc/self/mounts", "r");
432
433 if (!f) {
434 tst_brk(TBROK | TERRNO, "Can't open /proc/self/mounts");
435 return;
436 }
437
438 mnt = getmntent(f);
439 if (!mnt) {
440 tst_brk(TBROK | TERRNO, "Can't read mounts or no mounts?");
441 return;
442 }
443
444 do {
445 if (strncmp(mnt->mnt_type, "cgroup", 6))
446 continue;
447
448 cgroup_root_scan(mnt->mnt_type, mnt->mnt_dir, mnt->mnt_opts);
449 } while ((mnt = getmntent(f)));
450 }
451
cgroup_mount_v2(void)452 static void cgroup_mount_v2(void)
453 {
454 char mnt_path[PATH_MAX];
455
456 sprintf(mnt_path, "%s%s", ltp_mount_prefix, ltp_v2_mount);
457
458 if (!mkdir(mnt_path, 0777)) {
459 roots[0].mnt_dir.we_created_it = 1;
460 goto mount;
461 }
462
463 if (errno == EEXIST)
464 goto mount;
465
466 if (errno == EACCES) {
467 tst_res(TINFO | TERRNO,
468 "Lack permission to make %s, premake it or run as root",
469 mnt_path);
470 return;
471 }
472
473 tst_brk(TBROK | TERRNO, "mkdir(%s, 0777)", mnt_path);
474 return;
475
476 mount:
477 if (!mount("cgroup2", mnt_path, "cgroup2", 0, NULL)) {
478 tst_res(TINFO, "Mounted V2 CGroups on %s", mnt_path);
479 tst_cgroup_scan();
480 roots[0].we_mounted_it = 1;
481 return;
482 }
483
484 tst_res(TINFO | TERRNO, "Could not mount V2 CGroups on %s", mnt_path);
485
486 if (roots[0].mnt_dir.we_created_it) {
487 roots[0].mnt_dir.we_created_it = 0;
488 SAFE_RMDIR(mnt_path);
489 }
490 }
491
492 __attribute__ ((nonnull))
cgroup_mount_v1(struct cgroup_ctrl * const ctrl)493 static void cgroup_mount_v1(struct cgroup_ctrl *const ctrl)
494 {
495 char mnt_path[PATH_MAX];
496 int made_dir = 0;
497
498 sprintf(mnt_path, "%s%s", ltp_mount_prefix, ctrl->ctrl_name);
499
500 if (!mkdir(mnt_path, 0777)) {
501 made_dir = 1;
502 goto mount;
503 }
504
505 if (errno == EEXIST)
506 goto mount;
507
508 if (errno == EACCES) {
509 tst_res(TINFO | TERRNO,
510 "Lack permission to make %s, premake it or run as root",
511 mnt_path);
512 return;
513 }
514
515 tst_brk(TBROK | TERRNO, "mkdir(%s, 0777)", mnt_path);
516 return;
517
518 mount:
519 if (mount(ctrl->ctrl_name, mnt_path, "cgroup", 0, ctrl->ctrl_name)) {
520 tst_res(TINFO | TERRNO,
521 "Could not mount V1 CGroup on %s", mnt_path);
522
523 if (made_dir)
524 SAFE_RMDIR(mnt_path);
525 return;
526 }
527
528 tst_res(TINFO, "Mounted V1 %s CGroup on %s", ctrl->ctrl_name, mnt_path);
529 tst_cgroup_scan();
530 if (!ctrl->ctrl_root)
531 return;
532
533 ctrl->ctrl_root->we_mounted_it = 1;
534 ctrl->ctrl_root->mnt_dir.we_created_it = made_dir;
535
536 if (ctrl->ctrl_indx == CTRL_MEMORY) {
537 SAFE_FILE_PRINTFAT(ctrl->ctrl_root->mnt_dir.dir_fd,
538 "memory.use_hierarchy", "%d", 1);
539 }
540 }
541
542 __attribute__ ((nonnull))
cgroup_copy_cpuset(const struct cgroup_root * const root)543 static void cgroup_copy_cpuset(const struct cgroup_root *const root)
544 {
545 char knob_val[BUFSIZ];
546 int i;
547 const char *const n0[] = {"mems", "cpus"};
548 const char *const n1[] = {"cpuset.mems", "cpuset.cpus"};
549 const char *const *const fname = root->no_cpuset_prefix ? n0 : n1;
550
551 for (i = 0; i < 2; i++) {
552 SAFE_FILE_READAT(root->mnt_dir.dir_fd,
553 fname[i], knob_val, sizeof(knob_val));
554 SAFE_FILE_PRINTFAT(root->ltp_dir.dir_fd,
555 fname[i], "%s", knob_val);
556 }
557 }
558
559 /* Ensure the specified controller is available.
560 *
561 * First we check if the specified controller has a known mount point,
562 * if not then we scan the system. If we find it then we goto ensuring
563 * the LTP group exists in the hierarchy the controller is using.
564 *
565 * If we can't find the controller, then we try to create it. First we
566 * check if the V2 hierarchy/tree is mounted. If it isn't then we try
567 * mounting it and look for the controller. If it is already mounted
568 * then we know the controller is not available on V2 on this system.
569 *
570 * If we can't mount V2 or the controller is not on V2, then we try
571 * mounting it on its own V1 tree.
572 *
573 * Once we have mounted the controller somehow, we create a hierarchy
574 * of cgroups. If we are on V2 we first need to enable the controller
575 * for all children of root. Then we create hierarchy described in
576 * tst_cgroup.h.
577 *
578 * If we are using V1 cpuset then we copy the available mems and cpus
579 * from root to the ltp group and set clone_children on the ltp group
580 * to distribute these settings to the test cgroups. This means the
581 * test author does not have to copy these settings before using the
582 * cpuset.
583 *
584 */
tst_cgroup_require(const char * const ctrl_name,const struct tst_cgroup_opts * options)585 void tst_cgroup_require(const char *const ctrl_name,
586 const struct tst_cgroup_opts *options)
587 {
588 const char *const cgsc = "cgroup.subtree_control";
589 struct cgroup_ctrl *const ctrl = cgroup_find_ctrl(ctrl_name);
590 struct cgroup_root *root;
591
592 if (!options)
593 options = &default_opts;
594
595 if (ctrl->we_require_it) {
596 tst_res(TWARN, "Duplicate tst_cgroup_require(%s, )",
597 ctrl->ctrl_name);
598 }
599 ctrl->we_require_it = 1;
600
601 if (ctrl->ctrl_root)
602 goto mkdirs;
603
604 tst_cgroup_scan();
605
606 if (ctrl->ctrl_root)
607 goto mkdirs;
608
609 if (!cgroup_v2_mounted() && !options->only_mount_v1)
610 cgroup_mount_v2();
611
612 if (ctrl->ctrl_root)
613 goto mkdirs;
614
615 cgroup_mount_v1(ctrl);
616
617 if (!ctrl->ctrl_root) {
618 tst_brk(TCONF,
619 "'%s' controller required, but not available",
620 ctrl->ctrl_name);
621 return;
622 }
623
624 mkdirs:
625 root = ctrl->ctrl_root;
626 add_ctrl(&root->mnt_dir.ctrl_field, ctrl);
627
628 if (cgroup_ctrl_on_v2(ctrl)) {
629 if (root->we_mounted_it) {
630 SAFE_FILE_PRINTFAT(root->mnt_dir.dir_fd,
631 cgsc, "+%s", ctrl->ctrl_name);
632 } else {
633 tst_file_printfat(root->mnt_dir.dir_fd,
634 cgsc, "+%s", ctrl->ctrl_name);
635 }
636 }
637
638 if (!root->ltp_dir.dir_fd)
639 cgroup_dir_mk(&root->mnt_dir, ltp_cgroup_dir, &root->ltp_dir);
640 else
641 root->ltp_dir.ctrl_field |= root->mnt_dir.ctrl_field;
642
643 if (cgroup_ctrl_on_v2(ctrl)) {
644 SAFE_FILE_PRINTFAT(root->ltp_dir.dir_fd,
645 cgsc, "+%s", ctrl->ctrl_name);
646 } else {
647 SAFE_FILE_PRINTFAT(root->ltp_dir.dir_fd,
648 "cgroup.clone_children", "%d", 1);
649
650 if (ctrl->ctrl_indx == CTRL_CPUSET)
651 cgroup_copy_cpuset(root);
652 }
653
654 cgroup_dir_mk(&root->ltp_dir, ltp_cgroup_drain_dir, &root->drain_dir);
655
656 sprintf(test_cgroup_dir, "test-%d", getpid());
657 cgroup_dir_mk(&root->ltp_dir, test_cgroup_dir, &root->test_dir);
658 }
659
cgroup_drain(const enum tst_cgroup_ver ver,const int source_dfd,const int dest_dfd)660 static void cgroup_drain(const enum tst_cgroup_ver ver,
661 const int source_dfd, const int dest_dfd)
662 {
663 char pid_list[BUFSIZ];
664 char *tok;
665 const char *const file_name =
666 ver == TST_CGROUP_V1 ? "tasks" : "cgroup.procs";
667 int fd;
668 ssize_t ret;
669
670 ret = SAFE_FILE_READAT(source_dfd, file_name,
671 pid_list, sizeof(pid_list));
672 if (ret < 0)
673 return;
674
675 fd = SAFE_OPENAT(dest_dfd, file_name, O_WRONLY);
676 if (fd < 0)
677 return;
678
679 for (tok = strtok(pid_list, "\n"); tok; tok = strtok(NULL, "\n")) {
680 ret = dprintf(fd, "%s", tok);
681
682 if (ret < (ssize_t)strlen(tok))
683 tst_brk(TBROK | TERRNO, "Failed to drain %s", tok);
684 }
685 SAFE_CLOSE(fd);
686 }
687
688 __attribute__ ((nonnull))
close_path_fds(struct cgroup_root * const root)689 static void close_path_fds(struct cgroup_root *const root)
690 {
691 if (root->test_dir.dir_fd > 0)
692 SAFE_CLOSE(root->test_dir.dir_fd);
693 if (root->ltp_dir.dir_fd > 0)
694 SAFE_CLOSE(root->ltp_dir.dir_fd);
695 if (root->drain_dir.dir_fd > 0)
696 SAFE_CLOSE(root->drain_dir.dir_fd);
697 if (root->mnt_dir.dir_fd > 0)
698 SAFE_CLOSE(root->mnt_dir.dir_fd);
699 }
700
701 /* Maybe remove CGroups used during testing and clear our data
702 *
703 * This will never remove CGroups we did not create to allow tests to
704 * be run in parallel.
705 *
706 * Each test process is given its own unique CGroup. Unless we want to
707 * stress test the CGroup system. We should at least remove these
708 * unique per test CGroups.
709 *
710 * We probably also want to remove the LTP parent CGroup, although
711 * this may have been created by the system manager or another test
712 * (see notes on parallel testing).
713 *
714 * On systems with no initial CGroup setup we may try to destroy the
715 * CGroup roots we mounted so that they can be recreated by another
716 * test. Note that successfully unmounting a CGroup root does not
717 * necessarily indicate that it was destroyed.
718 *
719 * The ltp/drain CGroup is required for cleaning up test CGroups when
720 * we can not move them to the root CGroup. CGroups can only be
721 * removed when they have no members and only leaf or root CGroups may
722 * have processes within them. As test processes create and destroy
723 * their own CGroups they must move themselves either to root or
724 * another leaf CGroup. So we move them to drain while destroying the
725 * unique test CGroup.
726 *
727 * If we have access to root and created the LTP CGroup we then move
728 * the test process to root and destroy the drain and LTP
729 * CGroups. Otherwise we just leave the test process to die in the
730 * drain, much like many a unwanted terrapin.
731 *
732 * Finally we clear any data we have collected on CGroups. This will
733 * happen regardless of whether anything was removed.
734 */
tst_cgroup_cleanup(void)735 void tst_cgroup_cleanup(void)
736 {
737 struct cgroup_root *root;
738 struct cgroup_ctrl *ctrl;
739
740 if (!cgroup_mounted())
741 goto clear_data;
742
743 for_each_root(root) {
744 if (!root->test_dir.dir_name)
745 continue;
746
747 cgroup_drain(root->ver,
748 root->test_dir.dir_fd, root->drain_dir.dir_fd);
749 SAFE_UNLINKAT(root->ltp_dir.dir_fd, root->test_dir.dir_name,
750 AT_REMOVEDIR);
751 }
752
753 for_each_root(root) {
754 if (!root->ltp_dir.we_created_it)
755 continue;
756
757 cgroup_drain(root->ver,
758 root->drain_dir.dir_fd, root->mnt_dir.dir_fd);
759
760 if (root->drain_dir.dir_name) {
761 SAFE_UNLINKAT(root->ltp_dir.dir_fd,
762 root->drain_dir.dir_name, AT_REMOVEDIR);
763 }
764
765 if (root->ltp_dir.dir_name) {
766 SAFE_UNLINKAT(root->mnt_dir.dir_fd,
767 root->ltp_dir.dir_name, AT_REMOVEDIR);
768 }
769 }
770
771 for_each_ctrl(ctrl) {
772 if (!cgroup_ctrl_on_v2(ctrl) || !ctrl->ctrl_root->we_mounted_it)
773 continue;
774
775 SAFE_FILE_PRINTFAT(ctrl->ctrl_root->mnt_dir.dir_fd,
776 "cgroup.subtree_control",
777 "-%s", ctrl->ctrl_name);
778 }
779
780 for_each_root(root) {
781 if (!root->we_mounted_it)
782 continue;
783
784 /* This probably does not result in the CGroup root
785 * being destroyed */
786 if (umount2(root->mnt_path, MNT_DETACH))
787 continue;
788
789 SAFE_RMDIR(root->mnt_path);
790 }
791
792 clear_data:
793 for_each_ctrl(ctrl) {
794 ctrl->ctrl_root = NULL;
795 ctrl->we_require_it = 0;
796 }
797
798 for_each_root(root)
799 close_path_fds(root);
800
801 memset(roots, 0, sizeof(roots));
802 }
803
804 __attribute__ ((nonnull (1)))
cgroup_group_init(struct tst_cgroup_group * const cg,const char * const group_name)805 static void cgroup_group_init(struct tst_cgroup_group *const cg,
806 const char *const group_name)
807 {
808 memset(cg, 0, sizeof(*cg));
809
810 if (!group_name)
811 return;
812
813 if (strlen(group_name) > NAME_MAX)
814 tst_brk(TBROK, "Group name is too long");
815
816 strcpy(cg->group_name, group_name);
817 }
818
819 __attribute__ ((nonnull))
cgroup_group_add_dir(struct tst_cgroup_group * const cg,struct cgroup_dir * const dir)820 static void cgroup_group_add_dir(struct tst_cgroup_group *const cg,
821 struct cgroup_dir *const dir)
822 {
823 const struct cgroup_ctrl *ctrl;
824 int i;
825
826 if (dir->dir_root->ver == TST_CGROUP_V2)
827 cg->dirs_by_ctrl[0] = dir;
828
829 for_each_ctrl(ctrl) {
830 if (has_ctrl(dir->ctrl_field, ctrl))
831 cg->dirs_by_ctrl[ctrl->ctrl_indx] = dir;
832 }
833
834 for (i = 0; cg->dirs[i]; i++);
835 cg->dirs[i] = dir;
836 }
837
838 struct tst_cgroup_group *
tst_cgroup_group_mk(const struct tst_cgroup_group * const parent,const char * const group_name)839 tst_cgroup_group_mk(const struct tst_cgroup_group *const parent,
840 const char *const group_name)
841 {
842 struct tst_cgroup_group *cg;
843 struct cgroup_dir *const *dir;
844 struct cgroup_dir *new_dir;
845
846 cg = SAFE_MALLOC(sizeof(*cg));
847 cgroup_group_init(cg, group_name);
848
849 for_each_dir(parent, 0, dir) {
850 new_dir = SAFE_MALLOC(sizeof(*new_dir));
851 cgroup_dir_mk(*dir, group_name, new_dir);
852 cgroup_group_add_dir(cg, new_dir);
853 }
854
855 return cg;
856 }
857
tst_cgroup_group_rm(struct tst_cgroup_group * const cg)858 struct tst_cgroup_group *tst_cgroup_group_rm(struct tst_cgroup_group *const cg)
859 {
860 struct cgroup_dir **dir;
861
862 for_each_dir(cg, 0, dir) {
863 close((*dir)->dir_fd);
864 SAFE_UNLINKAT((*dir)->dir_parent->dir_fd,
865 (*dir)->dir_name,
866 AT_REMOVEDIR);
867 free(*dir);
868 }
869
870 free(cg);
871 return NULL;
872 }
873
874 __attribute__ ((nonnull, warn_unused_result))
cgroup_file_find(const char * const file,const int lineno,const char * const file_name)875 static const struct cgroup_file *cgroup_file_find(const char *const file,
876 const int lineno,
877 const char *const file_name)
878 {
879 const struct cgroup_file *cfile;
880 const struct cgroup_ctrl *ctrl;
881 char ctrl_name[32];
882 const char *const sep = strchr(file_name, '.');
883 size_t len;
884
885 if (!sep) {
886 tst_brk_(file, lineno, TBROK,
887 "Invalid file name '%s'; did not find controller separator '.'",
888 file_name);
889 return NULL;
890 }
891
892 len = sep - file_name;
893 memcpy(ctrl_name, file_name, len);
894 ctrl_name[len] = '\0';
895
896 ctrl = cgroup_find_ctrl(ctrl_name);
897
898 if (!ctrl) {
899 tst_brk_(file, lineno, TBROK,
900 "Did not find controller '%s'\n", ctrl_name);
901 return NULL;
902 }
903
904 for (cfile = ctrl->files; cfile->file_name; cfile++) {
905 if (!strcmp(file_name, cfile->file_name))
906 break;
907 }
908
909 if (!cfile->file_name) {
910 tst_brk_(file, lineno, TBROK,
911 "Did not find '%s' in '%s'\n",
912 file_name, ctrl->ctrl_name);
913 return NULL;
914 }
915
916 return cfile;
917 }
918
tst_cgroup_ver(const char * const file,const int lineno,const struct tst_cgroup_group * const cg,const char * const ctrl_name)919 enum tst_cgroup_ver tst_cgroup_ver(const char *const file, const int lineno,
920 const struct tst_cgroup_group *const cg,
921 const char *const ctrl_name)
922 {
923 const struct cgroup_ctrl *const ctrl = cgroup_find_ctrl(ctrl_name);
924 const struct cgroup_dir *dir;
925
926 if (!strcmp(ctrl_name, "cgroup")) {
927 tst_brk_(file, lineno,
928 TBROK,
929 "cgroup may be present on both V1 and V2 hierarchies");
930 return 0;
931 }
932
933 if (!ctrl) {
934 tst_brk_(file, lineno,
935 TBROK, "Unknown controller '%s'", ctrl_name);
936 return 0;
937 }
938
939 dir = cg->dirs_by_ctrl[ctrl->ctrl_indx];
940
941 if (!dir) {
942 tst_brk_(file, lineno,
943 TBROK, "%s controller not attached to CGroup %s",
944 ctrl_name, cg->group_name);
945 return 0;
946 }
947
948 return dir->dir_root->ver;
949 }
950
951 __attribute__ ((nonnull, warn_unused_result))
cgroup_file_alias(const struct cgroup_file * const cfile,const struct cgroup_dir * const dir)952 static const char *cgroup_file_alias(const struct cgroup_file *const cfile,
953 const struct cgroup_dir *const dir)
954 {
955 if (dir->dir_root->ver != TST_CGROUP_V1)
956 return cfile->file_name;
957
958 if (cfile->ctrl_indx == CTRL_CPUSET &&
959 dir->dir_root->no_cpuset_prefix &&
960 cfile->file_name_v1) {
961 return strchr(cfile->file_name_v1, '.') + 1;
962 }
963
964 return cfile->file_name_v1;
965 }
966
safe_cgroup_has(const char * const file,const int lineno,const struct tst_cgroup_group * cg,const char * const file_name)967 int safe_cgroup_has(const char *const file, const int lineno,
968 const struct tst_cgroup_group *cg,
969 const char *const file_name)
970 {
971 const struct cgroup_file *const cfile =
972 cgroup_file_find(file, lineno, file_name);
973 struct cgroup_dir *const *dir;
974 const char *alias;
975
976 if (!cfile)
977 return 0;
978
979 for_each_dir(cg, cfile->ctrl_indx, dir) {
980 if (!(alias = cgroup_file_alias(cfile, *dir)))
981 continue;
982
983 if (!faccessat((*dir)->dir_fd, file_name, F_OK, 0))
984 return 1;
985
986 if (errno == ENOENT)
987 continue;
988
989 tst_brk_(file, lineno, TBROK | TERRNO,
990 "faccessat(%d<%s>, %s, F_OK, 0)",
991 (*dir)->dir_fd, tst_decode_fd((*dir)->dir_fd), alias);
992 }
993
994 return 0;
995 }
996
997 __attribute__ ((warn_unused_result))
cgroup_group_from_roots(const size_t tree_off)998 static struct tst_cgroup_group *cgroup_group_from_roots(const size_t tree_off)
999 {
1000 struct cgroup_root *root;
1001 struct cgroup_dir *dir;
1002 struct tst_cgroup_group *cg;
1003
1004 cg = tst_alloc(sizeof(*cg));
1005 cgroup_group_init(cg, NULL);
1006
1007 for_each_root(root) {
1008 dir = (typeof(dir))(((char *)root) + tree_off);
1009
1010 if (dir->ctrl_field)
1011 cgroup_group_add_dir(cg, dir);
1012 }
1013
1014 if (cg->dirs[0]) {
1015 strncpy(cg->group_name, cg->dirs[0]->dir_name, NAME_MAX);
1016 return cg;
1017 }
1018
1019 tst_brk(TBROK,
1020 "No CGroups found; maybe you forgot to call tst_cgroup_require?");
1021
1022 return cg;
1023 }
1024
tst_cgroup_get_test_group(void)1025 const struct tst_cgroup_group *tst_cgroup_get_test_group(void)
1026 {
1027 return cgroup_group_from_roots(offsetof(struct cgroup_root, test_dir));
1028 }
1029
tst_cgroup_get_drain_group(void)1030 const struct tst_cgroup_group *tst_cgroup_get_drain_group(void)
1031 {
1032 return cgroup_group_from_roots(offsetof(struct cgroup_root, drain_dir));
1033 }
1034
safe_cgroup_read(const char * const file,const int lineno,const struct tst_cgroup_group * const cg,const char * const file_name,char * const out,const size_t len)1035 ssize_t safe_cgroup_read(const char *const file, const int lineno,
1036 const struct tst_cgroup_group *const cg,
1037 const char *const file_name,
1038 char *const out, const size_t len)
1039 {
1040 const struct cgroup_file *const cfile =
1041 cgroup_file_find(file, lineno, file_name);
1042 struct cgroup_dir *const *dir;
1043 const char *alias;
1044 size_t prev_len = 0;
1045 char prev_buf[BUFSIZ];
1046
1047 for_each_dir(cg, cfile->ctrl_indx, dir) {
1048 if (!(alias = cgroup_file_alias(cfile, *dir)))
1049 continue;
1050
1051 if (prev_len)
1052 memcpy(prev_buf, out, prev_len);
1053
1054 TEST(safe_file_readat(file, lineno,
1055 (*dir)->dir_fd, alias, out, len));
1056 if (TST_RET < 0)
1057 continue;
1058
1059 if (prev_len && memcmp(out, prev_buf, prev_len)) {
1060 tst_brk_(file, lineno, TBROK,
1061 "%s has different value across roots",
1062 file_name);
1063 break;
1064 }
1065
1066 prev_len = MIN(sizeof(prev_buf), (size_t)TST_RET);
1067 }
1068
1069 out[MAX(TST_RET, 0)] = '\0';
1070
1071 return TST_RET;
1072 }
1073
safe_cgroup_printf(const char * const file,const int lineno,const struct tst_cgroup_group * cg,const char * const file_name,const char * const fmt,...)1074 void safe_cgroup_printf(const char *const file, const int lineno,
1075 const struct tst_cgroup_group *cg,
1076 const char *const file_name,
1077 const char *const fmt, ...)
1078 {
1079 const struct cgroup_file *const cfile =
1080 cgroup_file_find(file, lineno, file_name);
1081 struct cgroup_dir *const *dir;
1082 const char *alias;
1083 va_list va;
1084
1085 for_each_dir(cg, cfile->ctrl_indx, dir) {
1086 if (!(alias = cgroup_file_alias(cfile, *dir)))
1087 continue;
1088
1089 va_start(va, fmt);
1090 safe_file_vprintfat(file, lineno,
1091 (*dir)->dir_fd, alias, fmt, va);
1092 va_end(va);
1093 }
1094 }
1095
safe_cgroup_scanf(const char * const file,const int lineno,const struct tst_cgroup_group * const cg,const char * const file_name,const char * const fmt,...)1096 void safe_cgroup_scanf(const char *const file, const int lineno,
1097 const struct tst_cgroup_group *const cg,
1098 const char *const file_name,
1099 const char *const fmt, ...)
1100 {
1101 va_list va;
1102 char buf[BUFSIZ];
1103 ssize_t len = safe_cgroup_read(file, lineno,
1104 cg, file_name, buf, sizeof(buf));
1105 const int conv_cnt = tst_count_scanf_conversions(fmt);
1106 int ret;
1107
1108 if (len < 1)
1109 return;
1110
1111 va_start(va, fmt);
1112 if ((ret = vsscanf(buf, fmt, va)) < 1) {
1113 tst_brk_(file, lineno, TBROK | TERRNO,
1114 "'%s': vsscanf('%s', '%s', ...)", file_name, buf, fmt);
1115 }
1116 va_end(va);
1117
1118 if (conv_cnt == ret)
1119 return;
1120
1121 tst_brk_(file, lineno, TBROK,
1122 "'%s': vsscanf('%s', '%s', ..): Less conversions than expected: %d != %d",
1123 file_name, buf, fmt, ret, conv_cnt);
1124 }
1125