diff --git a/src/cgroups/cgfsng.c b/src/cgroups/cgfsng.c index 562af3f2..ece65f60 100644 --- a/src/cgroups/cgfsng.c +++ b/src/cgroups/cgfsng.c @@ -399,27 +399,35 @@ static void trim(char *s) */ static int __cg_mount_direct(struct hierarchy *h, const char *controllerpath) { - __do_free char *controllers = NULL; - char *fstype = "cgroup2"; - unsigned long flags = 0; - int ret; - - flags |= MS_NOSUID; - flags |= MS_NOEXEC; - flags |= MS_NODEV; - flags |= MS_RELATIME; - - if (h->version != CGROUP2_SUPER_MAGIC) { - controllers = lxc_string_join(",", (const char **)h->controllers, false); - if (!controllers) - return -ENOMEM; - fstype = "cgroup"; + __do_free char *controllers = NULL; + __do_free const char *opts = NULL; + char *fstype = "cgroup2"; + unsigned long flags = 0; + int ret; + + flags |= MS_NOSUID; + flags |= MS_NOEXEC; + flags |= MS_NODEV; + flags |= MS_RELATIME; + + if (h->version != CGROUP2_SUPER_MAGIC) { + controllers = lxc_string_join(",", (const char **)h->controllers, false); + if (!controllers) + return -ENOMEM; + fstype = "cgroup"; + ret = mount("cgroup", controllerpath, fstype, flags, controllers); + } else { + opts = get_mount_opts(DEFAULT_CGROUP_MOUNTPOINT, fstype); + if (!opts) { + return -1; + } + const char *super_opts = extract_cgroup2_super_opts(opts); + ret = mount(fstype, controllerpath, fstype, flags, super_opts); } - ret = mount("cgroup", controllerpath, fstype, flags, controllers); if (ret < 0) return -1; - + return 0; } diff --git a/src/cgroups/cgroup_utils.c b/src/cgroups/cgroup_utils.c index 199c985a..9bd9ca77 100644 --- a/src/cgroups/cgroup_utils.c +++ b/src/cgroups/cgroup_utils.c @@ -13,6 +13,7 @@ #include #include #include +#include #include "../macro.h" #include "../memory_utils.h" @@ -810,3 +811,60 @@ int cgroup_walkup_to_root(int cgroup2_root_fd, int hierarchy_fd, return log_error_errno(-ELOOP, ELOOP, "To many nested cgroups or invalid mount tree. Terminating walk"); } + +// return first matching cgroup2 super option from opts string +// eg: "rw,nosuid,nodev,noexec,relatime,memory_localevents,memory_recursiveprot" +// returns "memory_localevents,memory_recursiveprot" +const char *extract_cgroup2_super_opts(const char *opts) { + static const char *wanted_opts[] = { + "nsdelegate", + "memory_recursiveprot", + "memory_localevents", + NULL + }; + if (opts == NULL) + return NULL; + + const char *p = opts; + while (*p) { + const char *start = p; + while (*p && *p != ',') + p++; + size_t len = p - start; + for (int i = 0; wanted_opts[i] != NULL; i++) { + size_t wanted_len = strlen(wanted_opts[i]); + if (len == wanted_len && strncmp(start, wanted_opts[i], len) == 0){ + return start; + } + } + if (*p == ',') + p++; + } + return NULL; +} + +// return mount options for given target and expected fs type +// eg: target="/sys/fs/cgroup", expect_type="cgroup2" +// return NULL if not found, else strdup'ed string of mount options +// the caller is responsible for freeing the returned string +char *get_mount_opts(const char *target, const char *expect_type) { + if (target == NULL || expect_type == NULL) + return NULL; + + char *res = NULL; + FILE *fp = setmntent("/proc/self/mounts", "r"); + if (!fp) + return NULL; + + struct mntent *ent; + while ((ent = getmntent(fp)) != NULL) { + if (strncmp(ent->mnt_dir, target, strlen(target)) == 0 + && strncmp(ent->mnt_type, expect_type, strlen(expect_type)) == 0) { + res = strdup(ent->mnt_opts); // allocate, caller frees + goto out; + } + } +out: + endmntent(fp); + return res; +} diff --git a/src/cgroups/cgroup_utils.h b/src/cgroups/cgroup_utils.h index fde4f9aa..849f8e16 100644 --- a/src/cgroups/cgroup_utils.h +++ b/src/cgroups/cgroup_utils.h @@ -98,4 +98,7 @@ static inline bool is_empty_string(const char *s) return !s || strcmp(s, "") == 0; } +const char *extract_cgroup2_super_opts(const char *opts); +char *get_mount_opts(const char *target, const char *expect_type); + #endif /* __LXC_CGROUP_UTILS_H */ diff --git a/tests/meson.build b/tests/meson.build index bca9e42e..d733ae02 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -152,3 +152,16 @@ test_programs += executable( include_directories: config_include, install: false, build_by_default: want_tests != false) + +test_cgroup_utils = executable( + 'test_cgroup_utils', + [ + 'test_cgroup_utils.c', + '../src/cgroups/cgroup_utils.c', + '../src/utils.c' + ], + include_directories: config_include, + install: false, + build_by_default: want_tests != false ) + +test('test_cgroup_utils', test_cgroup_utils) \ No newline at end of file diff --git a/tests/test_cgroup_utils.c b/tests/test_cgroup_utils.c new file mode 100644 index 00000000..4562c2c8 --- /dev/null +++ b/tests/test_cgroup_utils.c @@ -0,0 +1,26 @@ +#include "src/cgroups/cgroup_utils.h" + +void test_extract_cgroup2_super_opts(void) { + const char *opts = "rw,nosuid,nodev,noexec,relatime,memory_localevents,memory_recursiveprot"; + const char *result = extract_cgroup2_super_opts(opts); + if (result == NULL || strcmp(result, "memory_localevents,memory_recursiveprot") != 0) { + fprintf(stderr, "Test failed: expected 'memory_localevents,memory_recursiveprot', got '%s'\n", result ? result : "NULL"); + exit(1); + } +} + +void test_extract_cgroup2_super_opts_not_match(void) { + const char *opts = "rw,nosuid,nodev,noexec,relatime"; + const char *result = extract_cgroup2_super_opts(opts); + if (result != NULL) { + fprintf(stderr, "Test failed: expected NULL, got '%s'\n", result); + exit(1); + } +} + +int main(int argc, char *argv[]) { + test_extract_cgroup2_super_opts(); + test_extract_cgroup2_super_opts_not_match(); + printf("All tests passed\n"); + return 0; +} \ No newline at end of file