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 <[email protected]>
---
  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

_______________________________________________
Devel mailing list
[email protected]
https://lists.openvz.org/mailman/listinfo/devel

Reply via email to