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