298 lines · c
1/* SPDX-License-Identifier: GPL-2.0 */2 3#include <errno.h>4#include <linux/limits.h>5#include <stdbool.h>6#include <stdio.h>7#include <stdlib.h>8#include <string.h>9#include <sys/types.h>10#include <unistd.h>11 12#include "../kselftest.h"13#include "../pidfd/pidfd.h"14#include "cgroup_util.h"15 16/*17 * Kill the given cgroup and wait for the inotify signal.18 * If there are no events in 10 seconds, treat this as an error.19 * Then check that the cgroup is in the desired state.20 */21static int cg_kill_wait(const char *cgroup)22{23 int fd, ret = -1;24 25 fd = cg_prepare_for_wait(cgroup);26 if (fd < 0)27 return fd;28 29 ret = cg_write(cgroup, "cgroup.kill", "1");30 if (ret)31 goto out;32 33 ret = cg_wait_for(fd);34 if (ret)35 goto out;36 37out:38 close(fd);39 return ret;40}41 42/*43 * A simple process running in a sleep loop until being44 * re-parented.45 */46static int child_fn(const char *cgroup, void *arg)47{48 int ppid = getppid();49 50 while (getppid() == ppid)51 usleep(1000);52 53 return getppid() == ppid;54}55 56static int test_cgkill_simple(const char *root)57{58 pid_t pids[100];59 int ret = KSFT_FAIL;60 char *cgroup = NULL;61 int i;62 63 cgroup = cg_name(root, "cg_test_simple");64 if (!cgroup)65 goto cleanup;66 67 if (cg_create(cgroup))68 goto cleanup;69 70 for (i = 0; i < 100; i++)71 pids[i] = cg_run_nowait(cgroup, child_fn, NULL);72 73 if (cg_wait_for_proc_count(cgroup, 100))74 goto cleanup;75 76 if (cg_read_strcmp(cgroup, "cgroup.events", "populated 1\n"))77 goto cleanup;78 79 if (cg_kill_wait(cgroup))80 goto cleanup;81 82 ret = KSFT_PASS;83 84cleanup:85 for (i = 0; i < 100; i++)86 wait_for_pid(pids[i]);87 88 if (ret == KSFT_PASS &&89 cg_read_strcmp(cgroup, "cgroup.events", "populated 0\n"))90 ret = KSFT_FAIL;91 92 if (cgroup)93 cg_destroy(cgroup);94 free(cgroup);95 return ret;96}97 98/*99 * The test creates the following hierarchy:100 * A101 * / / \ \102 * B E I K103 * /\ |104 * C D F105 * |106 * G107 * |108 * H109 *110 * with a process in C, H and 3 processes in K.111 * Then it tries to kill the whole tree.112 */113static int test_cgkill_tree(const char *root)114{115 pid_t pids[5];116 char *cgroup[10] = {0};117 int ret = KSFT_FAIL;118 int i;119 120 cgroup[0] = cg_name(root, "cg_test_tree_A");121 if (!cgroup[0])122 goto cleanup;123 124 cgroup[1] = cg_name(cgroup[0], "B");125 if (!cgroup[1])126 goto cleanup;127 128 cgroup[2] = cg_name(cgroup[1], "C");129 if (!cgroup[2])130 goto cleanup;131 132 cgroup[3] = cg_name(cgroup[1], "D");133 if (!cgroup[3])134 goto cleanup;135 136 cgroup[4] = cg_name(cgroup[0], "E");137 if (!cgroup[4])138 goto cleanup;139 140 cgroup[5] = cg_name(cgroup[4], "F");141 if (!cgroup[5])142 goto cleanup;143 144 cgroup[6] = cg_name(cgroup[5], "G");145 if (!cgroup[6])146 goto cleanup;147 148 cgroup[7] = cg_name(cgroup[6], "H");149 if (!cgroup[7])150 goto cleanup;151 152 cgroup[8] = cg_name(cgroup[0], "I");153 if (!cgroup[8])154 goto cleanup;155 156 cgroup[9] = cg_name(cgroup[0], "K");157 if (!cgroup[9])158 goto cleanup;159 160 for (i = 0; i < 10; i++)161 if (cg_create(cgroup[i]))162 goto cleanup;163 164 pids[0] = cg_run_nowait(cgroup[2], child_fn, NULL);165 pids[1] = cg_run_nowait(cgroup[7], child_fn, NULL);166 pids[2] = cg_run_nowait(cgroup[9], child_fn, NULL);167 pids[3] = cg_run_nowait(cgroup[9], child_fn, NULL);168 pids[4] = cg_run_nowait(cgroup[9], child_fn, NULL);169 170 /*171 * Wait until all child processes will enter172 * corresponding cgroups.173 */174 175 if (cg_wait_for_proc_count(cgroup[2], 1) ||176 cg_wait_for_proc_count(cgroup[7], 1) ||177 cg_wait_for_proc_count(cgroup[9], 3))178 goto cleanup;179 180 /*181 * Kill A and check that we get an empty notification.182 */183 if (cg_kill_wait(cgroup[0]))184 goto cleanup;185 186 ret = KSFT_PASS;187 188cleanup:189 for (i = 0; i < 5; i++)190 wait_for_pid(pids[i]);191 192 if (ret == KSFT_PASS &&193 cg_read_strcmp(cgroup[0], "cgroup.events", "populated 0\n"))194 ret = KSFT_FAIL;195 196 for (i = 9; i >= 0 && cgroup[i]; i--) {197 cg_destroy(cgroup[i]);198 free(cgroup[i]);199 }200 201 return ret;202}203 204static int forkbomb_fn(const char *cgroup, void *arg)205{206 int ppid;207 208 fork();209 fork();210 211 ppid = getppid();212 213 while (getppid() == ppid)214 usleep(1000);215 216 return getppid() == ppid;217}218 219/*220 * The test runs a fork bomb in a cgroup and tries to kill it.221 */222static int test_cgkill_forkbomb(const char *root)223{224 int ret = KSFT_FAIL;225 char *cgroup = NULL;226 pid_t pid = -ESRCH;227 228 cgroup = cg_name(root, "cg_forkbomb_test");229 if (!cgroup)230 goto cleanup;231 232 if (cg_create(cgroup))233 goto cleanup;234 235 pid = cg_run_nowait(cgroup, forkbomb_fn, NULL);236 if (pid < 0)237 goto cleanup;238 239 usleep(100000);240 241 if (cg_kill_wait(cgroup))242 goto cleanup;243 244 if (cg_wait_for_proc_count(cgroup, 0))245 goto cleanup;246 247 ret = KSFT_PASS;248 249cleanup:250 if (pid > 0)251 wait_for_pid(pid);252 253 if (ret == KSFT_PASS &&254 cg_read_strcmp(cgroup, "cgroup.events", "populated 0\n"))255 ret = KSFT_FAIL;256 257 if (cgroup)258 cg_destroy(cgroup);259 free(cgroup);260 return ret;261}262 263#define T(x) { x, #x }264struct cgkill_test {265 int (*fn)(const char *root);266 const char *name;267} tests[] = {268 T(test_cgkill_simple),269 T(test_cgkill_tree),270 T(test_cgkill_forkbomb),271};272#undef T273 274int main(int argc, char *argv[])275{276 char root[PATH_MAX];277 int i, ret = EXIT_SUCCESS;278 279 if (cg_find_unified_root(root, sizeof(root), NULL))280 ksft_exit_skip("cgroup v2 isn't mounted\n");281 for (i = 0; i < ARRAY_SIZE(tests); i++) {282 switch (tests[i].fn(root)) {283 case KSFT_PASS:284 ksft_test_result_pass("%s\n", tests[i].name);285 break;286 case KSFT_SKIP:287 ksft_test_result_skip("%s\n", tests[i].name);288 break;289 default:290 ret = EXIT_FAILURE;291 ksft_test_result_fail("%s\n", tests[i].name);292 break;293 }294 }295 296 return ret;297}298