This comes useful when using selftests from mainline on older kernels/setups that still rely on cgroup v1 while maintains single variant of selftests. The core tests that rely on v2 specific features are skipped, the remaining ones are adjusted to work with a v1 hierarchy.
The expected output on v1 system: ok 1 # SKIP test_cgcore_internal_process_constraint ok 2 # SKIP test_cgcore_top_down_constraint_enable ok 3 # SKIP test_cgcore_top_down_constraint_disable ok 4 # SKIP test_cgcore_no_internal_process_constraint_on_threads ok 5 # SKIP test_cgcore_parent_becomes_threaded ok 6 # SKIP test_cgcore_invalid_domain ok 7 # SKIP test_cgcore_populated ok 8 test_cgcore_proc_migration ok 9 test_cgcore_thread_migration ok 10 test_cgcore_destroy ok 11 test_cgcore_lesser_euid_open ok 12 # SKIP test_cgcore_lesser_ns_open
Michal Koutný (4): selftests: cgroup_util: Add helpers for testing named v1 hierarchies selftests: cgroup: Add support for named v1 hierarchies in test_core selftests: cgroup: Optionally set up v1 environment selftests: cgroup: Fix compilation on pre-cgroupns kernels
.../selftests/cgroup/lib/cgroup_util.c | 4 +- .../cgroup/lib/include/cgroup_util.h | 5 ++ tools/testing/selftests/cgroup/test_core.c | 84 ++++++++++++++++--- 3 files changed, 82 insertions(+), 11 deletions(-)
base-commit: 9afe652958c3ee88f24df1e4a97f298afce89407
Non-functional change, the control variable will be wired in a separate commit.
Signed-off-by: Michal Koutný mkoutny@suse.com --- tools/testing/selftests/cgroup/lib/cgroup_util.c | 4 +++- tools/testing/selftests/cgroup/lib/include/cgroup_util.h | 5 +++++ tools/testing/selftests/cgroup/test_core.c | 6 +++--- 3 files changed, 11 insertions(+), 4 deletions(-)
diff --git a/tools/testing/selftests/cgroup/lib/cgroup_util.c b/tools/testing/selftests/cgroup/lib/cgroup_util.c index 8832f3d1cb614..0e89fcff4d05d 100644 --- a/tools/testing/selftests/cgroup/lib/cgroup_util.c +++ b/tools/testing/selftests/cgroup/lib/cgroup_util.c @@ -19,6 +19,8 @@ #include "cgroup_util.h" #include "../../clone3/clone3_selftests.h"
+bool cg_test_v1_named; + /* Returns read len on success, or -errno on failure. */ ssize_t read_text(const char *path, char *buf, size_t max_len) { @@ -361,7 +363,7 @@ int cg_enter_current(const char *cgroup)
int cg_enter_current_thread(const char *cgroup) { - return cg_write(cgroup, "cgroup.threads", "0"); + return cg_write(cgroup, CG_THREADS_FILE, "0"); }
int cg_run(const char *cgroup, diff --git a/tools/testing/selftests/cgroup/lib/include/cgroup_util.h b/tools/testing/selftests/cgroup/lib/include/cgroup_util.h index adb2bc1931839..c69cab66254b4 100644 --- a/tools/testing/selftests/cgroup/lib/include/cgroup_util.h +++ b/tools/testing/selftests/cgroup/lib/include/cgroup_util.h @@ -13,6 +13,10 @@
#define TEST_UID 65534 /* usually nobody, any !root is fine */
+#define CG_THREADS_FILE (!cg_test_v1_named ? "cgroup.threads" : "tasks") +#define CG_NAMED_NAME "selftest" +#define CG_PATH_FORMAT (!cg_test_v1_named ? "0::%s" : (":name=" CG_NAMED_NAME ":%s")) + /* * Checks if two given values differ by less than err% of their sum. */ @@ -65,3 +69,4 @@ extern int dirfd_open_opath(const char *dir); extern int cg_prepare_for_wait(const char *cgroup); extern int memcg_prepare_for_wait(const char *cgroup); extern int cg_wait_for(int fd); +extern bool cg_test_v1_named; diff --git a/tools/testing/selftests/cgroup/test_core.c b/tools/testing/selftests/cgroup/test_core.c index a5672a91d273c..0c4cc4e5fc8c2 100644 --- a/tools/testing/selftests/cgroup/test_core.c +++ b/tools/testing/selftests/cgroup/test_core.c @@ -573,7 +573,7 @@ static int test_cgcore_proc_migration(const char *root) }
cg_enter_current(dst); - if (cg_read_lc(dst, "cgroup.threads") != n_threads + 1) + if (cg_read_lc(dst, CG_THREADS_FILE) != n_threads + 1) goto cleanup;
ret = KSFT_PASS; @@ -605,7 +605,7 @@ static void *migrating_thread_fn(void *arg) char lines[3][PATH_MAX];
for (g = 1; g < 3; ++g) - snprintf(lines[g], sizeof(lines[g]), "0::%s", grps[g] + strlen(grps[0])); + snprintf(lines[g], sizeof(lines[g]), CG_PATH_FORMAT, grps[g] + strlen(grps[0]));
for (i = 0; i < n_iterations; ++i) { cg_enter_current_thread(grps[(i % 2) + 1]); @@ -659,7 +659,7 @@ static int test_cgcore_thread_migration(const char *root) if (retval) goto cleanup;
- snprintf(line, sizeof(line), "0::%s", grps[1] + strlen(grps[0])); + snprintf(line, sizeof(line), CG_PATH_FORMAT, grps[1] + strlen(grps[0])); if (proc_read_strstr(0, 1, "cgroup", line)) goto cleanup;
This comes useful when using selftests from mainline on older kernels/setups that still rely on cgroup v1. The core tests that rely on v2 specific features are skipped, the remaining ones are adjusted to work with a v1 hierarchy.
The expected output on v1 system: ok 1 # SKIP test_cgcore_internal_process_constraint ok 2 # SKIP test_cgcore_top_down_constraint_enable ok 3 # SKIP test_cgcore_top_down_constraint_disable ok 4 # SKIP test_cgcore_no_internal_process_constraint_on_threads ok 5 # SKIP test_cgcore_parent_becomes_threaded ok 6 # SKIP test_cgcore_invalid_domain ok 7 # SKIP test_cgcore_populated ok 8 test_cgcore_proc_migration ok 9 test_cgcore_thread_migration ok 10 test_cgcore_destroy ok 11 test_cgcore_lesser_euid_open ok 12 # SKIP test_cgcore_lesser_ns_open
Signed-off-by: Michal Koutný mkoutny@suse.com --- tools/testing/selftests/cgroup/test_core.c | 31 ++++++++++++++++++---- 1 file changed, 26 insertions(+), 5 deletions(-)
diff --git a/tools/testing/selftests/cgroup/test_core.c b/tools/testing/selftests/cgroup/test_core.c index 0c4cc4e5fc8c2..338e276aae5da 100644 --- a/tools/testing/selftests/cgroup/test_core.c +++ b/tools/testing/selftests/cgroup/test_core.c @@ -148,6 +148,9 @@ static int test_cgcore_populated(const char *root) 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"); @@ -277,6 +280,9 @@ 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"); @@ -339,6 +345,9 @@ 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) @@ -378,7 +387,8 @@ static int test_cgcore_no_internal_process_constraint_on_threads(const char *roo int ret = KSFT_FAIL; char *parent = NULL, *child = NULL;
- if (cg_read_strstr(root, "cgroup.controllers", "cpu") || + if (cg_test_v1_named || + cg_read_strstr(root, "cgroup.controllers", "cpu") || cg_write(root, "cgroup.subtree_control", "+cpu")) { ret = KSFT_SKIP; goto cleanup; @@ -430,6 +440,9 @@ 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) @@ -465,6 +478,9 @@ 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) @@ -506,6 +522,9 @@ static int test_cgcore_internal_process_constraint(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) @@ -642,10 +661,12 @@ static int test_cgcore_thread_migration(const char *root) if (cg_create(grps[2])) goto cleanup;
- if (cg_write(grps[1], "cgroup.type", "threaded")) - goto cleanup; - if (cg_write(grps[2], "cgroup.type", "threaded")) - goto cleanup; + if (!cg_test_v1_named) { + if (cg_write(grps[1], "cgroup.type", "threaded")) + goto cleanup; + if (cg_write(grps[2], "cgroup.type", "threaded")) + goto cleanup; + }
if (cg_enter_current(grps[1])) goto cleanup;
Use the missing mount of the unifier hierarchy as a hint of legacy system and prepare our own named v1 hierarchy for tests.
The code is only in test_core.c and not cgroup_util.c because other selftests are related to controllers which will be exposed on v2 hierarchy but named hierarchies are only v1 thing.
Signed-off-by: Michal Koutný mkoutny@suse.com --- tools/testing/selftests/cgroup/test_core.c | 44 +++++++++++++++++++++- 1 file changed, 42 insertions(+), 2 deletions(-)
diff --git a/tools/testing/selftests/cgroup/test_core.c b/tools/testing/selftests/cgroup/test_core.c index 338e276aae5da..452c2abf9794e 100644 --- a/tools/testing/selftests/cgroup/test_core.c +++ b/tools/testing/selftests/cgroup/test_core.c @@ -5,6 +5,8 @@ #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> @@ -863,6 +865,38 @@ static int test_cgcore_lesser_ns_open(const char *root) return ret; }
+static int setup_named_v1_root(char *root, size_t len, const char *name) +{ + char options[PATH_MAX]; + int r; + + r = snprintf(root, len, "/mnt/cg_selftest"); + if (r < 0) + return r; + + r = snprintf(options, sizeof(options), "none,name=%s", name); + if (r < 0) + return r; + + r = mkdir(root, 0755); + if (r < 0 && errno != EEXIST) + return r; + + r = mount("none", root, "cgroup", 0, options); + if (r < 0) + return r; + + return 0; +} + +static void cleanup_named_v1_root(char *root) +{ + if (!cg_test_v1_named) + return; + umount(root); + rmdir(root); +} + #define T(x) { x, #x } struct corecg_test { int (*fn)(const char *root); @@ -888,13 +922,18 @@ int main(int argc, char *argv[]) char root[PATH_MAX]; int i, ret = EXIT_SUCCESS;
- if (cg_find_unified_root(root, sizeof(root), &nsdelegate)) - ksft_exit_skip("cgroup v2 isn't mounted\n"); + if (cg_find_unified_root(root, sizeof(root), &nsdelegate)) { + if (setup_named_v1_root(root, sizeof(root), CG_NAMED_NAME)) + ksft_exit_skip("cgroup v2 isn't mounted and could not setup named v1 hierarchy\n"); + cg_test_v1_named = true; + goto post_v2_setup; + }
if (cg_read_strstr(root, "cgroup.subtree_control", "memory")) if (cg_write(root, "cgroup.subtree_control", "+memory")) ksft_exit_skip("Failed to set memory controller\n");
+post_v2_setup: for (i = 0; i < ARRAY_SIZE(tests); i++) { switch (tests[i].fn(root)) { case KSFT_PASS: @@ -910,5 +949,6 @@ int main(int argc, char *argv[]) } }
+ cleanup_named_v1_root(root); return ret; }
The test would be skipped because of nsdelegate, so the defined value is not used (0 is always acceptable).
Signed-off-by: Michal Koutný mkoutny@suse.com --- tools/testing/selftests/cgroup/test_core.c | 3 +++ 1 file changed, 3 insertions(+)
diff --git a/tools/testing/selftests/cgroup/test_core.c b/tools/testing/selftests/cgroup/test_core.c index 452c2abf9794e..a360e2eb2eefd 100644 --- a/tools/testing/selftests/cgroup/test_core.c +++ b/tools/testing/selftests/cgroup/test_core.c @@ -21,6 +21,9 @@ #include "cgroup_util.h"
static bool nsdelegate; +#ifndef CLONE_NEWCGROUP +#define CLONE_NEWCGROUP 0 +#endif
static int touch_anon(char *buf, size_t size) {
On Tue, Jun 17, 2025 at 03:36:52PM +0200, Michal Koutný wrote:
Michal Koutný (4): selftests: cgroup_util: Add helpers for testing named v1 hierarchies selftests: cgroup: Add support for named v1 hierarchies in test_core selftests: cgroup: Optionally set up v1 environment selftests: cgroup: Fix compilation on pre-cgroupns kernels
Applied to cgroup/for-6.17.
Thanks.
linux-kselftest-mirror@lists.linaro.org