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