[Devel] [PATCH vz10 v5] tests: add ve_printk selftest

Konstantin Khorenko khorenko at virtuozzo.com
Mon Dec 22 16:04:36 MSK 2025


On 12/19/25 19:28, Aleksei Oladko wrote:
> Add selftests for the printk virtualization feature. The new tests cover
> ve_printk, ve_printk_ratelimited, net_ve_ratelimited, and log buffer
> overflow handling.
> 
> Entering the VE cgroup v1 is required until it is switched to cgroup v2.
> 
> Test functions (executed inside containers):
>    - ve_printk_test_logct():     Verifies segfault messages are logged to container
>    - ve_printk_test_logve0():    Verifies trap messages are logged to VE0
>    - ve_printk_test_logboth():   Verifies net_veboth_ratelimited messages
>    - ve_printk_test_ratelimit(): Verifies ve_printk_ratelimited(VE_LOG) messages
>    - ve_printk_test_overflow():  Verifies log buffer overflow handling
> 
> https://virtuozzo.atlassian.net/browse/VSTOR-114252
> 
> v2:
>    - added checks for the return values of system() and write()
>    - added comments to clarify the code
>    - removed the hunk that set up the gid mapping,
>      as it is no required for creating the test container
> v3:
>    - added entering the VE namespace
>    - fixed typos
> 
> v4: fixed typos
> 
> v5:
>    - switched self-attachment to the ve controller from cgroup v1 to cgroup v2
>    - made child_func return a posiive exit code on failure.
>    - revorked entering the ve namespace to use clone 3 instead of unshare
> 
> Signed-off-by: Aleksei Oladko <aleksey.oladko at virtuozzo.com>
> ---
>   tools/testing/selftests/Makefile              |   1 +
>   tools/testing/selftests/ve_printk/.gitignore  |   3 +
>   tools/testing/selftests/ve_printk/Makefile    |   8 +
>   tools/testing/selftests/ve_printk/test_segf.c |  12 +
>   tools/testing/selftests/ve_printk/test_trap.c |   5 +
>   .../selftests/ve_printk/ve_printk_test.c      | 693 ++++++++++++++++++
>   6 files changed, 722 insertions(+)
>   create mode 100644 tools/testing/selftests/ve_printk/.gitignore
>   create mode 100644 tools/testing/selftests/ve_printk/Makefile
>   create mode 100644 tools/testing/selftests/ve_printk/test_segf.c
>   create mode 100644 tools/testing/selftests/ve_printk/test_trap.c
>   create mode 100644 tools/testing/selftests/ve_printk/ve_printk_test.c
> 
> diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile
> index 363d031a16f7..7334fb207676 100644
> --- a/tools/testing/selftests/Makefile
> +++ b/tools/testing/selftests/Makefile
> @@ -113,6 +113,7 @@ TARGETS += tty
>   TARGETS += uevent
>   TARGETS += user_events
>   TARGETS += vDSO
> +TARGETS += ve_printk
>   TARGETS += mm
>   TARGETS += x86
>   TARGETS += zram
> diff --git a/tools/testing/selftests/ve_printk/.gitignore b/tools/testing/selftests/ve_printk/.gitignore
> new file mode 100644
> index 000000000000..a4ad6620b803
> --- /dev/null
> +++ b/tools/testing/selftests/ve_printk/.gitignore
> @@ -0,0 +1,3 @@
> +ve_printk_test
> +test_segf
> +test_trap
> diff --git a/tools/testing/selftests/ve_printk/Makefile b/tools/testing/selftests/ve_printk/Makefile
> new file mode 100644
> index 000000000000..e3edcbacda1e
> --- /dev/null
> +++ b/tools/testing/selftests/ve_printk/Makefile
> @@ -0,0 +1,8 @@
> +# SPDX-License-Identifier: GPL-2.0
> +# Makefile for ve_printk selftests.
> +CFLAGS = -g -I../../../../usr/include/ -Wall -O2
> +
> +TEST_GEN_PROGS += ve_printk_test
> +TEST_GEN_FILES += test_segf test_trap
> +
> +include ../lib.mk
> diff --git a/tools/testing/selftests/ve_printk/test_segf.c b/tools/testing/selftests/ve_printk/test_segf.c
> new file mode 100644
> index 000000000000..cdc89068ca06
> --- /dev/null
> +++ b/tools/testing/selftests/ve_printk/test_segf.c
> @@ -0,0 +1,12 @@
> +#include <stdio.h>
> +#include <unistd.h>
> +
> +int main(void)
> +{
> +	int *p = (int *)0xffffffffffffff00;
> +
> +	printf("%d\n", getpid());
> +	fflush(stdout);
> +	*p = 1;
> +	return 0;
> +}
> diff --git a/tools/testing/selftests/ve_printk/test_trap.c b/tools/testing/selftests/ve_printk/test_trap.c
> new file mode 100644
> index 000000000000..b774e2b9484c
> --- /dev/null
> +++ b/tools/testing/selftests/ve_printk/test_trap.c
> @@ -0,0 +1,5 @@
> +int main(void)
> +{
> +	__asm__("int3");
> +	return 0;
> +}
> diff --git a/tools/testing/selftests/ve_printk/ve_printk_test.c b/tools/testing/selftests/ve_printk/ve_printk_test.c
> new file mode 100644
> index 000000000000..e150366ef580
> --- /dev/null
> +++ b/tools/testing/selftests/ve_printk/ve_printk_test.c
> @@ -0,0 +1,693 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * ve_printk selftests
> + *
> + * This file contains tests for the VE printk virtualization feature. It verifies
> + * that kernel messages are correctly routed to containers (VE_LOG), host (VE0_LOG),
> + * or both (VE_LOG_BOTH), and that rate limiting works correctly.
> + *
> + * Test functions (executed inside containers):
> + *   - ve_printk_test_logct():     Verifies segfault messages are logged to container
> + *   - ve_printk_test_logve0():    Verifies trap messages are logged to VE0
> + *   - ve_printk_test_logboth():   Verifies net_veboth_ratelimited messages
> + *   - ve_printk_test_ratelimit(): Verifies ve_printk_ratelimited(VE_LOG) messages
> + *   - ve_printk_test_overflow():  Verifies log buffer overflow handling
> + *
> + * Test cases:
> + *   - TEST_F(ve_printk, ve_log):              Verifies VE_LOG routing (segfault)
> + *   - TEST_F(ve_printk, ve0_log):             Verifies VE0_LOG routing (trap)
> + *   - TEST_F(ve_printk, ve_log_both):         Verifies VE_LOG_BOTH + ratelimit
> + *   - TEST_F(ve_printk, ve_printk_ratelimited): Verifies VE_LOG ratelimit
> + *   - TEST_F(ve_printk, ve_printk_overflow):   Verifies buffer overflow handling
> + */
> +#define _GNU_SOURCE
> +#include <linux/sched.h>
> +#include <time.h>
> +#include <sched.h>
> +#include <sys/wait.h>
> +#include <unistd.h>
> +#include <asm/unistd.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <fcntl.h>
> +#include <string.h>
> +#include <sys/stat.h>
> +#include <sys/mount.h>
> +#include <linux/limits.h>
> +#include <errno.h>
> +
> +#include "../kselftest_harness.h"
> +
> +#define __STACK_SIZE (8 * 1024 * 1024)

non-used define, i'll drop it

> +#define CTID_MIN 108
> +#define CTID_MAX 200
> +#define SEGFAULT_PROG "test_segf"
> +#define TRAP_PROG "test_trap"
> +#define TEST_RATELIMIT_BURST 10
> +#define TEST_RATELIMIT 5
> +
> +static int has_substr(char *buf, const char *str)
> +{
> +	char *token;
> +	char *str_ptr = buf;
> +
> +	while ((token = strsep(&str_ptr, ",")) != NULL) {
> +		if (!strcmp(token, str))
> +			return 1;
> +	}
> +	return 0;
> +}
> +
> +static int get_mount_path(const char *fstype, const char *subsys, char *out, int size)
> +{
> +	FILE *fp;
> +	int n;
> +	char buf[PATH_MAX];
> +	char target[4096];
> +	char ops[4096];
> +	char format[4096];
> +	int ret = 1;
> +
> +	snprintf(format, sizeof(format), "%%*s %%4095s %s %%4095s", fstype);
> +
> +	fp = fopen("/proc/mounts", "r");
> +	if (fp == NULL)
> +		return -1;
> +
> +	while (fgets(buf, sizeof(buf), fp)) {
> +		n = sscanf(buf, format, target, ops);
> +		if (n != 2)
> +			continue;
> +		if (subsys == NULL || has_substr(ops, subsys)) {
> +			strncpy(out, target, size);
> +			out[size-1] = '\0';
> +			ret = 0;
> +			break;
> +		}
> +	}
> +	fclose(fp);
> +
> +	return ret;
> +}
> +
> +FIXTURE(ve_printk)
> +{
> +	char cgv2_path[PATH_MAX];
> +	int ctid;
> +};
> +
> +FIXTURE_SETUP(ve_printk)
> +{
> +	char path[PATH_MAX * 2];
> +
> +	ASSERT_EQ(get_mount_path("cgroup2", NULL, self->cgv2_path, sizeof(self->cgv2_path)), 0);
> +
> +	self->ctid = CTID_MIN;
> +	while (self->ctid < CTID_MAX) {
> +		snprintf(path, sizeof(path), "%s/%d", self->cgv2_path, self->ctid);
> +		if (access(path, F_OK) && errno == ENOENT) {
> +			snprintf(path, sizeof(path), "%s/%d", self->cgv2_path, self->ctid);
> +			if (access(path, F_OK)) {
> +				break;
> +			}
> +		}
> +		self->ctid++;
> +	}

   Explanation of the Fix

   Original code (lines 108-112):

      1 │if (access(path, F_OK) && errno == ENOENT) {
      2 │    snprintf(path, sizeof(path), "%s/%d", self->cgv2_path, self->ctid);
      3 │    if (access(path, F_OK)) {
      4 │        break;
      5 │    }
      6 │}

   Problems:
   1. Incorrect condition: access(path, F_OK) && errno == ENOENT is wrong. access() returns 0 on 
success (file exists) and
       -1 on failure (file doesn't exist). In C, access(path, F_OK) in an if is true when non-zero 
(i.e., -1). So the
      condition means "file doesn't exist AND errno == ENOENT", but errno is only valid when access() 
returns -1. The
      check is redundant and confusing.
   2. Code duplication: snprintf() and access() are called twice with the same arguments, which is 
unnecessary.

   Fixed code:

      1 │if (access(path, F_OK) != 0 && errno == ENOENT) {
      2 │    break;
      3 │}

   Why this is correct:
   • access(path, F_OK) != 0 explicitly checks for failure (file doesn't exist).
   • errno == ENOENT confirms the specific error (file/directory not found).
   • Removed duplicate snprintf() and access() calls.
   • The logic is now clear: if the path doesn't exist (ENOENT), we found a free ctid and can break 
out of the loop.

   This correctly finds an available container ID by checking that the cgroup path doesn't exist yet.

i will handle this.

> +	ASSERT_LT(self->ctid, CTID_MAX);
> +
> +	snprintf(path, sizeof(path), "%s/%d", self->cgv2_path, self->ctid);

This line is redundant i suppose.

i will remove it.

> +	ASSERT_EQ(mkdir(path, 0755), 0);
> +
> +	snprintf(path, sizeof(path), "echo -ve > %s/%d/cgroup.controllers_hidden",
> +			self->cgv2_path, self->ctid);
> +	ASSERT_EQ(system(path), 0);
> +
> +	snprintf(path, sizeof(path), "echo %d > %s/%d/ve.veid",
> +			self->ctid, self->cgv2_path, self->ctid);
> +	ASSERT_EQ(system(path), 0);
> +};
> +
> +FIXTURE_TEARDOWN(ve_printk)
> +{
> +	char path[PATH_MAX * 2];
> +
> +	snprintf(path, sizeof(path), "%s/%d/vz.slice", self->cgv2_path, self->ctid);
> +	rmdir(path);
> +	snprintf(path, sizeof(path), "%s/%d", self->cgv2_path, self->ctid);
> +	rmdir(path);
> +}
> +
...


More information about the Devel mailing list