// SPDX-License-Identifier: GPL-2.0 #define _GNU_SOURCE #include #include #include #include #include #include #include #include "../kselftest.h" #include "cgroup_util.h" static int run_success(const char *cgroup, void *arg) { return 0; } static int run_pause(const char *cgroup, void *arg) { return pause(); } /* * This test checks that pids.max prevents forking new children above the * specified limit in the cgroup. */ static int test_pids_max(const char *root) { int ret = KSFT_FAIL; char *cg_pids; int pid; cg_pids = cg_name(root, "pids_test"); if (!cg_pids) goto cleanup; if (cg_create(cg_pids)) goto cleanup; if (cg_read_strcmp(cg_pids, "pids.max", "max\n")) goto cleanup; if (cg_write(cg_pids, "pids.max", "2")) goto cleanup; if (cg_enter_current(cg_pids)) goto cleanup; pid = cg_run_nowait(cg_pids, run_pause, NULL); if (pid < 0) goto cleanup; if (cg_run_nowait(cg_pids, run_success, NULL) != -1 || errno != EAGAIN) goto cleanup; if (kill(pid, SIGINT)) goto cleanup; ret = KSFT_PASS; cleanup: cg_enter_current(root); cg_destroy(cg_pids); free(cg_pids); return ret; } /* * This test checks that pids.events are counted in cgroup associated with pids.max */ static int test_pids_events(const char *root) { int ret = KSFT_FAIL; char *cg_parent = NULL, *cg_child = NULL; int pid; cg_parent = cg_name(root, "pids_parent"); cg_child = cg_name(cg_parent, "pids_child"); if (!cg_parent || !cg_child) goto cleanup; if (cg_create(cg_parent)) goto cleanup; if (cg_write(cg_parent, "cgroup.subtree_control", "+pids")) goto cleanup; if (cg_create(cg_child)) goto cleanup; if (cg_write(cg_parent, "pids.max", "2")) goto cleanup; if (cg_read_strcmp(cg_child, "pids.max", "max\n")) goto cleanup; if (cg_enter_current(cg_child)) goto cleanup; pid = cg_run_nowait(cg_child, run_pause, NULL); if (pid < 0) goto cleanup; if (cg_run_nowait(cg_child, run_success, NULL) != -1 || errno != EAGAIN) goto cleanup; if (kill(pid, SIGINT)) goto cleanup; if (cg_read_key_long(cg_child, "pids.events", "max ") != 0) goto cleanup; if (cg_read_key_long(cg_parent, "pids.events", "max ") != 1) goto cleanup; ret = KSFT_PASS; cleanup: cg_enter_current(root); if (cg_child) cg_destroy(cg_child); if (cg_parent) cg_destroy(cg_parent); free(cg_child); free(cg_parent); return ret; } #define T(x) { x, #x } struct pids_test { int (*fn)(const char *root); const char *name; } tests[] = { T(test_pids_max), T(test_pids_events), }; #undef T int main(int argc, char **argv) { char root[PATH_MAX]; ksft_print_header(); ksft_set_plan(ARRAY_SIZE(tests)); if (cg_find_unified_root(root, sizeof(root), NULL)) ksft_exit_skip("cgroup v2 isn't mounted\n"); /* * Check that pids controller is available: * pids is listed in cgroup.controllers */ if (cg_read_strstr(root, "cgroup.controllers", "pids")) ksft_exit_skip("pids controller isn't available\n"); if (cg_read_strstr(root, "cgroup.subtree_control", "pids")) if (cg_write(root, "cgroup.subtree_control", "+pids")) ksft_exit_skip("Failed to set pids controller\n"); for (int 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(); }