brintos

brintos / linux-shallow public Read only

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