[Devel] [PATCH vz10 v3 8/9] selftests/ve: factor common helpers into ve_selftest.h
Vladimir Riabchun
vladimir.riabchun at virtuozzo.com
Fri Jul 17 11:28:29 MSK 2026
On 17.07.2026 01:25, Mirian Shilakadze wrote:
> The ve selftests each keep their own copies of the same small pieces: a
> private cgroup2 mount, an openat based file write helper, a u64 file
> reader, a cgroup join helper, VE cgroup create and destroy, the
> CLONE_NEWVE fallback, the ctid search range, and the controller set a
> VE cgroup needs enabled. The new ve.proc_permissions test needs them
> too, so move them into a shared ve_selftest.h, the helpers made static
> inline so they can live in a header, and drop the copies from
> ve_ns_owner_test.
It would be really cool to move ve_devcg_bpf and ve_printk tests to
ve directory as well and reuse these nice helpers. Maybe in a separate
task.
>
> No functional change to the tests. The moved code keeps the same
> behaviour bar cosmetic reformatting. The shared destroy_ve additionally
> retries briefly on a busy cgroup, which only makes teardown more robust.
>
> https://virtuozzo.atlassian.net/browse/VSTOR-135286
> Signed-off-by: Mirian Shilakadze <mirian.shilakadze at virtuozzo.com>
> ---
> tools/testing/selftests/ve/ve_ns_owner_test.c | 111 +------------
> tools/testing/selftests/ve/ve_selftest.h | 151 ++++++++++++++++++
> 2 files changed, 158 insertions(+), 104 deletions(-)
> create mode 100644 tools/testing/selftests/ve/ve_selftest.h
>
> diff --git a/tools/testing/selftests/ve/ve_ns_owner_test.c b/tools/testing/selftests/ve/ve_ns_owner_test.c
> index b5b4f10be72b..82c31dff4b1b 100644
> --- a/tools/testing/selftests/ve/ve_ns_owner_test.c
> +++ b/tools/testing/selftests/ve/ve_ns_owner_test.c
> @@ -43,13 +43,7 @@
> #include <errno.h>
>
> #include "../kselftest_harness.h"
> -
> -#define CTID_MIN 108
> -#define CTID_MAX 200
> -
> -#ifndef CLONE_NEWVE
> -#define CLONE_NEWVE 0x00000040
> -#endif
> +#include "ve_selftest.h"
>
> /*
> * Make ve.netns_avail_nr movements easy to detect: a small cap means
> @@ -57,81 +51,6 @@
> */
> #define VE_NETNS_MAX 3
>
> -static int write_file_at(int dirfd, const char *path, const char *val)
> -{
> - int fd, ret;
> - size_t len = strlen(val);
> -
> - fd = openat(dirfd, path, O_WRONLY);
> - if (fd < 0)
> - return -1;
> -
> - ret = write(fd, val, len);
> - close(fd);
> - return (ret == (int)len) ? 0 : -1;
> -}
> -
> -static int read_u64_at(int dirfd, const char *path, unsigned long long *out)
> -{
> - char buf[32] = {0};
> - int fd, ret;
> -
> - fd = openat(dirfd, path, O_RDONLY);
> - if (fd < 0)
> - return -1;
> -
> - ret = read(fd, buf, sizeof(buf) - 1);
> - close(fd);
> - if (ret <= 0)
> - return -1;
> -
> - *out = strtoull(buf, NULL, 10);
> - return 0;
> -}
> -
> -static int mount_cg2_fd(void)
> -{
> - int fs_fd, mnt_fd;
> -
> - fs_fd = syscall(__NR_fsopen, "cgroup2", 0);
> - if (fs_fd < 0)
> - return -1;
> -
> - if (syscall(__NR_fsconfig, fs_fd, FSCONFIG_CMD_CREATE,
> - NULL, NULL, 0) < 0) {
> - close(fs_fd);
> - return -1;
> - }
> -
> - mnt_fd = syscall(__NR_fsmount, fs_fd, 0, 0);
> - close(fs_fd);
> - return mnt_fd;
> -}
> -
> -static int enter_cgroup(int cgv2_fd, int ctid)
> -{
> - char cg_path[64];
> - char pid_str[64];
> - int fd;
> - int ret;
> -
> - if (ctid)
> - snprintf(cg_path, sizeof(cg_path), "%d/cgroup.procs", ctid);
> - else
> - snprintf(cg_path, sizeof(cg_path), "cgroup.procs");
> - fd = openat(cgv2_fd, cg_path, O_WRONLY);
> - if (fd < 0)
> - return -1;
> -
> - snprintf(pid_str, sizeof(pid_str), "%d", getpid());
> - ret = write(fd, pid_str, strlen(pid_str));
> - if (ret < 0 || ret != (int)strlen(pid_str))
> - ret = -1;
> -
> - close(fd);
> - return ret;
> -}
> -
> /*
> * Synchronisation across the clone() boundary: child does its setup,
> * tells parent it is ready, then blocks until parent acknowledges.
> @@ -199,7 +118,7 @@ static void check_new_ve_owner(struct __test_metadata *_metadata,
> int cgv2_fd, int ctid)
> {
> unsigned long long avail, mnt;
> - char path[64];
> + char path[PATH_MAX];
>
> snprintf(path, sizeof(path), "%d/ve.netns_avail_nr", ctid);
> ASSERT_EQ(read_u64_at(cgv2_fd, path, &avail), 0);
> @@ -219,15 +138,14 @@ FIXTURE(ve_ns_owner)
> FIXTURE_SETUP(ve_ns_owner)
> {
> unsigned long long initial_mnt_nr;
> - char ctid_str[16];
> char val[16];
> - char path[64];
> + char path[PATH_MAX];
>
> self->cgv2_fd = mount_cg2_fd();
> ASSERT_GE(self->cgv2_fd, 0);
>
> ASSERT_EQ(write_file_at(self->cgv2_fd, "cgroup.subtree_control",
> - "+cpuset +cpu +cpuacct +io +memory +hugetlb +pids +rdma +misc +ve"), 0);
> + VE_CONTROLLERS), 0);
>
> ASSERT_EQ(write_file_at(self->cgv2_fd,
> "ve.default_sysfs_permissions", "/ rx"), 0);
> @@ -236,20 +154,8 @@ FIXTURE_SETUP(ve_ns_owner)
> ASSERT_EQ(write_file_at(self->cgv2_fd,
> "ve.default_sysfs_permissions", "fs/cgroup rw"), 0);
>
> - self->ctid = CTID_MIN;
> - while (self->ctid < CTID_MAX) {
> - snprintf(ctid_str, sizeof(ctid_str), "%d", self->ctid);
> - if (faccessat(self->cgv2_fd, ctid_str, F_OK, 0) != 0 &&
> - errno == ENOENT)
> - break;
> - self->ctid++;
> - }
> - ASSERT_LT(self->ctid, CTID_MAX);
> -
> - ASSERT_EQ(mkdirat(self->cgv2_fd, ctid_str, 0755), 0);
> -
> - snprintf(path, sizeof(path), "%d/cgroup.controllers_hidden", self->ctid);
> - ASSERT_EQ(write_file_at(self->cgv2_fd, path, "-ve"), 0);
> + self->ctid = make_ve(self->cgv2_fd, CTID_MIN);
> + ASSERT_GE(self->ctid, 0);
>
> /*
> * ve.veid and ve.features are deliberately not configured: the
> @@ -279,11 +185,8 @@ FIXTURE_SETUP(ve_ns_owner)
>
> FIXTURE_TEARDOWN(ve_ns_owner)
> {
> - char path[64];
> -
> enter_cgroup(self->cgv2_fd, 0);
> - snprintf(path, sizeof(path), "%d", self->ctid);
> - unlinkat(self->cgv2_fd, path, AT_REMOVEDIR);
> + destroy_ve(self->cgv2_fd, self->ctid);
> close(self->cgv2_fd);
> }
>
> diff --git a/tools/testing/selftests/ve/ve_selftest.h b/tools/testing/selftests/ve/ve_selftest.h
> new file mode 100644
> index 000000000000..dddb09e948f9
> --- /dev/null
> +++ b/tools/testing/selftests/ve/ve_selftest.h
> @@ -0,0 +1,151 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Shared helpers for the ve selftests: a private cgroup2 mount, small file and
> + * cgroup helpers, and VE cgroup create and destroy, used across the tests in
> + * this directory.
> + */
> +#ifndef __SELFTESTS_VE_VE_SELFTEST_H
> +#define __SELFTESTS_VE_VE_SELFTEST_H
> +
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +#include <unistd.h>
> +#include <fcntl.h>
> +#include <errno.h>
> +#include <limits.h>
> +#include <sys/stat.h>
> +#include <sys/syscall.h>
> +#include <sys/mount.h>
> +#include <linux/mount.h>
> +
> +#ifndef CLONE_NEWVE
> +#define CLONE_NEWVE 0x00000040
> +#endif
> +
> +/* VE cgroup id search range shared by the ve tests. */
> +#define CTID_MIN 108
> +#define CTID_MAX 200
> +
> +/* Controllers a VE cgroup needs enabled in the root subtree_control. */
> +#define VE_CONTROLLERS \
> + "+cpuset +cpu +cpuacct +io +memory +hugetlb +pids +rdma +misc +ve"
> +
> +static inline int write_file_at(int dirfd, const char *path, const char *val)
> +{
> + int fd, ret;
> + size_t len = strlen(val);
> +
> + fd = openat(dirfd, path, O_WRONLY);
> + if (fd < 0)
> + return -1;
> +
> + ret = write(fd, val, len);
> + close(fd);
> + return (ret == (int)len) ? 0 : -1;
> +}
> +
> +static inline int read_u64_at(int dirfd, const char *path,
> + unsigned long long *out)
> +{
> + char buf[32] = {0};
> + int fd, ret;
> +
> + fd = openat(dirfd, path, O_RDONLY);
> + if (fd < 0)
> + return -1;
> +
> + ret = read(fd, buf, sizeof(buf) - 1);
> + close(fd);
> + if (ret <= 0)
> + return -1;
> +
> + *out = strtoull(buf, NULL, 10);
Let's verify that number was actually read: check errno,
verify end pointer.
> + return 0;
> +}
> +
> +/* A fresh, private cgroup2 mount, returned as an fd to use with *at() calls. */
> +static inline int mount_cg2_fd(void)
> +{
> + int fs_fd, mnt_fd;
> +
> + fs_fd = syscall(__NR_fsopen, "cgroup2", 0);
This function is the only one using syscalls, others use
libc helpers. Maybe use them here as well?
> + if (fs_fd < 0)
> + return -1;
> +
> + if (syscall(__NR_fsconfig, fs_fd, FSCONFIG_CMD_CREATE,
> + NULL, NULL, 0) < 0) {
> + close(fs_fd);
> + return -1;
> + }
> +
> + mnt_fd = syscall(__NR_fsmount, fs_fd, 0, 0);
> + close(fs_fd);
> + return mnt_fd;
> +}
> +
> +/* Move the caller into cgroup @ctid (or the root when @ctid is 0). */
> +static inline int enter_cgroup(int cgv2_fd, int ctid)
> +{
> + char cg_path[PATH_MAX];
> + char pid_str[64];
> + int fd, ret;
> +
> + if (ctid)
> + snprintf(cg_path, sizeof(cg_path), "%d/cgroup.procs", ctid);
> + else
> + snprintf(cg_path, sizeof(cg_path), "cgroup.procs");
> + fd = openat(cgv2_fd, cg_path, O_WRONLY);
> + if (fd < 0)
> + return -1;
> +
> + snprintf(pid_str, sizeof(pid_str), "%d", getpid());
> + ret = write(fd, pid_str, strlen(pid_str));
> + if (ret < 0 || ret != (int)strlen(pid_str))
> + ret = -1;
> +
> + close(fd);
> + return ret;
> +}
> +
> +/*
> + * Create a fresh VE cgroup at the first free id at or after @from and unhide
> + * its ve.* control files. Return the new id, or -1.
> + */
> +static inline int make_ve(int cgv2_fd, int from)
> +{
> + char name[16], path[PATH_MAX];
> + int id;
> +
> + for (id = from; id < CTID_MAX; id++) {
> + snprintf(name, sizeof(name), "%d", id);
> + if (faccessat(cgv2_fd, name, F_OK, 0) != 0 && errno == ENOENT)
> + break;
> + }
> + if (id >= CTID_MAX)
> + return -1;
> + if (mkdirat(cgv2_fd, name, 0755) != 0)
> + return -1;
> + snprintf(path, sizeof(path), "%d/cgroup.controllers_hidden", id);
> + if (write_file_at(cgv2_fd, path, "-ve") < 0)
Missing cleanup.
> + return -1;
> + return id;
> +}
> +
> +/* Remove a VE cgroup, retrying briefly while it is still busy. */
> +static inline void destroy_ve(int cgv2_fd, int id)
> +{
> + char name[16];
> + int i;
> +
> + if (id < 0)
> + return;
> + snprintf(name, sizeof(name), "%d", id);
> + for (i = 0; i < 100; i++) {
> + if (unlinkat(cgv2_fd, name, AT_REMOVEDIR) == 0 || errno != EBUSY)
> + break;
> + usleep(10000);
> + }
Let's report here that destroy failed - it usually is a problem.
> +}
> +
> +#endif /* __SELFTESTS_VE_VE_SELFTEST_H */
--
Best regards, Riabchun Vladimir
Linux Kernel Developer, Virtuozzo
More information about the Devel
mailing list