/* SPDX-License-Identifier: GPL-2.0 */ #define _GNU_SOURCE#include <linux/limits.h>#include <linux/sched.h>#include <sys/types.h>#include <sys/mman.h>#include <sys/mount.h>#include <sys/stat.h>#include <sys/wait.h>#include <unistd.h>#include <fcntl.h>#include <sched.h>#include <stdio.h>#include <errno.h>#include <signal.h>#include <string.h>#include <pthread.h> #include "kselftest.h"#include "cgroup_util.h" static bool nsdelegate;#ifndef CLONE_NEWCGROUP#define CLONE_NEWCGROUP 0#endif static int touch_anon(char *buf, size_t size){ int fd; char *pos = buf; fd = open("/dev/urandom", O_RDONLY); if (fd < 0) return -1; while (size > 0) { ssize_t ret = read(fd, pos, size); if (ret < 0) { if (errno != EINTR) { close(fd); return -1; } } else { pos += ret; size -= ret; } } close(fd); return 0;} static int alloc_and_touch_anon_noexit(const char *cgroup, void *arg){ int ppid = getppid(); size_t size = (size_t)arg; void *buf; buf = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, 0, 0); if (buf == MAP_FAILED) return -1; if (touch_anon((char *)buf, size)) { munmap(buf, size); return -1; } while (getppid() == ppid) sleep(1); munmap(buf, size); return 0;} /* * Create a child process that allocates and touches 100MB, then waits to be * killed. Wait until the child is attached to the cgroup, kill all processes * in that cgroup and wait until "cgroup.procs" is empty. At this point try to * destroy the empty cgroup. The test helps detect race conditions between * dying processes leaving the cgroup and cgroup destruction path. */static int test_cgcore_destroy(const char *root){ int ret = KSFT_FAIL; char *cg_test = NULL; int child_pid; char buf[BUF_SIZE]; cg_test = cg_name(root, "cg_test"); if (!cg_test) goto cleanup; for (int i = 0; i < 10; i++) { if (cg_create(cg_test)) goto cleanup; child_pid = cg_run_nowait(cg_test, alloc_and_touch_anon_noexit, (void *) MB(100)); if (child_pid < 0) goto cleanup; /* wait for the child to enter cgroup */ if (cg_wait_for_proc_count(cg_test, 1)) goto cleanup; if (cg_killall(cg_test)) goto cleanup; /* wait for cgroup to be empty */ while (1) { if (cg_read(cg_test, "cgroup.procs", buf, sizeof(buf))) goto cleanup; if (buf[0] == '\0') break; usleep(1000); } if (rmdir(cg_test)) goto cleanup; if (waitpid(child_pid, NULL, 0) < 0) goto cleanup; } ret = KSFT_PASS;cleanup: if (cg_test) cg_destroy(cg_test); free(cg_test); return ret;} /* * A(0) - B(0) - C(1) * \ D(0) * * A, B and C's "populated" fields would be 1 while D's 0. * test that after the one process in C is moved to root, * A,B and C's "populated" fields would flip to "0" and file * modified events will be generated on the * "cgroup.events" files of both cgroups. */static int test_cgcore_populated(const char *root){ int ret = KSFT_FAIL; int err; char *cg_test_a = NULL, *cg_test_b = NULL; char *cg_test_c = NULL, *cg_test_d = NULL; int cgroup_fd = -EBADF; pid_t pid; if (cg_test_v1_named) return KSFT_SKIP; cg_test_a = cg_name(root, "cg_test_a"); cg_test_b = cg_name(root, "cg_test_a/cg_test_b"); cg_test_c = cg_name(root, "cg_test_a/cg_test_b/cg_test_c"); cg_test_d = cg_name(root, "cg_test_a/cg_test_b/cg_test_d"); if (!cg_test_a || !cg_test_b || !cg_test_c || !cg_test_d) goto cleanup; if (cg_create(cg_test_a)) goto cleanup; if (cg_create(cg_test_b)) goto cleanup; if (cg_create(cg_test_c)) goto cleanup; if (cg_create(cg_test_d)) goto cleanup; if (cg_enter_current(cg_test_c)) goto cleanup; if (cg_read_strcmp(cg_test_a, "cgroup.events", "populated 1\n")) goto cleanup; if (cg_read_strcmp(cg_test_b, "cgroup.events", "populated 1\n")) goto cleanup; if (cg_read_strcmp(cg_test_c, "cgroup.events", "populated 1\n")) goto cleanup; if (cg_read_strcmp(cg_test_d, "cgroup.events", "populated 0\n")) goto cleanup; if (cg_enter_current(root)) goto cleanup; if (cg_read_strcmp(cg_test_a, "cgroup.events", "populated 0\n")) goto cleanup; if (cg_read_strcmp(cg_test_b, "cgroup.events", "populated 0\n")) goto cleanup; if (cg_read_strcmp(cg_test_c, "cgroup.events", "populated 0\n")) goto cleanup; if (cg_read_strcmp(cg_test_d, "cgroup.events", "populated 0\n")) goto cleanup; /* Test that we can directly clone into a new cgroup. */ cgroup_fd = dirfd_open_opath(cg_test_d); if (cgroup_fd < 0) goto cleanup; pid = clone_into_cgroup(cgroup_fd); if (pid < 0) { if (errno == ENOSYS) goto cleanup_pass; goto cleanup; } if (pid == 0) { if (raise(SIGSTOP)) exit(EXIT_FAILURE); exit(EXIT_SUCCESS); } err = cg_read_strcmp(cg_test_d, "cgroup.events", "populated 1\n"); (void)clone_reap(pid, WSTOPPED); (void)kill(pid, SIGCONT); (void)clone_reap(pid, WEXITED); if (err) goto cleanup; if (cg_read_strcmp_wait(cg_test_d, "cgroup.events", "populated 0\n")) goto cleanup; /* Remove cgroup. */ if (cg_test_d) { cg_destroy(cg_test_d); free(cg_test_d); cg_test_d = NULL; } pid = clone_into_cgroup(cgroup_fd); if (pid < 0) goto cleanup_pass; if (pid == 0) exit(EXIT_SUCCESS); (void)clone_reap(pid, WEXITED); goto cleanup; cleanup_pass: ret = KSFT_PASS; cleanup: if (cg_test_d) cg_destroy(cg_test_d); if (cg_test_c) cg_destroy(cg_test_c); if (cg_test_b) cg_destroy(cg_test_b); if (cg_test_a) cg_destroy(cg_test_a); free(cg_test_d); free(cg_test_c); free(cg_test_b); free(cg_test_a); if (cgroup_fd >= 0) close(cgroup_fd); return ret;} /* * A (domain threaded) - B (threaded) - C (domain) * * test that C can't be used until it is turned into a * threaded cgroup. "cgroup.type" file will report "domain (invalid)" in * these cases. Operations which fail due to invalid topology use * EOPNOTSUPP as the errno. */static int test_cgcore_invalid_domain(const char *root){ int ret = KSFT_FAIL; char *grandparent = NULL, *parent = NULL, *child = NULL; if (cg_test_v1_named) return KSFT_SKIP; grandparent = cg_name(root, "cg_test_grandparent"); parent = cg_name(root, "cg_test_grandparent/cg_test_parent"); child = cg_name(root, "cg_test_grandparent/cg_test_parent/cg_test_child"); if (!parent || !child || !grandparent) goto cleanup; if (cg_create(grandparent)) goto cleanup; if (cg_create(parent)) goto cleanup; if (cg_create(child)) goto cleanup; if (cg_write(parent, "cgroup.type", "threaded")) goto cleanup; if (cg_read_strcmp(child, "cgroup.type", "domain invalid\n")) goto cleanup; if (!cg_enter_current(child)) goto cleanup; if (errno != EOPNOTSUPP) goto cleanup; if (!clone_into_cgroup_run_wait(child)) goto cleanup; if (errno == ENOSYS) goto cleanup_pass; if (errno != EOPNOTSUPP) goto cleanup; cleanup_pass: ret = KSFT_PASS; cleanup: cg_enter_current(root); if (child) cg_destroy(child); if (parent) cg_destroy(parent); if (grandparent) cg_destroy(grandparent); free(child); free(parent); free(grandparent); return ret;} /* * Test that when a child becomes threaded * the parent type becomes domain threaded. */static int test_cgcore_parent_becomes_threaded(const char *root){ int ret = KSFT_FAIL; char *parent = NULL, *child = NULL; if (cg_test_v1_named) return KSFT_SKIP; parent = cg_name(root, "cg_test_parent"); child = cg_name(root, "cg_test_parent/cg_test_child"); if (!parent || !child) goto cleanup; if (cg_create(parent)) goto cleanup; if (cg_create(child)) goto cleanup; if (cg_write(child, "cgroup.type", "threaded")) goto cleanup; if (cg_read_strcmp(parent, "cgroup.type", "domain threaded\n")) goto cleanup; ret = KSFT_PASS; cleanup: if (child) cg_destroy(child); if (parent) cg_destroy(parent); free(child); free(parent); return ret; } /* * Test that there's no internal process constrain on threaded cgroups. * You can add threads/processes on a parent with a controller enabled. */static int test_cgcore_no_internal_process_constraint_on_threads(const char *root){ int ret = KSFT_FAIL; char *parent = NULL, *child = NULL; if (cg_test_v1_named || cg_read_strstr(root, "cgroup.controllers", "cpu") || cg_write(root, "cgroup.subtree_control", "+cpu")) { ret = KSFT_SKIP; goto cleanup; } parent = cg_name(root, "cg_test_parent"); child = cg_name(root, "cg_test_parent/cg_test_child"); if (!parent || !child) goto cleanup; if (cg_create(parent)) goto cleanup; if (cg_create(child)) goto cleanup; if (cg_write(parent, "cgroup.type", "threaded")) goto cleanup; if (cg_write(child, "cgroup.type", "threaded")) goto cleanup; if (cg_write(parent, "cgroup.subtree_control", "+cpu")) goto cleanup; if (cg_enter_current(parent)) goto cleanup; ret = KSFT_PASS; cleanup: cg_enter_current(root); if (child) cg_destroy(child); if (parent) cg_destroy(parent); free(child); free(parent); return ret;} /* * Test that you can't enable a controller on a child if it's not enabled * on the parent. */static int test_cgcore_top_down_constraint_enable(const char *root){ int ret = KSFT_FAIL; char *parent = NULL, *child = NULL; if (cg_test_v1_named) return KSFT_SKIP; parent = cg_name(root, "cg_test_parent"); child = cg_name(root, "cg_test_parent/cg_test_child"); if (!parent || !child) goto cleanup; if (cg_create(parent)) goto cleanup; if (cg_create(child)) goto cleanup; if (!cg_write(child, "cgroup.subtree_control", "+memory")) goto cleanup; ret = KSFT_PASS; cleanup: if (child) cg_destroy(child); if (parent) cg_destroy(parent); free(child); free(parent); return ret;} /* * Test that you can't disable a controller on a parent * if it's enabled in a child. */static int test_cgcore_top_down_constraint_disable(const char *root){ int ret = KSFT_FAIL; char *parent = NULL, *child = NULL; if (cg_test_v1_named) return KSFT_SKIP; parent = cg_name(root, "cg_test_parent"); child = cg_name(root, "cg_test_parent/cg_test_child"); if (!parent || !child) goto cleanup; if (cg_create(parent)) goto cleanup; if (cg_create(child)) goto cleanup; if (cg_write(parent, "cgroup.subtree_control", "+memory"))Lines 1–500 of 951. Load more
/* SPDX-License-Identifier: GPL-2.0 */ #define _GNU_SOURCE #include <linux/limits.h> #include <linux/sched.h> #include <sys/types.h> #include <sys/mman.h> #include <sys/mount.h> #include <sys/stat.h> #include <sys/wait.h> #include <unistd.h> #include <fcntl.h> #include <sched.h> #include <stdio.h> #include <errno.h> #include <signal.h> #include <string.h> #include <pthread.h> #include "kselftest.h" #include "cgroup_util.h" static bool nsdelegate; #ifndef CLONE_NEWCGROUP #define CLONE_NEWCGROUP 0 #endif static int touch_anon(char *buf, size_t size) { int fd; char *pos = buf; fd = open("/dev/urandom", O_RDONLY); if (fd < 0) return -1; while (size > 0) { ssize_t ret = read(fd, pos, size); if (ret < 0) { if (errno != EINTR) { close(fd); return -1; } } else { pos += ret; size -= ret; } } close(fd); return 0; } static int alloc_and_touch_anon_noexit(const char *cgroup, void *arg) { int ppid = getppid(); size_t size = (size_t)arg; void *buf; buf = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, 0, 0); if (buf == MAP_FAILED) return -1; if (touch_anon((char *)buf, size)) { munmap(buf, size); return -1; } while (getppid() == ppid) sleep(1); munmap(buf, size); return 0; } /* * Create a child process that allocates and touches 100MB, then waits to be * killed. Wait until the child is attached to the cgroup, kill all processes * in that cgroup and wait until "cgroup.procs" is empty. At this point try to * destroy the empty cgroup. The test helps detect race conditions between * dying processes leaving the cgroup and cgroup destruction path. */ static int test_cgcore_destroy(const char *root) { int ret = KSFT_FAIL; char *cg_test = NULL; int child_pid; char buf[BUF_SIZE]; cg_test = cg_name(root, "cg_test"); if (!cg_test) goto cleanup; for (int i = 0; i < 10; i++) { if (cg_create(cg_test)) goto cleanup; child_pid = cg_run_nowait(cg_test, alloc_and_touch_anon_noexit, (void *) MB(100)); if (child_pid < 0) goto cleanup; /* wait for the child to enter cgroup */ if (cg_wait_for_proc_count(cg_test, 1)) goto cleanup; if (cg_killall(cg_test)) goto cleanup; /* wait for cgroup to be empty */ while (1) { if (cg_read(cg_test, "cgroup.procs", buf, sizeof(buf))) goto cleanup; if (buf[0] == '\0') break; usleep(1000); } if (rmdir(cg_test)) goto cleanup; if (waitpid(child_pid, NULL, 0) < 0) goto cleanup; } ret = KSFT_PASS; cleanup: if (cg_test) cg_destroy(cg_test); free(cg_test); return ret; } /* * A(0) - B(0) - C(1) * \ D(0) * * A, B and C's "populated" fields would be 1 while D's 0. * test that after the one process in C is moved to root, * A,B and C's "populated" fields would flip to "0" and file * modified events will be generated on the * "cgroup.events" files of both cgroups. */ static int test_cgcore_populated(const char *root) { int ret = KSFT_FAIL; int err; char *cg_test_a = NULL, *cg_test_b = NULL; char *cg_test_c = NULL, *cg_test_d = NULL; int cgroup_fd = -EBADF; pid_t pid; if (cg_test_v1_named) return KSFT_SKIP; cg_test_a = cg_name(root, "cg_test_a"); cg_test_b = cg_name(root, "cg_test_a/cg_test_b"); cg_test_c = cg_name(root, "cg_test_a/cg_test_b/cg_test_c"); cg_test_d = cg_name(root, "cg_test_a/cg_test_b/cg_test_d"); if (!cg_test_a || !cg_test_b || !cg_test_c || !cg_test_d) goto cleanup; if (cg_create(cg_test_a)) goto cleanup; if (cg_create(cg_test_b)) goto cleanup; if (cg_create(cg_test_c)) goto cleanup; if (cg_create(cg_test_d)) goto cleanup; if (cg_enter_current(cg_test_c)) goto cleanup; if (cg_read_strcmp(cg_test_a, "cgroup.events", "populated 1\n")) goto cleanup; if (cg_read_strcmp(cg_test_b, "cgroup.events", "populated 1\n")) goto cleanup; if (cg_read_strcmp(cg_test_c, "cgroup.events", "populated 1\n")) goto cleanup; if (cg_read_strcmp(cg_test_d, "cgroup.events", "populated 0\n")) goto cleanup; if (cg_enter_current(root)) goto cleanup; if (cg_read_strcmp(cg_test_a, "cgroup.events", "populated 0\n")) goto cleanup; if (cg_read_strcmp(cg_test_b, "cgroup.events", "populated 0\n")) goto cleanup; if (cg_read_strcmp(cg_test_c, "cgroup.events", "populated 0\n")) goto cleanup; if (cg_read_strcmp(cg_test_d, "cgroup.events", "populated 0\n")) goto cleanup; /* Test that we can directly clone into a new cgroup. */ cgroup_fd = dirfd_open_opath(cg_test_d); if (cgroup_fd < 0) goto cleanup; pid = clone_into_cgroup(cgroup_fd); if (pid < 0) { if (errno == ENOSYS) goto cleanup_pass; goto cleanup; } if (pid == 0) { if (raise(SIGSTOP)) exit(EXIT_FAILURE); exit(EXIT_SUCCESS); } err = cg_read_strcmp(cg_test_d, "cgroup.events", "populated 1\n"); (void)clone_reap(pid, WSTOPPED); (void)kill(pid, SIGCONT); (void)clone_reap(pid, WEXITED); if (err) goto cleanup; if (cg_read_strcmp_wait(cg_test_d, "cgroup.events", "populated 0\n")) goto cleanup; /* Remove cgroup. */ if (cg_test_d) { cg_destroy(cg_test_d); free(cg_test_d); cg_test_d = NULL; } pid = clone_into_cgroup(cgroup_fd); if (pid < 0) goto cleanup_pass; if (pid == 0) exit(EXIT_SUCCESS); (void)clone_reap(pid, WEXITED); goto cleanup; cleanup_pass: ret = KSFT_PASS; cleanup: if (cg_test_d) cg_destroy(cg_test_d); if (cg_test_c) cg_destroy(cg_test_c); if (cg_test_b) cg_destroy(cg_test_b); if (cg_test_a) cg_destroy(cg_test_a); free(cg_test_d); free(cg_test_c); free(cg_test_b); free(cg_test_a); if (cgroup_fd >= 0) close(cgroup_fd); return ret; } /* * A (domain threaded) - B (threaded) - C (domain) * * test that C can't be used until it is turned into a * threaded cgroup. "cgroup.type" file will report "domain (invalid)" in * these cases. Operations which fail due to invalid topology use * EOPNOTSUPP as the errno. */ static int test_cgcore_invalid_domain(const char *root) { int ret = KSFT_FAIL; char *grandparent = NULL, *parent = NULL, *child = NULL; if (cg_test_v1_named) return KSFT_SKIP; grandparent = cg_name(root, "cg_test_grandparent"); parent = cg_name(root, "cg_test_grandparent/cg_test_parent"); child = cg_name(root, "cg_test_grandparent/cg_test_parent/cg_test_child"); if (!parent || !child || !grandparent) goto cleanup; if (cg_create(grandparent)) goto cleanup; if (cg_create(parent)) goto cleanup; if (cg_create(child)) goto cleanup; if (cg_write(parent, "cgroup.type", "threaded")) goto cleanup; if (cg_read_strcmp(child, "cgroup.type", "domain invalid\n")) goto cleanup; if (!cg_enter_current(child)) goto cleanup; if (errno != EOPNOTSUPP) goto cleanup; if (!clone_into_cgroup_run_wait(child)) goto cleanup; if (errno == ENOSYS) goto cleanup_pass; if (errno != EOPNOTSUPP) goto cleanup; cleanup_pass: ret = KSFT_PASS; cleanup: cg_enter_current(root); if (child) cg_destroy(child); if (parent) cg_destroy(parent); if (grandparent) cg_destroy(grandparent); free(child); free(parent); free(grandparent); return ret; } /* * Test that when a child becomes threaded * the parent type becomes domain threaded. */ static int test_cgcore_parent_becomes_threaded(const char *root) { int ret = KSFT_FAIL; char *parent = NULL, *child = NULL; if (cg_test_v1_named) return KSFT_SKIP; parent = cg_name(root, "cg_test_parent"); child = cg_name(root, "cg_test_parent/cg_test_child"); if (!parent || !child) goto cleanup; if (cg_create(parent)) goto cleanup; if (cg_create(child)) goto cleanup; if (cg_write(child, "cgroup.type", "threaded")) goto cleanup; if (cg_read_strcmp(parent, "cgroup.type", "domain threaded\n")) goto cleanup; ret = KSFT_PASS; cleanup: if (child) cg_destroy(child); if (parent) cg_destroy(parent); free(child); free(parent); return ret; } /* * Test that there's no internal process constrain on threaded cgroups. * You can add threads/processes on a parent with a controller enabled. */ static int test_cgcore_no_internal_process_constraint_on_threads(const char *root) { int ret = KSFT_FAIL; char *parent = NULL, *child = NULL; if (cg_test_v1_named || cg_read_strstr(root, "cgroup.controllers", "cpu") || cg_write(root, "cgroup.subtree_control", "+cpu")) { ret = KSFT_SKIP; goto cleanup; } parent = cg_name(root, "cg_test_parent"); child = cg_name(root, "cg_test_parent/cg_test_child"); if (!parent || !child) goto cleanup; if (cg_create(parent)) goto cleanup; if (cg_create(child)) goto cleanup; if (cg_write(parent, "cgroup.type", "threaded")) goto cleanup; if (cg_write(child, "cgroup.type", "threaded")) goto cleanup; if (cg_write(parent, "cgroup.subtree_control", "+cpu")) goto cleanup; if (cg_enter_current(parent)) goto cleanup; ret = KSFT_PASS; cleanup: cg_enter_current(root); if (child) cg_destroy(child); if (parent) cg_destroy(parent); free(child); free(parent); return ret; } /* * Test that you can't enable a controller on a child if it's not enabled * on the parent. */ static int test_cgcore_top_down_constraint_enable(const char *root) { int ret = KSFT_FAIL; char *parent = NULL, *child = NULL; if (cg_test_v1_named) return KSFT_SKIP; parent = cg_name(root, "cg_test_parent"); child = cg_name(root, "cg_test_parent/cg_test_child"); if (!parent || !child) goto cleanup; if (cg_create(parent)) goto cleanup; if (cg_create(child)) goto cleanup; if (!cg_write(child, "cgroup.subtree_control", "+memory")) goto cleanup; ret = KSFT_PASS; cleanup: if (child) cg_destroy(child); if (parent) cg_destroy(parent); free(child); free(parent); return ret; } /* * Test that you can't disable a controller on a parent * if it's enabled in a child. */ static int test_cgcore_top_down_constraint_disable(const char *root) { int ret = KSFT_FAIL; char *parent = NULL, *child = NULL; if (cg_test_v1_named) return KSFT_SKIP; parent = cg_name(root, "cg_test_parent"); child = cg_name(root, "cg_test_parent/cg_test_child"); if (!parent || !child) goto cleanup; if (cg_create(parent)) goto cleanup; if (cg_create(child)) goto cleanup; if (cg_write(parent, "cgroup.subtree_control", "+memory"))