Verify that a fork rejected by pids.max increments pids.events and emits an IN_MODIFY notification.
Run the test in a temporary cgroup v2 hierarchy with pids_localevents enabled. Signed-off-by: Sahaj Chaudhari <[email protected]> --- tools/testing/selftests/cgroup/.gitignore | 1 + tools/testing/selftests/cgroup/Makefile | 2 + .../selftests/cgroup/test_pids_local_events.c | 139 ++++++++++++++++++ 3 files changed, 142 insertions(+) create mode 100644 tools/testing/selftests/cgroup/test_pids_local_events.c diff --git a/tools/testing/selftests/cgroup/.gitignore b/tools/testing/selftests/cgroup/.gitignore index 952e4448bf070..0a00eaab14cb1 100644 --- a/tools/testing/selftests/cgroup/.gitignore +++ b/tools/testing/selftests/cgroup/.gitignore @@ -8,5 +8,6 @@ test_kill test_kmem test_memcontrol test_pids +test_pids_local_events test_zswap wait_inotify diff --git a/tools/testing/selftests/cgroup/Makefile b/tools/testing/selftests/cgroup/Makefile index e01584c2189ac..e494487307418 100644 --- a/tools/testing/selftests/cgroup/Makefile +++ b/tools/testing/selftests/cgroup/Makefile @@ -16,6 +16,7 @@ TEST_GEN_PROGS += test_kill TEST_GEN_PROGS += test_kmem TEST_GEN_PROGS += test_memcontrol TEST_GEN_PROGS += test_pids +TEST_GEN_PROGS += test_pids_local_events TEST_GEN_PROGS += test_zswap LOCAL_HDRS += $(selfdir)/clone3/clone3_selftests.h $(selfdir)/pidfd/pidfd.h @@ -32,4 +33,5 @@ $(OUTPUT)/test_kill: $(LIBCGROUP_O) $(OUTPUT)/test_kmem: $(LIBCGROUP_O) $(OUTPUT)/test_memcontrol: $(LIBCGROUP_O) $(OUTPUT)/test_pids: $(LIBCGROUP_O) +$(OUTPUT)/test_pids_local_events: $(LIBCGROUP_O) $(OUTPUT)/test_zswap: $(LIBCGROUP_O) diff --git a/tools/testing/selftests/cgroup/test_pids_local_events.c b/tools/testing/selftests/cgroup/test_pids_local_events.c new file mode 100644 index 0000000000000..fa501e2091873 --- /dev/null +++ b/tools/testing/selftests/cgroup/test_pids_local_events.c @@ -0,0 +1,139 @@ +// SPDX-License-Identifier: GPL-2.0 +#define _GNU_SOURCE + +#include <errno.h> +#include <fcntl.h> +#include <linux/limits.h> +#include <poll.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/inotify.h> +#include <sys/wait.h> +#include <unistd.h> + +#include "cgroup_util.h" +#include "kselftest.h" + +static int test_pids_events_notify(const char *root) +{ + union { + struct inotify_event align; + char bytes[sizeof(struct inotify_event) + NAME_MAX + 1]; + } event_buf; + struct pollfd pfd; + char *cgroup = NULL, *events_path = NULL; + const char *reason = "pids.events did not notify after a rejected fork"; + bool moved = false; + int inotify_fd = -1, ret = KSFT_FAIL, watch; + ssize_t n; + pid_t child; + + cgroup = cg_name(root, "pids_local_events_notify"); + if (!cgroup) { + reason = "failed to allocate cgroup path"; + goto cleanup; + } + if (cg_create(cgroup)) { + reason = "failed to create test cgroup"; + goto cleanup; + } + if (cg_write(cgroup, "pids.max", "1")) { + reason = "failed to set pids.max"; + goto cleanup; + } + + if (asprintf(&events_path, "%s/pids.events", cgroup) < 0) { + reason = "failed to allocate pids.events path"; + goto cleanup; + } + inotify_fd = inotify_init1(IN_CLOEXEC | IN_NONBLOCK); + if (inotify_fd < 0) { + reason = "inotify_init1 failed"; + goto cleanup; + } + watch = inotify_add_watch(inotify_fd, events_path, IN_MODIFY); + if (watch < 0) { + reason = "inotify_add_watch on pids.events failed"; + goto cleanup; + } + + if (cg_enter_current(cgroup)) { + reason = "failed to move test into cgroup"; + goto cleanup; + } + moved = true; + + child = fork(); + if (child == 0) + _exit(0); + if (child > 0) { + waitpid(child, NULL, 0); + reason = "fork succeeded despite pids.max"; + goto cleanup; + } + if (errno != EAGAIN) { + reason = "fork failed for a reason other than pids.max"; + goto cleanup; + } + + if (cg_read_key_long(cgroup, "pids.events", "max ") != 1) { + reason = "pids.events did not count the rejected fork"; + goto cleanup; + } + + pfd.fd = inotify_fd; + pfd.events = POLLIN; + if (poll(&pfd, 1, 1000) <= 0) { + reason = "timed out waiting for pids.events notification"; + goto cleanup; + } + n = read(inotify_fd, event_buf.bytes, sizeof(event_buf.bytes)); + if (n < sizeof(struct inotify_event) || + !(event_buf.align.mask & IN_MODIFY)) { + reason = "received no pids.events modify notification"; + goto cleanup; + } + if (cg_read_key_long(cgroup, "pids.events", "max ") != 1) { + reason = "pids.events did not count the rejected fork"; + goto cleanup; + } + + ret = KSFT_PASS; + +cleanup: + if (inotify_fd >= 0) + close(inotify_fd); + if (moved) + cg_enter_current(root); + if (cgroup) + cg_destroy(cgroup); + free(events_path); + free(cgroup); + if (ret != KSFT_PASS) + ksft_print_msg("%s\n", reason); + return ret; +} + +int main(void) +{ + char root[PATH_MAX]; + int ret; + + ksft_print_header(); + if (cg_find_unified_root(root, sizeof(root), NULL)) + ksft_exit_skip("cgroup v2 isn't mounted\n"); + if (proc_mount_contains("pids_localevents") <= 0) + ksft_exit_skip("mount cgroup2 with pids_localevents to run this test\n"); + 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") && + cg_write(root, "cgroup.subtree_control", "+pids")) + ksft_exit_skip("failed to enable pids controller\n"); + + ksft_set_plan(1); + ret = test_pids_events_notify(root); + ksft_test_result_report(ret, "pids.events notifies on a rejected fork\n"); + ksft_finished(); +} -- 2.43.0

