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((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, "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;
123cleanup:
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 */
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, "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
244cleanup_pass:
245	ret = KSFT_PASS;
246
247cleanup:
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 */
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, "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
314cleanup_pass:
315	ret = KSFT_PASS;
316
317cleanup:
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 */
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, "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
359cleanup:
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 */
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(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
410cleanup:
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 */
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, "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
447cleanup:
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 */
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, "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
488cleanup:
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 */
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, "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
529cleanup:
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
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, "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
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(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
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(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 */
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, "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
666cleanup:
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 */
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, "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
732cleanup:
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
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, "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
825cleanup:
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 }
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, 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