xref: /linux/tools/testing/selftests/cgroup/test_core.c (revision 36ec807b627b4c0a0a382f0ae48eac7187d14b2b)
1d863cb03SClaudio /* SPDX-License-Identifier: GPL-2.0 */
2*a97853f2SShuah Khan 
3*a97853f2SShuah Khan #define _GNU_SOURCE
4d863cb03SClaudio #include <linux/limits.h>
5bf35a787STejun Heo #include <linux/sched.h>
6d863cb03SClaudio #include <sys/types.h>
704189382SSuren Baghdasaryan #include <sys/mman.h>
804189382SSuren Baghdasaryan #include <sys/wait.h>
9d863cb03SClaudio #include <unistd.h>
1004189382SSuren Baghdasaryan #include <fcntl.h>
11bf35a787STejun Heo #include <sched.h>
12d863cb03SClaudio #include <stdio.h>
13d863cb03SClaudio #include <errno.h>
1411318989SMichal Koutný #include <signal.h>
1511318989SMichal Koutný #include <string.h>
1611318989SMichal Koutný #include <pthread.h>
17d863cb03SClaudio 
18d863cb03SClaudio #include "../kselftest.h"
19d863cb03SClaudio #include "cgroup_util.h"
20d863cb03SClaudio 
214793cb59STianchen Ding static bool nsdelegate;
224793cb59STianchen Ding 
2304189382SSuren Baghdasaryan static int touch_anon(char *buf, size_t size)
2404189382SSuren Baghdasaryan {
2504189382SSuren Baghdasaryan 	int fd;
2604189382SSuren Baghdasaryan 	char *pos = buf;
2704189382SSuren Baghdasaryan 
2804189382SSuren Baghdasaryan 	fd = open("/dev/urandom", O_RDONLY);
2904189382SSuren Baghdasaryan 	if (fd < 0)
3004189382SSuren Baghdasaryan 		return -1;
3104189382SSuren Baghdasaryan 
3204189382SSuren Baghdasaryan 	while (size > 0) {
3304189382SSuren Baghdasaryan 		ssize_t ret = read(fd, pos, size);
3404189382SSuren Baghdasaryan 
3504189382SSuren Baghdasaryan 		if (ret < 0) {
3604189382SSuren Baghdasaryan 			if (errno != EINTR) {
3704189382SSuren Baghdasaryan 				close(fd);
3804189382SSuren Baghdasaryan 				return -1;
3904189382SSuren Baghdasaryan 			}
4004189382SSuren Baghdasaryan 		} else {
4104189382SSuren Baghdasaryan 			pos += ret;
4204189382SSuren Baghdasaryan 			size -= ret;
4304189382SSuren Baghdasaryan 		}
4404189382SSuren Baghdasaryan 	}
4504189382SSuren Baghdasaryan 	close(fd);
4604189382SSuren Baghdasaryan 
4704189382SSuren Baghdasaryan 	return 0;
4804189382SSuren Baghdasaryan }
4904189382SSuren Baghdasaryan 
5004189382SSuren Baghdasaryan static int alloc_and_touch_anon_noexit(const char *cgroup, void *arg)
5104189382SSuren Baghdasaryan {
5204189382SSuren Baghdasaryan 	int ppid = getppid();
5304189382SSuren Baghdasaryan 	size_t size = (size_t)arg;
5404189382SSuren Baghdasaryan 	void *buf;
5504189382SSuren Baghdasaryan 
5604189382SSuren Baghdasaryan 	buf = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON,
5704189382SSuren Baghdasaryan 		   0, 0);
5804189382SSuren Baghdasaryan 	if (buf == MAP_FAILED)
5904189382SSuren Baghdasaryan 		return -1;
6004189382SSuren Baghdasaryan 
6104189382SSuren Baghdasaryan 	if (touch_anon((char *)buf, size)) {
6204189382SSuren Baghdasaryan 		munmap(buf, size);
6304189382SSuren Baghdasaryan 		return -1;
6404189382SSuren Baghdasaryan 	}
6504189382SSuren Baghdasaryan 
6604189382SSuren Baghdasaryan 	while (getppid() == ppid)
6704189382SSuren Baghdasaryan 		sleep(1);
6804189382SSuren Baghdasaryan 
6904189382SSuren Baghdasaryan 	munmap(buf, size);
7004189382SSuren Baghdasaryan 	return 0;
7104189382SSuren Baghdasaryan }
7204189382SSuren Baghdasaryan 
7304189382SSuren Baghdasaryan /*
7404189382SSuren Baghdasaryan  * Create a child process that allocates and touches 100MB, then waits to be
7504189382SSuren Baghdasaryan  * killed. Wait until the child is attached to the cgroup, kill all processes
7604189382SSuren Baghdasaryan  * in that cgroup and wait until "cgroup.procs" is empty. At this point try to
7704189382SSuren Baghdasaryan  * destroy the empty cgroup. The test helps detect race conditions between
7804189382SSuren Baghdasaryan  * dying processes leaving the cgroup and cgroup destruction path.
7904189382SSuren Baghdasaryan  */
8004189382SSuren Baghdasaryan static int test_cgcore_destroy(const char *root)
8104189382SSuren Baghdasaryan {
8204189382SSuren Baghdasaryan 	int ret = KSFT_FAIL;
8304189382SSuren Baghdasaryan 	char *cg_test = NULL;
8404189382SSuren Baghdasaryan 	int child_pid;
8504189382SSuren Baghdasaryan 	char buf[PAGE_SIZE];
8604189382SSuren Baghdasaryan 
8704189382SSuren Baghdasaryan 	cg_test = cg_name(root, "cg_test");
8804189382SSuren Baghdasaryan 
8904189382SSuren Baghdasaryan 	if (!cg_test)
9004189382SSuren Baghdasaryan 		goto cleanup;
9104189382SSuren Baghdasaryan 
9204189382SSuren Baghdasaryan 	for (int i = 0; i < 10; i++) {
9304189382SSuren Baghdasaryan 		if (cg_create(cg_test))
9404189382SSuren Baghdasaryan 			goto cleanup;
9504189382SSuren Baghdasaryan 
9604189382SSuren Baghdasaryan 		child_pid = cg_run_nowait(cg_test, alloc_and_touch_anon_noexit,
9704189382SSuren Baghdasaryan 					  (void *) MB(100));
9804189382SSuren Baghdasaryan 
9904189382SSuren Baghdasaryan 		if (child_pid < 0)
10004189382SSuren Baghdasaryan 			goto cleanup;
10104189382SSuren Baghdasaryan 
10204189382SSuren Baghdasaryan 		/* wait for the child to enter cgroup */
10304189382SSuren Baghdasaryan 		if (cg_wait_for_proc_count(cg_test, 1))
10404189382SSuren Baghdasaryan 			goto cleanup;
10504189382SSuren Baghdasaryan 
10604189382SSuren Baghdasaryan 		if (cg_killall(cg_test))
10704189382SSuren Baghdasaryan 			goto cleanup;
10804189382SSuren Baghdasaryan 
10904189382SSuren Baghdasaryan 		/* wait for cgroup to be empty */
11004189382SSuren Baghdasaryan 		while (1) {
11104189382SSuren Baghdasaryan 			if (cg_read(cg_test, "cgroup.procs", buf, sizeof(buf)))
11204189382SSuren Baghdasaryan 				goto cleanup;
11304189382SSuren Baghdasaryan 			if (buf[0] == '\0')
11404189382SSuren Baghdasaryan 				break;
11504189382SSuren Baghdasaryan 			usleep(1000);
11604189382SSuren Baghdasaryan 		}
11704189382SSuren Baghdasaryan 
11804189382SSuren Baghdasaryan 		if (rmdir(cg_test))
11904189382SSuren Baghdasaryan 			goto cleanup;
12004189382SSuren Baghdasaryan 
12104189382SSuren Baghdasaryan 		if (waitpid(child_pid, NULL, 0) < 0)
12204189382SSuren Baghdasaryan 			goto cleanup;
12304189382SSuren Baghdasaryan 	}
12404189382SSuren Baghdasaryan 	ret = KSFT_PASS;
12504189382SSuren Baghdasaryan cleanup:
12604189382SSuren Baghdasaryan 	if (cg_test)
12704189382SSuren Baghdasaryan 		cg_destroy(cg_test);
12804189382SSuren Baghdasaryan 	free(cg_test);
12904189382SSuren Baghdasaryan 	return ret;
13004189382SSuren Baghdasaryan }
13104189382SSuren Baghdasaryan 
132d863cb03SClaudio /*
133d863cb03SClaudio  * A(0) - B(0) - C(1)
134d863cb03SClaudio  *        \ D(0)
135d863cb03SClaudio  *
136d863cb03SClaudio  * A, B and C's "populated" fields would be 1 while D's 0.
137d863cb03SClaudio  * test that after the one process in C is moved to root,
138d863cb03SClaudio  * A,B and C's "populated" fields would flip to "0" and file
139d863cb03SClaudio  * modified events will be generated on the
140d863cb03SClaudio  * "cgroup.events" files of both cgroups.
141d863cb03SClaudio  */
142d863cb03SClaudio static int test_cgcore_populated(const char *root)
143d863cb03SClaudio {
144d863cb03SClaudio 	int ret = KSFT_FAIL;
1459bd5910dSChristian Brauner 	int err;
146d863cb03SClaudio 	char *cg_test_a = NULL, *cg_test_b = NULL;
147d863cb03SClaudio 	char *cg_test_c = NULL, *cg_test_d = NULL;
1489bd5910dSChristian Brauner 	int cgroup_fd = -EBADF;
1499bd5910dSChristian Brauner 	pid_t pid;
150d863cb03SClaudio 
151d863cb03SClaudio 	cg_test_a = cg_name(root, "cg_test_a");
152d863cb03SClaudio 	cg_test_b = cg_name(root, "cg_test_a/cg_test_b");
153d863cb03SClaudio 	cg_test_c = cg_name(root, "cg_test_a/cg_test_b/cg_test_c");
154d863cb03SClaudio 	cg_test_d = cg_name(root, "cg_test_a/cg_test_b/cg_test_d");
155d863cb03SClaudio 
156d863cb03SClaudio 	if (!cg_test_a || !cg_test_b || !cg_test_c || !cg_test_d)
157d863cb03SClaudio 		goto cleanup;
158d863cb03SClaudio 
159d863cb03SClaudio 	if (cg_create(cg_test_a))
160d863cb03SClaudio 		goto cleanup;
161d863cb03SClaudio 
162d863cb03SClaudio 	if (cg_create(cg_test_b))
163d863cb03SClaudio 		goto cleanup;
164d863cb03SClaudio 
165d863cb03SClaudio 	if (cg_create(cg_test_c))
166d863cb03SClaudio 		goto cleanup;
167d863cb03SClaudio 
168d863cb03SClaudio 	if (cg_create(cg_test_d))
169d863cb03SClaudio 		goto cleanup;
170d863cb03SClaudio 
171d863cb03SClaudio 	if (cg_enter_current(cg_test_c))
172d863cb03SClaudio 		goto cleanup;
173d863cb03SClaudio 
174d863cb03SClaudio 	if (cg_read_strcmp(cg_test_a, "cgroup.events", "populated 1\n"))
175d863cb03SClaudio 		goto cleanup;
176d863cb03SClaudio 
177d863cb03SClaudio 	if (cg_read_strcmp(cg_test_b, "cgroup.events", "populated 1\n"))
178d863cb03SClaudio 		goto cleanup;
179d863cb03SClaudio 
180d863cb03SClaudio 	if (cg_read_strcmp(cg_test_c, "cgroup.events", "populated 1\n"))
181d863cb03SClaudio 		goto cleanup;
182d863cb03SClaudio 
183d863cb03SClaudio 	if (cg_read_strcmp(cg_test_d, "cgroup.events", "populated 0\n"))
184d863cb03SClaudio 		goto cleanup;
185d863cb03SClaudio 
186d863cb03SClaudio 	if (cg_enter_current(root))
187d863cb03SClaudio 		goto cleanup;
188d863cb03SClaudio 
189d863cb03SClaudio 	if (cg_read_strcmp(cg_test_a, "cgroup.events", "populated 0\n"))
190d863cb03SClaudio 		goto cleanup;
191d863cb03SClaudio 
192d863cb03SClaudio 	if (cg_read_strcmp(cg_test_b, "cgroup.events", "populated 0\n"))
193d863cb03SClaudio 		goto cleanup;
194d863cb03SClaudio 
195d863cb03SClaudio 	if (cg_read_strcmp(cg_test_c, "cgroup.events", "populated 0\n"))
196d863cb03SClaudio 		goto cleanup;
197d863cb03SClaudio 
198d863cb03SClaudio 	if (cg_read_strcmp(cg_test_d, "cgroup.events", "populated 0\n"))
199d863cb03SClaudio 		goto cleanup;
200d863cb03SClaudio 
2019bd5910dSChristian Brauner 	/* Test that we can directly clone into a new cgroup. */
2029bd5910dSChristian Brauner 	cgroup_fd = dirfd_open_opath(cg_test_d);
2039bd5910dSChristian Brauner 	if (cgroup_fd < 0)
2049bd5910dSChristian Brauner 		goto cleanup;
2059bd5910dSChristian Brauner 
2069bd5910dSChristian Brauner 	pid = clone_into_cgroup(cgroup_fd);
2079bd5910dSChristian Brauner 	if (pid < 0) {
2089bd5910dSChristian Brauner 		if (errno == ENOSYS)
2099bd5910dSChristian Brauner 			goto cleanup_pass;
2109bd5910dSChristian Brauner 		goto cleanup;
2119bd5910dSChristian Brauner 	}
2129bd5910dSChristian Brauner 
2139bd5910dSChristian Brauner 	if (pid == 0) {
2149bd5910dSChristian Brauner 		if (raise(SIGSTOP))
2159bd5910dSChristian Brauner 			exit(EXIT_FAILURE);
2169bd5910dSChristian Brauner 		exit(EXIT_SUCCESS);
2179bd5910dSChristian Brauner 	}
2189bd5910dSChristian Brauner 
2199bd5910dSChristian Brauner 	err = cg_read_strcmp(cg_test_d, "cgroup.events", "populated 1\n");
2209bd5910dSChristian Brauner 
2219bd5910dSChristian Brauner 	(void)clone_reap(pid, WSTOPPED);
2229bd5910dSChristian Brauner 	(void)kill(pid, SIGCONT);
2239bd5910dSChristian Brauner 	(void)clone_reap(pid, WEXITED);
2249bd5910dSChristian Brauner 
2259bd5910dSChristian Brauner 	if (err)
2269bd5910dSChristian Brauner 		goto cleanup;
2279bd5910dSChristian Brauner 
2289bd5910dSChristian Brauner 	if (cg_read_strcmp(cg_test_d, "cgroup.events", "populated 0\n"))
2299bd5910dSChristian Brauner 		goto cleanup;
2309bd5910dSChristian Brauner 
2319bd5910dSChristian Brauner 	/* Remove cgroup. */
2329bd5910dSChristian Brauner 	if (cg_test_d) {
2339bd5910dSChristian Brauner 		cg_destroy(cg_test_d);
2349bd5910dSChristian Brauner 		free(cg_test_d);
2359bd5910dSChristian Brauner 		cg_test_d = NULL;
2369bd5910dSChristian Brauner 	}
2379bd5910dSChristian Brauner 
2389bd5910dSChristian Brauner 	pid = clone_into_cgroup(cgroup_fd);
2399bd5910dSChristian Brauner 	if (pid < 0)
2409bd5910dSChristian Brauner 		goto cleanup_pass;
2419bd5910dSChristian Brauner 	if (pid == 0)
2429bd5910dSChristian Brauner 		exit(EXIT_SUCCESS);
2439bd5910dSChristian Brauner 	(void)clone_reap(pid, WEXITED);
2449bd5910dSChristian Brauner 	goto cleanup;
2459bd5910dSChristian Brauner 
2469bd5910dSChristian Brauner cleanup_pass:
247d863cb03SClaudio 	ret = KSFT_PASS;
248d863cb03SClaudio 
249d863cb03SClaudio cleanup:
250d863cb03SClaudio 	if (cg_test_d)
251d863cb03SClaudio 		cg_destroy(cg_test_d);
252d863cb03SClaudio 	if (cg_test_c)
253d863cb03SClaudio 		cg_destroy(cg_test_c);
254d863cb03SClaudio 	if (cg_test_b)
255d863cb03SClaudio 		cg_destroy(cg_test_b);
256d863cb03SClaudio 	if (cg_test_a)
257d863cb03SClaudio 		cg_destroy(cg_test_a);
258d863cb03SClaudio 	free(cg_test_d);
259d863cb03SClaudio 	free(cg_test_c);
260d863cb03SClaudio 	free(cg_test_b);
261d863cb03SClaudio 	free(cg_test_a);
2629bd5910dSChristian Brauner 	if (cgroup_fd >= 0)
2639bd5910dSChristian Brauner 		close(cgroup_fd);
264d863cb03SClaudio 	return ret;
265d863cb03SClaudio }
266d863cb03SClaudio 
267d863cb03SClaudio /*
268d863cb03SClaudio  * A (domain threaded) - B (threaded) - C (domain)
269d863cb03SClaudio  *
270d863cb03SClaudio  * test that C can't be used until it is turned into a
271d863cb03SClaudio  * threaded cgroup.  "cgroup.type" file will report "domain (invalid)" in
272d863cb03SClaudio  * these cases. Operations which fail due to invalid topology use
273d863cb03SClaudio  * EOPNOTSUPP as the errno.
274d863cb03SClaudio  */
275d863cb03SClaudio static int test_cgcore_invalid_domain(const char *root)
276d863cb03SClaudio {
277d863cb03SClaudio 	int ret = KSFT_FAIL;
278d863cb03SClaudio 	char *grandparent = NULL, *parent = NULL, *child = NULL;
279d863cb03SClaudio 
280d863cb03SClaudio 	grandparent = cg_name(root, "cg_test_grandparent");
281d863cb03SClaudio 	parent = cg_name(root, "cg_test_grandparent/cg_test_parent");
282d863cb03SClaudio 	child = cg_name(root, "cg_test_grandparent/cg_test_parent/cg_test_child");
283d863cb03SClaudio 	if (!parent || !child || !grandparent)
284d863cb03SClaudio 		goto cleanup;
285d863cb03SClaudio 
286d863cb03SClaudio 	if (cg_create(grandparent))
287d863cb03SClaudio 		goto cleanup;
288d863cb03SClaudio 
289d863cb03SClaudio 	if (cg_create(parent))
290d863cb03SClaudio 		goto cleanup;
291d863cb03SClaudio 
292d863cb03SClaudio 	if (cg_create(child))
293d863cb03SClaudio 		goto cleanup;
294d863cb03SClaudio 
295d863cb03SClaudio 	if (cg_write(parent, "cgroup.type", "threaded"))
296d863cb03SClaudio 		goto cleanup;
297d863cb03SClaudio 
298d863cb03SClaudio 	if (cg_read_strcmp(child, "cgroup.type", "domain invalid\n"))
299d863cb03SClaudio 		goto cleanup;
300d863cb03SClaudio 
301d863cb03SClaudio 	if (!cg_enter_current(child))
302d863cb03SClaudio 		goto cleanup;
303d863cb03SClaudio 
304d863cb03SClaudio 	if (errno != EOPNOTSUPP)
305d863cb03SClaudio 		goto cleanup;
306d863cb03SClaudio 
3079bd5910dSChristian Brauner 	if (!clone_into_cgroup_run_wait(child))
3089bd5910dSChristian Brauner 		goto cleanup;
3099bd5910dSChristian Brauner 
3109bd5910dSChristian Brauner 	if (errno == ENOSYS)
3119bd5910dSChristian Brauner 		goto cleanup_pass;
3129bd5910dSChristian Brauner 
3139bd5910dSChristian Brauner 	if (errno != EOPNOTSUPP)
3149bd5910dSChristian Brauner 		goto cleanup;
3159bd5910dSChristian Brauner 
3169bd5910dSChristian Brauner cleanup_pass:
317d863cb03SClaudio 	ret = KSFT_PASS;
318d863cb03SClaudio 
319d863cb03SClaudio cleanup:
320d863cb03SClaudio 	cg_enter_current(root);
321d863cb03SClaudio 	if (child)
322d863cb03SClaudio 		cg_destroy(child);
323d863cb03SClaudio 	if (parent)
324d863cb03SClaudio 		cg_destroy(parent);
325d863cb03SClaudio 	if (grandparent)
326d863cb03SClaudio 		cg_destroy(grandparent);
327d863cb03SClaudio 	free(child);
328d863cb03SClaudio 	free(parent);
329d863cb03SClaudio 	free(grandparent);
330d863cb03SClaudio 	return ret;
331d863cb03SClaudio }
332d863cb03SClaudio 
333d863cb03SClaudio /*
334d863cb03SClaudio  * Test that when a child becomes threaded
335d863cb03SClaudio  * the parent type becomes domain threaded.
336d863cb03SClaudio  */
337d863cb03SClaudio static int test_cgcore_parent_becomes_threaded(const char *root)
338d863cb03SClaudio {
339d863cb03SClaudio 	int ret = KSFT_FAIL;
340d863cb03SClaudio 	char *parent = NULL, *child = NULL;
341d863cb03SClaudio 
342d863cb03SClaudio 	parent = cg_name(root, "cg_test_parent");
343d863cb03SClaudio 	child = cg_name(root, "cg_test_parent/cg_test_child");
344d863cb03SClaudio 	if (!parent || !child)
345d863cb03SClaudio 		goto cleanup;
346d863cb03SClaudio 
347d863cb03SClaudio 	if (cg_create(parent))
348d863cb03SClaudio 		goto cleanup;
349d863cb03SClaudio 
350d863cb03SClaudio 	if (cg_create(child))
351d863cb03SClaudio 		goto cleanup;
352d863cb03SClaudio 
353d863cb03SClaudio 	if (cg_write(child, "cgroup.type", "threaded"))
354d863cb03SClaudio 		goto cleanup;
355d863cb03SClaudio 
356d863cb03SClaudio 	if (cg_read_strcmp(parent, "cgroup.type", "domain threaded\n"))
357d863cb03SClaudio 		goto cleanup;
358d863cb03SClaudio 
359d863cb03SClaudio 	ret = KSFT_PASS;
360d863cb03SClaudio 
361d863cb03SClaudio cleanup:
362d863cb03SClaudio 	if (child)
363d863cb03SClaudio 		cg_destroy(child);
364d863cb03SClaudio 	if (parent)
365d863cb03SClaudio 		cg_destroy(parent);
366d863cb03SClaudio 	free(child);
367d863cb03SClaudio 	free(parent);
368d863cb03SClaudio 	return ret;
369d863cb03SClaudio 
370d863cb03SClaudio }
371d863cb03SClaudio 
372d863cb03SClaudio /*
373d863cb03SClaudio  * Test that there's no internal process constrain on threaded cgroups.
374d863cb03SClaudio  * You can add threads/processes on a parent with a controller enabled.
375d863cb03SClaudio  */
376d863cb03SClaudio static int test_cgcore_no_internal_process_constraint_on_threads(const char *root)
377d863cb03SClaudio {
378d863cb03SClaudio 	int ret = KSFT_FAIL;
379d863cb03SClaudio 	char *parent = NULL, *child = NULL;
380d863cb03SClaudio 
381d863cb03SClaudio 	if (cg_read_strstr(root, "cgroup.controllers", "cpu") ||
382f97f3f88SAlex Shi 	    cg_write(root, "cgroup.subtree_control", "+cpu")) {
383d863cb03SClaudio 		ret = KSFT_SKIP;
384d863cb03SClaudio 		goto cleanup;
385d863cb03SClaudio 	}
386d863cb03SClaudio 
387d863cb03SClaudio 	parent = cg_name(root, "cg_test_parent");
388d863cb03SClaudio 	child = cg_name(root, "cg_test_parent/cg_test_child");
389d863cb03SClaudio 	if (!parent || !child)
390d863cb03SClaudio 		goto cleanup;
391d863cb03SClaudio 
392d863cb03SClaudio 	if (cg_create(parent))
393d863cb03SClaudio 		goto cleanup;
394d863cb03SClaudio 
395d863cb03SClaudio 	if (cg_create(child))
396d863cb03SClaudio 		goto cleanup;
397d863cb03SClaudio 
398d863cb03SClaudio 	if (cg_write(parent, "cgroup.type", "threaded"))
399d863cb03SClaudio 		goto cleanup;
400d863cb03SClaudio 
401d863cb03SClaudio 	if (cg_write(child, "cgroup.type", "threaded"))
402d863cb03SClaudio 		goto cleanup;
403d863cb03SClaudio 
404d863cb03SClaudio 	if (cg_write(parent, "cgroup.subtree_control", "+cpu"))
405d863cb03SClaudio 		goto cleanup;
406d863cb03SClaudio 
407d863cb03SClaudio 	if (cg_enter_current(parent))
408d863cb03SClaudio 		goto cleanup;
409d863cb03SClaudio 
410d863cb03SClaudio 	ret = KSFT_PASS;
411d863cb03SClaudio 
412d863cb03SClaudio cleanup:
413d863cb03SClaudio 	cg_enter_current(root);
414d863cb03SClaudio 	cg_enter_current(root);
415d863cb03SClaudio 	if (child)
416d863cb03SClaudio 		cg_destroy(child);
417d863cb03SClaudio 	if (parent)
418d863cb03SClaudio 		cg_destroy(parent);
419d863cb03SClaudio 	free(child);
420d863cb03SClaudio 	free(parent);
421d863cb03SClaudio 	return ret;
422d863cb03SClaudio }
423d863cb03SClaudio 
424d863cb03SClaudio /*
425d863cb03SClaudio  * Test that you can't enable a controller on a child if it's not enabled
426d863cb03SClaudio  * on the parent.
427d863cb03SClaudio  */
428d863cb03SClaudio static int test_cgcore_top_down_constraint_enable(const char *root)
429d863cb03SClaudio {
430d863cb03SClaudio 	int ret = KSFT_FAIL;
431d863cb03SClaudio 	char *parent = NULL, *child = NULL;
432d863cb03SClaudio 
433d863cb03SClaudio 	parent = cg_name(root, "cg_test_parent");
434d863cb03SClaudio 	child = cg_name(root, "cg_test_parent/cg_test_child");
435d863cb03SClaudio 	if (!parent || !child)
436d863cb03SClaudio 		goto cleanup;
437d863cb03SClaudio 
438d863cb03SClaudio 	if (cg_create(parent))
439d863cb03SClaudio 		goto cleanup;
440d863cb03SClaudio 
441d863cb03SClaudio 	if (cg_create(child))
442d863cb03SClaudio 		goto cleanup;
443d863cb03SClaudio 
444d863cb03SClaudio 	if (!cg_write(child, "cgroup.subtree_control", "+memory"))
445d863cb03SClaudio 		goto cleanup;
446d863cb03SClaudio 
447d863cb03SClaudio 	ret = KSFT_PASS;
448d863cb03SClaudio 
449d863cb03SClaudio cleanup:
450d863cb03SClaudio 	if (child)
451d863cb03SClaudio 		cg_destroy(child);
452d863cb03SClaudio 	if (parent)
453d863cb03SClaudio 		cg_destroy(parent);
454d863cb03SClaudio 	free(child);
455d863cb03SClaudio 	free(parent);
456d863cb03SClaudio 	return ret;
457d863cb03SClaudio }
458d863cb03SClaudio 
459d863cb03SClaudio /*
460d863cb03SClaudio  * Test that you can't disable a controller on a parent
461d863cb03SClaudio  * if it's enabled in a child.
462d863cb03SClaudio  */
463d863cb03SClaudio static int test_cgcore_top_down_constraint_disable(const char *root)
464d863cb03SClaudio {
465d863cb03SClaudio 	int ret = KSFT_FAIL;
466d863cb03SClaudio 	char *parent = NULL, *child = NULL;
467d863cb03SClaudio 
468d863cb03SClaudio 	parent = cg_name(root, "cg_test_parent");
469d863cb03SClaudio 	child = cg_name(root, "cg_test_parent/cg_test_child");
470d863cb03SClaudio 	if (!parent || !child)
471d863cb03SClaudio 		goto cleanup;
472d863cb03SClaudio 
473d863cb03SClaudio 	if (cg_create(parent))
474d863cb03SClaudio 		goto cleanup;
475d863cb03SClaudio 
476d863cb03SClaudio 	if (cg_create(child))
477d863cb03SClaudio 		goto cleanup;
478d863cb03SClaudio 
479d863cb03SClaudio 	if (cg_write(parent, "cgroup.subtree_control", "+memory"))
480d863cb03SClaudio 		goto cleanup;
481d863cb03SClaudio 
482d863cb03SClaudio 	if (cg_write(child, "cgroup.subtree_control", "+memory"))
483d863cb03SClaudio 		goto cleanup;
484d863cb03SClaudio 
485d863cb03SClaudio 	if (!cg_write(parent, "cgroup.subtree_control", "-memory"))
486d863cb03SClaudio 		goto cleanup;
487d863cb03SClaudio 
488d863cb03SClaudio 	ret = KSFT_PASS;
489d863cb03SClaudio 
490d863cb03SClaudio cleanup:
491d863cb03SClaudio 	if (child)
492d863cb03SClaudio 		cg_destroy(child);
493d863cb03SClaudio 	if (parent)
494d863cb03SClaudio 		cg_destroy(parent);
495d863cb03SClaudio 	free(child);
496d863cb03SClaudio 	free(parent);
497d863cb03SClaudio 	return ret;
498d863cb03SClaudio }
499d863cb03SClaudio 
500d863cb03SClaudio /*
501d863cb03SClaudio  * Test internal process constraint.
502d863cb03SClaudio  * You can't add a pid to a domain parent if a controller is enabled.
503d863cb03SClaudio  */
504d863cb03SClaudio static int test_cgcore_internal_process_constraint(const char *root)
505d863cb03SClaudio {
506d863cb03SClaudio 	int ret = KSFT_FAIL;
507d863cb03SClaudio 	char *parent = NULL, *child = NULL;
508d863cb03SClaudio 
509d863cb03SClaudio 	parent = cg_name(root, "cg_test_parent");
510d863cb03SClaudio 	child = cg_name(root, "cg_test_parent/cg_test_child");
511d863cb03SClaudio 	if (!parent || !child)
512d863cb03SClaudio 		goto cleanup;
513d863cb03SClaudio 
514d863cb03SClaudio 	if (cg_create(parent))
515d863cb03SClaudio 		goto cleanup;
516d863cb03SClaudio 
517d863cb03SClaudio 	if (cg_create(child))
518d863cb03SClaudio 		goto cleanup;
519d863cb03SClaudio 
520d863cb03SClaudio 	if (cg_write(parent, "cgroup.subtree_control", "+memory"))
521d863cb03SClaudio 		goto cleanup;
522d863cb03SClaudio 
523d863cb03SClaudio 	if (!cg_enter_current(parent))
524d863cb03SClaudio 		goto cleanup;
525d863cb03SClaudio 
5269bd5910dSChristian Brauner 	if (!clone_into_cgroup_run_wait(parent))
5279bd5910dSChristian Brauner 		goto cleanup;
5289bd5910dSChristian Brauner 
529d863cb03SClaudio 	ret = KSFT_PASS;
530d863cb03SClaudio 
531d863cb03SClaudio cleanup:
532d863cb03SClaudio 	if (child)
533d863cb03SClaudio 		cg_destroy(child);
534d863cb03SClaudio 	if (parent)
535d863cb03SClaudio 		cg_destroy(parent);
536d863cb03SClaudio 	free(child);
537d863cb03SClaudio 	free(parent);
538d863cb03SClaudio 	return ret;
539d863cb03SClaudio }
540d863cb03SClaudio 
54111318989SMichal Koutný static void *dummy_thread_fn(void *arg)
54211318989SMichal Koutný {
54311318989SMichal Koutný 	return (void *)(size_t)pause();
54411318989SMichal Koutný }
54511318989SMichal Koutný 
54611318989SMichal Koutný /*
54711318989SMichal Koutný  * Test threadgroup migration.
54811318989SMichal Koutný  * All threads of a process are migrated together.
54911318989SMichal Koutný  */
55011318989SMichal Koutný static int test_cgcore_proc_migration(const char *root)
55111318989SMichal Koutný {
55211318989SMichal Koutný 	int ret = KSFT_FAIL;
553192c197cSDan Carpenter 	int t, c_threads = 0, n_threads = 13;
55411318989SMichal Koutný 	char *src = NULL, *dst = NULL;
55511318989SMichal Koutný 	pthread_t threads[n_threads];
55611318989SMichal Koutný 
55711318989SMichal Koutný 	src = cg_name(root, "cg_src");
55811318989SMichal Koutný 	dst = cg_name(root, "cg_dst");
55911318989SMichal Koutný 	if (!src || !dst)
56011318989SMichal Koutný 		goto cleanup;
56111318989SMichal Koutný 
56211318989SMichal Koutný 	if (cg_create(src))
56311318989SMichal Koutný 		goto cleanup;
56411318989SMichal Koutný 	if (cg_create(dst))
56511318989SMichal Koutný 		goto cleanup;
56611318989SMichal Koutný 
56711318989SMichal Koutný 	if (cg_enter_current(src))
56811318989SMichal Koutný 		goto cleanup;
56911318989SMichal Koutný 
57011318989SMichal Koutný 	for (c_threads = 0; c_threads < n_threads; ++c_threads) {
57111318989SMichal Koutný 		if (pthread_create(&threads[c_threads], NULL, dummy_thread_fn, NULL))
57211318989SMichal Koutný 			goto cleanup;
57311318989SMichal Koutný 	}
57411318989SMichal Koutný 
57511318989SMichal Koutný 	cg_enter_current(dst);
57611318989SMichal Koutný 	if (cg_read_lc(dst, "cgroup.threads") != n_threads + 1)
57711318989SMichal Koutný 		goto cleanup;
57811318989SMichal Koutný 
57911318989SMichal Koutný 	ret = KSFT_PASS;
58011318989SMichal Koutný 
58111318989SMichal Koutný cleanup:
58211318989SMichal Koutný 	for (t = 0; t < c_threads; ++t) {
58311318989SMichal Koutný 		pthread_cancel(threads[t]);
58411318989SMichal Koutný 	}
58511318989SMichal Koutný 
58611318989SMichal Koutný 	for (t = 0; t < c_threads; ++t) {
58711318989SMichal Koutný 		pthread_join(threads[t], NULL);
58811318989SMichal Koutný 	}
58911318989SMichal Koutný 
59011318989SMichal Koutný 	cg_enter_current(root);
59111318989SMichal Koutný 
59211318989SMichal Koutný 	if (dst)
59311318989SMichal Koutný 		cg_destroy(dst);
59411318989SMichal Koutný 	if (src)
59511318989SMichal Koutný 		cg_destroy(src);
59611318989SMichal Koutný 	free(dst);
59711318989SMichal Koutný 	free(src);
59811318989SMichal Koutný 	return ret;
59911318989SMichal Koutný }
60011318989SMichal Koutný 
60111318989SMichal Koutný static void *migrating_thread_fn(void *arg)
60211318989SMichal Koutný {
60311318989SMichal Koutný 	int g, i, n_iterations = 1000;
60411318989SMichal Koutný 	char **grps = arg;
60511318989SMichal Koutný 	char lines[3][PATH_MAX];
60611318989SMichal Koutný 
60711318989SMichal Koutný 	for (g = 1; g < 3; ++g)
60811318989SMichal Koutný 		snprintf(lines[g], sizeof(lines[g]), "0::%s", grps[g] + strlen(grps[0]));
60911318989SMichal Koutný 
61011318989SMichal Koutný 	for (i = 0; i < n_iterations; ++i) {
61111318989SMichal Koutný 		cg_enter_current_thread(grps[(i % 2) + 1]);
61211318989SMichal Koutný 
61311318989SMichal Koutný 		if (proc_read_strstr(0, 1, "cgroup", lines[(i % 2) + 1]))
61411318989SMichal Koutný 			return (void *)-1;
61511318989SMichal Koutný 	}
61611318989SMichal Koutný 	return NULL;
61711318989SMichal Koutný }
61811318989SMichal Koutný 
61911318989SMichal Koutný /*
62011318989SMichal Koutný  * Test single thread migration.
62111318989SMichal Koutný  * Threaded cgroups allow successful migration of a thread.
62211318989SMichal Koutný  */
62311318989SMichal Koutný static int test_cgcore_thread_migration(const char *root)
62411318989SMichal Koutný {
62511318989SMichal Koutný 	int ret = KSFT_FAIL;
62611318989SMichal Koutný 	char *dom = NULL;
62711318989SMichal Koutný 	char line[PATH_MAX];
62811318989SMichal Koutný 	char *grps[3] = { (char *)root, NULL, NULL };
62911318989SMichal Koutný 	pthread_t thr;
63011318989SMichal Koutný 	void *retval;
63111318989SMichal Koutný 
63211318989SMichal Koutný 	dom = cg_name(root, "cg_dom");
63311318989SMichal Koutný 	grps[1] = cg_name(root, "cg_dom/cg_src");
63411318989SMichal Koutný 	grps[2] = cg_name(root, "cg_dom/cg_dst");
63511318989SMichal Koutný 	if (!grps[1] || !grps[2] || !dom)
63611318989SMichal Koutný 		goto cleanup;
63711318989SMichal Koutný 
63811318989SMichal Koutný 	if (cg_create(dom))
63911318989SMichal Koutný 		goto cleanup;
64011318989SMichal Koutný 	if (cg_create(grps[1]))
64111318989SMichal Koutný 		goto cleanup;
64211318989SMichal Koutný 	if (cg_create(grps[2]))
64311318989SMichal Koutný 		goto cleanup;
64411318989SMichal Koutný 
64511318989SMichal Koutný 	if (cg_write(grps[1], "cgroup.type", "threaded"))
64611318989SMichal Koutný 		goto cleanup;
64711318989SMichal Koutný 	if (cg_write(grps[2], "cgroup.type", "threaded"))
64811318989SMichal Koutný 		goto cleanup;
64911318989SMichal Koutný 
65011318989SMichal Koutný 	if (cg_enter_current(grps[1]))
65111318989SMichal Koutný 		goto cleanup;
65211318989SMichal Koutný 
65311318989SMichal Koutný 	if (pthread_create(&thr, NULL, migrating_thread_fn, grps))
65411318989SMichal Koutný 		goto cleanup;
65511318989SMichal Koutný 
65611318989SMichal Koutný 	if (pthread_join(thr, &retval))
65711318989SMichal Koutný 		goto cleanup;
65811318989SMichal Koutný 
65911318989SMichal Koutný 	if (retval)
66011318989SMichal Koutný 		goto cleanup;
66111318989SMichal Koutný 
66211318989SMichal Koutný 	snprintf(line, sizeof(line), "0::%s", grps[1] + strlen(grps[0]));
66311318989SMichal Koutný 	if (proc_read_strstr(0, 1, "cgroup", line))
66411318989SMichal Koutný 		goto cleanup;
66511318989SMichal Koutný 
66611318989SMichal Koutný 	ret = KSFT_PASS;
66711318989SMichal Koutný 
66811318989SMichal Koutný cleanup:
66911318989SMichal Koutný 	cg_enter_current(root);
67011318989SMichal Koutný 	if (grps[2])
67111318989SMichal Koutný 		cg_destroy(grps[2]);
67211318989SMichal Koutný 	if (grps[1])
67311318989SMichal Koutný 		cg_destroy(grps[1]);
67411318989SMichal Koutný 	if (dom)
67511318989SMichal Koutný 		cg_destroy(dom);
67611318989SMichal Koutný 	free(grps[2]);
67711318989SMichal Koutný 	free(grps[1]);
67811318989SMichal Koutný 	free(dom);
67911318989SMichal Koutný 	return ret;
68011318989SMichal Koutný }
68111318989SMichal Koutný 
682613e040eSTejun Heo /*
683613e040eSTejun Heo  * cgroup migration permission check should be performed based on the
684613e040eSTejun Heo  * credentials at the time of open instead of write.
685613e040eSTejun Heo  */
686613e040eSTejun Heo static int test_cgcore_lesser_euid_open(const char *root)
687613e040eSTejun Heo {
68812101424SMichal Koutný 	const uid_t test_euid = TEST_UID;
689613e040eSTejun Heo 	int ret = KSFT_FAIL;
690613e040eSTejun Heo 	char *cg_test_a = NULL, *cg_test_b = NULL;
691613e040eSTejun Heo 	char *cg_test_a_procs = NULL, *cg_test_b_procs = NULL;
692613e040eSTejun Heo 	int cg_test_b_procs_fd = -1;
693613e040eSTejun Heo 	uid_t saved_uid;
694613e040eSTejun Heo 
695613e040eSTejun Heo 	cg_test_a = cg_name(root, "cg_test_a");
696613e040eSTejun Heo 	cg_test_b = cg_name(root, "cg_test_b");
697613e040eSTejun Heo 
698613e040eSTejun Heo 	if (!cg_test_a || !cg_test_b)
699613e040eSTejun Heo 		goto cleanup;
700613e040eSTejun Heo 
701613e040eSTejun Heo 	cg_test_a_procs = cg_name(cg_test_a, "cgroup.procs");
702613e040eSTejun Heo 	cg_test_b_procs = cg_name(cg_test_b, "cgroup.procs");
703613e040eSTejun Heo 
704613e040eSTejun Heo 	if (!cg_test_a_procs || !cg_test_b_procs)
705613e040eSTejun Heo 		goto cleanup;
706613e040eSTejun Heo 
707613e040eSTejun Heo 	if (cg_create(cg_test_a) || cg_create(cg_test_b))
708613e040eSTejun Heo 		goto cleanup;
709613e040eSTejun Heo 
710613e040eSTejun Heo 	if (cg_enter_current(cg_test_a))
711613e040eSTejun Heo 		goto cleanup;
712613e040eSTejun Heo 
713613e040eSTejun Heo 	if (chown(cg_test_a_procs, test_euid, -1) ||
714613e040eSTejun Heo 	    chown(cg_test_b_procs, test_euid, -1))
715613e040eSTejun Heo 		goto cleanup;
716613e040eSTejun Heo 
717613e040eSTejun Heo 	saved_uid = geteuid();
718613e040eSTejun Heo 	if (seteuid(test_euid))
719613e040eSTejun Heo 		goto cleanup;
720613e040eSTejun Heo 
721613e040eSTejun Heo 	cg_test_b_procs_fd = open(cg_test_b_procs, O_RDWR);
722613e040eSTejun Heo 
723613e040eSTejun Heo 	if (seteuid(saved_uid))
724613e040eSTejun Heo 		goto cleanup;
725613e040eSTejun Heo 
726613e040eSTejun Heo 	if (cg_test_b_procs_fd < 0)
727613e040eSTejun Heo 		goto cleanup;
728613e040eSTejun Heo 
729613e040eSTejun Heo 	if (write(cg_test_b_procs_fd, "0", 1) >= 0 || errno != EACCES)
730613e040eSTejun Heo 		goto cleanup;
731613e040eSTejun Heo 
732613e040eSTejun Heo 	ret = KSFT_PASS;
733613e040eSTejun Heo 
734613e040eSTejun Heo cleanup:
735613e040eSTejun Heo 	cg_enter_current(root);
736613e040eSTejun Heo 	if (cg_test_b_procs_fd >= 0)
737613e040eSTejun Heo 		close(cg_test_b_procs_fd);
738613e040eSTejun Heo 	if (cg_test_b)
739613e040eSTejun Heo 		cg_destroy(cg_test_b);
740613e040eSTejun Heo 	if (cg_test_a)
741613e040eSTejun Heo 		cg_destroy(cg_test_a);
742613e040eSTejun Heo 	free(cg_test_b_procs);
743613e040eSTejun Heo 	free(cg_test_a_procs);
744613e040eSTejun Heo 	free(cg_test_b);
745613e040eSTejun Heo 	free(cg_test_a);
746613e040eSTejun Heo 	return ret;
747613e040eSTejun Heo }
748613e040eSTejun Heo 
749bf35a787STejun Heo struct lesser_ns_open_thread_arg {
750bf35a787STejun Heo 	const char	*path;
751bf35a787STejun Heo 	int		fd;
752bf35a787STejun Heo 	int		err;
753bf35a787STejun Heo };
754bf35a787STejun Heo 
755bf35a787STejun Heo static int lesser_ns_open_thread_fn(void *arg)
756bf35a787STejun Heo {
757bf35a787STejun Heo 	struct lesser_ns_open_thread_arg *targ = arg;
758bf35a787STejun Heo 
759bf35a787STejun Heo 	targ->fd = open(targ->path, O_RDWR);
760bf35a787STejun Heo 	targ->err = errno;
761bf35a787STejun Heo 	return 0;
762bf35a787STejun Heo }
763bf35a787STejun Heo 
764bf35a787STejun Heo /*
765bf35a787STejun Heo  * cgroup migration permission check should be performed based on the cgroup
766bf35a787STejun Heo  * namespace at the time of open instead of write.
767bf35a787STejun Heo  */
768bf35a787STejun Heo static int test_cgcore_lesser_ns_open(const char *root)
769bf35a787STejun Heo {
770bf35a787STejun Heo 	static char stack[65536];
771bf35a787STejun Heo 	const uid_t test_euid = 65534;	/* usually nobody, any !root is fine */
772bf35a787STejun Heo 	int ret = KSFT_FAIL;
773bf35a787STejun Heo 	char *cg_test_a = NULL, *cg_test_b = NULL;
774bf35a787STejun Heo 	char *cg_test_a_procs = NULL, *cg_test_b_procs = NULL;
775bf35a787STejun Heo 	int cg_test_b_procs_fd = -1;
776bf35a787STejun Heo 	struct lesser_ns_open_thread_arg targ = { .fd = -1 };
777bf35a787STejun Heo 	pid_t pid;
778bf35a787STejun Heo 	int status;
779bf35a787STejun Heo 
7804793cb59STianchen Ding 	if (!nsdelegate)
7814793cb59STianchen Ding 		return KSFT_SKIP;
7824793cb59STianchen Ding 
783bf35a787STejun Heo 	cg_test_a = cg_name(root, "cg_test_a");
784bf35a787STejun Heo 	cg_test_b = cg_name(root, "cg_test_b");
785bf35a787STejun Heo 
786bf35a787STejun Heo 	if (!cg_test_a || !cg_test_b)
787bf35a787STejun Heo 		goto cleanup;
788bf35a787STejun Heo 
789bf35a787STejun Heo 	cg_test_a_procs = cg_name(cg_test_a, "cgroup.procs");
790bf35a787STejun Heo 	cg_test_b_procs = cg_name(cg_test_b, "cgroup.procs");
791bf35a787STejun Heo 
792bf35a787STejun Heo 	if (!cg_test_a_procs || !cg_test_b_procs)
793bf35a787STejun Heo 		goto cleanup;
794bf35a787STejun Heo 
795bf35a787STejun Heo 	if (cg_create(cg_test_a) || cg_create(cg_test_b))
796bf35a787STejun Heo 		goto cleanup;
797bf35a787STejun Heo 
798bf35a787STejun Heo 	if (cg_enter_current(cg_test_b))
799bf35a787STejun Heo 		goto cleanup;
800bf35a787STejun Heo 
801bf35a787STejun Heo 	if (chown(cg_test_a_procs, test_euid, -1) ||
802bf35a787STejun Heo 	    chown(cg_test_b_procs, test_euid, -1))
803bf35a787STejun Heo 		goto cleanup;
804bf35a787STejun Heo 
805bf35a787STejun Heo 	targ.path = cg_test_b_procs;
806bf35a787STejun Heo 	pid = clone(lesser_ns_open_thread_fn, stack + sizeof(stack),
807bf35a787STejun Heo 		    CLONE_NEWCGROUP | CLONE_FILES | CLONE_VM | SIGCHLD,
808bf35a787STejun Heo 		    &targ);
809bf35a787STejun Heo 	if (pid < 0)
810bf35a787STejun Heo 		goto cleanup;
811bf35a787STejun Heo 
812bf35a787STejun Heo 	if (waitpid(pid, &status, 0) < 0)
813bf35a787STejun Heo 		goto cleanup;
814bf35a787STejun Heo 
815bf35a787STejun Heo 	if (!WIFEXITED(status))
816bf35a787STejun Heo 		goto cleanup;
817bf35a787STejun Heo 
818bf35a787STejun Heo 	cg_test_b_procs_fd = targ.fd;
819bf35a787STejun Heo 	if (cg_test_b_procs_fd < 0)
820bf35a787STejun Heo 		goto cleanup;
821bf35a787STejun Heo 
822bf35a787STejun Heo 	if (cg_enter_current(cg_test_a))
823bf35a787STejun Heo 		goto cleanup;
824bf35a787STejun Heo 
825bf35a787STejun Heo 	if ((status = write(cg_test_b_procs_fd, "0", 1)) >= 0 || errno != ENOENT)
826bf35a787STejun Heo 		goto cleanup;
827bf35a787STejun Heo 
828bf35a787STejun Heo 	ret = KSFT_PASS;
829bf35a787STejun Heo 
830bf35a787STejun Heo cleanup:
831bf35a787STejun Heo 	cg_enter_current(root);
832bf35a787STejun Heo 	if (cg_test_b_procs_fd >= 0)
833bf35a787STejun Heo 		close(cg_test_b_procs_fd);
834bf35a787STejun Heo 	if (cg_test_b)
835bf35a787STejun Heo 		cg_destroy(cg_test_b);
836bf35a787STejun Heo 	if (cg_test_a)
837bf35a787STejun Heo 		cg_destroy(cg_test_a);
838bf35a787STejun Heo 	free(cg_test_b_procs);
839bf35a787STejun Heo 	free(cg_test_a_procs);
840bf35a787STejun Heo 	free(cg_test_b);
841bf35a787STejun Heo 	free(cg_test_a);
842bf35a787STejun Heo 	return ret;
843bf35a787STejun Heo }
844bf35a787STejun Heo 
845d863cb03SClaudio #define T(x) { x, #x }
846d863cb03SClaudio struct corecg_test {
847d863cb03SClaudio 	int (*fn)(const char *root);
848d863cb03SClaudio 	const char *name;
849d863cb03SClaudio } tests[] = {
850d863cb03SClaudio 	T(test_cgcore_internal_process_constraint),
851d863cb03SClaudio 	T(test_cgcore_top_down_constraint_enable),
852d863cb03SClaudio 	T(test_cgcore_top_down_constraint_disable),
853d863cb03SClaudio 	T(test_cgcore_no_internal_process_constraint_on_threads),
854d863cb03SClaudio 	T(test_cgcore_parent_becomes_threaded),
855d863cb03SClaudio 	T(test_cgcore_invalid_domain),
856d863cb03SClaudio 	T(test_cgcore_populated),
85711318989SMichal Koutný 	T(test_cgcore_proc_migration),
85811318989SMichal Koutný 	T(test_cgcore_thread_migration),
85904189382SSuren Baghdasaryan 	T(test_cgcore_destroy),
860613e040eSTejun Heo 	T(test_cgcore_lesser_euid_open),
861bf35a787STejun Heo 	T(test_cgcore_lesser_ns_open),
862d863cb03SClaudio };
863d863cb03SClaudio #undef T
864d863cb03SClaudio 
865d863cb03SClaudio int main(int argc, char *argv[])
866d863cb03SClaudio {
867d863cb03SClaudio 	char root[PATH_MAX];
868d863cb03SClaudio 	int i, ret = EXIT_SUCCESS;
869d863cb03SClaudio 
8704793cb59STianchen Ding 	if (cg_find_unified_root(root, sizeof(root), &nsdelegate))
871d863cb03SClaudio 		ksft_exit_skip("cgroup v2 isn't mounted\n");
87200e38a5dSAlex Shi 
87300e38a5dSAlex Shi 	if (cg_read_strstr(root, "cgroup.subtree_control", "memory"))
87400e38a5dSAlex Shi 		if (cg_write(root, "cgroup.subtree_control", "+memory"))
87500e38a5dSAlex Shi 			ksft_exit_skip("Failed to set memory controller\n");
87600e38a5dSAlex Shi 
877d863cb03SClaudio 	for (i = 0; i < ARRAY_SIZE(tests); i++) {
878d863cb03SClaudio 		switch (tests[i].fn(root)) {
879d863cb03SClaudio 		case KSFT_PASS:
880d863cb03SClaudio 			ksft_test_result_pass("%s\n", tests[i].name);
881d863cb03SClaudio 			break;
882d863cb03SClaudio 		case KSFT_SKIP:
883d863cb03SClaudio 			ksft_test_result_skip("%s\n", tests[i].name);
884d863cb03SClaudio 			break;
885d863cb03SClaudio 		default:
886d863cb03SClaudio 			ret = EXIT_FAILURE;
887d863cb03SClaudio 			ksft_test_result_fail("%s\n", tests[i].name);
888d863cb03SClaudio 			break;
889d863cb03SClaudio 		}
890d863cb03SClaudio 	}
891d863cb03SClaudio 
892d863cb03SClaudio 	return ret;
893d863cb03SClaudio }
894