[Devel] [PATCH vz10 v4 8/9] selftests/ve: factor common helpers into ve_selftest.h

Mirian Shilakadze mirian.shilakadze at virtuozzo.com
Tue Jul 21 22:34:50 MSK 2026


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.

The moved helpers keep the same behaviour apart from cosmetic
reformatting, with a few robustness touches: read_u64_at rejects a non
numeric read, make_ve removes the cgroup it created if the unhide write
fails, and destroy_ve retries briefly on a busy cgroup and reports if it
still cannot remove it. mount_cg2_fd uses the libc mount API wrappers
(fsopen, fsconfig, fsmount).

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      | 157 ++++++++++++++++++
 2 files changed, 164 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..3ca1c41e0d6c
--- /dev/null
+++ b/tools/testing/selftests/ve/ve_selftest.h
@@ -0,0 +1,157 @@
+/* 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/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}, *end;
+	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;
+
+	errno = 0;
+	*out = strtoull(buf, &end, 10);
+	if (errno || end == buf)
+		return -1;
+	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 = fsopen("cgroup2", 0);
+	if (fs_fd < 0)
+		return -1;
+
+	if (fsconfig(fs_fd, FSCONFIG_CMD_CREATE, NULL, NULL, 0) < 0) {
+		close(fs_fd);
+		return -1;
+	}
+
+	mnt_fd = 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) {
+		unlinkat(cgv2_fd, name, AT_REMOVEDIR);
+		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)
+			return;
+		if (errno != EBUSY)
+			break;
+		usleep(10000);
+	}
+	fprintf(stderr, "%s: could not remove cgroup %d: %s\n",
+		__func__, id, strerror(errno));
+}
+
+#endif /* __SELFTESTS_VE_VE_SELFTEST_H */
-- 
2.43.0



More information about the Devel mailing list