354 lines · 6.5 KB · C
RawDownload
/* SPDX-License-Identifier: GPL-2.0 */ #include <errno.h>#include <linux/limits.h>#include <stdbool.h>#include <stdio.h>#include <stdlib.h>#include <string.h>#include <sys/types.h>#include <sys/wait.h>#include <unistd.h> #include "kselftest.h"#include "../pidfd/pidfd.h"#include "cgroup_util.h" /* * Kill the given cgroup and wait for the inotify signal. * If there are no events in 10 seconds, treat this as an error. * Then check that the cgroup is in the desired state. */static int cg_kill_wait(const char *cgroup){	int fd, ret = -1; 	fd = cg_prepare_for_wait(cgroup);	if (fd < 0)		return fd; 	ret = cg_write(cgroup, "cgroup.kill", "1");	if (ret)		goto out; 	ret = cg_wait_for(fd);	if (ret)		goto out; out:	close(fd);	return ret;} /* * A simple process running in a sleep loop until being * re-parented. */static int child_fn(const char *cgroup, void *arg){	int ppid = getppid(); 	while (getppid() == ppid)		usleep(1000); 	return getppid() == ppid;} static int test_cgkill_simple(const char *root){	pid_t pids[100];	int ret = KSFT_FAIL;	char *cgroup = NULL;	int i; 	cgroup = cg_name(root, "cg_test_simple");	if (!cgroup)		goto cleanup; 	if (cg_create(cgroup))		goto cleanup; 	for (i = 0; i < 100; i++)		pids[i] = cg_run_nowait(cgroup, child_fn, NULL); 	if (cg_wait_for_proc_count(cgroup, 100))		goto cleanup; 	if (cg_read_strcmp(cgroup, "cgroup.events", "populated 1\n"))		goto cleanup; 	if (cg_kill_wait(cgroup))		goto cleanup; 	ret = KSFT_PASS; cleanup:	for (i = 0; i < 100; i++)		wait_for_pid(pids[i]); 	if (ret == KSFT_PASS &&	    cg_read_strcmp_wait(cgroup, "cgroup.events", "populated 0\n"))		ret = KSFT_FAIL; 	if (cgroup)		cg_destroy(cgroup);	free(cgroup);	return ret;} /* * The test creates the following hierarchy: *       A *    / / \ \ *   B  E  I K *  /\  | * C  D F *      | *      G *      | *      H * * with a process in C, H and 3 processes in K. * Then it tries to kill the whole tree. */static int test_cgkill_tree(const char *root){	pid_t pids[5];	char *cgroup[10] = {0};	int ret = KSFT_FAIL;	int i; 	cgroup[0] = cg_name(root, "cg_test_tree_A");	if (!cgroup[0])		goto cleanup; 	cgroup[1] = cg_name(cgroup[0], "B");	if (!cgroup[1])		goto cleanup; 	cgroup[2] = cg_name(cgroup[1], "C");	if (!cgroup[2])		goto cleanup; 	cgroup[3] = cg_name(cgroup[1], "D");	if (!cgroup[3])		goto cleanup; 	cgroup[4] = cg_name(cgroup[0], "E");	if (!cgroup[4])		goto cleanup; 	cgroup[5] = cg_name(cgroup[4], "F");	if (!cgroup[5])		goto cleanup; 	cgroup[6] = cg_name(cgroup[5], "G");	if (!cgroup[6])		goto cleanup; 	cgroup[7] = cg_name(cgroup[6], "H");	if (!cgroup[7])		goto cleanup; 	cgroup[8] = cg_name(cgroup[0], "I");	if (!cgroup[8])		goto cleanup; 	cgroup[9] = cg_name(cgroup[0], "K");	if (!cgroup[9])		goto cleanup; 	for (i = 0; i < 10; i++)		if (cg_create(cgroup[i]))			goto cleanup; 	pids[0] = cg_run_nowait(cgroup[2], child_fn, NULL);	pids[1] = cg_run_nowait(cgroup[7], child_fn, NULL);	pids[2] = cg_run_nowait(cgroup[9], child_fn, NULL);	pids[3] = cg_run_nowait(cgroup[9], child_fn, NULL);	pids[4] = cg_run_nowait(cgroup[9], child_fn, NULL); 	/*	 * Wait until all child processes will enter	 * corresponding cgroups.	 */ 	if (cg_wait_for_proc_count(cgroup[2], 1) ||	    cg_wait_for_proc_count(cgroup[7], 1) ||	    cg_wait_for_proc_count(cgroup[9], 3))		goto cleanup; 	/*	 * Kill A and check that we get an empty notification.	 */	if (cg_kill_wait(cgroup[0]))		goto cleanup; 	ret = KSFT_PASS; cleanup:	for (i = 0; i < 5; i++)		wait_for_pid(pids[i]); 	if (ret == KSFT_PASS &&	    cg_read_strcmp_wait(cgroup[0], "cgroup.events",				   "populated 0\n"))		ret = KSFT_FAIL; 	for (i = 9; i >= 0 && cgroup[i]; i--) {		cg_destroy(cgroup[i]);		free(cgroup[i]);	} 	return ret;} static int forkbomb_fn(const char *cgroup, void *arg){	int ppid; 	fork();	fork(); 	ppid = getppid(); 	while (getppid() == ppid)		usleep(1000); 	return getppid() == ppid;} /* * The test runs a fork bomb in a cgroup and tries to kill it. */static int test_cgkill_forkbomb(const char *root){	int ret = KSFT_FAIL;	char *cgroup = NULL;	pid_t pid = -ESRCH; 	cgroup = cg_name(root, "cg_forkbomb_test");	if (!cgroup)		goto cleanup; 	if (cg_create(cgroup))		goto cleanup; 	pid = cg_run_nowait(cgroup, forkbomb_fn, NULL);	if (pid < 0)		goto cleanup; 	usleep(100000); 	if (cg_kill_wait(cgroup))		goto cleanup; 	if (cg_wait_for_proc_count(cgroup, 0))		goto cleanup; 	ret = KSFT_PASS; cleanup:	if (pid > 0)		wait_for_pid(pid); 	if (ret == KSFT_PASS &&	    cg_read_strcmp_wait(cgroup, "cgroup.events", "populated 0\n"))		ret = KSFT_FAIL; 	if (cgroup)		cg_destroy(cgroup);	free(cgroup);	return ret;} /* * Test that a cgroup that was killed in the past can still be the target * of clone3(CLONE_INTO_CGROUP): writing cgroup.kill must only kill the * tasks in the cgroup at the time of the write, not tasks cloned into * it afterwards. */static int test_cgkill_clone_into_killed(const char *root){	pid_t pid;	int cgroup_fd = -EBADF;	int ret = KSFT_FAIL;	char *cgroup = NULL; 	cgroup = cg_name(root, "cg_test_clone_into_killed");	if (!cgroup)		goto cleanup; 	if (cg_create(cgroup))		goto cleanup; 	/* Kill the cgroup while it is still empty. */	if (cg_write(cgroup, "cgroup.kill", "1"))		goto cleanup; 	cgroup_fd = dirfd_open_opath(cgroup);	if (cgroup_fd < 0)		goto cleanup; 	pid = clone_into_cgroup(cgroup_fd);	if (pid < 0) {		if (errno == ENOSYS)			ret = KSFT_SKIP;		goto cleanup;	} 	if (pid == 0)		exit(EXIT_SUCCESS); 	/* The child must not be SIGKILLed; it has to exit cleanly. */	if (clone_reap(pid, WEXITED) != EXIT_SUCCESS)		goto cleanup; 	ret = KSFT_PASS; cleanup:	if (cgroup_fd >= 0)		close(cgroup_fd);	if (cgroup)		cg_destroy(cgroup);	free(cgroup);	return ret;} #define T(x) { x, #x }struct cgkill_test {	int (*fn)(const char *root);	const char *name;} tests[] = {	T(test_cgkill_simple),	T(test_cgkill_tree),	T(test_cgkill_forkbomb),	T(test_cgkill_clone_into_killed),};#undef T int main(int argc, char *argv[]){	char root[PATH_MAX];	int i; 	ksft_print_header();	if (cg_find_unified_root(root, sizeof(root), NULL))		ksft_exit_skip("cgroup v2 isn't mounted\n");	ksft_set_plan(ARRAY_SIZE(tests));	for (i = 0; i < ARRAY_SIZE(tests); i++) {		switch (tests[i].fn(root)) {		case KSFT_PASS:			ksft_test_result_pass("%s\n", tests[i].name);			break;		case KSFT_SKIP:			ksft_test_result_skip("%s\n", tests[i].name);			break;		default:			ksft_test_result_fail("%s\n", tests[i].name);			break;		}	} 	ksft_finished();}