1/* SPDX-License-Identifier: GPL-2.0 */
2
3#define _GNU_SOURCE
4#include <linux/limits.h>
5#include <linux/sched.h>
6#include <sys/types.h>
7#include <sys/mman.h>
8#include <sys/wait.h>
9#include <unistd.h>
10#include <fcntl.h>
11#include <sched.h>
12#include <stdio.h>
13#include <errno.h>
14#include <signal.h>
15#include <string.h>
16#include <pthread.h>
17
18#include "../kselftest.h"
19#include "cgroup_util.h"
20
21static int touch_anon(char *buf, size_t size)
22{
23 int fd;
24 char *pos = buf;
25
26 fd = open("/dev/urandom", O_RDONLY);
27 if (fd < 0)
28 return -1;
29
30 while (size > 0) {
31 ssize_t ret = read(fd, pos, size);
32
33 if (ret < 0) {
34 if (errno != EINTR) {
35 close(fd);
36 return -1;
37 }
38 } else {
39 pos += ret;
40 size -= ret;
41 }
42 }
43 close(fd);
44
45 return 0;
46}
47
48static int alloc_and_touch_anon_noexit(const char *cgroup, void *arg)
49{
50 int ppid = getppid();
51 size_t size = (size_t)arg;
52 void *buf;
53
54 buf = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON,
55 0, 0);
56 if (buf == MAP_FAILED)
57 return -1;
58
59 if (touch_anon(buf: (char *)buf, size)) {
60 munmap(buf, size);
61 return -1;
62 }
63
64 while (getppid() == ppid)
65 sleep(1);
66
67 munmap(buf, size);
68 return 0;
69}
70
71/*
72 * Create a child process that allocates and touches 100MB, then waits to be
73 * killed. Wait until the child is attached to the cgroup, kill all processes
74 * in that cgroup and wait until "cgroup.procs" is empty. At this point try to
75 * destroy the empty cgroup. The test helps detect race conditions between
76 * dying processes leaving the cgroup and cgroup destruction path.
77 */
78static int test_cgcore_destroy(const char *root)
79{
80 int ret = KSFT_FAIL;
81 char *cg_test = NULL;
82 int child_pid;
83 char buf[PAGE_SIZE];
84
85 cg_test = cg_name(root, name: "cg_test");
86
87 if (!cg_test)
88 goto cleanup;
89
90 for (int i = 0; i < 10; i++) {
91 if (cg_create(cgroup: cg_test))
92 goto cleanup;
93
94 child_pid = cg_run_nowait(cgroup: cg_test, fn: alloc_and_touch_anon_noexit,
95 arg: (void *) MB(100));
96
97 if (child_pid < 0)
98 goto cleanup;
99
100 /* wait for the child to enter cgroup */
101 if (cg_wait_for_proc_count(cgroup: cg_test, count: 1))
102 goto cleanup;
103
104 if (cg_killall(cgroup: cg_test))
105 goto cleanup;
106
107 /* wait for cgroup to be empty */
108 while (1) {
109 if (cg_read(cgroup: cg_test, control: "cgroup.procs", buf, len: sizeof(buf)))
110 goto cleanup;
111 if (buf[0] == '\0')
112 break;
113 usleep(1000);
114 }
115
116 if (rmdir(cg_test))
117 goto cleanup;
118
119 if (waitpid(child_pid, NULL, 0) < 0)
120 goto cleanup;
121 }
122 ret = KSFT_PASS;
123cleanup:
124 if (cg_test)
125 cg_destroy(cgroup: cg_test);
126 free(cg_test);
127 return ret;
128}
129
130/*
131 * A(0) - B(0) - C(1)
132 * \ D(0)
133 *
134 * A, B and C's "populated" fields would be 1 while D's 0.
135 * test that after the one process in C is moved to root,
136 * A,B and C's "populated" fields would flip to "0" and file
137 * modified events will be generated on the
138 * "cgroup.events" files of both cgroups.
139 */
140static int test_cgcore_populated(const char *root)
141{
142 int ret = KSFT_FAIL;
143 int err;
144 char *cg_test_a = NULL, *cg_test_b = NULL;
145 char *cg_test_c = NULL, *cg_test_d = NULL;
146 int cgroup_fd = -EBADF;
147 pid_t pid;
148
149 cg_test_a = cg_name(root, name: "cg_test_a");
150 cg_test_b = cg_name(root, name: "cg_test_a/cg_test_b");
151 cg_test_c = cg_name(root, name: "cg_test_a/cg_test_b/cg_test_c");
152 cg_test_d = cg_name(root, name: "cg_test_a/cg_test_b/cg_test_d");
153
154 if (!cg_test_a || !cg_test_b || !cg_test_c || !cg_test_d)
155 goto cleanup;
156
157 if (cg_create(cgroup: cg_test_a))
158 goto cleanup;
159
160 if (cg_create(cgroup: cg_test_b))
161 goto cleanup;
162
163 if (cg_create(cgroup: cg_test_c))
164 goto cleanup;
165
166 if (cg_create(cgroup: cg_test_d))
167 goto cleanup;
168
169 if (cg_enter_current(cgroup: cg_test_c))
170 goto cleanup;
171
172 if (cg_read_strcmp(cgroup: cg_test_a, control: "cgroup.events", expected: "populated 1\n"))
173 goto cleanup;
174
175 if (cg_read_strcmp(cgroup: cg_test_b, control: "cgroup.events", expected: "populated 1\n"))
176 goto cleanup;
177
178 if (cg_read_strcmp(cgroup: cg_test_c, control: "cgroup.events", expected: "populated 1\n"))
179 goto cleanup;
180
181 if (cg_read_strcmp(cgroup: cg_test_d, control: "cgroup.events", expected: "populated 0\n"))
182 goto cleanup;
183
184 if (cg_enter_current(cgroup: root))
185 goto cleanup;
186
187 if (cg_read_strcmp(cgroup: cg_test_a, control: "cgroup.events", expected: "populated 0\n"))
188 goto cleanup;
189
190 if (cg_read_strcmp(cgroup: cg_test_b, control: "cgroup.events", expected: "populated 0\n"))
191 goto cleanup;
192
193 if (cg_read_strcmp(cgroup: cg_test_c, control: "cgroup.events", expected: "populated 0\n"))
194 goto cleanup;
195
196 if (cg_read_strcmp(cgroup: cg_test_d, control: "cgroup.events", expected: "populated 0\n"))
197 goto cleanup;
198
199 /* Test that we can directly clone into a new cgroup. */
200 cgroup_fd = dirfd_open_opath(dir: cg_test_d);
201 if (cgroup_fd < 0)
202 goto cleanup;
203
204 pid = clone_into_cgroup(cgroup_fd);
205 if (pid < 0) {
206 if (errno == ENOSYS)
207 goto cleanup_pass;
208 goto cleanup;
209 }
210
211 if (pid == 0) {
212 if (raise(SIGSTOP))
213 exit(EXIT_FAILURE);
214 exit(EXIT_SUCCESS);
215 }
216
217 err = cg_read_strcmp(cgroup: cg_test_d, control: "cgroup.events", expected: "populated 1\n");
218
219 (void)clone_reap(pid, WSTOPPED);
220 (void)kill(pid, SIGCONT);
221 (void)clone_reap(pid, WEXITED);
222
223 if (err)
224 goto cleanup;
225
226 if (cg_read_strcmp(cgroup: cg_test_d, control: "cgroup.events", expected: "populated 0\n"))
227 goto cleanup;
228
229 /* Remove cgroup. */
230 if (cg_test_d) {
231 cg_destroy(cgroup: cg_test_d);
232 free(cg_test_d);
233 cg_test_d = NULL;
234 }
235
236 pid = clone_into_cgroup(cgroup_fd);
237 if (pid < 0)
238 goto cleanup_pass;
239 if (pid == 0)
240 exit(EXIT_SUCCESS);
241 (void)clone_reap(pid, WEXITED);
242 goto cleanup;
243
244cleanup_pass:
245 ret = KSFT_PASS;
246
247cleanup:
248 if (cg_test_d)
249 cg_destroy(cgroup: cg_test_d);
250 if (cg_test_c)
251 cg_destroy(cgroup: cg_test_c);
252 if (cg_test_b)
253 cg_destroy(cgroup: cg_test_b);
254 if (cg_test_a)
255 cg_destroy(cgroup: cg_test_a);
256 free(cg_test_d);
257 free(cg_test_c);
258 free(cg_test_b);
259 free(cg_test_a);
260 if (cgroup_fd >= 0)
261 close(cgroup_fd);
262 return ret;
263}
264
265/*
266 * A (domain threaded) - B (threaded) - C (domain)
267 *
268 * test that C can't be used until it is turned into a
269 * threaded cgroup. "cgroup.type" file will report "domain (invalid)" in
270 * these cases. Operations which fail due to invalid topology use
271 * EOPNOTSUPP as the errno.
272 */
273static int test_cgcore_invalid_domain(const char *root)
274{
275 int ret = KSFT_FAIL;
276 char *grandparent = NULL, *parent = NULL, *child = NULL;
277
278 grandparent = cg_name(root, name: "cg_test_grandparent");
279 parent = cg_name(root, name: "cg_test_grandparent/cg_test_parent");
280 child = cg_name(root, name: "cg_test_grandparent/cg_test_parent/cg_test_child");
281 if (!parent || !child || !grandparent)
282 goto cleanup;
283
284 if (cg_create(cgroup: grandparent))
285 goto cleanup;
286
287 if (cg_create(cgroup: parent))
288 goto cleanup;
289
290 if (cg_create(cgroup: child))
291 goto cleanup;
292
293 if (cg_write(cgroup: parent, control: "cgroup.type", buf: "threaded"))
294 goto cleanup;
295
296 if (cg_read_strcmp(cgroup: child, control: "cgroup.type", expected: "domain invalid\n"))
297 goto cleanup;
298
299 if (!cg_enter_current(cgroup: child))
300 goto cleanup;
301
302 if (errno != EOPNOTSUPP)
303 goto cleanup;
304
305 if (!clone_into_cgroup_run_wait(cgroup: child))
306 goto cleanup;
307
308 if (errno == ENOSYS)
309 goto cleanup_pass;
310
311 if (errno != EOPNOTSUPP)
312 goto cleanup;
313
314cleanup_pass:
315 ret = KSFT_PASS;
316
317cleanup:
318 cg_enter_current(cgroup: root);
319 if (child)
320 cg_destroy(cgroup: child);
321 if (parent)
322 cg_destroy(cgroup: parent);
323 if (grandparent)
324 cg_destroy(cgroup: grandparent);
325 free(child);
326 free(parent);
327 free(grandparent);
328 return ret;
329}
330
331/*
332 * Test that when a child becomes threaded
333 * the parent type becomes domain threaded.
334 */
335static int test_cgcore_parent_becomes_threaded(const char *root)
336{
337 int ret = KSFT_FAIL;
338 char *parent = NULL, *child = NULL;
339
340 parent = cg_name(root, name: "cg_test_parent");
341 child = cg_name(root, name: "cg_test_parent/cg_test_child");
342 if (!parent || !child)
343 goto cleanup;
344
345 if (cg_create(cgroup: parent))
346 goto cleanup;
347
348 if (cg_create(cgroup: child))
349 goto cleanup;
350
351 if (cg_write(cgroup: child, control: "cgroup.type", buf: "threaded"))
352 goto cleanup;
353
354 if (cg_read_strcmp(cgroup: parent, control: "cgroup.type", expected: "domain threaded\n"))
355 goto cleanup;
356
357 ret = KSFT_PASS;
358
359cleanup:
360 if (child)
361 cg_destroy(cgroup: child);
362 if (parent)
363 cg_destroy(cgroup: parent);
364 free(child);
365 free(parent);
366 return ret;
367
368}
369
370/*
371 * Test that there's no internal process constrain on threaded cgroups.
372 * You can add threads/processes on a parent with a controller enabled.
373 */
374static int test_cgcore_no_internal_process_constraint_on_threads(const char *root)
375{
376 int ret = KSFT_FAIL;
377 char *parent = NULL, *child = NULL;
378
379 if (cg_read_strstr(cgroup: root, control: "cgroup.controllers", needle: "cpu") ||
380 cg_write(cgroup: root, control: "cgroup.subtree_control", buf: "+cpu")) {
381 ret = KSFT_SKIP;
382 goto cleanup;
383 }
384
385 parent = cg_name(root, name: "cg_test_parent");
386 child = cg_name(root, name: "cg_test_parent/cg_test_child");
387 if (!parent || !child)
388 goto cleanup;
389
390 if (cg_create(cgroup: parent))
391 goto cleanup;
392
393 if (cg_create(cgroup: child))
394 goto cleanup;
395
396 if (cg_write(cgroup: parent, control: "cgroup.type", buf: "threaded"))
397 goto cleanup;
398
399 if (cg_write(cgroup: child, control: "cgroup.type", buf: "threaded"))
400 goto cleanup;
401
402 if (cg_write(cgroup: parent, control: "cgroup.subtree_control", buf: "+cpu"))
403 goto cleanup;
404
405 if (cg_enter_current(cgroup: parent))
406 goto cleanup;
407
408 ret = KSFT_PASS;
409
410cleanup:
411 cg_enter_current(cgroup: root);
412 cg_enter_current(cgroup: root);
413 if (child)
414 cg_destroy(cgroup: child);
415 if (parent)
416 cg_destroy(cgroup: parent);
417 free(child);
418 free(parent);
419 return ret;
420}
421
422/*
423 * Test that you can't enable a controller on a child if it's not enabled
424 * on the parent.
425 */
426static int test_cgcore_top_down_constraint_enable(const char *root)
427{
428 int ret = KSFT_FAIL;
429 char *parent = NULL, *child = NULL;
430
431 parent = cg_name(root, name: "cg_test_parent");
432 child = cg_name(root, name: "cg_test_parent/cg_test_child");
433 if (!parent || !child)
434 goto cleanup;
435
436 if (cg_create(cgroup: parent))
437 goto cleanup;
438
439 if (cg_create(cgroup: child))
440 goto cleanup;
441
442 if (!cg_write(cgroup: child, control: "cgroup.subtree_control", buf: "+memory"))
443 goto cleanup;
444
445 ret = KSFT_PASS;
446
447cleanup:
448 if (child)
449 cg_destroy(cgroup: child);
450 if (parent)
451 cg_destroy(cgroup: parent);
452 free(child);
453 free(parent);
454 return ret;
455}
456
457/*
458 * Test that you can't disable a controller on a parent
459 * if it's enabled in a child.
460 */
461static int test_cgcore_top_down_constraint_disable(const char *root)
462{
463 int ret = KSFT_FAIL;
464 char *parent = NULL, *child = NULL;
465
466 parent = cg_name(root, name: "cg_test_parent");
467 child = cg_name(root, name: "cg_test_parent/cg_test_child");
468 if (!parent || !child)
469 goto cleanup;
470
471 if (cg_create(cgroup: parent))
472 goto cleanup;
473
474 if (cg_create(cgroup: child))
475 goto cleanup;
476
477 if (cg_write(cgroup: parent, control: "cgroup.subtree_control", buf: "+memory"))
478 goto cleanup;
479
480 if (cg_write(cgroup: child, control: "cgroup.subtree_control", buf: "+memory"))
481 goto cleanup;
482
483 if (!cg_write(cgroup: parent, control: "cgroup.subtree_control", buf: "-memory"))
484 goto cleanup;
485
486 ret = KSFT_PASS;
487
488cleanup:
489 if (child)
490 cg_destroy(cgroup: child);
491 if (parent)
492 cg_destroy(cgroup: parent);
493 free(child);
494 free(parent);
495 return ret;
496}
497
498/*
499 * Test internal process constraint.
500 * You can't add a pid to a domain parent if a controller is enabled.
501 */
502static int test_cgcore_internal_process_constraint(const char *root)
503{
504 int ret = KSFT_FAIL;
505 char *parent = NULL, *child = NULL;
506
507 parent = cg_name(root, name: "cg_test_parent");
508 child = cg_name(root, name: "cg_test_parent/cg_test_child");
509 if (!parent || !child)
510 goto cleanup;
511
512 if (cg_create(cgroup: parent))
513 goto cleanup;
514
515 if (cg_create(cgroup: child))
516 goto cleanup;
517
518 if (cg_write(cgroup: parent, control: "cgroup.subtree_control", buf: "+memory"))
519 goto cleanup;
520
521 if (!cg_enter_current(cgroup: parent))
522 goto cleanup;
523
524 if (!clone_into_cgroup_run_wait(cgroup: parent))
525 goto cleanup;
526
527 ret = KSFT_PASS;
528
529cleanup:
530 if (child)
531 cg_destroy(cgroup: child);
532 if (parent)
533 cg_destroy(cgroup: parent);
534 free(child);
535 free(parent);
536 return ret;
537}
538
539static void *dummy_thread_fn(void *arg)
540{
541 return (void *)(size_t)pause();
542}
543
544/*
545 * Test threadgroup migration.
546 * All threads of a process are migrated together.
547 */
548static int test_cgcore_proc_migration(const char *root)
549{
550 int ret = KSFT_FAIL;
551 int t, c_threads = 0, n_threads = 13;
552 char *src = NULL, *dst = NULL;
553 pthread_t threads[n_threads];
554
555 src = cg_name(root, name: "cg_src");
556 dst = cg_name(root, name: "cg_dst");
557 if (!src || !dst)
558 goto cleanup;
559
560 if (cg_create(cgroup: src))
561 goto cleanup;
562 if (cg_create(cgroup: dst))
563 goto cleanup;
564
565 if (cg_enter_current(cgroup: src))
566 goto cleanup;
567
568 for (c_threads = 0; c_threads < n_threads; ++c_threads) {
569 if (pthread_create(&threads[c_threads], NULL, dummy_thread_fn, NULL))
570 goto cleanup;
571 }
572
573 cg_enter_current(cgroup: dst);
574 if (cg_read_lc(cgroup: dst, control: "cgroup.threads") != n_threads + 1)
575 goto cleanup;
576
577 ret = KSFT_PASS;
578
579cleanup:
580 for (t = 0; t < c_threads; ++t) {
581 pthread_cancel(threads[t]);
582 }
583
584 for (t = 0; t < c_threads; ++t) {
585 pthread_join(threads[t], NULL);
586 }
587
588 cg_enter_current(cgroup: root);
589
590 if (dst)
591 cg_destroy(cgroup: dst);
592 if (src)
593 cg_destroy(cgroup: src);
594 free(dst);
595 free(src);
596 return ret;
597}
598
599static void *migrating_thread_fn(void *arg)
600{
601 int g, i, n_iterations = 1000;
602 char **grps = arg;
603 char lines[3][PATH_MAX];
604
605 for (g = 1; g < 3; ++g)
606 snprintf(buf: lines[g], size: sizeof(lines[g]), fmt: "0::%s", grps[g] + strlen(grps[0]));
607
608 for (i = 0; i < n_iterations; ++i) {
609 cg_enter_current_thread(cgroup: grps[(i % 2) + 1]);
610
611 if (proc_read_strstr(pid: 0, thread: 1, item: "cgroup", needle: lines[(i % 2) + 1]))
612 return (void *)-1;
613 }
614 return NULL;
615}
616
617/*
618 * Test single thread migration.
619 * Threaded cgroups allow successful migration of a thread.
620 */
621static int test_cgcore_thread_migration(const char *root)
622{
623 int ret = KSFT_FAIL;
624 char *dom = NULL;
625 char line[PATH_MAX];
626 char *grps[3] = { (char *)root, NULL, NULL };
627 pthread_t thr;
628 void *retval;
629
630 dom = cg_name(root, name: "cg_dom");
631 grps[1] = cg_name(root, name: "cg_dom/cg_src");
632 grps[2] = cg_name(root, name: "cg_dom/cg_dst");
633 if (!grps[1] || !grps[2] || !dom)
634 goto cleanup;
635
636 if (cg_create(cgroup: dom))
637 goto cleanup;
638 if (cg_create(cgroup: grps[1]))
639 goto cleanup;
640 if (cg_create(cgroup: grps[2]))
641 goto cleanup;
642
643 if (cg_write(cgroup: grps[1], control: "cgroup.type", buf: "threaded"))
644 goto cleanup;
645 if (cg_write(cgroup: grps[2], control: "cgroup.type", buf: "threaded"))
646 goto cleanup;
647
648 if (cg_enter_current(cgroup: grps[1]))
649 goto cleanup;
650
651 if (pthread_create(&thr, NULL, migrating_thread_fn, grps))
652 goto cleanup;
653
654 if (pthread_join(thr, &retval))
655 goto cleanup;
656
657 if (retval)
658 goto cleanup;
659
660 snprintf(buf: line, size: sizeof(line), fmt: "0::%s", grps[1] + strlen(grps[0]));
661 if (proc_read_strstr(pid: 0, thread: 1, item: "cgroup", needle: line))
662 goto cleanup;
663
664 ret = KSFT_PASS;
665
666cleanup:
667 cg_enter_current(cgroup: root);
668 if (grps[2])
669 cg_destroy(cgroup: grps[2]);
670 if (grps[1])
671 cg_destroy(cgroup: grps[1]);
672 if (dom)
673 cg_destroy(cgroup: dom);
674 free(grps[2]);
675 free(grps[1]);
676 free(dom);
677 return ret;
678}
679
680/*
681 * cgroup migration permission check should be performed based on the
682 * credentials at the time of open instead of write.
683 */
684static int test_cgcore_lesser_euid_open(const char *root)
685{
686 const uid_t test_euid = TEST_UID;
687 int ret = KSFT_FAIL;
688 char *cg_test_a = NULL, *cg_test_b = NULL;
689 char *cg_test_a_procs = NULL, *cg_test_b_procs = NULL;
690 int cg_test_b_procs_fd = -1;
691 uid_t saved_uid;
692
693 cg_test_a = cg_name(root, name: "cg_test_a");
694 cg_test_b = cg_name(root, name: "cg_test_b");
695
696 if (!cg_test_a || !cg_test_b)
697 goto cleanup;
698
699 cg_test_a_procs = cg_name(root: cg_test_a, name: "cgroup.procs");
700 cg_test_b_procs = cg_name(root: cg_test_b, name: "cgroup.procs");
701
702 if (!cg_test_a_procs || !cg_test_b_procs)
703 goto cleanup;
704
705 if (cg_create(cgroup: cg_test_a) || cg_create(cgroup: cg_test_b))
706 goto cleanup;
707
708 if (cg_enter_current(cgroup: cg_test_a))
709 goto cleanup;
710
711 if (chown(cg_test_a_procs, test_euid, -1) ||
712 chown(cg_test_b_procs, test_euid, -1))
713 goto cleanup;
714
715 saved_uid = geteuid();
716 if (seteuid(test_euid))
717 goto cleanup;
718
719 cg_test_b_procs_fd = open(cg_test_b_procs, O_RDWR);
720
721 if (seteuid(saved_uid))
722 goto cleanup;
723
724 if (cg_test_b_procs_fd < 0)
725 goto cleanup;
726
727 if (write(cg_test_b_procs_fd, "0", 1) >= 0 || errno != EACCES)
728 goto cleanup;
729
730 ret = KSFT_PASS;
731
732cleanup:
733 cg_enter_current(cgroup: root);
734 if (cg_test_b_procs_fd >= 0)
735 close(cg_test_b_procs_fd);
736 if (cg_test_b)
737 cg_destroy(cgroup: cg_test_b);
738 if (cg_test_a)
739 cg_destroy(cgroup: cg_test_a);
740 free(cg_test_b_procs);
741 free(cg_test_a_procs);
742 free(cg_test_b);
743 free(cg_test_a);
744 return ret;
745}
746
747struct lesser_ns_open_thread_arg {
748 const char *path;
749 int fd;
750 int err;
751};
752
753static int lesser_ns_open_thread_fn(void *arg)
754{
755 struct lesser_ns_open_thread_arg *targ = arg;
756
757 targ->fd = open(targ->path, O_RDWR);
758 targ->err = errno;
759 return 0;
760}
761
762/*
763 * cgroup migration permission check should be performed based on the cgroup
764 * namespace at the time of open instead of write.
765 */
766static int test_cgcore_lesser_ns_open(const char *root)
767{
768 static char stack[65536];
769 const uid_t test_euid = 65534; /* usually nobody, any !root is fine */
770 int ret = KSFT_FAIL;
771 char *cg_test_a = NULL, *cg_test_b = NULL;
772 char *cg_test_a_procs = NULL, *cg_test_b_procs = NULL;
773 int cg_test_b_procs_fd = -1;
774 struct lesser_ns_open_thread_arg targ = { .fd = -1 };
775 pid_t pid;
776 int status;
777
778 cg_test_a = cg_name(root, name: "cg_test_a");
779 cg_test_b = cg_name(root, name: "cg_test_b");
780
781 if (!cg_test_a || !cg_test_b)
782 goto cleanup;
783
784 cg_test_a_procs = cg_name(root: cg_test_a, name: "cgroup.procs");
785 cg_test_b_procs = cg_name(root: cg_test_b, name: "cgroup.procs");
786
787 if (!cg_test_a_procs || !cg_test_b_procs)
788 goto cleanup;
789
790 if (cg_create(cgroup: cg_test_a) || cg_create(cgroup: cg_test_b))
791 goto cleanup;
792
793 if (cg_enter_current(cgroup: cg_test_b))
794 goto cleanup;
795
796 if (chown(cg_test_a_procs, test_euid, -1) ||
797 chown(cg_test_b_procs, test_euid, -1))
798 goto cleanup;
799
800 targ.path = cg_test_b_procs;
801 pid = clone(lesser_ns_open_thread_fn, stack + sizeof(stack),
802 CLONE_NEWCGROUP | CLONE_FILES | CLONE_VM | SIGCHLD,
803 &targ);
804 if (pid < 0)
805 goto cleanup;
806
807 if (waitpid(pid, &status, 0) < 0)
808 goto cleanup;
809
810 if (!WIFEXITED(status))
811 goto cleanup;
812
813 cg_test_b_procs_fd = targ.fd;
814 if (cg_test_b_procs_fd < 0)
815 goto cleanup;
816
817 if (cg_enter_current(cgroup: cg_test_a))
818 goto cleanup;
819
820 if ((status = write(cg_test_b_procs_fd, "0", 1)) >= 0 || errno != ENOENT)
821 goto cleanup;
822
823 ret = KSFT_PASS;
824
825cleanup:
826 cg_enter_current(cgroup: root);
827 if (cg_test_b_procs_fd >= 0)
828 close(cg_test_b_procs_fd);
829 if (cg_test_b)
830 cg_destroy(cgroup: cg_test_b);
831 if (cg_test_a)
832 cg_destroy(cgroup: cg_test_a);
833 free(cg_test_b_procs);
834 free(cg_test_a_procs);
835 free(cg_test_b);
836 free(cg_test_a);
837 return ret;
838}
839
840#define T(x) { x, #x }
841struct corecg_test {
842 int (*fn)(const char *root);
843 const char *name;
844} tests[] = {
845 T(test_cgcore_internal_process_constraint),
846 T(test_cgcore_top_down_constraint_enable),
847 T(test_cgcore_top_down_constraint_disable),
848 T(test_cgcore_no_internal_process_constraint_on_threads),
849 T(test_cgcore_parent_becomes_threaded),
850 T(test_cgcore_invalid_domain),
851 T(test_cgcore_populated),
852 T(test_cgcore_proc_migration),
853 T(test_cgcore_thread_migration),
854 T(test_cgcore_destroy),
855 T(test_cgcore_lesser_euid_open),
856 T(test_cgcore_lesser_ns_open),
857};
858#undef T
859
860int main(int argc, char *argv[])
861{
862 char root[PATH_MAX];
863 int i, ret = EXIT_SUCCESS;
864
865 if (cg_find_unified_root(root, len: sizeof(root)))
866 ksft_exit_skip(msg: "cgroup v2 isn't mounted\n");
867
868 if (cg_read_strstr(cgroup: root, control: "cgroup.subtree_control", needle: "memory"))
869 if (cg_write(cgroup: root, control: "cgroup.subtree_control", buf: "+memory"))
870 ksft_exit_skip(msg: "Failed to set memory controller\n");
871
872 for (i = 0; i < ARRAY_SIZE(tests); i++) {
873 switch (tests[i].fn(root)) {
874 case KSFT_PASS:
875 ksft_test_result_pass(msg: "%s\n", tests[i].name);
876 break;
877 case KSFT_SKIP:
878 ksft_test_result_skip(msg: "%s\n", tests[i].name);
879 break;
880 default:
881 ret = EXIT_FAILURE;
882 ksft_test_result_fail(msg: "%s\n", tests[i].name);
883 break;
884 }
885 }
886
887 return ret;
888}
889

source code of linux/tools/testing/selftests/cgroup/test_core.c