xref: /linux/tools/testing/selftests/cgroup/test_core.c (revision 6f23fc47c1b2ac226704fb7294f43ed3b0965e51)
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 
21 static 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 
48 static 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((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  */
78 static 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, "cg_test");
86 
87 	if (!cg_test)
88 		goto cleanup;
89 
90 	for (int i = 0; i < 10; i++) {
91 		if (cg_create(cg_test))
92 			goto cleanup;
93 
94 		child_pid = cg_run_nowait(cg_test, alloc_and_touch_anon_noexit,
95 					  (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(cg_test, 1))
102 			goto cleanup;
103 
104 		if (cg_killall(cg_test))
105 			goto cleanup;
106 
107 		/* wait for cgroup to be empty */
108 		while (1) {
109 			if (cg_read(cg_test, "cgroup.procs", buf, 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;
123 cleanup:
124 	if (cg_test)
125 		cg_destroy(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  */
140 static 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, "cg_test_a");
150 	cg_test_b = cg_name(root, "cg_test_a/cg_test_b");
151 	cg_test_c = cg_name(root, "cg_test_a/cg_test_b/cg_test_c");
152 	cg_test_d = cg_name(root, "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(cg_test_a))
158 		goto cleanup;
159 
160 	if (cg_create(cg_test_b))
161 		goto cleanup;
162 
163 	if (cg_create(cg_test_c))
164 		goto cleanup;
165 
166 	if (cg_create(cg_test_d))
167 		goto cleanup;
168 
169 	if (cg_enter_current(cg_test_c))
170 		goto cleanup;
171 
172 	if (cg_read_strcmp(cg_test_a, "cgroup.events", "populated 1\n"))
173 		goto cleanup;
174 
175 	if (cg_read_strcmp(cg_test_b, "cgroup.events", "populated 1\n"))
176 		goto cleanup;
177 
178 	if (cg_read_strcmp(cg_test_c, "cgroup.events", "populated 1\n"))
179 		goto cleanup;
180 
181 	if (cg_read_strcmp(cg_test_d, "cgroup.events", "populated 0\n"))
182 		goto cleanup;
183 
184 	if (cg_enter_current(root))
185 		goto cleanup;
186 
187 	if (cg_read_strcmp(cg_test_a, "cgroup.events", "populated 0\n"))
188 		goto cleanup;
189 
190 	if (cg_read_strcmp(cg_test_b, "cgroup.events", "populated 0\n"))
191 		goto cleanup;
192 
193 	if (cg_read_strcmp(cg_test_c, "cgroup.events", "populated 0\n"))
194 		goto cleanup;
195 
196 	if (cg_read_strcmp(cg_test_d, "cgroup.events", "populated 0\n"))
197 		goto cleanup;
198 
199 	/* Test that we can directly clone into a new cgroup. */
200 	cgroup_fd = dirfd_open_opath(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(cg_test_d, "cgroup.events", "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(cg_test_d, "cgroup.events", "populated 0\n"))
227 		goto cleanup;
228 
229 	/* Remove cgroup. */
230 	if (cg_test_d) {
231 		cg_destroy(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 
244 cleanup_pass:
245 	ret = KSFT_PASS;
246 
247 cleanup:
248 	if (cg_test_d)
249 		cg_destroy(cg_test_d);
250 	if (cg_test_c)
251 		cg_destroy(cg_test_c);
252 	if (cg_test_b)
253 		cg_destroy(cg_test_b);
254 	if (cg_test_a)
255 		cg_destroy(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  */
273 static 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, "cg_test_grandparent");
279 	parent = cg_name(root, "cg_test_grandparent/cg_test_parent");
280 	child = cg_name(root, "cg_test_grandparent/cg_test_parent/cg_test_child");
281 	if (!parent || !child || !grandparent)
282 		goto cleanup;
283 
284 	if (cg_create(grandparent))
285 		goto cleanup;
286 
287 	if (cg_create(parent))
288 		goto cleanup;
289 
290 	if (cg_create(child))
291 		goto cleanup;
292 
293 	if (cg_write(parent, "cgroup.type", "threaded"))
294 		goto cleanup;
295 
296 	if (cg_read_strcmp(child, "cgroup.type", "domain invalid\n"))
297 		goto cleanup;
298 
299 	if (!cg_enter_current(child))
300 		goto cleanup;
301 
302 	if (errno != EOPNOTSUPP)
303 		goto cleanup;
304 
305 	if (!clone_into_cgroup_run_wait(child))
306 		goto cleanup;
307 
308 	if (errno == ENOSYS)
309 		goto cleanup_pass;
310 
311 	if (errno != EOPNOTSUPP)
312 		goto cleanup;
313 
314 cleanup_pass:
315 	ret = KSFT_PASS;
316 
317 cleanup:
318 	cg_enter_current(root);
319 	if (child)
320 		cg_destroy(child);
321 	if (parent)
322 		cg_destroy(parent);
323 	if (grandparent)
324 		cg_destroy(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  */
335 static 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, "cg_test_parent");
341 	child = cg_name(root, "cg_test_parent/cg_test_child");
342 	if (!parent || !child)
343 		goto cleanup;
344 
345 	if (cg_create(parent))
346 		goto cleanup;
347 
348 	if (cg_create(child))
349 		goto cleanup;
350 
351 	if (cg_write(child, "cgroup.type", "threaded"))
352 		goto cleanup;
353 
354 	if (cg_read_strcmp(parent, "cgroup.type", "domain threaded\n"))
355 		goto cleanup;
356 
357 	ret = KSFT_PASS;
358 
359 cleanup:
360 	if (child)
361 		cg_destroy(child);
362 	if (parent)
363 		cg_destroy(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  */
374 static 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(root, "cgroup.controllers", "cpu") ||
380 	    cg_write(root, "cgroup.subtree_control", "+cpu")) {
381 		ret = KSFT_SKIP;
382 		goto cleanup;
383 	}
384 
385 	parent = cg_name(root, "cg_test_parent");
386 	child = cg_name(root, "cg_test_parent/cg_test_child");
387 	if (!parent || !child)
388 		goto cleanup;
389 
390 	if (cg_create(parent))
391 		goto cleanup;
392 
393 	if (cg_create(child))
394 		goto cleanup;
395 
396 	if (cg_write(parent, "cgroup.type", "threaded"))
397 		goto cleanup;
398 
399 	if (cg_write(child, "cgroup.type", "threaded"))
400 		goto cleanup;
401 
402 	if (cg_write(parent, "cgroup.subtree_control", "+cpu"))
403 		goto cleanup;
404 
405 	if (cg_enter_current(parent))
406 		goto cleanup;
407 
408 	ret = KSFT_PASS;
409 
410 cleanup:
411 	cg_enter_current(root);
412 	cg_enter_current(root);
413 	if (child)
414 		cg_destroy(child);
415 	if (parent)
416 		cg_destroy(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  */
426 static 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, "cg_test_parent");
432 	child = cg_name(root, "cg_test_parent/cg_test_child");
433 	if (!parent || !child)
434 		goto cleanup;
435 
436 	if (cg_create(parent))
437 		goto cleanup;
438 
439 	if (cg_create(child))
440 		goto cleanup;
441 
442 	if (!cg_write(child, "cgroup.subtree_control", "+memory"))
443 		goto cleanup;
444 
445 	ret = KSFT_PASS;
446 
447 cleanup:
448 	if (child)
449 		cg_destroy(child);
450 	if (parent)
451 		cg_destroy(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  */
461 static 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, "cg_test_parent");
467 	child = cg_name(root, "cg_test_parent/cg_test_child");
468 	if (!parent || !child)
469 		goto cleanup;
470 
471 	if (cg_create(parent))
472 		goto cleanup;
473 
474 	if (cg_create(child))
475 		goto cleanup;
476 
477 	if (cg_write(parent, "cgroup.subtree_control", "+memory"))
478 		goto cleanup;
479 
480 	if (cg_write(child, "cgroup.subtree_control", "+memory"))
481 		goto cleanup;
482 
483 	if (!cg_write(parent, "cgroup.subtree_control", "-memory"))
484 		goto cleanup;
485 
486 	ret = KSFT_PASS;
487 
488 cleanup:
489 	if (child)
490 		cg_destroy(child);
491 	if (parent)
492 		cg_destroy(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  */
502 static 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, "cg_test_parent");
508 	child = cg_name(root, "cg_test_parent/cg_test_child");
509 	if (!parent || !child)
510 		goto cleanup;
511 
512 	if (cg_create(parent))
513 		goto cleanup;
514 
515 	if (cg_create(child))
516 		goto cleanup;
517 
518 	if (cg_write(parent, "cgroup.subtree_control", "+memory"))
519 		goto cleanup;
520 
521 	if (!cg_enter_current(parent))
522 		goto cleanup;
523 
524 	if (!clone_into_cgroup_run_wait(parent))
525 		goto cleanup;
526 
527 	ret = KSFT_PASS;
528 
529 cleanup:
530 	if (child)
531 		cg_destroy(child);
532 	if (parent)
533 		cg_destroy(parent);
534 	free(child);
535 	free(parent);
536 	return ret;
537 }
538 
539 static 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  */
548 static 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, "cg_src");
556 	dst = cg_name(root, "cg_dst");
557 	if (!src || !dst)
558 		goto cleanup;
559 
560 	if (cg_create(src))
561 		goto cleanup;
562 	if (cg_create(dst))
563 		goto cleanup;
564 
565 	if (cg_enter_current(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(dst);
574 	if (cg_read_lc(dst, "cgroup.threads") != n_threads + 1)
575 		goto cleanup;
576 
577 	ret = KSFT_PASS;
578 
579 cleanup:
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(root);
589 
590 	if (dst)
591 		cg_destroy(dst);
592 	if (src)
593 		cg_destroy(src);
594 	free(dst);
595 	free(src);
596 	return ret;
597 }
598 
599 static 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(lines[g], sizeof(lines[g]), "0::%s", grps[g] + strlen(grps[0]));
607 
608 	for (i = 0; i < n_iterations; ++i) {
609 		cg_enter_current_thread(grps[(i % 2) + 1]);
610 
611 		if (proc_read_strstr(0, 1, "cgroup", 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  */
621 static 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, "cg_dom");
631 	grps[1] = cg_name(root, "cg_dom/cg_src");
632 	grps[2] = cg_name(root, "cg_dom/cg_dst");
633 	if (!grps[1] || !grps[2] || !dom)
634 		goto cleanup;
635 
636 	if (cg_create(dom))
637 		goto cleanup;
638 	if (cg_create(grps[1]))
639 		goto cleanup;
640 	if (cg_create(grps[2]))
641 		goto cleanup;
642 
643 	if (cg_write(grps[1], "cgroup.type", "threaded"))
644 		goto cleanup;
645 	if (cg_write(grps[2], "cgroup.type", "threaded"))
646 		goto cleanup;
647 
648 	if (cg_enter_current(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(line, sizeof(line), "0::%s", grps[1] + strlen(grps[0]));
661 	if (proc_read_strstr(0, 1, "cgroup", line))
662 		goto cleanup;
663 
664 	ret = KSFT_PASS;
665 
666 cleanup:
667 	cg_enter_current(root);
668 	if (grps[2])
669 		cg_destroy(grps[2]);
670 	if (grps[1])
671 		cg_destroy(grps[1]);
672 	if (dom)
673 		cg_destroy(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  */
684 static 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, "cg_test_a");
694 	cg_test_b = cg_name(root, "cg_test_b");
695 
696 	if (!cg_test_a || !cg_test_b)
697 		goto cleanup;
698 
699 	cg_test_a_procs = cg_name(cg_test_a, "cgroup.procs");
700 	cg_test_b_procs = cg_name(cg_test_b, "cgroup.procs");
701 
702 	if (!cg_test_a_procs || !cg_test_b_procs)
703 		goto cleanup;
704 
705 	if (cg_create(cg_test_a) || cg_create(cg_test_b))
706 		goto cleanup;
707 
708 	if (cg_enter_current(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 
732 cleanup:
733 	cg_enter_current(root);
734 	if (cg_test_b_procs_fd >= 0)
735 		close(cg_test_b_procs_fd);
736 	if (cg_test_b)
737 		cg_destroy(cg_test_b);
738 	if (cg_test_a)
739 		cg_destroy(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 
747 struct lesser_ns_open_thread_arg {
748 	const char	*path;
749 	int		fd;
750 	int		err;
751 };
752 
753 static 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  */
766 static 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, "cg_test_a");
779 	cg_test_b = cg_name(root, "cg_test_b");
780 
781 	if (!cg_test_a || !cg_test_b)
782 		goto cleanup;
783 
784 	cg_test_a_procs = cg_name(cg_test_a, "cgroup.procs");
785 	cg_test_b_procs = cg_name(cg_test_b, "cgroup.procs");
786 
787 	if (!cg_test_a_procs || !cg_test_b_procs)
788 		goto cleanup;
789 
790 	if (cg_create(cg_test_a) || cg_create(cg_test_b))
791 		goto cleanup;
792 
793 	if (cg_enter_current(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(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 
825 cleanup:
826 	cg_enter_current(root);
827 	if (cg_test_b_procs_fd >= 0)
828 		close(cg_test_b_procs_fd);
829 	if (cg_test_b)
830 		cg_destroy(cg_test_b);
831 	if (cg_test_a)
832 		cg_destroy(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 }
841 struct 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 
860 int 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, sizeof(root)))
866 		ksft_exit_skip("cgroup v2 isn't mounted\n");
867 
868 	if (cg_read_strstr(root, "cgroup.subtree_control", "memory"))
869 		if (cg_write(root, "cgroup.subtree_control", "+memory"))
870 			ksft_exit_skip("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("%s\n", tests[i].name);
876 			break;
877 		case KSFT_SKIP:
878 			ksft_test_result_skip("%s\n", tests[i].name);
879 			break;
880 		default:
881 			ret = EXIT_FAILURE;
882 			ksft_test_result_fail("%s\n", tests[i].name);
883 			break;
884 		}
885 	}
886 
887 	return ret;
888 }
889