OpenVZ / Virtuozzo kernel development (devel@openvz.org)
 help / color / mirror / Atom feed
* [Devel] [PATCH VZ10 v5 0/9] Add per-VE failcount support
@ 2026-08-02 11:40 Vladimir Riabchun
  2026-08-02 11:40 ` [Devel] [PATCH VZ10 v5 1/9] ve/ve.{h, c}: Farewell to spaces as indents Vladimir Riabchun
                   ` (9 more replies)
  0 siblings, 10 replies; 16+ messages in thread
From: Vladimir Riabchun @ 2026-08-02 11:40 UTC (permalink / raw)


This patchset introduces per-VE failcount support.
We have several resources, that have per-VE limits,
the users might want to know what limit hits make
operations fail.

- Patch 1 is cosmetic, nothing special.
- Patch 2 is a bugfix, found when testing per-VE mount
  limits (but now comes before these changes).
- Patches 3-5 change mount number and limit processing,
  unifying it with other resources. VE selftest is updated
  in patch 4.
- Patch 6 greatly reduces the amount of copy-pasted code
  by introducing some useful macros.
- Patch 7 adds per-VE failcount file.
- Patch 8 adds new helpers to our VE selftests infra.
- Patch 9 introduces new VE selftest, that checks mounts
  accounting, limits and failcount updates.

https://virtuozzo.atlassian.net/browse/VSTOR-135520

Changes since v4:
 - Change approach in patch 3. Now pseudosuper just disables
   mnt_avail_nr > 0 check, but every mount is still accounted.
 - Updated run_in_ve helper in patch 8. Now CLONE_NEWVE flag
   is always added to unshare_flags.
 - Updated patch 9. pseudosuper test splitted into two parts,
   added a new test for partial mounts.

Vladimir Riabchun (9):
  ve/ve.{h,c}: Farewell to spaces as indents
  ve/namespace: Fix UAF in alloc_mnt_ns
  ve/fs: Rework per-ve mount count
  selftests/ve: Update ve_ns_owner_test
  ve: Move from global VE mounts limit to per-VE limit
  ve/ve.c: Generate VE resource accessors using macros
  ve: Introduce per-VE failcount
  selftests/ve: Add more helpers
  selftests/ve: Add mount accounting selftest

 fs/aio.c                                      |   1 +
 fs/namespace.c                                |  88 ++--
 include/linux/ve.h                            |  25 +-
 kernel/bpf/syscall.c                          |   1 +
 kernel/ve/ve.c                                | 278 ++++++------
 kernel/ve/veowner.c                           |  19 -
 net/core/dev.c                                |   2 +
 net/core/neighbour.c                          |   1 +
 net/core/net_namespace.c                      |   4 +-
 tools/testing/selftests/ve/.gitignore         |   1 +
 tools/testing/selftests/ve/Makefile           |   1 +
 .../selftests/ve/ve_mount_accounting_test.c   | 419 ++++++++++++++++++
 tools/testing/selftests/ve/ve_ns_owner_test.c |  35 +-
 tools/testing/selftests/ve/ve_selftest.h      |  81 +++-
 14 files changed, 734 insertions(+), 222 deletions(-)
 create mode 100644 tools/testing/selftests/ve/ve_mount_accounting_test.c

-- 
2.47.1


^ permalink raw reply	[flat|nested] 16+ messages in thread

* [Devel] [PATCH VZ10 v5 1/9] ve/ve.{h, c}: Farewell to spaces as indents
  2026-08-02 11:40 [Devel] [PATCH VZ10 v5 0/9] Add per-VE failcount support Vladimir Riabchun
@ 2026-08-02 11:40 ` Vladimir Riabchun
  2026-08-02 11:40 ` [Devel] [PATCH VZ10 v5 2/9] ve/namespace: Fix UAF in alloc_mnt_ns Vladimir Riabchun
                   ` (8 subsequent siblings)
  9 siblings, 0 replies; 16+ messages in thread
From: Vladimir Riabchun @ 2026-08-02 11:40 UTC (permalink / raw)


Fix formatting while here.

Signed-off-by: Vladimir Riabchun <vladimir.riabchun@virtuozzo.com>
---
 include/linux/ve.h |  8 ++++----
 kernel/ve/ve.c     | 48 +++++++++++++++++++++++-----------------------
 2 files changed, 28 insertions(+), 28 deletions(-)

diff --git a/include/linux/ve.h b/include/linux/ve.h
index cba827260d07..3facbd1759df 100644
--- a/include/linux/ve.h
+++ b/include/linux/ve.h
@@ -63,7 +63,7 @@ struct ve_struct {
 	void			*log_state;
 #define VE_LOG_BUF_LEN		4096
 
-	struct kstat_lat_pcpu_struct    sched_lat_ve;
+	struct kstat_lat_pcpu_struct	sched_lat_ve;
 	int			odirect_enable;
 
 	int			fsync_enable;
@@ -100,8 +100,8 @@ struct ve_struct {
 	struct kthread_worker	*kthreadd_worker;
 	struct task_struct	*kthreadd_task;
 
-	atomic_t                umh_running_helpers;
-	struct wait_queue_head  umh_helpers_waitq;
+	atomic_t		umh_running_helpers;
+	struct wait_queue_head	umh_helpers_waitq;
 	struct kthread_worker	umh_worker;
 	struct task_struct	*umh_task;
 
@@ -122,7 +122,7 @@ struct ve_struct {
 struct ve_devmnt {
 	struct list_head	link;
 
-	dev_t                   dev;
+	dev_t			dev;
 	char			*allowed_options;
 	char			*hidden_options; /* balloon_ino, etc. */
 };
diff --git a/kernel/ve/ve.c b/kernel/ve/ve.c
index d8ef28eedabd..dddf2393326d 100644
--- a/kernel/ve/ve.c
+++ b/kernel/ve/ve.c
@@ -83,8 +83,8 @@ struct ve_struct ve0 = {
 	.nd_neigh_nr		= ATOMIC_INIT(0),
 	.mnt_nr			= ATOMIC_INIT(0),
 	.meminfo_val		= VE_MEMINFO_SYSTEM,
-	.umh_running_helpers    = ATOMIC_INIT(0),
-	.umh_helpers_waitq      = __WAIT_QUEUE_HEAD_INITIALIZER(ve0.umh_helpers_waitq),
+	.umh_running_helpers	= ATOMIC_INIT(0),
+	.umh_helpers_waitq	= __WAIT_QUEUE_HEAD_INITIALIZER(ve0.umh_helpers_waitq),
 	.vdso_64		= (struct vdso_image*)&vdso_image_64,
 	.vdso_32		= (struct vdso_image*)&vdso_image_32,
 };
@@ -286,7 +286,7 @@ EXPORT_SYMBOL(is_ve_init_net);
 
 int nr_threads_ve(struct ve_struct *ve)
 {
-        return cgroup_task_count(ve->css.cgroup);
+	return cgroup_task_count(ve->css.cgroup);
 }
 EXPORT_SYMBOL(nr_threads_ve);
 
@@ -327,8 +327,8 @@ static void ve_drop_context(struct ve_struct *ve)
 	ve_nsproxy = rcu_dereference_protected(ve->ve_nsproxy, lockdep_is_held(&ve->op_sem));
 
 	/* Allows to dereference init_cred and init_task if ve_nsproxy is set */
-        rcu_assign_pointer(ve->ve_nsproxy, NULL);
-        synchronize_rcu();
+	rcu_assign_pointer(ve->ve_nsproxy, NULL);
+	synchronize_rcu();
 	put_nsproxy(ve_nsproxy);
 
 	put_cred(ve->init_cred);
@@ -887,23 +887,23 @@ static int ve_state_show(struct seq_file *sf, void *v)
 	struct ve_struct *ve = css_to_ve(css);
 
 	down_read(&ve->op_sem);
-        switch (ve->state) {
-                case VE_STATE_STARTING:
-                        seq_puts(sf, "STARTING");
-                        break;
-                case VE_STATE_RUNNING:
-                        seq_puts(sf, "RUNNING");
-                        break;
-                case VE_STATE_STOPPING:
-                        seq_puts(sf, "STOPPING");
-                        break;
-                case VE_STATE_STOPPED:
-                case VE_STATE_DEAD:
-                        seq_puts(sf, "STOPPED");
-                        break;
-                default:
-                        seq_puts(sf, "UNKNOWN");
-        }
+	switch (ve->state) {
+	case VE_STATE_STARTING:
+		seq_puts(sf, "STARTING");
+		break;
+	case VE_STATE_RUNNING:
+		seq_puts(sf, "RUNNING");
+		break;
+	case VE_STATE_STOPPING:
+		seq_puts(sf, "STOPPING");
+		break;
+	case VE_STATE_STOPPED:
+	case VE_STATE_DEAD:
+		seq_puts(sf, "STOPPED");
+		break;
+	default:
+		seq_puts(sf, "UNKNOWN");
+	}
 	seq_putc(sf, '\n');
 	up_read(&ve->op_sem);
 
@@ -915,7 +915,7 @@ static ssize_t ve_state_write(struct kernfs_open_file *of, char *buf,
 
 {
 	struct cgroup_subsys_state *css = of_css(of);
-        struct ve_struct *ve = css_to_ve(css);
+	struct ve_struct *ve = css_to_ve(css);
 	int ret = -EINVAL;
 
 	if (!strcmp(buf, "START")) {
@@ -1344,7 +1344,7 @@ static int ve_parse_mount_options(const char *ptr, const char *endp,
 			goto next;
 		}
 
-	        options = kmalloc(options_size, GFP_KERNEL);
+		options = kmalloc(options_size, GFP_KERNEL);
 		if (!options)
 			return -ENOMEM;
 
-- 
2.47.1


^ permalink raw reply	[flat|nested] 16+ messages in thread

* [Devel] [PATCH VZ10 v5 2/9] ve/namespace: Fix UAF in alloc_mnt_ns
  2026-08-02 11:40 [Devel] [PATCH VZ10 v5 0/9] Add per-VE failcount support Vladimir Riabchun
  2026-08-02 11:40 ` [Devel] [PATCH VZ10 v5 1/9] ve/ve.{h, c}: Farewell to spaces as indents Vladimir Riabchun
@ 2026-08-02 11:40 ` Vladimir Riabchun
  2026-08-02 11:40 ` [Devel] [PATCH VZ10 v5 3/9] ve/fs: Rework per-ve mount count Vladimir Riabchun
                   ` (7 subsequent siblings)
  9 siblings, 0 replies; 16+ messages in thread
From: Vladimir Riabchun @ 2026-08-02 11:40 UTC (permalink / raw)


KASAN reports a UAF in alloc_mnt_ns when running ve_ns_owner_test
selftest with low per-VE mount limit:

[ 2011.939093] CT#0 reached the limit on mounts.
[ 2012.049101] ==================================================================
[ 2012.050328] BUG: KASAN: slab-use-after-free in __list_add_valid_or_report+0xaa/0xb0
[ 2012.051686] Read of size 8 at addr ffff8881f4321cd0 by task (coredump)/6230

[ 2012.053069] CPU: 1 UID: 0 PID: 6230 Comm: (coredump) ve: 0 Kdump: loaded Not tainted 6.12.0-211.16.1.12.5.ovz10-v3-debug #1 12.5.ovz10 PREEMPT(voluntary)
[ 2012.053076] Hardware name: Acronis OpenStack Compute/Virtuozzo, BIOS 1.16.1-1.vz9.2 04/01/2014
[ 2012.053079] Call Trace:
[ 2012.053083]  <TASK>
[ 2012.053087]  dump_stack_lvl+0x6f/0xb0
[ 2012.053097]  ? __list_add_valid_or_report+0xaa/0xb0
[ 2012.053102]  print_address_description.constprop.0+0x88/0x380
[ 2012.053109]  ? __list_add_valid_or_report+0xaa/0xb0
[ 2012.053113]  print_report+0x108/0x209
[ 2012.053117]  ? __virt_addr_valid+0x1a3/0x370
[ 2012.053123]  ? __list_add_valid_or_report+0xaa/0xb0
[ 2012.053127]  kasan_report+0xa3/0xe0
[ 2012.053133]  ? __list_add_valid_or_report+0xaa/0xb0
[ 2012.053139]  __list_add_valid_or_report+0xaa/0xb0
[ 2012.053143]  alloc_mnt_ns+0x25c/0x620
[ 2012.053149]  open_detached_copy+0xa4/0x480
[ 2012.053155]  __x64_sys_open_tree+0x2c2/0x330
[ 2012.053161]  ? __pfx___x64_sys_open_tree+0x10/0x10
[ 2012.053168]  do_syscall_64+0x92/0x180
[ 2012.053175]  ? rcu_is_watching+0x15/0xb0
[ 2012.053180]  ? trace_irq_enable.constprop.0+0x14d/0x1c0
[ 2012.053187]  entry_SYSCALL_64_after_hwframe+0x76/0x7e
[ 2012.053193] RIP: 0033:0x7f096c12db3b
[ 2012.053219] Code: 73 01 c3 48 8b 0d d5 82 0c 00 f7 d8 64 89 01 48 83 c8 ff c3 66 2e 0f 1f 84 00 00 00 00 00 90 f3 0f 1e fa b8 ac 01 00 00 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 8b 0d a5 82 0c 00 f7 d8 64 89 01 48
[ 2012.053223] RSP: 002b:00007ffce7d6dff8 EFLAGS: 00000206 ORIG_RAX: 00000000000001ac
[ 2012.053230] RAX: ffffffffffffffda RBX: 000055ab331ebdc0 RCX: 00007f096c12db3b
[ 2012.053234] RDX: 0000000000081001 RSI: 000055ab220012fb RDI: 0000000000000003
[ 2012.053237] RBP: 00007ffce7d6e190 R08: 0000000000000000 R09: 000055ab331f12e0
[ 2012.053241] R10: 000055ab331ebe40 R11: 0000000000000206 R12: 000055ab331ecd50
[ 2012.053244] R13: 00007ffce7d6e7f0 R14: 00007ffce7d6e900 R15: 000055ab331ebdc0
[ 2012.053256]  </TASK>

[ 2012.080481] Allocated by task 6228:
[ 2012.081075]  kasan_save_stack+0x30/0x50
[ 2012.081723]  kasan_save_track+0x14/0x30
[ 2012.082358]  __kasan_kmalloc+0x8f/0xa0
[ 2012.082976]  alloc_mnt_ns+0xcf/0x620
[ 2012.083576]  copy_mnt_ns+0xb2/0xa30
[ 2012.084159]  create_new_namespaces+0xd7/0x940
[ 2012.084946]  copy_namespaces+0x323/0x3f0
[ 2012.085605]  copy_process+0x1d3c/0x53e0
[ 2012.086235]  kernel_clone+0xc8/0x5f0
[ 2012.086928]  __do_sys_clone3+0x172/0x1c0
[ 2012.087599]  do_syscall_64+0x92/0x180
[ 2012.088218]  entry_SYSCALL_64_after_hwframe+0x76/0x7e

[ 2012.089332] Freed by task 6228:
[ 2012.089871]  kasan_save_stack+0x30/0x50
[ 2012.090497]  kasan_save_track+0x14/0x30
[ 2012.091262]  kasan_save_free_info+0x3b/0x70
[ 2012.091944]  __kasan_slab_free+0x37/0x50
[ 2012.092631]  kfree+0x129/0x3f0
[ 2012.093194]  copy_mnt_ns+0x74e/0xa30
[ 2012.093817]  create_new_namespaces+0xd7/0x940
[ 2012.094554]  copy_namespaces+0x323/0x3f0
[ 2012.095215]  copy_process+0x1d3c/0x53e0
[ 2012.095868]  kernel_clone+0xc8/0x5f0
[ 2012.096503]  __do_sys_clone3+0x172/0x1c0
[ 2012.097174]  do_syscall_64+0x92/0x180
[ 2012.097770]  entry_SYSCALL_64_after_hwframe+0x76/0x7e

The root cause is an incorrect rebase in commit
229fd15908fe ("fs: don't try and remove empty rbtree node").
The error path in copy_mnt_ns didn't include list removal,
so after the free list contained an invalid element.

To reduce the probability of such issues in the future,
extract ve-specific logic in a special helper and use it
in free_mnt_ns and copy_mnt_ns error path.

https://virtuozzo.atlassian.net/browse/VSTOR-135520

Fixes: 229fd15908fe ("fs: don't try and remove empty rbtree node")
Signed-off-by: Vladimir Riabchun <vladimir.riabchun@virtuozzo.com>
---
 fs/namespace.c | 15 ++++++++++-----
 1 file changed, 10 insertions(+), 5 deletions(-)

diff --git a/fs/namespace.c b/fs/namespace.c
index 0f4a3668e558..cd6aa2127203 100644
--- a/fs/namespace.c
+++ b/fs/namespace.c
@@ -4179,17 +4179,21 @@ static void dec_mnt_namespaces(struct ucounts *ucounts)
 static LIST_HEAD(all_mntns_list);
 static DEFINE_SPINLOCK(all_mntns_list_lock);
 
-static void free_mnt_ns(struct mnt_namespace *ns)
+static void ve_cleanup_mnt_ns(struct mnt_namespace *ns)
 {
-	if (!is_anon_ns(ns))
-		ns_free_inum(&ns->ns);
-	dec_mnt_namespaces(ns->ucounts);
-
 	spin_lock(&all_mntns_list_lock);
 	list_del(&ns->mntns_list);
 	spin_unlock(&all_mntns_list_lock);
 
 	put_ve(ns->ve_owner);
+}
+
+static void free_mnt_ns(struct mnt_namespace *ns)
+{
+	if (!is_anon_ns(ns))
+		ns_free_inum(&ns->ns);
+	dec_mnt_namespaces(ns->ucounts);
+	ve_cleanup_mnt_ns(ns);
 
 	mnt_ns_tree_remove(ns);
 }
@@ -4283,6 +4287,7 @@ struct mnt_namespace *copy_mnt_ns(unsigned long flags, struct mnt_namespace *ns,
 		namespace_unlock();
 		ns_free_inum(&new_ns->ns);
 		dec_mnt_namespaces(new_ns->ucounts);
+		ve_cleanup_mnt_ns(new_ns);
 		mnt_ns_release(new_ns);
 		return ERR_CAST(new);
 	}
-- 
2.47.1


^ permalink raw reply	[flat|nested] 16+ messages in thread

* [Devel] [PATCH VZ10 v5 3/9] ve/fs: Rework per-ve mount count
  2026-08-02 11:40 [Devel] [PATCH VZ10 v5 0/9] Add per-VE failcount support Vladimir Riabchun
  2026-08-02 11:40 ` [Devel] [PATCH VZ10 v5 1/9] ve/ve.{h, c}: Farewell to spaces as indents Vladimir Riabchun
  2026-08-02 11:40 ` [Devel] [PATCH VZ10 v5 2/9] ve/namespace: Fix UAF in alloc_mnt_ns Vladimir Riabchun
@ 2026-08-02 11:40 ` Vladimir Riabchun
  2026-08-02 11:40 ` [Devel] [PATCH VZ10 v5 4/9] selftests/ve: Update ve_ns_owner_test Vladimir Riabchun
                   ` (6 subsequent siblings)
  9 siblings, 0 replies; 16+ messages in thread
From: Vladimir Riabchun @ 2026-08-02 11:40 UTC (permalink / raw)


Previous approach with current mounts counter had an issue:
there was a gap between ve_mount_allowed check and ve_mount_nr_inc,
which could allow CT to have more mounts than expected.

Fix this by tracking the number of available mounts instead
of current ones. This also makes resources accounting
more consistent - we are using ***_avail_nr approach more.

One more issue with inconsistent ve value is fixed:
ve_mount_allowed always used ve from get_exec_env, but
ve_mount_nr_inc operated with owner_ve.
Now actual ve value is calculated in the beginning of alloc_vfsmnt.

To avoid incorrect accounting when is_pseudosuper is changed,
update avail_nr count without > 0 check if VE is ve0 or pseudosuper.
This also simplifies ve_mount_put, since increment is
now unconditional.

https://virtuozzo.atlassian.net/browse/VSTOR-135520

Feature: per-ve failcounters
Signed-off-by: Vladimir Riabchun <vladimir.riabchun@virtuozzo.com>
---
v4 -> v5:
 - Dropped created_as_pseudosuper, now all mounts are accounted,
   but for ve0 and pseudosuper mnt_avail_nr is allowed to go below 0.

 fs/namespace.c     | 71 +++++++++++++++++++++++++++-------------------
 include/linux/ve.h |  2 +-
 kernel/ve/ve.c     | 12 ++++----
 3 files changed, 49 insertions(+), 36 deletions(-)

diff --git a/fs/namespace.c b/fs/namespace.c
index cd6aa2127203..2550aeba2f1e 100644
--- a/fs/namespace.c
+++ b/fs/namespace.c
@@ -317,18 +317,21 @@ int mnt_get_count(struct mount *mnt)
 #endif
 }
 
-static inline int ve_mount_allowed(void);
-static inline void ve_mount_nr_inc(struct mount *mnt, struct ve_struct *ve);
-static inline void ve_mount_nr_dec(struct mount *mnt);
+static inline int ve_try_reserve_mount(struct ve_struct *ve);
+static inline void ve_mount_put(struct mount *mnt, struct ve_struct *ve);
 
 static struct mount *alloc_vfsmnt(const char *name, struct ve_struct *owner_ve)
 {
 	struct mount *mnt;
+	struct ve_struct *ve = owner_ve;
 
-	if (!ve_mount_allowed()) {
+	if (!ve)
+		ve = get_exec_env();
+
+	if (!ve_try_reserve_mount(ve)) {
 		pr_warn_ratelimited(
 			"CT#%s reached the limit on mounts.\n",
-			ve_name(get_exec_env()));
+			ve_name(ve));
 		return NULL;
 	}
 
@@ -336,6 +339,14 @@ static struct mount *alloc_vfsmnt(const char *name, struct ve_struct *owner_ve)
 	if (mnt) {
 		int err;
 
+#ifdef CONFIG_VE
+		/*
+		 * Got ve reference in ve_try_reserve_mount, set mnt ve data
+		 * here, so in case of error ve_mount_put sees correct info.
+		 */
+		mnt->ve_owner = ve;
+#endif
+
 		err = mnt_alloc_id(mnt);
 		if (err)
 			goto out_free_cache;
@@ -370,7 +381,8 @@ static struct mount *alloc_vfsmnt(const char *name, struct ve_struct *owner_ve)
 		INIT_LIST_HEAD(&mnt->mnt_umounting);
 		INIT_HLIST_HEAD(&mnt->mnt_stuck_children);
 		mnt->mnt.mnt_idmap = &nop_mnt_idmap;
-		ve_mount_nr_inc(mnt, owner_ve);
+	} else {
+		ve_mount_put(mnt, ve);
 	}
 	return mnt;
 
@@ -381,6 +393,8 @@ static struct mount *alloc_vfsmnt(const char *name, struct ve_struct *owner_ve)
 out_free_id:
 	mnt_free_id(mnt);
 out_free_cache:
+	/* Got ve reference in ve_try_reserve_mount */
+	ve_mount_put(mnt, ve);
 	kmem_cache_free(mnt_cache, mnt);
 	return NULL;
 }
@@ -750,7 +764,7 @@ int sb_prepare_remount_readonly(struct super_block *sb)
 static void free_vfsmnt(struct mount *mnt)
 {
 	mnt_idmap_put(mnt_idmap(&mnt->mnt));
-	ve_mount_nr_dec(mnt);
+	ve_mount_put(mnt, mnt->ve_owner);
 	kfree_const(mnt->mnt_devname);
 #ifdef CONFIG_SMP
 	free_percpu(mnt->mnt_pcp);
@@ -3205,7 +3219,7 @@ int ve_devmnt_process(struct ve_struct *ve, dev_t dev, void **data_pp, int remou
 		if (devmnt->dev == dev) {
 			err = ve_devmnt_check(data, devmnt->allowed_options);
 			/*
-			 * In case of @is_pseudouser set, ie restore procedure,
+			 * In case of @is_pseudosuper set, ie restore procedure,
 			 * we don't check for allowed options filtering, since
 			 * restore mode is special.
 			 */
@@ -3258,30 +3272,30 @@ int ve_devmnt_process(struct ve_struct *ve, dev_t dev, void **data_pp, int remou
 	return err;
 }
 
-static inline int ve_mount_allowed(void)
-{
-	struct ve_struct *ve = get_exec_env();
-
-	return ve_is_super(ve) || ve->is_pseudosuper ||
-		atomic_read(&ve->mnt_nr) < (int)sysctl_ve_mount_nr;
-}
-
-static inline void ve_mount_nr_inc(struct mount *mnt, struct ve_struct *ve)
+static inline int ve_try_reserve_mount(struct ve_struct *ve)
 {
-	if (!ve)
-		ve = get_exec_env();
+	int ret = ve_is_super(ve) || ve->is_pseudosuper;
+	/* Ignore limits in ve0 and pseudosuper cases, but still count. */
+	if (ret)
+		atomic_dec(&ve->mnt_avail_nr);
+	else
+		ret = atomic_dec_if_positive(&ve->mnt_avail_nr) >= 0;
 
-	mnt->ve_owner = get_ve(ve);
-	atomic_inc(&ve->mnt_nr);
+	if (ret)
+		get_ve(ve);
+	return ret;
 }
 
-static inline void ve_mount_nr_dec(struct mount *mnt)
+static inline void ve_mount_put(struct mount *mnt, struct ve_struct *ve)
 {
-	struct ve_struct *ve = mnt->ve_owner;
-
-	atomic_dec(&ve->mnt_nr);
+	/*
+	 * ve argument is needed to reuse this function in alloc_vfsmnt error path.
+	 * Other users should pass mnt->ve_owner value.
+	 */
+	atomic_inc(&ve->mnt_avail_nr);
 	put_ve(ve);
-	mnt->ve_owner = NULL;
+	if (mnt)
+		mnt->ve_owner = NULL;
 }
 
 bool is_sb_ve_accessible(struct ve_struct *ve, struct super_block *sb)
@@ -3303,9 +3317,8 @@ bool is_sb_ve_accessible(struct ve_struct *ve, struct super_block *sb)
 
 #else /* CONFIG_VE */
 
-static inline int ve_mount_allowed(void) { return 1; }
-static inline void ve_mount_nr_inc(struct mount *mnt, struct ve_struct *ve) { }
-static inline void ve_mount_nr_dec(struct mount *mnt) { }
+static inline int ve_try_reserve_mount(struct ve_struct *ve) { return 1; }
+static inline void ve_mount_put(struct mount *mnt, struct ve_struct *ve) { }
 #endif /* CONFIG_VE */
 
 static int ve_prepare_mount_options(struct fs_context *fc, void *data)
diff --git a/include/linux/ve.h b/include/linux/ve.h
index 3facbd1759df..cca0a2bc1aac 100644
--- a/include/linux/ve.h
+++ b/include/linux/ve.h
@@ -88,7 +88,7 @@ struct ve_struct {
 	atomic_t		nd_neigh_nr;
 	unsigned long		meminfo_val;
 
-	atomic_t		mnt_nr; /* number of present VE mounts */
+	atomic_t		mnt_avail_nr; /* number of available VE mounts */
 
 #ifdef CONFIG_COREDUMP
 	char			core_pattern[CORENAME_MAX_SIZE];
diff --git a/kernel/ve/ve.c b/kernel/ve/ve.c
index dddf2393326d..3f66144eeb7e 100644
--- a/kernel/ve/ve.c
+++ b/kernel/ve/ve.c
@@ -81,7 +81,7 @@ struct ve_struct ve0 = {
 
 	.arp_neigh_nr		= ATOMIC_INIT(0),
 	.nd_neigh_nr		= ATOMIC_INIT(0),
-	.mnt_nr			= ATOMIC_INIT(0),
+	.mnt_avail_nr		= ATOMIC_INIT(INT_MAX),
 	.meminfo_val		= VE_MEMINFO_SYSTEM,
 	.umh_running_helpers	= ATOMIC_INIT(0),
 	.umh_helpers_waitq	= __WAIT_QUEUE_HEAD_INITIALIZER(ve0.umh_helpers_waitq),
@@ -778,7 +778,7 @@ static struct cgroup_subsys_state *ve_create(struct cgroup_subsys_state *parent_
 
 	atomic_set(&ve->arp_neigh_nr, 0);
 	atomic_set(&ve->nd_neigh_nr, 0);
-	atomic_set(&ve->mnt_nr, 0);
+	atomic_set(&ve->mnt_avail_nr, sysctl_ve_mount_nr);
 
 #ifdef CONFIG_COREDUMP
 	strcpy(ve->core_pattern, "core");
@@ -1055,9 +1055,9 @@ static u64 ve_netns_avail_nr_read(struct cgroup_subsys_state *css, struct cftype
 	return atomic_read(&css_to_ve(css)->netns_avail_nr);
 }
 
-static u64 ve_mnt_nr_read(struct cgroup_subsys_state *css, struct cftype *cft)
+static s64 ve_mnt_avail_nr_read(struct cgroup_subsys_state *css, struct cftype *cft)
 {
-	return atomic_read(&css_to_ve(css)->mnt_nr);
+	return atomic_read(&css_to_ve(css)->mnt_avail_nr);
 }
 
 static u64 ve_netif_max_nr_read(struct cgroup_subsys_state *css, struct cftype *cft)
@@ -1617,8 +1617,8 @@ static struct cftype ve_cftypes[] = {
 		.read_u64		= ve_netns_avail_nr_read,
 	},
 	{
-		.name			= "mnt_nr",
-		.read_u64		= ve_mnt_nr_read,
+		.name			= "mnt_avail_nr",
+		.read_s64		= ve_mnt_avail_nr_read,
 	},
 	{
 		.name			= "netif_max_nr",
-- 
2.47.1


^ permalink raw reply	[flat|nested] 16+ messages in thread

* [Devel] [PATCH VZ10 v5 4/9] selftests/ve: Update ve_ns_owner_test
  2026-08-02 11:40 [Devel] [PATCH VZ10 v5 0/9] Add per-VE failcount support Vladimir Riabchun
                   ` (2 preceding siblings ...)
  2026-08-02 11:40 ` [Devel] [PATCH VZ10 v5 3/9] ve/fs: Rework per-ve mount count Vladimir Riabchun
@ 2026-08-02 11:40 ` Vladimir Riabchun
  2026-08-02 11:40 ` [Devel] [PATCH VZ10 v5 5/9] ve: Move from global VE mounts limit to per-VE limit Vladimir Riabchun
                   ` (5 subsequent siblings)
  9 siblings, 0 replies; 16+ messages in thread
From: Vladimir Riabchun @ 2026-08-02 11:40 UTC (permalink / raw)


Mount accounting interface has changed, now it shows the number
of available mounts.

https://virtuozzo.atlassian.net/browse/VSTOR-135520

Feature: per-ve failcounters
Signed-off-by: Vladimir Riabchun <vladimir.riabchun@virtuozzo.com>
---
 tools/testing/selftests/ve/ve_ns_owner_test.c | 31 ++++++++++---------
 1 file changed, 16 insertions(+), 15 deletions(-)

diff --git a/tools/testing/selftests/ve/ve_ns_owner_test.c b/tools/testing/selftests/ve/ve_ns_owner_test.c
index 82c31dff4b1b..adcbc4051442 100644
--- a/tools/testing/selftests/ve/ve_ns_owner_test.c
+++ b/tools/testing/selftests/ve/ve_ns_owner_test.c
@@ -18,7 +18,7 @@
  * with a new netns and mntns, the parent reads the new ve's counters
  * via cgroupfs and asserts they reflect the just-created namespaces:
  *   - ve.netns_avail_nr drops by exactly one (the new netns);
- *   - ve.mnt_nr is strictly greater than zero (mounts copied into the
+ *   - ve.mnt_avail_nr drops by exactly one (mounts copied into the
  *     new mntns are accounted to the new ve).
  *
  * We never assert against the parent ve's counters: those are shared
@@ -50,6 +50,7 @@
  * any spurious accounting against the parent ve would overflow it.
  */
 #define VE_NETNS_MAX		3
+#define VE_MOUNTS_MAX		4096
 
 /*
  * Synchronisation across the clone() boundary: child does its setup,
@@ -106,7 +107,7 @@ static int clone_child_func(void *arg)
 /*
  * Before fix:
  *   - clone path: ve.netns_avail_nr stays at VE_NETNS_MAX and
- *     ve.mnt_nr stays at 0 because copy_net_ns()/copy_mnt_ns()
+ *     ve.mnt_avail_nr stays at VE_MOUNTS_MAX because copy_net_ns()/copy_mnt_ns()
  *     charged the parent ve via get_exec_env().
  *   - unshare path: the syscall itself returned -EINVAL, so this
  *     check was unreachable.
@@ -117,16 +118,16 @@ static int clone_child_func(void *arg)
 static void check_new_ve_owner(struct __test_metadata *_metadata,
 			       int cgv2_fd, int ctid)
 {
-	unsigned long long avail, mnt;
-	char path[PATH_MAX];
+	unsigned long long avail_netns, avail_mnt;
+	char path[64];
 
 	snprintf(path, sizeof(path), "%d/ve.netns_avail_nr", ctid);
-	ASSERT_EQ(read_u64_at(cgv2_fd, path, &avail), 0);
-	EXPECT_EQ(avail, VE_NETNS_MAX - 1);
+	ASSERT_EQ(read_u64_at(cgv2_fd, path, &avail_netns), 0);
+	EXPECT_EQ(avail_netns, VE_NETNS_MAX - 1);
 
-	snprintf(path, sizeof(path), "%d/ve.mnt_nr", ctid);
-	ASSERT_EQ(read_u64_at(cgv2_fd, path, &mnt), 0);
-	EXPECT_GT(mnt, 0);
+	snprintf(path, sizeof(path), "%d/ve.mnt_avail_nr", ctid);
+	ASSERT_EQ(read_u64_at(cgv2_fd, path, &avail_mnt), 0);
+	EXPECT_LT(avail_mnt, VE_MOUNTS_MAX);
 }
 
 FIXTURE(ve_ns_owner)
@@ -137,7 +138,7 @@ FIXTURE(ve_ns_owner)
 
 FIXTURE_SETUP(ve_ns_owner)
 {
-	unsigned long long initial_mnt_nr;
+	unsigned long long initial_mnt_avail_nr;
 	char val[16];
 	char path[PATH_MAX];
 
@@ -174,13 +175,13 @@ FIXTURE_SETUP(ve_ns_owner)
 
 	/*
 	 * The new ve cgroup has not been entered by anything yet, so its
-	 * mnt_nr counter must start at 0. Each test below verifies that
+	 * mnt_avail_nr counter be VE_MOUNTS_MAX. Each test below verifies that
 	 * the clone/unshare populates the new mntns under this ve, i.e.
-	 * mnt_nr rises strictly above zero.
+	 * mnt_avail_nr strictly decreases.
 	 */
-	snprintf(path, sizeof(path), "%d/ve.mnt_nr", self->ctid);
-	ASSERT_EQ(read_u64_at(self->cgv2_fd, path, &initial_mnt_nr), 0);
-	ASSERT_EQ(initial_mnt_nr, 0);
+	snprintf(path, sizeof(path), "%d/ve.mnt_avail_nr", self->ctid);
+	ASSERT_EQ(read_u64_at(self->cgv2_fd, path, &initial_mnt_avail_nr), 0);
+	ASSERT_EQ(initial_mnt_avail_nr, VE_MOUNTS_MAX);
 };
 
 FIXTURE_TEARDOWN(ve_ns_owner)
-- 
2.47.1


^ permalink raw reply	[flat|nested] 16+ messages in thread

* [Devel] [PATCH VZ10 v5 5/9] ve: Move from global VE mounts limit to per-VE limit
  2026-08-02 11:40 [Devel] [PATCH VZ10 v5 0/9] Add per-VE failcount support Vladimir Riabchun
                   ` (3 preceding siblings ...)
  2026-08-02 11:40 ` [Devel] [PATCH VZ10 v5 4/9] selftests/ve: Update ve_ns_owner_test Vladimir Riabchun
@ 2026-08-02 11:40 ` Vladimir Riabchun
  2026-08-02 11:40 ` [Devel] [PATCH VZ10 v5 6/9] ve/ve.c: Generate VE resource accessors using macros Vladimir Riabchun
                   ` (4 subsequent siblings)
  9 siblings, 0 replies; 16+ messages in thread
From: Vladimir Riabchun @ 2026-08-02 11:40 UTC (permalink / raw)


This change unifies mount limit handling with other VE limits.
This will simplify refactoring later and allow more precise limits
control from one place.

https://virtuozzo.atlassian.net/browse/VSTOR-135520

Feature: per-ve failcounters
Signed-off-by: Vladimir Riabchun <vladimir.riabchun@virtuozzo.com>
---
 include/linux/ve.h                            |  9 +++--
 kernel/ve/ve.c                                | 34 ++++++++++++++++++-
 kernel/ve/veowner.c                           | 19 -----------
 tools/testing/selftests/ve/ve_ns_owner_test.c |  6 +++-
 4 files changed, 45 insertions(+), 23 deletions(-)

diff --git a/include/linux/ve.h b/include/linux/ve.h
index cca0a2bc1aac..5687faad46ff 100644
--- a/include/linux/ve.h
+++ b/include/linux/ve.h
@@ -88,7 +88,13 @@ struct ve_struct {
 	atomic_t		nd_neigh_nr;
 	unsigned long		meminfo_val;
 
+	/*
+	 * Operations with a big amount of mount points can require a lot of time.
+	 * These operations take the global lock namespace_sem, so they can affect
+	 * other containers.
+	 */
 	atomic_t		mnt_avail_nr; /* number of available VE mounts */
+	int			mnt_max_nr;
 
 #ifdef CONFIG_COREDUMP
 	char			core_pattern[CORENAME_MAX_SIZE];
@@ -135,8 +141,7 @@ extern int nr_ve;
 #define NETNS_MAX_NR_DEFAULT	256	/* number of net-namespaces per-VE */
 #define NETIF_MAX_NR_DEFAULT	256	/* number of net-interfaces per-VE */
 #define BPF_PROG_MAX_NR_DEFAULT	256	/* number of loaded BPF progs per-VE */
-
-extern unsigned int sysctl_ve_mount_nr;
+#define MNT_MAX_NR_DEFAULT	4096	/* number of mounts per-VE */
 
 #ifdef CONFIG_VE
 static inline void ve_set_state(struct ve_struct *ve, int new_state)
diff --git a/kernel/ve/ve.c b/kernel/ve/ve.c
index 3f66144eeb7e..949df9b6f3e8 100644
--- a/kernel/ve/ve.c
+++ b/kernel/ve/ve.c
@@ -82,6 +82,7 @@ struct ve_struct ve0 = {
 	.arp_neigh_nr		= ATOMIC_INIT(0),
 	.nd_neigh_nr		= ATOMIC_INIT(0),
 	.mnt_avail_nr		= ATOMIC_INIT(INT_MAX),
+	.mnt_max_nr		= INT_MAX,
 	.meminfo_val		= VE_MEMINFO_SYSTEM,
 	.umh_running_helpers	= ATOMIC_INIT(0),
 	.umh_helpers_waitq	= __WAIT_QUEUE_HEAD_INITIALIZER(ve0.umh_helpers_waitq),
@@ -778,7 +779,8 @@ static struct cgroup_subsys_state *ve_create(struct cgroup_subsys_state *parent_
 
 	atomic_set(&ve->arp_neigh_nr, 0);
 	atomic_set(&ve->nd_neigh_nr, 0);
-	atomic_set(&ve->mnt_avail_nr, sysctl_ve_mount_nr);
+	ve->mnt_max_nr = MNT_MAX_NR_DEFAULT;
+	atomic_set(&ve->mnt_avail_nr, MNT_MAX_NR_DEFAULT);
 
 #ifdef CONFIG_COREDUMP
 	strcpy(ve->core_pattern, "core");
@@ -1060,6 +1062,30 @@ static s64 ve_mnt_avail_nr_read(struct cgroup_subsys_state *css, struct cftype *
 	return atomic_read(&css_to_ve(css)->mnt_avail_nr);
 }
 
+static u64 ve_mnt_max_nr_read(struct cgroup_subsys_state *css, struct cftype *cft)
+{
+	return css_to_ve(css)->mnt_max_nr;
+}
+
+static int ve_mnt_max_nr_write(struct cgroup_subsys_state *css, struct cftype *cft, u64 val)
+{
+	struct ve_struct *ve = css_to_ve(css);
+	int delta;
+
+	if (!ve_is_super(get_exec_env()))
+		return -EPERM;
+
+	if (val > INT_MAX)
+		return -EOVERFLOW;
+
+	down_write(&ve->op_sem);
+	delta = val - ve->mnt_max_nr;
+	ve->mnt_max_nr = val;
+	atomic_add(delta, &ve->mnt_avail_nr);
+	up_write(&ve->op_sem);
+	return 0;
+}
+
 static u64 ve_netif_max_nr_read(struct cgroup_subsys_state *css, struct cftype *cft)
 {
 	return css_to_ve(css)->netif_max_nr;
@@ -1620,6 +1646,12 @@ static struct cftype ve_cftypes[] = {
 		.name			= "mnt_avail_nr",
 		.read_s64		= ve_mnt_avail_nr_read,
 	},
+	{
+		.name			= "mnt_max_nr",
+		.flags			= CFTYPE_NOT_ON_ROOT,
+		.read_u64		= ve_mnt_max_nr_read,
+		.write_u64		= ve_mnt_max_nr_write,
+	},
 	{
 		.name			= "netif_max_nr",
 		.flags			= CFTYPE_NOT_ON_ROOT,
diff --git a/kernel/ve/veowner.c b/kernel/ve/veowner.c
index 1e631baf0de9..c77735f8c035 100644
--- a/kernel/ve/veowner.c
+++ b/kernel/ve/veowner.c
@@ -48,26 +48,7 @@ static void prepare_proc(void)
  * ------------------------------------------------------------------------
  */
 
-/*
- * Operations with a big amount of mount points can require a lot of time.
- * These operations take the global lock namespace_sem, so they can affect
- * other containers. Let us allow no more than sysctl_ve_mount_nr mount
- * points for a VE.
- */
-unsigned int sysctl_ve_mount_nr = 4096;
-static int ve_mount_nr_min = 0;
-static int ve_mount_nr_max = INT_MAX;
-
 static struct ctl_table vz_fs_table[] = {
-	{
-		.procname	= "ve-mount-nr",
-		.data		= &sysctl_ve_mount_nr,
-		.maxlen		= sizeof(sysctl_ve_mount_nr),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_minmax,
-		.extra1		= &ve_mount_nr_min,
-		.extra2		= &ve_mount_nr_max,
-	},
 	{
 		.procname	= "fsync-enable",
 		.data		= &ve0.fsync_enable,
diff --git a/tools/testing/selftests/ve/ve_ns_owner_test.c b/tools/testing/selftests/ve/ve_ns_owner_test.c
index adcbc4051442..2c3fd1f83189 100644
--- a/tools/testing/selftests/ve/ve_ns_owner_test.c
+++ b/tools/testing/selftests/ve/ve_ns_owner_test.c
@@ -50,7 +50,7 @@
  * any spurious accounting against the parent ve would overflow it.
  */
 #define VE_NETNS_MAX		3
-#define VE_MOUNTS_MAX		4096
+#define VE_MOUNTS_MAX		32
 
 /*
  * Synchronisation across the clone() boundary: child does its setup,
@@ -173,6 +173,10 @@ FIXTURE_SETUP(ve_ns_owner)
 	snprintf(val, sizeof(val), "%d", VE_NETNS_MAX);
 	ASSERT_EQ(write_file_at(self->cgv2_fd, path, val), 0);
 
+	snprintf(path, sizeof(path), "%d/ve.mnt_max_nr", self->ctid);
+	snprintf(val, sizeof(val), "%d", VE_MOUNTS_MAX);
+	ASSERT_EQ(write_file_at(self->cgv2_fd, path, val), 0);
+
 	/*
 	 * The new ve cgroup has not been entered by anything yet, so its
 	 * mnt_avail_nr counter be VE_MOUNTS_MAX. Each test below verifies that
-- 
2.47.1


^ permalink raw reply	[flat|nested] 16+ messages in thread

* [Devel] [PATCH VZ10 v5 6/9] ve/ve.c: Generate VE resource accessors using macros
  2026-08-02 11:40 [Devel] [PATCH VZ10 v5 0/9] Add per-VE failcount support Vladimir Riabchun
                   ` (4 preceding siblings ...)
  2026-08-02 11:40 ` [Devel] [PATCH VZ10 v5 5/9] ve: Move from global VE mounts limit to per-VE limit Vladimir Riabchun
@ 2026-08-02 11:40 ` Vladimir Riabchun
  2026-08-02 11:40 ` [Devel] [PATCH VZ10 v5 7/9] ve: Introduce per-VE failcount Vladimir Riabchun
                   ` (3 subsequent siblings)
  9 siblings, 0 replies; 16+ messages in thread
From: Vladimir Riabchun @ 2026-08-02 11:40 UTC (permalink / raw)


We have a bunch of almost identical code, which takes place
and has some unclear variations in it. Unify all resource
getters and setters using macros.

This changes behavior a little bit: some resources (netns number
for example) required stopped VE. Now such limits could be updated
with a running VE.
Also, some max_nr_write functions didn't verify provided value,
now it is not possible to pass overflowing value.

https://virtuozzo.atlassian.net/browse/VSTOR-135520

Feature: per-ve failcounters
Signed-off-by: Vladimir Riabchun <vladimir.riabchun@virtuozzo.com>
---
 kernel/ve/ve.c | 209 +++++++++++++------------------------------------
 1 file changed, 54 insertions(+), 155 deletions(-)

diff --git a/kernel/ve/ve.c b/kernel/ve/ve.c
index 949df9b6f3e8..dc3faa0b1d76 100644
--- a/kernel/ve/ve.c
+++ b/kernel/ve/ve.c
@@ -43,6 +43,39 @@
 #include <linux/cpuset.h>		/* For css_tg() */
 #include "../sched/sched.h"		/* For css_tg() */
 
+#define VE_RESOURCE(name)						\
+static u64 ve_##name##_max_nr_read(struct cgroup_subsys_state *css,	\
+				 struct cftype *cft)			\
+{									\
+	return css_to_ve(css)->name##_max_nr;				\
+}									\
+									\
+static int ve_##name##_max_nr_write(struct cgroup_subsys_state *css,	\
+				  struct cftype *cft, u64 val)		\
+{									\
+	struct ve_struct *ve = css_to_ve(css);				\
+	int delta;							\
+									\
+	if (!ve_is_super(get_exec_env()))				\
+		return -EPERM;						\
+									\
+	if (val > INT_MAX)						\
+		return -EOVERFLOW;					\
+									\
+	down_write(&ve->op_sem);					\
+	delta = val - ve->name##_max_nr;				\
+	ve->name##_max_nr = val;					\
+	atomic_add(delta, &ve->name##_avail_nr);			\
+	up_write(&ve->op_sem);						\
+	return 0;							\
+}									\
+									\
+static s64 ve_##name##_avail_nr_read(struct cgroup_subsys_state *css,	\
+				   struct cftype *cft)			\
+{									\
+	return atomic_read(&css_to_ve(css)->name##_avail_nr);		\
+}
+
 extern struct kmapset_set sysfs_ve_perms_set;
 #ifdef CONFIG_PROC_FS
 extern struct kmapset_set proc_ve_perms_set;
@@ -1028,121 +1061,10 @@ static int ve_features_write(struct cgroup_subsys_state *css, struct cftype *cft
 	return 0;
 }
 
-static u64 ve_netns_max_nr_read(struct cgroup_subsys_state *css, struct cftype *cft)
-{
-	return css_to_ve(css)->netns_max_nr;
-}
-
-static int ve_netns_max_nr_write(struct cgroup_subsys_state *css, struct cftype *cft, u64 val)
-{
-	struct ve_struct *ve = css_to_ve(css);
-	int delta;
-
-	if (!ve_is_super(get_exec_env()))
-		return -EPERM;
-
-	down_write(&ve->op_sem);
-	if (VE_IS_RUNNING(ve) || ve->ve_nsproxy) {
-		up_write(&ve->op_sem);
-		return -EBUSY;
-	}
-	delta = val - ve->netns_max_nr;
-	ve->netns_max_nr = val;
-	atomic_add(delta, &ve->netns_avail_nr);
-	up_write(&ve->op_sem);
-	return 0;
-}
-static u64 ve_netns_avail_nr_read(struct cgroup_subsys_state *css, struct cftype *cft)
-{
-	return atomic_read(&css_to_ve(css)->netns_avail_nr);
-}
-
-static s64 ve_mnt_avail_nr_read(struct cgroup_subsys_state *css, struct cftype *cft)
-{
-	return atomic_read(&css_to_ve(css)->mnt_avail_nr);
-}
-
-static u64 ve_mnt_max_nr_read(struct cgroup_subsys_state *css, struct cftype *cft)
-{
-	return css_to_ve(css)->mnt_max_nr;
-}
-
-static int ve_mnt_max_nr_write(struct cgroup_subsys_state *css, struct cftype *cft, u64 val)
-{
-	struct ve_struct *ve = css_to_ve(css);
-	int delta;
-
-	if (!ve_is_super(get_exec_env()))
-		return -EPERM;
-
-	if (val > INT_MAX)
-		return -EOVERFLOW;
-
-	down_write(&ve->op_sem);
-	delta = val - ve->mnt_max_nr;
-	ve->mnt_max_nr = val;
-	atomic_add(delta, &ve->mnt_avail_nr);
-	up_write(&ve->op_sem);
-	return 0;
-}
-
-static u64 ve_netif_max_nr_read(struct cgroup_subsys_state *css, struct cftype *cft)
-{
-	return css_to_ve(css)->netif_max_nr;
-}
-
-static int ve_netif_max_nr_write(struct cgroup_subsys_state *css, struct cftype *cft, u64 val)
-{
-	struct ve_struct *ve = css_to_ve(css);
-	int delta;
-
-	if (!ve_is_super(get_exec_env()))
-		return -EPERM;
-
-	if (val > INT_MAX)
-		return -EOVERFLOW;
-
-	down_write(&ve->op_sem);
-	delta = val - ve->netif_max_nr;
-	ve->netif_max_nr = val;
-	atomic_add(delta, &ve->netif_avail_nr);
-	up_write(&ve->op_sem);
-	return 0;
-}
-
-static s64 ve_netif_avail_nr_read(struct cgroup_subsys_state *css, struct cftype *cft)
-{
-	return atomic_read(&css_to_ve(css)->netif_avail_nr);
-}
-
-static u64 ve_bpf_prog_max_nr_read(struct cgroup_subsys_state *css, struct cftype *cft)
-{
-	return css_to_ve(css)->bpf_prog_max_nr;
-}
-
-static int ve_bpf_prog_max_nr_write(struct cgroup_subsys_state *css, struct cftype *cft, u64 val)
-{
-	struct ve_struct *ve = css_to_ve(css);
-	int delta;
-
-	if (!ve_is_super(get_exec_env()))
-		return -EPERM;
-
-	if (val > INT_MAX)
-		return -EOVERFLOW;
-
-	down_write(&ve->op_sem);
-	delta = val - ve->bpf_prog_max_nr;
-	ve->bpf_prog_max_nr = val;
-	atomic_add(delta, &ve->bpf_prog_avail_nr);
-	up_write(&ve->op_sem);
-	return 0;
-}
-
-static s64 ve_bpf_prog_avail_nr_read(struct cgroup_subsys_state *css, struct cftype *cft)
-{
-	return atomic_read(&css_to_ve(css)->bpf_prog_avail_nr);
-}
+VE_RESOURCE(netns);
+VE_RESOURCE(mnt);
+VE_RESOURCE(netif);
+VE_RESOURCE(bpf_prog);
 
 static int ve_os_release_read(struct seq_file *sf, void *v)
 {
@@ -1588,6 +1510,19 @@ static int ve_rpc_kill_write(struct cgroup_subsys_state *css,
 	return 0;
 }
 
+/* checkpatch will report it, can't enclose this in parentheses. */
+#define VE_RESOURCE_CFTYPE(res)					\
+{								\
+	.name			= #res "_max_nr",		\
+	.flags			= CFTYPE_NOT_ON_ROOT,		\
+	.read_u64		= ve_##res##_max_nr_read,	\
+	.write_u64		= ve_##res##_max_nr_write,	\
+},								\
+{								\
+	.name			= #res "_avail_nr",		\
+	.read_s64		= ve_##res##_avail_nr_read,	\
+}
+
 static struct cftype ve_cftypes[] = {
 
 	{
@@ -1632,46 +1567,10 @@ static struct cftype ve_cftypes[] = {
 		.read_u64		= ve_pid_max_read_u64,
 		.write_u64		= ve_pid_max_write_running_u64,
 	},
-	{
-		.name			= "netns_max_nr",
-		.flags			= CFTYPE_NOT_ON_ROOT,
-		.read_u64		= ve_netns_max_nr_read,
-		.write_u64		= ve_netns_max_nr_write,
-	},
-	{
-		.name			= "netns_avail_nr",
-		.read_u64		= ve_netns_avail_nr_read,
-	},
-	{
-		.name			= "mnt_avail_nr",
-		.read_s64		= ve_mnt_avail_nr_read,
-	},
-	{
-		.name			= "mnt_max_nr",
-		.flags			= CFTYPE_NOT_ON_ROOT,
-		.read_u64		= ve_mnt_max_nr_read,
-		.write_u64		= ve_mnt_max_nr_write,
-	},
-	{
-		.name			= "netif_max_nr",
-		.flags			= CFTYPE_NOT_ON_ROOT,
-		.read_u64		= ve_netif_max_nr_read,
-		.write_u64		= ve_netif_max_nr_write,
-	},
-	{
-		.name			= "netif_avail_nr",
-		.read_s64		= ve_netif_avail_nr_read,
-	},
-	{
-		.name			= "bpf_prog_max_nr",
-		.flags			= CFTYPE_NOT_ON_ROOT,
-		.read_u64		= ve_bpf_prog_max_nr_read,
-		.write_u64		= ve_bpf_prog_max_nr_write,
-	},
-	{
-		.name			= "bpf_prog_avail_nr",
-		.read_s64		= ve_bpf_prog_avail_nr_read,
-	},
+	VE_RESOURCE_CFTYPE(netns),
+	VE_RESOURCE_CFTYPE(mnt),
+	VE_RESOURCE_CFTYPE(netif),
+	VE_RESOURCE_CFTYPE(bpf_prog),
 	{
 		.name			= "os_release",
 		.max_write_len		= __NEW_UTS_LEN + 1,
-- 
2.47.1


^ permalink raw reply	[flat|nested] 16+ messages in thread

* [Devel] [PATCH VZ10 v5 7/9] ve: Introduce per-VE failcount
  2026-08-02 11:40 [Devel] [PATCH VZ10 v5 0/9] Add per-VE failcount support Vladimir Riabchun
                   ` (5 preceding siblings ...)
  2026-08-02 11:40 ` [Devel] [PATCH VZ10 v5 6/9] ve/ve.c: Generate VE resource accessors using macros Vladimir Riabchun
@ 2026-08-02 11:40 ` Vladimir Riabchun
  2026-08-07  9:25   ` Vasileios Almpanis
  2026-08-17 11:54   ` Vasileios Almpanis
  2026-08-02 11:40 ` [Devel] [PATCH VZ10 v5 8/9] selftests/ve: Add more helpers Vladimir Riabchun
                   ` (2 subsequent siblings)
  9 siblings, 2 replies; 16+ messages in thread
From: Vladimir Riabchun @ 2026-08-02 11:40 UTC (permalink / raw)


It may be useful to have a history of resource limit hits for every VE,
this may simplify debugging and provide some information about the
resources usage.

This information is provided by ve.failcount file, any write to it
resets all failcounts.

To add a new failcounter we need to create a new atomic_t field
name_failcount in ve structure and add a new VE_FC_ENTRY in
ve_failcounts array.

One change, unrelated to failcounts: aio fields are now initialized
in ve0.

https://virtuozzo.atlassian.net/browse/VSTOR-135520

Feature: per-ve failcounters
Signed-off-by: Vladimir Riabchun <vladimir.riabchun@virtuozzo.com>
---
 fs/aio.c                 |  1 +
 fs/namespace.c           |  2 ++
 include/linux/ve.h       |  6 ++++
 kernel/bpf/syscall.c     |  1 +
 kernel/ve/ve.c           | 67 ++++++++++++++++++++++++++++++++++++++++
 net/core/dev.c           |  2 ++
 net/core/neighbour.c     |  1 +
 net/core/net_namespace.c |  4 ++-
 8 files changed, 83 insertions(+), 1 deletion(-)

diff --git a/fs/aio.c b/fs/aio.c
index cb63416af135..3fa07cc626f8 100644
--- a/fs/aio.c
+++ b/fs/aio.c
@@ -814,6 +814,7 @@ static struct kioctx *ioctx_alloc(unsigned nr_events)
 	spin_lock(&ve->aio_nr_lock);
 	if (ve->aio_nr + ctx->max_reqs > ve->aio_max_nr ||
 	    ve->aio_nr + ctx->max_reqs < ve->aio_nr) {
+		atomic_inc(&ve->aio_failcount);
 		spin_unlock(&ve->aio_nr_lock);
 		err = -EAGAIN;
 		goto err_ctx;
diff --git a/fs/namespace.c b/fs/namespace.c
index 2550aeba2f1e..c4e2c7f7f725 100644
--- a/fs/namespace.c
+++ b/fs/namespace.c
@@ -3283,6 +3283,8 @@ static inline int ve_try_reserve_mount(struct ve_struct *ve)
 
 	if (ret)
 		get_ve(ve);
+	else
+		atomic_inc(&ve->mnt_failcount);
 	return ret;
 }
 
diff --git a/include/linux/ve.h b/include/linux/ve.h
index 5687faad46ff..9e73527e970e 100644
--- a/include/linux/ve.h
+++ b/include/linux/ve.h
@@ -72,12 +72,15 @@ struct ve_struct {
 	struct kmapset_key	proc_perms_key;
 
 	atomic_t		netns_avail_nr;
+	atomic_t		netns_failcount;
 	int			netns_max_nr;
 
 	atomic_t		netif_avail_nr;
+	atomic_t		netif_failcount;
 	int			netif_max_nr;
 
 	atomic_t		bpf_prog_avail_nr;
+	atomic_t		bpf_prog_failcount;
 	int			bpf_prog_max_nr;
 
 	atomic64_t		_uevent_seqnum;
@@ -86,6 +89,7 @@ struct ve_struct {
 
 	atomic_t		arp_neigh_nr;
 	atomic_t		nd_neigh_nr;
+	atomic_t		neigh_tbl_failcount;
 	unsigned long		meminfo_val;
 
 	/*
@@ -94,6 +98,7 @@ struct ve_struct {
 	 * other containers.
 	 */
 	atomic_t		mnt_avail_nr; /* number of available VE mounts */
+	atomic_t		mnt_failcount;
 	int			mnt_max_nr;
 
 #ifdef CONFIG_COREDUMP
@@ -121,6 +126,7 @@ struct ve_struct {
 	spinlock_t		aio_nr_lock;
 	unsigned long		aio_nr;
 	unsigned long		aio_max_nr;
+	atomic_t		aio_failcount;
 #endif
 	struct vfsmount		*devtmpfs_mnt;
 };
diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c
index ff2a51c59f04..95e806fa19f4 100644
--- a/kernel/bpf/syscall.c
+++ b/kernel/bpf/syscall.c
@@ -2891,6 +2891,7 @@ static int bpf_prog_load(union bpf_attr *attr, bpfptr_t uattr, u32 uattr_size)
 	if (!bpf_cap && type == BPF_PROG_TYPE_CGROUP_DEVICE) {
 		load_ve = get_exec_env();
 		if (atomic_dec_if_positive(&load_ve->bpf_prog_avail_nr) < 0) {
+			atomic_inc(&load_ve->bpf_prog_failcount);
 			load_ve = NULL;
 			err = -ENOSPC;
 			goto put_token;
diff --git a/kernel/ve/ve.c b/kernel/ve/ve.c
index dc3faa0b1d76..a5c93be759ac 100644
--- a/kernel/ve/ve.c
+++ b/kernel/ve/ve.c
@@ -99,10 +99,13 @@ struct ve_struct ve0 = {
 	.features		= -1,
 	.sched_lat_ve.cur	= &ve0_lat_stats,
 	.netns_avail_nr		= ATOMIC_INIT(INT_MAX),
+	.netns_failcount	= ATOMIC_INIT(0),
 	.netns_max_nr		= INT_MAX,
 	.netif_avail_nr		= ATOMIC_INIT(INT_MAX),
+	.netif_failcount	= ATOMIC_INIT(0),
 	.netif_max_nr		= INT_MAX,
 	.bpf_prog_avail_nr	= ATOMIC_INIT(INT_MAX),
+	.bpf_prog_failcount	= ATOMIC_INIT(0),
 	.bpf_prog_max_nr	= INT_MAX,
 	.fsync_enable		= FSYNC_FILTERED,
 	._randomize_va_space	=
@@ -114,8 +117,16 @@ struct ve_struct ve0 = {
 
 	.arp_neigh_nr		= ATOMIC_INIT(0),
 	.nd_neigh_nr		= ATOMIC_INIT(0),
+	.neigh_tbl_failcount	= ATOMIC_INIT(0),
 	.mnt_avail_nr		= ATOMIC_INIT(INT_MAX),
 	.mnt_max_nr		= INT_MAX,
+	.mnt_failcount		= ATOMIC_INIT(0),
+#ifdef CONFIG_AIO
+	.aio_nr_lock		= __SPIN_LOCK_UNLOCKED(aio_nr_lock),
+	.aio_nr			= 0,
+	.aio_max_nr		= AIO_MAX_NR_DEFAULT,
+	.aio_failcount		= ATOMIC_INIT(0),
+#endif
 	.meminfo_val		= VE_MEMINFO_SYSTEM,
 	.umh_running_helpers	= ATOMIC_INIT(0),
 	.umh_helpers_waitq	= __WAIT_QUEUE_HEAD_INITIALIZER(ve0.umh_helpers_waitq),
@@ -780,12 +791,15 @@ static struct cgroup_subsys_state *ve_create(struct cgroup_subsys_state *parent_
 	ve->fsync_enable = FSYNC_FILTERED;
 
 	atomic_set(&ve->netns_avail_nr, NETNS_MAX_NR_DEFAULT);
+	atomic_set(&ve->netns_failcount, 0);
 	ve->netns_max_nr = NETNS_MAX_NR_DEFAULT;
 
 	atomic_set(&ve->netif_avail_nr, NETIF_MAX_NR_DEFAULT);
+	atomic_set(&ve->netif_failcount, 0);
 	ve->netif_max_nr = NETIF_MAX_NR_DEFAULT;
 
 	atomic_set(&ve->bpf_prog_avail_nr, BPF_PROG_MAX_NR_DEFAULT);
+	atomic_set(&ve->bpf_prog_failcount, 0);
 	ve->bpf_prog_max_nr = BPF_PROG_MAX_NR_DEFAULT;
 
 	err = ve_log_init(ve);
@@ -812,7 +826,9 @@ static struct cgroup_subsys_state *ve_create(struct cgroup_subsys_state *parent_
 
 	atomic_set(&ve->arp_neigh_nr, 0);
 	atomic_set(&ve->nd_neigh_nr, 0);
+	atomic_set(&ve->neigh_tbl_failcount, 0);
 	ve->mnt_max_nr = MNT_MAX_NR_DEFAULT;
+	atomic_set(&ve->mnt_failcount, 0);
 	atomic_set(&ve->mnt_avail_nr, MNT_MAX_NR_DEFAULT);
 
 #ifdef CONFIG_COREDUMP
@@ -825,6 +841,7 @@ static struct cgroup_subsys_state *ve_create(struct cgroup_subsys_state *parent_
 	spin_lock_init(&ve->aio_nr_lock);
 	ve->aio_nr = 0;
 	ve->aio_max_nr = AIO_MAX_NR_DEFAULT;
+	atomic_set(&ve->aio_failcount, 0);
 #endif
 
 	return &ve->css;
@@ -1066,6 +1083,50 @@ VE_RESOURCE(mnt);
 VE_RESOURCE(netif);
 VE_RESOURCE(bpf_prog);
 
+static const struct ve_failcount_entry {
+	const char *name;
+	size_t offset;
+} ve_failcounts[] = {
+#define VE_FC_ENTRY(name) { #name, offsetof(struct ve_struct, name##_failcount) }
+	VE_FC_ENTRY(netns),
+	VE_FC_ENTRY(mnt),
+	VE_FC_ENTRY(netif),
+	VE_FC_ENTRY(bpf_prog),
+	VE_FC_ENTRY(neigh_tbl),
+#ifdef CONFIG_AIO
+	VE_FC_ENTRY(aio),
+#endif
+	{}
+};
+
+static int ve_failcount_read(struct seq_file *sf, void *v)
+{
+	struct ve_struct *ve = css_to_ve(seq_css(sf));
+	struct ve_failcount_entry *entry;
+	atomic_t *fc;
+
+	for (entry = ve_failcounts; entry->name; entry++) {
+		fc = (void *)ve + entry->offset;
+		seq_printf(sf, "%s: %d\n", entry->name, atomic_read(fc));
+	}
+	return 0;
+}
+
+static ssize_t ve_failcount_write(struct kernfs_open_file *of, char *buf,
+				  size_t nbytes, loff_t off)
+{
+	struct ve_struct *ve = css_to_ve(of_css(of));
+	struct ve_failcount_entry *entry;
+	atomic_t *fc;
+
+	for (entry = ve_failcounts; entry->name; entry++) {
+		fc = (void *)ve + entry->offset;
+		atomic_set(fc, 0);
+	}
+
+	return nbytes;
+}
+
 static int ve_os_release_read(struct seq_file *sf, void *v)
 {
 	struct cgroup_subsys_state *css = seq_css(sf);
@@ -1603,6 +1664,12 @@ static struct cftype ve_cftypes[] = {
 		.flags			= CFTYPE_NOT_ON_ROOT,
 		.write_u64		= ve_rpc_kill_write,
 	},
+	{
+		.name			= "failcount",
+		.flags			= CFTYPE_NOT_ON_ROOT,
+		.seq_show		= ve_failcount_read,
+		.write			= ve_failcount_write,
+	},
 	{ }
 };
 
diff --git a/net/core/dev.c b/net/core/dev.c
index c7dddb200489..05e0b9b6ba23 100644
--- a/net/core/dev.c
+++ b/net/core/dev.c
@@ -10997,6 +10997,7 @@ int register_netdevice(struct net_device *dev)
 
 	ret = -ENOMEM;
 	if (atomic_dec_if_positive(&net->owner_ve->netif_avail_nr) < 0) {
+		atomic_inc(&net->owner_ve->netif_failcount);
 		ve_pr_warn_ratelimited(VE_LOG_BOTH,
 			"CT%s: hits max number of network devices, "
 			"increase ve::netif_max_nr parameter\n",
@@ -12211,6 +12212,7 @@ int __dev_change_net_namespace(struct net_device *dev, struct net *net,
 
 	err = -ENOMEM;
 	if (atomic_dec_if_positive(&net->owner_ve->netif_avail_nr) < 0) {
+		atomic_inc(&net->owner_ve->netif_failcount);
 		ve_pr_warn_ratelimited(VE_LOG_BOTH,
 			"CT%s: hits max number of network devices, "
 			"increase ve::netif_max_nr parameter\n",
diff --git a/net/core/neighbour.c b/net/core/neighbour.c
index f90deb17fb25..57a49d9c98a7 100644
--- a/net/core/neighbour.c
+++ b/net/core/neighbour.c
@@ -520,6 +520,7 @@ static struct neighbour *neigh_alloc(struct neigh_table *tbl,
 	    (glob_entries >= READ_ONCE(tbl->gc_thresh2) &&
 	     time_after(now, READ_ONCE(tbl->last_flush) + 5 * HZ))) {
 		if (!neigh_forced_gc(tbl, ve) && entries >= gc_thresh3) {
+			atomic_inc(&ve->neigh_tbl_failcount);
 			net_info_ratelimited("%s: neighbor table overflow!\n",
 					     tbl->id);
 			NEIGH_CACHE_STAT_INC(tbl, table_fulls);
diff --git a/net/core/net_namespace.c b/net/core/net_namespace.c
index b3d54cad984a..9a3376d2682f 100644
--- a/net/core/net_namespace.c
+++ b/net/core/net_namespace.c
@@ -486,8 +486,10 @@ void net_drop_ns(void *p)
 #ifdef CONFIG_VE
 static int dec_netns_avail(struct ve_struct *ve)
 {
-	if (atomic_dec_if_positive(&ve->netns_avail_nr) < 0)
+	if (atomic_dec_if_positive(&ve->netns_avail_nr) < 0) {
+		atomic_inc(&ve->netns_failcount);
 		return -ENOSPC;
+	}
 	return 0;
 }
 
-- 
2.47.1


^ permalink raw reply	[flat|nested] 16+ messages in thread

* [Devel] [PATCH VZ10 v5 8/9] selftests/ve: Add more helpers
  2026-08-02 11:40 [Devel] [PATCH VZ10 v5 0/9] Add per-VE failcount support Vladimir Riabchun
                   ` (6 preceding siblings ...)
  2026-08-02 11:40 ` [Devel] [PATCH VZ10 v5 7/9] ve: Introduce per-VE failcount Vladimir Riabchun
@ 2026-08-02 11:40 ` Vladimir Riabchun
  2026-08-02 11:40 ` [Devel] [PATCH VZ10 v5 9/9] selftests/ve: Add mount accounting selftest Vladimir Riabchun
  2026-08-17 11:54 ` [Devel] [PATCH VZ10 v5 0/9] Add per-VE failcount support Vasileios Almpanis
  9 siblings, 0 replies; 16+ messages in thread
From: Vladimir Riabchun @ 2026-08-02 11:40 UTC (permalink / raw)


Some more read/write helpers may be useful.

Also, add a helper to execute functions in child process with
switched namespaces and cgroup.

https://virtuozzo.atlassian.net/browse/VSTOR-135520

Feature: per-ve failcounters
Signed-off-by: Vladimir Riabchun <vladimir.riabchun@virtuozzo.com>
---
v4 -> v5:
 - In run_in_ve helper CLONE_NEWVE flag is added to unshare_flags
   unconditionally.

 tools/testing/selftests/ve/ve_selftest.h | 81 ++++++++++++++++++++++--
 1 file changed, 75 insertions(+), 6 deletions(-)

diff --git a/tools/testing/selftests/ve/ve_selftest.h b/tools/testing/selftests/ve/ve_selftest.h
index 69c0a52dd7ef..48bb7d1871bd 100644
--- a/tools/testing/selftests/ve/ve_selftest.h
+++ b/tools/testing/selftests/ve/ve_selftest.h
@@ -43,6 +43,14 @@ static inline int write_file_at(int dirfd, const char *path, const char *val)
 	return (ret == (int)len) ? 0 : -1;
 }
 
+static inline int write_u64_at(int dirfd, const char *path, unsigned long long val)
+{
+	char s[20];
+
+	snprintf(s, sizeof(s), "%llu", val);
+	return write_file_at(dirfd, path, s);
+}
+
 static inline int read_file_at(int dirfd, const char *path, char *buf,
 			       size_t buflen)
 {
@@ -73,19 +81,31 @@ static inline int read_u64_at(int dirfd, const char *path,
 			      unsigned long long *out)
 {
 	char buf[32] = {0}, *end;
-	int fd, ret;
+	int ret;
 
-	fd = openat(dirfd, path, O_RDONLY);
-	if (fd < 0)
+	ret = read_file_at(dirfd, path, buf, sizeof(buf));
+	if (ret <= 0)
 		return -1;
 
-	ret = read(fd, buf, sizeof(buf) - 1);
-	close(fd);
+	errno = 0;
+	*out = strtoull(buf, &end, 10);
+	if (errno || end == buf)
+		return -1;
+	return 0;
+}
+
+static inline int read_s32_at(int dirfd, const char *path,
+			      int *out)
+{
+	char buf[32] = {0}, *end;
+	int ret;
+
+	ret = read_file_at(dirfd, path, buf, sizeof(buf));
 	if (ret <= 0)
 		return -1;
 
 	errno = 0;
-	*out = strtoull(buf, &end, 10);
+	*out = strtol(buf, &end, 10);
 	if (errno || end == buf)
 		return -1;
 	return 0;
@@ -134,6 +154,55 @@ static inline int enter_cgroup(int cgv2_fd, int ctid)
 	return ret;
 }
 
+/*
+ * Run function in VE cgroup and new namespaces.
+ *
+ * Namespaces are provided via unshare_flags.
+ * CLONE_NEWVE flag is set by this function.
+ * Return values:
+ *  -  0 if function returns zero
+ *  - -1 if function returns negative value
+ *  -  1 if setup fails or function returns positive value
+ */
+static inline int run_in_ve(int cgv2_fd, int ctid, int unshare_flags,
+		int (*fn)(void *), void *arg)
+{
+	int status;
+	pid_t pid;
+
+	unshare_flags |= CLONE_NEWVE;
+	pid = fork();
+	if (pid < 0) {
+		fprintf(stderr, "%s: fork failed\n", __func__);
+		return 1;
+	}
+	if (pid == 0) {
+		int ret;
+
+		if (enter_cgroup(cgv2_fd, ctid) < 0) {
+			fprintf(stderr, "%s: enter_cgroup failed\n", __func__);
+			_exit(255);
+		}
+		if (unshare(unshare_flags) < 0) {
+			fprintf(stderr, "%s: unshare(%d) failed\n",
+				__func__, unshare_flags);
+			_exit(255);
+		}
+		ret = fn(arg);
+		if (ret < 0)
+			ret = 1;
+		else if (ret > 0)
+			ret = 255;
+		_exit(ret);
+	}
+	if (waitpid(pid, &status, 0) < 0 || !WIFEXITED(status) || WEXITSTATUS(status) == 255)
+		return 1;
+	if (WEXITSTATUS(status))
+		return -1;
+	return 0;
+
+}
+
 /*
  * 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.
-- 
2.47.1


^ permalink raw reply	[flat|nested] 16+ messages in thread

* [Devel] [PATCH VZ10 v5 9/9] selftests/ve: Add mount accounting selftest
  2026-08-02 11:40 [Devel] [PATCH VZ10 v5 0/9] Add per-VE failcount support Vladimir Riabchun
                   ` (7 preceding siblings ...)
  2026-08-02 11:40 ` [Devel] [PATCH VZ10 v5 8/9] selftests/ve: Add more helpers Vladimir Riabchun
@ 2026-08-02 11:40 ` Vladimir Riabchun
  2026-08-07 10:01   ` Vasileios Almpanis
  2026-08-17 11:54   ` Vasileios Almpanis
  2026-08-17 11:54 ` [Devel] [PATCH VZ10 v5 0/9] Add per-VE failcount support Vasileios Almpanis
  9 siblings, 2 replies; 16+ messages in thread
From: Vladimir Riabchun @ 2026-08-02 11:40 UTC (permalink / raw)


There are 6 test cases, covered in the new test:
1. Simple mount accouting correctness, just mount/umount.
2. Verification of correct limit hits and changes, including
   negative values.
3. Patial mounts test, when mount limit is hit in the middle
   of creation.
4. Test that enabled pseudosuper allows overuse.
5. Test that pseudosuper doesn't affect mount accoutning.
6. Failcount feature verification.

https://virtuozzo.atlassian.net/browse/VSTOR-135520

Feature: per-ve failcounters
Signed-off-by: Vladimir Riabchun <vladimir.riabchun@virtuozzo.com>
---
v4 -> v5:
 - Splitted pseudosuper test into two.
 - Added new test to check that things go smoothly when
   we run out of mounts in the middle of a new mount.

 tools/testing/selftests/ve/.gitignore         |   1 +
 tools/testing/selftests/ve/Makefile           |   1 +
 .../selftests/ve/ve_mount_accounting_test.c   | 419 ++++++++++++++++++
 3 files changed, 421 insertions(+)
 create mode 100644 tools/testing/selftests/ve/ve_mount_accounting_test.c

diff --git a/tools/testing/selftests/ve/.gitignore b/tools/testing/selftests/ve/.gitignore
index afa4c568c2c9..3df4d05888dc 100644
--- a/tools/testing/selftests/ve/.gitignore
+++ b/tools/testing/selftests/ve/.gitignore
@@ -1,2 +1,3 @@
 ve_ns_owner_test
 ve_perms_test
+ve_mount_accounting_test
diff --git a/tools/testing/selftests/ve/Makefile b/tools/testing/selftests/ve/Makefile
index ec40cbc7b3a1..c6efe7c4b4fb 100644
--- a/tools/testing/selftests/ve/Makefile
+++ b/tools/testing/selftests/ve/Makefile
@@ -4,5 +4,6 @@ CFLAGS += -g -Wall -O2
 
 TEST_GEN_PROGS += ve_ns_owner_test
 TEST_GEN_PROGS += ve_perms_test
+TEST_GEN_PROGS += ve_mount_accounting_test
 
 include ../lib.mk
diff --git a/tools/testing/selftests/ve/ve_mount_accounting_test.c b/tools/testing/selftests/ve/ve_mount_accounting_test.c
new file mode 100644
index 000000000000..b295290ec6e8
--- /dev/null
+++ b/tools/testing/selftests/ve/ve_mount_accounting_test.c
@@ -0,0 +1,419 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * ve_mount_accounting selftests
+ *
+ * Tests to check the correctness of mount accounting.
+ */
+#define _GNU_SOURCE
+#include <linux/sched.h>
+#include <linux/mount.h>
+#include <sched.h>
+#include <sys/wait.h>
+#include <sys/syscall.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"
+#include "ve_selftest.h"
+
+#define TMP_DIR			"/ve-mnt-tmp/"
+#define VE_MOUNTS_MAX		128
+
+static int set_pseudosuper(int cgv2_fd, int ctid, int value)
+{
+	char path[64];
+
+	snprintf(path, sizeof(path), "%d/ve.pseudosuper", ctid);
+	return write_u64_at(cgv2_fd, path, value);
+}
+
+static int _create_mount(void *id_ptr)
+{
+	char path[PATH_MAX];
+	int id = *(int *)id_ptr, ret;
+
+	snprintf(path, sizeof(path), TMP_DIR "%d", id);
+
+	if (mkdir(path, 0755) < 0) {
+		fprintf(stderr, "Failed to create directory %s: %s\n", path, strerror(errno));
+		return -1;
+	}
+	ret = mount("tmpfs", path, "tmpfs", 0, "size=1M");
+	if (!ret)
+		return 0;
+	fprintf(stderr, "Failed to mount tmpfs to %s: %s\n", path, strerror(errno));
+
+	rmdir(path);
+	return ret;
+}
+
+static int create_mount(int cgv2_fd, int ctid, int id)
+{
+	int ret = run_in_ve(cgv2_fd, ctid, CLONE_NEWVE, _create_mount, &id);
+	/*
+	 * If mount fails, cleanup by free_vfsmnt will be called
+	 * via call_rcu, need to wait for update.
+	 */
+	sleep(1);
+	return ret;
+}
+
+static int _destroy_mount(void *id_ptr)
+{
+	char path[PATH_MAX];
+	struct stat st;
+	int id = *(int *)id_ptr;
+
+	snprintf(path, sizeof(path), TMP_DIR "%d", id);
+
+	if (stat(path, &st))
+		return 1;
+	if (umount(path)) {
+		fprintf(stderr, "failed to umount directory %s: %s\n", path, strerror(errno));
+		return -1;
+	}
+	if (rmdir(path)) {
+		fprintf(stderr, "failed to remove directory %s: %s\n", path, strerror(errno));
+		return -1;
+	}
+	return 0;
+}
+
+static int destroy_mount(int cgv2_fd, int ctid, int id)
+{
+	int ret = run_in_ve(cgv2_fd, ctid, CLONE_NEWVE, _destroy_mount, &id);
+	/* free_vfsmnt is called via call_rcu, need to wait for update */
+	sleep(1);
+	return ret;
+}
+
+#define MAX_MNT_ID 32
+
+static int get_free_mnt_id(void)
+{
+	int i;
+	struct stat st;
+	char path[PATH_MAX];
+
+	for (i = 0; i < MAX_MNT_ID; i++) {
+		snprintf(path, sizeof(path), TMP_DIR "%d", i);
+		if (stat(path, &st))
+			return i;
+	}
+	return -1;
+}
+
+static int get_mount_cost(int cgv2_fd, int ctid)
+{
+	int avail1, avail2, mnt_id;
+	char path[64];
+
+	mnt_id = get_free_mnt_id();
+
+	snprintf(path, sizeof(path), "%d/ve.mnt_avail_nr", ctid);
+	if (mnt_id < 0 ||
+	    read_s32_at(cgv2_fd, path, &avail1) ||
+	    create_mount(cgv2_fd, ctid, mnt_id) ||
+	    read_s32_at(cgv2_fd, path, &avail2) ||
+	    destroy_mount(cgv2_fd, ctid, mnt_id))
+		return -1;
+
+	return avail1 - avail2;
+}
+
+/* Expect mount success and return new avail value */
+static int mount_and_get_avail(struct __test_metadata *_metadata,
+			int cgv2_fd, int ctid, int mnt_id)
+{
+	char path_avail[64];
+	int mnt_avail_nr;
+
+	snprintf(path_avail, sizeof(path_avail), "%d/ve.mnt_avail_nr", ctid);
+
+	ASSERT_EQ(create_mount(cgv2_fd, ctid, mnt_id), 0);
+	ASSERT_EQ(read_s32_at(cgv2_fd, path_avail, &mnt_avail_nr), 0);
+	return mnt_avail_nr;
+}
+
+/* Expect mount failure and ensure intact avail number */
+static void assert_mount_fails(struct __test_metadata *_metadata,
+			int cgv2_fd, int ctid, int mnt_id, int avail_count)
+{
+	char path_avail[64];
+	int mnt_avail_nr;
+
+	snprintf(path_avail, sizeof(path_avail), "%d/ve.mnt_avail_nr", ctid);
+
+	ASSERT_EQ(read_s32_at(cgv2_fd, path_avail, &mnt_avail_nr), 0);
+	ASSERT_EQ(mnt_avail_nr, avail_count);
+	ASSERT_LT(create_mount(cgv2_fd, ctid, mnt_id), 0);
+	ASSERT_EQ(read_s32_at(cgv2_fd, path_avail, &mnt_avail_nr), 0);
+	ASSERT_EQ(mnt_avail_nr, avail_count);
+}
+
+FIXTURE(ve_mnt_acc)
+{
+	int cgv2_fd;
+	int ctid;
+};
+
+FIXTURE_SETUP(ve_mnt_acc)
+{
+	unsigned long long initial_mnt_avail_nr;
+	char path[64];
+
+	self->cgv2_fd = mount_cg2_fd();
+	ASSERT_GE(self->cgv2_fd, 0);
+	mkdir(TMP_DIR, 0755);
+
+	ASSERT_EQ(write_file_at(self->cgv2_fd, "cgroup.subtree_control",
+		  VE_CONTROLLERS), 0);
+
+	self->ctid = make_ve(self->cgv2_fd, CTID_MIN);
+	ASSERT_GE(self->ctid, 0);
+
+	snprintf(path, sizeof(path), "%d/ve.mnt_max_nr", self->ctid);
+	ASSERT_EQ(write_u64_at(self->cgv2_fd, path, VE_MOUNTS_MAX), 0);
+
+	/*
+	 * The new ve cgroup has not been entered by anything yet, so its
+	 * mnt_avail_nr counter should be VE_MOUNTS_MAX.
+	 */
+	snprintf(path, sizeof(path), "%d/ve.mnt_avail_nr", self->ctid);
+	ASSERT_EQ(read_u64_at(self->cgv2_fd, path, &initial_mnt_avail_nr), 0);
+	ASSERT_EQ(initial_mnt_avail_nr, VE_MOUNTS_MAX);
+};
+
+FIXTURE_TEARDOWN(ve_mnt_acc)
+{
+	destroy_ve(self->cgv2_fd, self->ctid);
+	close(self->cgv2_fd);
+	rmdir(TMP_DIR);
+}
+
+/* Simple test to check mount/umount accounting correctness */
+TEST_F(ve_mnt_acc, mount_umount)
+{
+	int original_mnt_avail, mnt_avail_nr;
+	char path[64];
+
+	snprintf(path, sizeof(path), "%d/ve.mnt_avail_nr", self->ctid);
+
+	ASSERT_EQ(read_s32_at(self->cgv2_fd, path, &original_mnt_avail), 0);
+
+	ASSERT_LT(mount_and_get_avail(_metadata, self->cgv2_fd, self->ctid, 0),
+		  original_mnt_avail);
+
+	ASSERT_EQ(destroy_mount(self->cgv2_fd, self->ctid, 0), 0);
+	ASSERT_EQ(read_s32_at(self->cgv2_fd, path, &mnt_avail_nr), 0);
+	ASSERT_EQ(mnt_avail_nr, original_mnt_avail);
+}
+
+/* Test mount limit hits */
+TEST_F(ve_mnt_acc, hit_limits)
+{
+	int original_mnt_avail, mnt_avail_nr, mnt_cost;
+	int original_have_mnt;
+	char path_avail[64], path_max_nr[64];
+
+	snprintf(path_avail, sizeof(path_avail), "%d/ve.mnt_avail_nr", self->ctid);
+	snprintf(path_max_nr, sizeof(path_max_nr), "%d/ve.mnt_max_nr", self->ctid);
+
+	mnt_cost = get_mount_cost(self->cgv2_fd, self->ctid);
+	ASSERT_GE(mnt_cost, 1);
+
+	ASSERT_EQ(read_s32_at(self->cgv2_fd, path_avail, &original_mnt_avail), 0);
+	original_have_mnt = VE_MOUNTS_MAX - original_mnt_avail;
+
+	/* Step 1: reduce number of available mounts to mnt_cost */
+	ASSERT_EQ(write_u64_at(self->cgv2_fd, path_max_nr, original_have_mnt + 1 * mnt_cost), 0);
+	ASSERT_EQ(read_s32_at(self->cgv2_fd, path_avail, &mnt_avail_nr), 0);
+	ASSERT_EQ(mnt_avail_nr, 1 * mnt_cost);
+
+	/* Step 2: do one mount, no mounts should be available */
+	ASSERT_EQ(mount_and_get_avail(_metadata, self->cgv2_fd, self->ctid, 0),
+		  0);
+
+	/* Step 3: check that one more mount falils */
+	assert_mount_fails(_metadata, self->cgv2_fd, self->ctid, 1, 0);
+
+	/* Step 4: increase mount limit a little bit, mount should still fail */
+	ASSERT_EQ(write_u64_at(self->cgv2_fd, path_max_nr,
+				original_have_mnt + 2 * mnt_cost - 1), 0);
+	assert_mount_fails(_metadata, self->cgv2_fd, self->ctid, 1, mnt_cost - 1);
+
+	/* Step 5: increase by 1 and win now */
+	ASSERT_EQ(write_u64_at(self->cgv2_fd, path_max_nr, original_have_mnt + 2 * mnt_cost), 0);
+	ASSERT_EQ(read_s32_at(self->cgv2_fd, path_avail, &mnt_avail_nr), 0);
+	ASSERT_EQ(mnt_avail_nr, mnt_cost);
+
+	ASSERT_EQ(mount_and_get_avail(_metadata, self->cgv2_fd, self->ctid, 1),
+		  0);
+
+	/* Step 6: reduce mnt_max_nr so we have more mounts than allowed */
+	ASSERT_EQ(write_u64_at(self->cgv2_fd, path_max_nr, original_have_mnt + 1 * mnt_cost), 0);
+	ASSERT_EQ(read_s32_at(self->cgv2_fd, path_avail, &mnt_avail_nr), 0);
+	ASSERT_EQ(mnt_avail_nr, -1 * mnt_cost);
+
+	/* Step 7: try to do mount when avail < 0, ensure number is intact */
+	assert_mount_fails(_metadata, self->cgv2_fd, self->ctid, 2, -1 * mnt_cost);
+
+	/* Step 8: remove one mount, check avail value update, mount should fail */
+	ASSERT_EQ(destroy_mount(self->cgv2_fd, self->ctid, 0), 0);
+	assert_mount_fails(_metadata, self->cgv2_fd, self->ctid, 2, 0);
+
+	/* Step 9: remove one more mount and check that new mount succeeds */
+	ASSERT_EQ(destroy_mount(self->cgv2_fd, self->ctid, 1), 0);
+	ASSERT_EQ(read_s32_at(self->cgv2_fd, path_avail, &mnt_avail_nr), 0);
+	ASSERT_EQ(mnt_avail_nr, 1 * mnt_cost);
+	ASSERT_EQ(mount_and_get_avail(_metadata, self->cgv2_fd, self->ctid, 2),
+		  0);
+
+	ASSERT_EQ(destroy_mount(self->cgv2_fd, self->ctid, 2), 0);
+}
+
+/*
+ * Mount propagation makes one mount cost more.
+ * This test check that if we run out or mounts in the middle of creating
+ * a new one, everything is restored smoothly and nothing leaks.
+ */
+TEST_F(ve_mnt_acc, partial_mounts)
+{
+	char path_avail[64], path_max_nr[64];
+	int mount_cost, i, orig_have, orig_mnt_avail;
+
+	snprintf(path_max_nr, sizeof(path_max_nr), "%d/ve.mnt_max_nr", self->ctid);
+	snprintf(path_avail, sizeof(path_avail), "%d/ve.mnt_avail_nr", self->ctid);
+
+	mount_cost = get_mount_cost(self->cgv2_fd, self->ctid);
+	ASSERT_GE(mount_cost, 1);
+	ASSERT_EQ(read_s32_at(self->cgv2_fd, path_avail, &orig_mnt_avail), 0);
+	orig_have = VE_MOUNTS_MAX - orig_mnt_avail;
+
+	if (mount_cost == 1)
+		SKIP(return, "mount cost is 1, no partial mounts possible");
+
+	for (i = 0; i < mount_cost; i++) {
+		ASSERT_EQ(write_u64_at(self->cgv2_fd, path_max_nr, orig_have + i), 0);
+		assert_mount_fails(_metadata, self->cgv2_fd, self->ctid, 0, i);
+	}
+}
+
+/* Test that pseudosuper allows negative avail with correct accounting. */
+TEST_F(ve_mnt_acc, pseudosuper_allows_overuse)
+{
+	int orig_mnt_avail, orig_have;
+	int mount_cost;
+	char path_avail[64], path_max_nr[64];
+
+	snprintf(path_max_nr, sizeof(path_max_nr), "%d/ve.mnt_max_nr", self->ctid);
+	snprintf(path_avail, sizeof(path_avail), "%d/ve.mnt_avail_nr", self->ctid);
+	ASSERT_EQ(read_s32_at(self->cgv2_fd, path_avail, &orig_mnt_avail), 0);
+	orig_have = VE_MOUNTS_MAX - orig_mnt_avail;
+
+	mount_cost = get_mount_cost(self->cgv2_fd, self->ctid);
+	ASSERT_GE(mount_cost, 1);
+
+	ASSERT_EQ(write_u64_at(self->cgv2_fd, path_max_nr, orig_have + mount_cost), 0);
+	ASSERT_EQ(set_pseudosuper(self->cgv2_fd, self->ctid, 1), 0);
+
+	ASSERT_EQ(mount_and_get_avail(_metadata, self->cgv2_fd, self->ctid, 0),
+		  0);
+	ASSERT_EQ(mount_and_get_avail(_metadata, self->cgv2_fd, self->ctid, 1),
+		  -1 * mount_cost);
+
+	ASSERT_EQ(set_pseudosuper(self->cgv2_fd, self->ctid, 0), 0);
+
+	assert_mount_fails(_metadata, self->cgv2_fd, self->ctid, 2, -1 * mount_cost);
+	ASSERT_EQ(destroy_mount(self->cgv2_fd, self->ctid, 0), 0);
+	assert_mount_fails(_metadata, self->cgv2_fd, self->ctid, 2, 0);
+	ASSERT_EQ(destroy_mount(self->cgv2_fd, self->ctid, 1), 0);
+	ASSERT_EQ(get_mount_cost(self->cgv2_fd, self->ctid), mount_cost);
+}
+
+/* Test that pseudosuper doesn't disable accounting. */
+TEST_F(ve_mnt_acc, pseudosuper_continues_accounting)
+{
+	int orig_mnt_avail, mount_cost, mnt_avail_nr;
+	char path_avail[64];
+
+	snprintf(path_avail, sizeof(path_avail), "%d/ve.mnt_avail_nr", self->ctid);
+
+	ASSERT_EQ(read_s32_at(self->cgv2_fd, path_avail, &orig_mnt_avail), 0);
+	mount_cost = get_mount_cost(self->cgv2_fd, self->ctid);
+	ASSERT_GE(mount_cost, 1);
+
+	ASSERT_EQ(set_pseudosuper(self->cgv2_fd, self->ctid, 0), 0);
+
+	/* mnt 0 - mounted without pseudosuper, umounted with it. */
+	ASSERT_EQ(mount_and_get_avail(_metadata, self->cgv2_fd, self->ctid, 0),
+		  orig_mnt_avail - mount_cost);
+	ASSERT_EQ(set_pseudosuper(self->cgv2_fd, self->ctid, 1), 0);
+
+	/* Cost is the same when mount/umount happen under pseudosuper. */
+	ASSERT_EQ(get_mount_cost(self->cgv2_fd, self->ctid), mount_cost);
+
+	/* mnt 1 - mounted with pseudosuper, umounted without it. */
+	ASSERT_EQ(mount_and_get_avail(_metadata, self->cgv2_fd, self->ctid, 1),
+		  orig_mnt_avail - 2 * mount_cost);
+
+	ASSERT_EQ(destroy_mount(self->cgv2_fd, self->ctid, 0), 0);
+	ASSERT_EQ(read_s32_at(self->cgv2_fd, path_avail, &mnt_avail_nr), 0);
+	ASSERT_EQ(mnt_avail_nr, orig_mnt_avail - mount_cost);
+
+	ASSERT_EQ(set_pseudosuper(self->cgv2_fd, self->ctid, 0), 0);
+
+	ASSERT_EQ(destroy_mount(self->cgv2_fd, self->ctid, 1), 0);
+	ASSERT_EQ(read_s32_at(self->cgv2_fd, path_avail, &mnt_avail_nr), 0);
+	ASSERT_EQ(mnt_avail_nr, orig_mnt_avail);
+}
+
+/* Test failcount feature */
+TEST_F(ve_mnt_acc, failcount)
+{
+	char path_fc[64], failcount_str[512], path_max_nr[64];
+
+	snprintf(path_fc, sizeof(path_fc), "%d/ve.failcount", self->ctid);
+	snprintf(path_max_nr, sizeof(path_max_nr), "%d/ve.mnt_max_nr", self->ctid);
+
+	ASSERT_GE(read_file_at(self->cgv2_fd, path_fc,
+			       failcount_str, sizeof(failcount_str)), 0);
+	ASSERT_TRUE(strstr(failcount_str, "mnt: 0\n") != NULL);
+
+	/* Check successful mount doesn't affect failcount */
+	mount_and_get_avail(_metadata, self->cgv2_fd, self->ctid, 0);
+	ASSERT_GE(read_file_at(self->cgv2_fd, path_fc,
+			       failcount_str, sizeof(failcount_str)), 0);
+	ASSERT_TRUE(strstr(failcount_str, "mnt: 0\n") != NULL);
+	ASSERT_EQ(destroy_mount(self->cgv2_fd, self->ctid, 0), 0);
+
+	/* Check failcount update when mount fails */
+	ASSERT_EQ(write_u64_at(self->cgv2_fd, path_max_nr, 0), 0);
+	ASSERT_LT(create_mount(self->cgv2_fd, self->ctid, 1), 0);
+	ASSERT_GE(read_file_at(self->cgv2_fd, path_fc,
+			       failcount_str, sizeof(failcount_str)), 0);
+	ASSERT_TRUE(strstr(failcount_str, "mnt: 1\n") != NULL);
+
+	/* Check failcount flush */
+	ASSERT_EQ(write_u64_at(self->cgv2_fd, path_fc, 0), 0);
+	ASSERT_GE(read_file_at(self->cgv2_fd, path_fc,
+			       failcount_str, sizeof(failcount_str)), 0);
+	ASSERT_TRUE(strstr(failcount_str, "mnt: 0\n") != NULL);
+
+	/* Check failcount update when mount fails again */
+	ASSERT_EQ(write_u64_at(self->cgv2_fd, path_max_nr, 0), 0);
+	ASSERT_LT(create_mount(self->cgv2_fd, self->ctid, 1), 0);
+	ASSERT_GE(read_file_at(self->cgv2_fd, path_fc,
+			       failcount_str, sizeof(failcount_str)), 0);
+	ASSERT_TRUE(strstr(failcount_str, "mnt: 1\n") != NULL);
+}
+
+TEST_HARNESS_MAIN
-- 
2.47.1


^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [Devel] [PATCH VZ10 v5 7/9] ve: Introduce per-VE failcount
  2026-08-02 11:40 ` [Devel] [PATCH VZ10 v5 7/9] ve: Introduce per-VE failcount Vladimir Riabchun
@ 2026-08-07  9:25   ` Vasileios Almpanis
  2026-08-17 11:54   ` Vasileios Almpanis
  1 sibling, 0 replies; 16+ messages in thread
From: Vasileios Almpanis @ 2026-08-07  9:25 UTC (permalink / raw)



On 8/2/26 1:40 PM, Vladimir Riabchun wrote:
> It may be useful to have a history of resource limit hits for every VE,
> this may simplify debugging and provide some information about the
> resources usage.
>
> This information is provided by ve.failcount file, any write to it
> resets all failcounts.
>
> To add a new failcounter we need to create a new atomic_t field
> name_failcount in ve structure and add a new VE_FC_ENTRY in
> ve_failcounts array.
>
> One change, unrelated to failcounts: aio fields are now initialized
> in ve0.
>
> https://virtuozzo.atlassian.net/browse/VSTOR-135520
>
> Feature: per-ve failcounters
> Signed-off-by: Vladimir Riabchun <vladimir.riabchun@virtuozzo.com>
> ---
>   fs/aio.c                 |  1 +
>   fs/namespace.c           |  2 ++
>   include/linux/ve.h       |  6 ++++
>   kernel/bpf/syscall.c     |  1 +
>   kernel/ve/ve.c           | 67 ++++++++++++++++++++++++++++++++++++++++
>   net/core/dev.c           |  2 ++
>   net/core/neighbour.c     |  1 +
>   net/core/net_namespace.c |  4 ++-
>   8 files changed, 83 insertions(+), 1 deletion(-)
>
> diff --git a/fs/aio.c b/fs/aio.c
> index cb63416af135..3fa07cc626f8 100644
> --- a/fs/aio.c
> +++ b/fs/aio.c
> @@ -814,6 +814,7 @@ static struct kioctx *ioctx_alloc(unsigned nr_events)
>   	spin_lock(&ve->aio_nr_lock);
>   	if (ve->aio_nr + ctx->max_reqs > ve->aio_max_nr ||
>   	    ve->aio_nr + ctx->max_reqs < ve->aio_nr) {
> +		atomic_inc(&ve->aio_failcount);
>   		spin_unlock(&ve->aio_nr_lock);
>   		err = -EAGAIN;
>   		goto err_ctx;
> diff --git a/fs/namespace.c b/fs/namespace.c
> index 2550aeba2f1e..c4e2c7f7f725 100644
> --- a/fs/namespace.c
> +++ b/fs/namespace.c
> @@ -3283,6 +3283,8 @@ static inline int ve_try_reserve_mount(struct ve_struct *ve)
>   
>   	if (ret)
>   		get_ve(ve);
> +	else
> +		atomic_inc(&ve->mnt_failcount);
>   	return ret;
>   }
>   
> diff --git a/include/linux/ve.h b/include/linux/ve.h
> index 5687faad46ff..9e73527e970e 100644
> --- a/include/linux/ve.h
> +++ b/include/linux/ve.h
> @@ -72,12 +72,15 @@ struct ve_struct {
>   	struct kmapset_key	proc_perms_key;
>   
>   	atomic_t		netns_avail_nr;
> +	atomic_t		netns_failcount;
>   	int			netns_max_nr;
>   
>   	atomic_t		netif_avail_nr;
> +	atomic_t		netif_failcount;
>   	int			netif_max_nr;
>   
>   	atomic_t		bpf_prog_avail_nr;
> +	atomic_t		bpf_prog_failcount;
>   	int			bpf_prog_max_nr;
>   
>   	atomic64_t		_uevent_seqnum;
> @@ -86,6 +89,7 @@ struct ve_struct {
>   
>   	atomic_t		arp_neigh_nr;
>   	atomic_t		nd_neigh_nr;
> +	atomic_t		neigh_tbl_failcount;
>   	unsigned long		meminfo_val;
>   
>   	/*
> @@ -94,6 +98,7 @@ struct ve_struct {
>   	 * other containers.
>   	 */
>   	atomic_t		mnt_avail_nr; /* number of available VE mounts */
> +	atomic_t		mnt_failcount;
>   	int			mnt_max_nr;
>   
>   #ifdef CONFIG_COREDUMP
> @@ -121,6 +126,7 @@ struct ve_struct {
>   	spinlock_t		aio_nr_lock;
>   	unsigned long		aio_nr;
>   	unsigned long		aio_max_nr;
> +	atomic_t		aio_failcount;
>   #endif
>   	struct vfsmount		*devtmpfs_mnt;
>   };
> diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c
> index ff2a51c59f04..95e806fa19f4 100644
> --- a/kernel/bpf/syscall.c
> +++ b/kernel/bpf/syscall.c
> @@ -2891,6 +2891,7 @@ static int bpf_prog_load(union bpf_attr *attr, bpfptr_t uattr, u32 uattr_size)
>   	if (!bpf_cap && type == BPF_PROG_TYPE_CGROUP_DEVICE) {
>   		load_ve = get_exec_env();
>   		if (atomic_dec_if_positive(&load_ve->bpf_prog_avail_nr) < 0) {
> +			atomic_inc(&load_ve->bpf_prog_failcount);
>   			load_ve = NULL;
>   			err = -ENOSPC;
>   			goto put_token;
> diff --git a/kernel/ve/ve.c b/kernel/ve/ve.c
> index dc3faa0b1d76..a5c93be759ac 100644
> --- a/kernel/ve/ve.c
> +++ b/kernel/ve/ve.c
> @@ -99,10 +99,13 @@ struct ve_struct ve0 = {
>   	.features		= -1,
>   	.sched_lat_ve.cur	= &ve0_lat_stats,
>   	.netns_avail_nr		= ATOMIC_INIT(INT_MAX),
> +	.netns_failcount	= ATOMIC_INIT(0),
>   	.netns_max_nr		= INT_MAX,
>   	.netif_avail_nr		= ATOMIC_INIT(INT_MAX),
> +	.netif_failcount	= ATOMIC_INIT(0),
>   	.netif_max_nr		= INT_MAX,
>   	.bpf_prog_avail_nr	= ATOMIC_INIT(INT_MAX),
> +	.bpf_prog_failcount	= ATOMIC_INIT(0),
>   	.bpf_prog_max_nr	= INT_MAX,
>   	.fsync_enable		= FSYNC_FILTERED,
>   	._randomize_va_space	=
> @@ -114,8 +117,16 @@ struct ve_struct ve0 = {
>   
>   	.arp_neigh_nr		= ATOMIC_INIT(0),
>   	.nd_neigh_nr		= ATOMIC_INIT(0),
> +	.neigh_tbl_failcount	= ATOMIC_INIT(0),
>   	.mnt_avail_nr		= ATOMIC_INIT(INT_MAX),
>   	.mnt_max_nr		= INT_MAX,
> +	.mnt_failcount		= ATOMIC_INIT(0),
> +#ifdef CONFIG_AIO
> +	.aio_nr_lock		= __SPIN_LOCK_UNLOCKED(aio_nr_lock),
> +	.aio_nr			= 0,
> +	.aio_max_nr		= AIO_MAX_NR_DEFAULT,
> +	.aio_failcount		= ATOMIC_INIT(0),
> +#endif
>   	.meminfo_val		= VE_MEMINFO_SYSTEM,
>   	.umh_running_helpers	= ATOMIC_INIT(0),
>   	.umh_helpers_waitq	= __WAIT_QUEUE_HEAD_INITIALIZER(ve0.umh_helpers_waitq),
> @@ -780,12 +791,15 @@ static struct cgroup_subsys_state *ve_create(struct cgroup_subsys_state *parent_
>   	ve->fsync_enable = FSYNC_FILTERED;
>   
>   	atomic_set(&ve->netns_avail_nr, NETNS_MAX_NR_DEFAULT);
> +	atomic_set(&ve->netns_failcount, 0);
>   	ve->netns_max_nr = NETNS_MAX_NR_DEFAULT;
>   
>   	atomic_set(&ve->netif_avail_nr, NETIF_MAX_NR_DEFAULT);
> +	atomic_set(&ve->netif_failcount, 0);
>   	ve->netif_max_nr = NETIF_MAX_NR_DEFAULT;
>   
>   	atomic_set(&ve->bpf_prog_avail_nr, BPF_PROG_MAX_NR_DEFAULT);
> +	atomic_set(&ve->bpf_prog_failcount, 0);
>   	ve->bpf_prog_max_nr = BPF_PROG_MAX_NR_DEFAULT;
>   
>   	err = ve_log_init(ve);
> @@ -812,7 +826,9 @@ static struct cgroup_subsys_state *ve_create(struct cgroup_subsys_state *parent_
>   
>   	atomic_set(&ve->arp_neigh_nr, 0);
>   	atomic_set(&ve->nd_neigh_nr, 0);
> +	atomic_set(&ve->neigh_tbl_failcount, 0);
>   	ve->mnt_max_nr = MNT_MAX_NR_DEFAULT;
> +	atomic_set(&ve->mnt_failcount, 0);
>   	atomic_set(&ve->mnt_avail_nr, MNT_MAX_NR_DEFAULT);
>   
>   #ifdef CONFIG_COREDUMP
> @@ -825,6 +841,7 @@ static struct cgroup_subsys_state *ve_create(struct cgroup_subsys_state *parent_
>   	spin_lock_init(&ve->aio_nr_lock);
>   	ve->aio_nr = 0;
>   	ve->aio_max_nr = AIO_MAX_NR_DEFAULT;
> +	atomic_set(&ve->aio_failcount, 0);
>   #endif
>   
>   	return &ve->css;
> @@ -1066,6 +1083,50 @@ VE_RESOURCE(mnt);
>   VE_RESOURCE(netif);
>   VE_RESOURCE(bpf_prog);
>   
> +static const struct ve_failcount_entry {
> +	const char *name;
> +	size_t offset;
> +} ve_failcounts[] = {
> +#define VE_FC_ENTRY(name) { #name, offsetof(struct ve_struct, name##_failcount) }
> +	VE_FC_ENTRY(netns),
> +	VE_FC_ENTRY(mnt),
> +	VE_FC_ENTRY(netif),
> +	VE_FC_ENTRY(bpf_prog),
> +	VE_FC_ENTRY(neigh_tbl),
> +#ifdef CONFIG_AIO
> +	VE_FC_ENTRY(aio),
> +#endif
> +	{}
> +};
> +
> +static int ve_failcount_read(struct seq_file *sf, void *v)
> +{
> +	struct ve_struct *ve = css_to_ve(seq_css(sf));
> +	struct ve_failcount_entry *entry;
> +	atomic_t *fc;
> +
kernel/ve/ve.c: In function ?ve_failcount_read?:
kernel/ve/ve.c:1107:20: warning: assignment discards ?const? qualifier 
from pointer target type [-Wdiscarded-qualifiers]
 ?1107 |? ? ? ? ?for (entry = ve_failcounts; entry->name; entry++) {
 ? ? ? |? ? ? ? ? ? ? ? ? ? ^
kernel/ve/ve.c: In function ?ve_failcount_write?:
kernel/ve/ve.c:1121:20: warning: assignment discards ?const? qualifier 
from pointer target type [-Wdiscarded-qualifiers]
 ?1121 |? ? ? ? ?for (entry = ve_failcounts; entry->name; entry++) {

the left-hand pointee must carry all qualifiers of the right-hand pointee according to C11


> +	for (entry = ve_failcounts; entry->name; entry++) {
> +		fc = (void *)ve + entry->offset;
> +		seq_printf(sf, "%s: %d\n", entry->name, atomic_read(fc));
> +	}
> +	return 0;
> +}
> +
> +static ssize_t ve_failcount_write(struct kernfs_open_file *of, char *buf,
> +				  size_t nbytes, loff_t off)
> +{
> +	struct ve_struct *ve = css_to_ve(of_css(of));
> +	struct ve_failcount_entry *entry;
> +	atomic_t *fc;
> +
> +	for (entry = ve_failcounts; entry->name; entry++) {
> +		fc = (void *)ve + entry->offset;
> +		atomic_set(fc, 0);
> +	}
> +
> +	return nbytes;
> +}
> +
>   static int ve_os_release_read(struct seq_file *sf, void *v)
>   {
>   	struct cgroup_subsys_state *css = seq_css(sf);
> @@ -1603,6 +1664,12 @@ static struct cftype ve_cftypes[] = {
>   		.flags			= CFTYPE_NOT_ON_ROOT,
>   		.write_u64		= ve_rpc_kill_write,
>   	},
> +	{
> +		.name			= "failcount",
> +		.flags			= CFTYPE_NOT_ON_ROOT,
> +		.seq_show		= ve_failcount_read,
> +		.write			= ve_failcount_write,
> +	},
>   	{ }
>   };
>   
> diff --git a/net/core/dev.c b/net/core/dev.c
> index c7dddb200489..05e0b9b6ba23 100644
> --- a/net/core/dev.c
> +++ b/net/core/dev.c
> @@ -10997,6 +10997,7 @@ int register_netdevice(struct net_device *dev)
>   
>   	ret = -ENOMEM;
>   	if (atomic_dec_if_positive(&net->owner_ve->netif_avail_nr) < 0) {
> +		atomic_inc(&net->owner_ve->netif_failcount);
>   		ve_pr_warn_ratelimited(VE_LOG_BOTH,
>   			"CT%s: hits max number of network devices, "
>   			"increase ve::netif_max_nr parameter\n",
> @@ -12211,6 +12212,7 @@ int __dev_change_net_namespace(struct net_device *dev, struct net *net,
>   
>   	err = -ENOMEM;
>   	if (atomic_dec_if_positive(&net->owner_ve->netif_avail_nr) < 0) {
> +		atomic_inc(&net->owner_ve->netif_failcount);
>   		ve_pr_warn_ratelimited(VE_LOG_BOTH,
>   			"CT%s: hits max number of network devices, "
>   			"increase ve::netif_max_nr parameter\n",
> diff --git a/net/core/neighbour.c b/net/core/neighbour.c
> index f90deb17fb25..57a49d9c98a7 100644
> --- a/net/core/neighbour.c
> +++ b/net/core/neighbour.c
> @@ -520,6 +520,7 @@ static struct neighbour *neigh_alloc(struct neigh_table *tbl,
>   	    (glob_entries >= READ_ONCE(tbl->gc_thresh2) &&
>   	     time_after(now, READ_ONCE(tbl->last_flush) + 5 * HZ))) {
>   		if (!neigh_forced_gc(tbl, ve) && entries >= gc_thresh3) {
> +			atomic_inc(&ve->neigh_tbl_failcount);
>   			net_info_ratelimited("%s: neighbor table overflow!\n",
>   					     tbl->id);
>   			NEIGH_CACHE_STAT_INC(tbl, table_fulls);
> diff --git a/net/core/net_namespace.c b/net/core/net_namespace.c
> index b3d54cad984a..9a3376d2682f 100644
> --- a/net/core/net_namespace.c
> +++ b/net/core/net_namespace.c
> @@ -486,8 +486,10 @@ void net_drop_ns(void *p)
>   #ifdef CONFIG_VE
>   static int dec_netns_avail(struct ve_struct *ve)
>   {
> -	if (atomic_dec_if_positive(&ve->netns_avail_nr) < 0)
> +	if (atomic_dec_if_positive(&ve->netns_avail_nr) < 0) {
> +		atomic_inc(&ve->netns_failcount);
>   		return -ENOSPC;
> +	}
>   	return 0;
>   }
>   

-- 
Best regards, Vasileios Almpanis
Software Developer, Virtuozzo.


^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [Devel] [PATCH VZ10 v5 9/9] selftests/ve: Add mount accounting selftest
  2026-08-02 11:40 ` [Devel] [PATCH VZ10 v5 9/9] selftests/ve: Add mount accounting selftest Vladimir Riabchun
@ 2026-08-07 10:01   ` Vasileios Almpanis
  2026-08-17 11:54   ` Vasileios Almpanis
  1 sibling, 0 replies; 16+ messages in thread
From: Vasileios Almpanis @ 2026-08-07 10:01 UTC (permalink / raw)



On 8/2/26 1:40 PM, Vladimir Riabchun wrote:
> There are 6 test cases, covered in the new test:
> 1. Simple mount accouting correctness, just mount/umount.
> 2. Verification of correct limit hits and changes, including
>     negative values.
> 3. Patial mounts test, when mount limit is hit in the middle
>     of creation.
nit: Partial
> 4. Test that enabled pseudosuper allows overuse.
> 5. Test that pseudosuper doesn't affect mount accoutning.
nit: accounting
> 6. Failcount feature verification.
>
> https://virtuozzo.atlassian.net/browse/VSTOR-135520
>
> Feature: per-ve failcounters
> Signed-off-by: Vladimir Riabchun <vladimir.riabchun@virtuozzo.com>
> ---
> v4 -> v5:
>   - Splitted pseudosuper test into two.
>   - Added new test to check that things go smoothly when
>     we run out of mounts in the middle of a new mount.
>
>   tools/testing/selftests/ve/.gitignore         |   1 +
>   tools/testing/selftests/ve/Makefile           |   1 +
>   .../selftests/ve/ve_mount_accounting_test.c   | 419 ++++++++++++++++++
>   3 files changed, 421 insertions(+)
>   create mode 100644 tools/testing/selftests/ve/ve_mount_accounting_test.c
>
> diff --git a/tools/testing/selftests/ve/.gitignore b/tools/testing/selftests/ve/.gitignore
> index afa4c568c2c9..3df4d05888dc 100644
> --- a/tools/testing/selftests/ve/.gitignore
> +++ b/tools/testing/selftests/ve/.gitignore
> @@ -1,2 +1,3 @@
>   ve_ns_owner_test
>   ve_perms_test
> +ve_mount_accounting_test
> diff --git a/tools/testing/selftests/ve/Makefile b/tools/testing/selftests/ve/Makefile
> index ec40cbc7b3a1..c6efe7c4b4fb 100644
> --- a/tools/testing/selftests/ve/Makefile
> +++ b/tools/testing/selftests/ve/Makefile
> @@ -4,5 +4,6 @@ CFLAGS += -g -Wall -O2
>   
>   TEST_GEN_PROGS += ve_ns_owner_test
>   TEST_GEN_PROGS += ve_perms_test
> +TEST_GEN_PROGS += ve_mount_accounting_test
>   
>   include ../lib.mk
> diff --git a/tools/testing/selftests/ve/ve_mount_accounting_test.c b/tools/testing/selftests/ve/ve_mount_accounting_test.c
> new file mode 100644
> index 000000000000..b295290ec6e8
> --- /dev/null
> +++ b/tools/testing/selftests/ve/ve_mount_accounting_test.c
> @@ -0,0 +1,419 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * ve_mount_accounting selftests
> + *
> + * Tests to check the correctness of mount accounting.
> + */
> +#define _GNU_SOURCE
> +#include <linux/sched.h>
> +#include <linux/mount.h>
Is linux/mount needed here? You include both linux/mount and sys/mount.
You only call mount,umount so dropping it looks generally safe.
> +#include <sched.h>
> +#include <sys/wait.h>
> +#include <sys/syscall.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"
> +#include "ve_selftest.h"
> +
> +#define TMP_DIR			"/ve-mnt-tmp/"
> +#define VE_MOUNTS_MAX		128
> +
> +static int set_pseudosuper(int cgv2_fd, int ctid, int value)
> +{
> +	char path[64];
> +
> +	snprintf(path, sizeof(path), "%d/ve.pseudosuper", ctid);
> +	return write_u64_at(cgv2_fd, path, value);
> +}
> +
> +static int _create_mount(void *id_ptr)
> +{
> +	char path[PATH_MAX];
> +	int id = *(int *)id_ptr, ret;
> +
> +	snprintf(path, sizeof(path), TMP_DIR "%d", id);
> +
> +	if (mkdir(path, 0755) < 0) {
> +		fprintf(stderr, "Failed to create directory %s: %s\n", path, strerror(errno));
> +		return -1;
> +	}
> +	ret = mount("tmpfs", path, "tmpfs", 0, "size=1M");
> +	if (!ret)
> +		return 0;
> +	fprintf(stderr, "Failed to mount tmpfs to %s: %s\n", path, strerror(errno));
> +
> +	rmdir(path);
> +	return ret;
> +}
> +
> +static int create_mount(int cgv2_fd, int ctid, int id)
> +{
> +	int ret = run_in_ve(cgv2_fd, ctid, CLONE_NEWVE, _create_mount, &id);
> +	/*
> +	 * If mount fails, cleanup by free_vfsmnt will be called
> +	 * via call_rcu, need to wait for update.
> +	 */
> +	sleep(1);
> +	return ret;
> +}
> +
> +static int _destroy_mount(void *id_ptr)
> +{
> +	char path[PATH_MAX];
> +	struct stat st;
> +	int id = *(int *)id_ptr;
> +
> +	snprintf(path, sizeof(path), TMP_DIR "%d", id);
> +
> +	if (stat(path, &st))
> +		return 1;
> +	if (umount(path)) {
> +		fprintf(stderr, "failed to umount directory %s: %s\n", path, strerror(errno));
> +		return -1;
> +	}
> +	if (rmdir(path)) {
> +		fprintf(stderr, "failed to remove directory %s: %s\n", path, strerror(errno));
> +		return -1;
> +	}
> +	return 0;
> +}
> +
> +static int destroy_mount(int cgv2_fd, int ctid, int id)
> +{
> +	int ret = run_in_ve(cgv2_fd, ctid, CLONE_NEWVE, _destroy_mount, &id);
> +	/* free_vfsmnt is called via call_rcu, need to wait for update */
> +	sleep(1);
> +	return ret;
> +}
> +
> +#define MAX_MNT_ID 32
> +
> +static int get_free_mnt_id(void)
> +{
> +	int i;
> +	struct stat st;
> +	char path[PATH_MAX];
> +
> +	for (i = 0; i < MAX_MNT_ID; i++) {
> +		snprintf(path, sizeof(path), TMP_DIR "%d", i);
> +		if (stat(path, &st))
> +			return i;
> +	}
> +	return -1;
> +}
> +
> +static int get_mount_cost(int cgv2_fd, int ctid)
> +{
> +	int avail1, avail2, mnt_id;
> +	char path[64];
> +
> +	mnt_id = get_free_mnt_id();
> +
> +	snprintf(path, sizeof(path), "%d/ve.mnt_avail_nr", ctid);
> +	if (mnt_id < 0 ||
> +	    read_s32_at(cgv2_fd, path, &avail1) ||
> +	    create_mount(cgv2_fd, ctid, mnt_id) ||
> +	    read_s32_at(cgv2_fd, path, &avail2) ||
> +	    destroy_mount(cgv2_fd, ctid, mnt_id))
> +		return -1;
> +
> +	return avail1 - avail2;
> +}
> +
> +/* Expect mount success and return new avail value */
> +static int mount_and_get_avail(struct __test_metadata *_metadata,
> +			int cgv2_fd, int ctid, int mnt_id)
> +{
> +	char path_avail[64];
> +	int mnt_avail_nr;
> +
> +	snprintf(path_avail, sizeof(path_avail), "%d/ve.mnt_avail_nr", ctid);
> +
> +	ASSERT_EQ(create_mount(cgv2_fd, ctid, mnt_id), 0);
> +	ASSERT_EQ(read_s32_at(cgv2_fd, path_avail, &mnt_avail_nr), 0);
> +	return mnt_avail_nr;
> +}
> +
> +/* Expect mount failure and ensure intact avail number */
> +static void assert_mount_fails(struct __test_metadata *_metadata,
> +			int cgv2_fd, int ctid, int mnt_id, int avail_count)
> +{
> +	char path_avail[64];
> +	int mnt_avail_nr;
> +
> +	snprintf(path_avail, sizeof(path_avail), "%d/ve.mnt_avail_nr", ctid);
> +
> +	ASSERT_EQ(read_s32_at(cgv2_fd, path_avail, &mnt_avail_nr), 0);
> +	ASSERT_EQ(mnt_avail_nr, avail_count);
> +	ASSERT_LT(create_mount(cgv2_fd, ctid, mnt_id), 0);
> +	ASSERT_EQ(read_s32_at(cgv2_fd, path_avail, &mnt_avail_nr), 0);
> +	ASSERT_EQ(mnt_avail_nr, avail_count);
> +}
> +
> +FIXTURE(ve_mnt_acc)
> +{
> +	int cgv2_fd;
> +	int ctid;
> +};
> +
> +FIXTURE_SETUP(ve_mnt_acc)
> +{
> +	unsigned long long initial_mnt_avail_nr;
> +	char path[64];
> +
> +	self->cgv2_fd = mount_cg2_fd();
> +	ASSERT_GE(self->cgv2_fd, 0);
> +	mkdir(TMP_DIR, 0755);
> +
> +	ASSERT_EQ(write_file_at(self->cgv2_fd, "cgroup.subtree_control",
> +		  VE_CONTROLLERS), 0);
> +
> +	self->ctid = make_ve(self->cgv2_fd, CTID_MIN);
> +	ASSERT_GE(self->ctid, 0);
> +
> +	snprintf(path, sizeof(path), "%d/ve.mnt_max_nr", self->ctid);
> +	ASSERT_EQ(write_u64_at(self->cgv2_fd, path, VE_MOUNTS_MAX), 0);
> +
> +	/*
> +	 * The new ve cgroup has not been entered by anything yet, so its
> +	 * mnt_avail_nr counter should be VE_MOUNTS_MAX.
> +	 */
> +	snprintf(path, sizeof(path), "%d/ve.mnt_avail_nr", self->ctid);
> +	ASSERT_EQ(read_u64_at(self->cgv2_fd, path, &initial_mnt_avail_nr), 0);
> +	ASSERT_EQ(initial_mnt_avail_nr, VE_MOUNTS_MAX);
> +};
> +
> +FIXTURE_TEARDOWN(ve_mnt_acc)
> +{
Since we mount tmpfs on host mount namespace (we dont pass CLONE_NEWNS), 
should we iterate here and umount all the ids that remain mounted after 
tests bail? There are maybe places where create_mount is tried and if 
assertion fails the mount remains and leaks to the host possibly also 
pinning the ve namespace since in ve_try_reserve_mount we get a refcount 
on it.

> +	destroy_ve(self->cgv2_fd, self->ctid);
> +	close(self->cgv2_fd);
> +	rmdir(TMP_DIR);
> +}
> +
> +/* Simple test to check mount/umount accounting correctness */
> +TEST_F(ve_mnt_acc, mount_umount)
> +{
> +	int original_mnt_avail, mnt_avail_nr;
> +	char path[64];
> +
> +	snprintf(path, sizeof(path), "%d/ve.mnt_avail_nr", self->ctid);
> +
> +	ASSERT_EQ(read_s32_at(self->cgv2_fd, path, &original_mnt_avail), 0);
> +
> +	ASSERT_LT(mount_and_get_avail(_metadata, self->cgv2_fd, self->ctid, 0),
> +		  original_mnt_avail);
> +
> +	ASSERT_EQ(destroy_mount(self->cgv2_fd, self->ctid, 0), 0);
> +	ASSERT_EQ(read_s32_at(self->cgv2_fd, path, &mnt_avail_nr), 0);
> +	ASSERT_EQ(mnt_avail_nr, original_mnt_avail);
> +}
> +
> +/* Test mount limit hits */
> +TEST_F(ve_mnt_acc, hit_limits)
> +{
> +	int original_mnt_avail, mnt_avail_nr, mnt_cost;
> +	int original_have_mnt;
> +	char path_avail[64], path_max_nr[64];
> +
> +	snprintf(path_avail, sizeof(path_avail), "%d/ve.mnt_avail_nr", self->ctid);
> +	snprintf(path_max_nr, sizeof(path_max_nr), "%d/ve.mnt_max_nr", self->ctid);
> +
> +	mnt_cost = get_mount_cost(self->cgv2_fd, self->ctid);
> +	ASSERT_GE(mnt_cost, 1);
> +
> +	ASSERT_EQ(read_s32_at(self->cgv2_fd, path_avail, &original_mnt_avail), 0);
> +	original_have_mnt = VE_MOUNTS_MAX - original_mnt_avail;
> +
> +	/* Step 1: reduce number of available mounts to mnt_cost */
> +	ASSERT_EQ(write_u64_at(self->cgv2_fd, path_max_nr, original_have_mnt + 1 * mnt_cost), 0);
> +	ASSERT_EQ(read_s32_at(self->cgv2_fd, path_avail, &mnt_avail_nr), 0);
> +	ASSERT_EQ(mnt_avail_nr, 1 * mnt_cost);
> +
> +	/* Step 2: do one mount, no mounts should be available */
> +	ASSERT_EQ(mount_and_get_avail(_metadata, self->cgv2_fd, self->ctid, 0),
> +		  0);
> +
> +	/* Step 3: check that one more mount falils */
> +	assert_mount_fails(_metadata, self->cgv2_fd, self->ctid, 1, 0);
> +
> +	/* Step 4: increase mount limit a little bit, mount should still fail */
> +	ASSERT_EQ(write_u64_at(self->cgv2_fd, path_max_nr,
> +				original_have_mnt + 2 * mnt_cost - 1), 0);
> +	assert_mount_fails(_metadata, self->cgv2_fd, self->ctid, 1, mnt_cost - 1);
> +
> +	/* Step 5: increase by 1 and win now */
> +	ASSERT_EQ(write_u64_at(self->cgv2_fd, path_max_nr, original_have_mnt + 2 * mnt_cost), 0);
> +	ASSERT_EQ(read_s32_at(self->cgv2_fd, path_avail, &mnt_avail_nr), 0);
> +	ASSERT_EQ(mnt_avail_nr, mnt_cost);
> +
> +	ASSERT_EQ(mount_and_get_avail(_metadata, self->cgv2_fd, self->ctid, 1),
> +		  0);
> +
> +	/* Step 6: reduce mnt_max_nr so we have more mounts than allowed */
> +	ASSERT_EQ(write_u64_at(self->cgv2_fd, path_max_nr, original_have_mnt + 1 * mnt_cost), 0);
> +	ASSERT_EQ(read_s32_at(self->cgv2_fd, path_avail, &mnt_avail_nr), 0);
> +	ASSERT_EQ(mnt_avail_nr, -1 * mnt_cost);
> +
> +	/* Step 7: try to do mount when avail < 0, ensure number is intact */
> +	assert_mount_fails(_metadata, self->cgv2_fd, self->ctid, 2, -1 * mnt_cost);
> +
> +	/* Step 8: remove one mount, check avail value update, mount should fail */
> +	ASSERT_EQ(destroy_mount(self->cgv2_fd, self->ctid, 0), 0);
> +	assert_mount_fails(_metadata, self->cgv2_fd, self->ctid, 2, 0);
> +
> +	/* Step 9: remove one more mount and check that new mount succeeds */
> +	ASSERT_EQ(destroy_mount(self->cgv2_fd, self->ctid, 1), 0);
> +	ASSERT_EQ(read_s32_at(self->cgv2_fd, path_avail, &mnt_avail_nr), 0);
> +	ASSERT_EQ(mnt_avail_nr, 1 * mnt_cost);
> +	ASSERT_EQ(mount_and_get_avail(_metadata, self->cgv2_fd, self->ctid, 2),
> +		  0);
> +
> +	ASSERT_EQ(destroy_mount(self->cgv2_fd, self->ctid, 2), 0);
> +}
> +
> +/*
> + * Mount propagation makes one mount cost more.
> + * This test check that if we run out or mounts in the middle of creating
> + * a new one, everything is restored smoothly and nothing leaks.
> + */
> +TEST_F(ve_mnt_acc, partial_mounts)
> +{
> +	char path_avail[64], path_max_nr[64];
> +	int mount_cost, i, orig_have, orig_mnt_avail;
> +
> +	snprintf(path_max_nr, sizeof(path_max_nr), "%d/ve.mnt_max_nr", self->ctid);
> +	snprintf(path_avail, sizeof(path_avail), "%d/ve.mnt_avail_nr", self->ctid);
> +
> +	mount_cost = get_mount_cost(self->cgv2_fd, self->ctid);
> +	ASSERT_GE(mount_cost, 1);
> +	ASSERT_EQ(read_s32_at(self->cgv2_fd, path_avail, &orig_mnt_avail), 0);
> +	orig_have = VE_MOUNTS_MAX - orig_mnt_avail;
> +
> +	if (mount_cost == 1)
> +		SKIP(return, "mount cost is 1, no partial mounts possible");
> +
> +	for (i = 0; i < mount_cost; i++) {
> +		ASSERT_EQ(write_u64_at(self->cgv2_fd, path_max_nr, orig_have + i), 0);
> +		assert_mount_fails(_metadata, self->cgv2_fd, self->ctid, 0, i);
> +	}
> +}
> +
> +/* Test that pseudosuper allows negative avail with correct accounting. */
> +TEST_F(ve_mnt_acc, pseudosuper_allows_overuse)
> +{
> +	int orig_mnt_avail, orig_have;
> +	int mount_cost;
> +	char path_avail[64], path_max_nr[64];
> +
> +	snprintf(path_max_nr, sizeof(path_max_nr), "%d/ve.mnt_max_nr", self->ctid);
> +	snprintf(path_avail, sizeof(path_avail), "%d/ve.mnt_avail_nr", self->ctid);
> +	ASSERT_EQ(read_s32_at(self->cgv2_fd, path_avail, &orig_mnt_avail), 0);
> +	orig_have = VE_MOUNTS_MAX - orig_mnt_avail;
> +
> +	mount_cost = get_mount_cost(self->cgv2_fd, self->ctid);
> +	ASSERT_GE(mount_cost, 1);
> +
> +	ASSERT_EQ(write_u64_at(self->cgv2_fd, path_max_nr, orig_have + mount_cost), 0);
> +	ASSERT_EQ(set_pseudosuper(self->cgv2_fd, self->ctid, 1), 0);
> +
> +	ASSERT_EQ(mount_and_get_avail(_metadata, self->cgv2_fd, self->ctid, 0),
> +		  0);
> +	ASSERT_EQ(mount_and_get_avail(_metadata, self->cgv2_fd, self->ctid, 1),
> +		  -1 * mount_cost);
> +
> +	ASSERT_EQ(set_pseudosuper(self->cgv2_fd, self->ctid, 0), 0);
> +
> +	assert_mount_fails(_metadata, self->cgv2_fd, self->ctid, 2, -1 * mount_cost);
> +	ASSERT_EQ(destroy_mount(self->cgv2_fd, self->ctid, 0), 0);
> +	assert_mount_fails(_metadata, self->cgv2_fd, self->ctid, 2, 0);
> +	ASSERT_EQ(destroy_mount(self->cgv2_fd, self->ctid, 1), 0);
> +	ASSERT_EQ(get_mount_cost(self->cgv2_fd, self->ctid), mount_cost);
> +}
> +
> +/* Test that pseudosuper doesn't disable accounting. */
> +TEST_F(ve_mnt_acc, pseudosuper_continues_accounting)
> +{
> +	int orig_mnt_avail, mount_cost, mnt_avail_nr;
> +	char path_avail[64];
> +
> +	snprintf(path_avail, sizeof(path_avail), "%d/ve.mnt_avail_nr", self->ctid);
> +
> +	ASSERT_EQ(read_s32_at(self->cgv2_fd, path_avail, &orig_mnt_avail), 0);
> +	mount_cost = get_mount_cost(self->cgv2_fd, self->ctid);
> +	ASSERT_GE(mount_cost, 1);
> +
> +	ASSERT_EQ(set_pseudosuper(self->cgv2_fd, self->ctid, 0), 0);
> +
> +	/* mnt 0 - mounted without pseudosuper, umounted with it. */
> +	ASSERT_EQ(mount_and_get_avail(_metadata, self->cgv2_fd, self->ctid, 0),
> +		  orig_mnt_avail - mount_cost);
> +	ASSERT_EQ(set_pseudosuper(self->cgv2_fd, self->ctid, 1), 0);
> +
> +	/* Cost is the same when mount/umount happen under pseudosuper. */
> +	ASSERT_EQ(get_mount_cost(self->cgv2_fd, self->ctid), mount_cost);
> +
> +	/* mnt 1 - mounted with pseudosuper, umounted without it. */
> +	ASSERT_EQ(mount_and_get_avail(_metadata, self->cgv2_fd, self->ctid, 1),
> +		  orig_mnt_avail - 2 * mount_cost);
> +
> +	ASSERT_EQ(destroy_mount(self->cgv2_fd, self->ctid, 0), 0);
> +	ASSERT_EQ(read_s32_at(self->cgv2_fd, path_avail, &mnt_avail_nr), 0);
> +	ASSERT_EQ(mnt_avail_nr, orig_mnt_avail - mount_cost);
> +
> +	ASSERT_EQ(set_pseudosuper(self->cgv2_fd, self->ctid, 0), 0);
> +
> +	ASSERT_EQ(destroy_mount(self->cgv2_fd, self->ctid, 1), 0);
> +	ASSERT_EQ(read_s32_at(self->cgv2_fd, path_avail, &mnt_avail_nr), 0);
> +	ASSERT_EQ(mnt_avail_nr, orig_mnt_avail);
> +}
> +
> +/* Test failcount feature */
> +TEST_F(ve_mnt_acc, failcount)
> +{
> +	char path_fc[64], failcount_str[512], path_max_nr[64];
> +
> +	snprintf(path_fc, sizeof(path_fc), "%d/ve.failcount", self->ctid);
> +	snprintf(path_max_nr, sizeof(path_max_nr), "%d/ve.mnt_max_nr", self->ctid);
> +
> +	ASSERT_GE(read_file_at(self->cgv2_fd, path_fc,
> +			       failcount_str, sizeof(failcount_str)), 0);
> +	ASSERT_TRUE(strstr(failcount_str, "mnt: 0\n") != NULL);
> +
> +	/* Check successful mount doesn't affect failcount */
> +	mount_and_get_avail(_metadata, self->cgv2_fd, self->ctid, 0);
> +	ASSERT_GE(read_file_at(self->cgv2_fd, path_fc,
> +			       failcount_str, sizeof(failcount_str)), 0);
> +	ASSERT_TRUE(strstr(failcount_str, "mnt: 0\n") != NULL);
> +	ASSERT_EQ(destroy_mount(self->cgv2_fd, self->ctid, 0), 0);
> +
> +	/* Check failcount update when mount fails */
> +	ASSERT_EQ(write_u64_at(self->cgv2_fd, path_max_nr, 0), 0);
> +	ASSERT_LT(create_mount(self->cgv2_fd, self->ctid, 1), 0);
> +	ASSERT_GE(read_file_at(self->cgv2_fd, path_fc,
> +			       failcount_str, sizeof(failcount_str)), 0);
> +	ASSERT_TRUE(strstr(failcount_str, "mnt: 1\n") != NULL);
> +
> +	/* Check failcount flush */
> +	ASSERT_EQ(write_u64_at(self->cgv2_fd, path_fc, 0), 0);
> +	ASSERT_GE(read_file_at(self->cgv2_fd, path_fc,
> +			       failcount_str, sizeof(failcount_str)), 0);
> +	ASSERT_TRUE(strstr(failcount_str, "mnt: 0\n") != NULL);
> +
> +	/* Check failcount update when mount fails again */
> +	ASSERT_EQ(write_u64_at(self->cgv2_fd, path_max_nr, 0), 0);
> +	ASSERT_LT(create_mount(self->cgv2_fd, self->ctid, 1), 0);
> +	ASSERT_GE(read_file_at(self->cgv2_fd, path_fc,
> +			       failcount_str, sizeof(failcount_str)), 0);
> +	ASSERT_TRUE(strstr(failcount_str, "mnt: 1\n") != NULL);
> +}
> +
> +TEST_HARNESS_MAIN

-- 
Best regards, Vasileios Almpanis
Software Developer, Virtuozzo.


^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [Devel] [PATCH VZ10 v5 0/9] Add per-VE failcount support
  2026-08-02 11:40 [Devel] [PATCH VZ10 v5 0/9] Add per-VE failcount support Vladimir Riabchun
                   ` (8 preceding siblings ...)
  2026-08-02 11:40 ` [Devel] [PATCH VZ10 v5 9/9] selftests/ve: Add mount accounting selftest Vladimir Riabchun
@ 2026-08-17 11:54 ` Vasileios Almpanis
  2026-08-17 14:09   ` Vasileios Almpanis
  9 siblings, 1 reply; 16+ messages in thread
From: Vasileios Almpanis @ 2026-08-17 11:54 UTC (permalink / raw)


On Sun, 02 Aug 2026 11:40:30 +0000, Vladimir Riabchun <vladimir.riabchun@virtuozzo.com> wrote:
> Add per-VE failcount support
> 
> This patchset introduces per-VE failcount support.
> We have several resources, that have per-VE limits,
> the users might want to know what limit hits make
> operations fail.
> 
> [...]

Reviewed-by: Vasileios Almpanis <vasileios.almpanis@virtuozzo.com>

-- 
Vasileios Almpanis <vasileios.almpanis@virtuozzo.com>

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [Devel] [PATCH VZ10 v5 7/9] ve: Introduce per-VE failcount
  2026-08-02 11:40 ` [Devel] [PATCH VZ10 v5 7/9] ve: Introduce per-VE failcount Vladimir Riabchun
  2026-08-07  9:25   ` Vasileios Almpanis
@ 2026-08-17 11:54   ` Vasileios Almpanis
  1 sibling, 0 replies; 16+ messages in thread
From: Vasileios Almpanis @ 2026-08-17 11:54 UTC (permalink / raw)


On Sun, 02 Aug 2026 11:40:37 +0000, Vladimir Riabchun <vladimir.riabchun@virtuozzo.com> wrote:
> diff --git a/kernel/ve/ve.c b/kernel/ve/ve.c
> index dc3faa0b1d76..a5c93be759ac 100644
> --- a/kernel/ve/ve.c
> +++ b/kernel/ve/ve.c
> @@ -1066,6 +1083,50 @@ VE_RESOURCE(mnt);
> [ ... skip 19 lines ... ]
> +static int ve_failcount_read(struct seq_file *sf, void *v)
> +{
> +	struct ve_struct *ve = css_to_ve(seq_css(sf));
> +	struct ve_failcount_entry *entry;
> +	atomic_t *fc;
> +

kernel/ve/ve.c: In function ?ve_failcount_read?:
kernel/ve/ve.c:1107:20: warning: assignment discards ?const? qualifier 
from pointer target type [-Wdiscarded-qualifiers]
 ?1107 |? ? ? ? ?for (entry = ve_failcounts; entry->name; entry++) {
 ? ? ? |? ? ? ? ? ? ? ? ? ? ^
kernel/ve/ve.c: In function ?ve_failcount_write?:
kernel/ve/ve.c:1121:20: warning: assignment discards ?const? qualifier 
from pointer target type [-Wdiscarded-qualifiers]
 ?1121 |? ? ? ? ?for (entry = ve_failcounts; entry->name; entry++) {

the left-hand pointee must carry all qualifiers of the right-hand pointee according to C11

-- 
Vasileios Almpanis <vasileios.almpanis@virtuozzo.com>

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [Devel] [PATCH VZ10 v5 9/9] selftests/ve: Add mount accounting selftest
  2026-08-02 11:40 ` [Devel] [PATCH VZ10 v5 9/9] selftests/ve: Add mount accounting selftest Vladimir Riabchun
  2026-08-07 10:01   ` Vasileios Almpanis
@ 2026-08-17 11:54   ` Vasileios Almpanis
  1 sibling, 0 replies; 16+ messages in thread
From: Vasileios Almpanis @ 2026-08-17 11:54 UTC (permalink / raw)


On Sun, 02 Aug 2026 11:40:39 +0000, Vladimir Riabchun <vladimir.riabchun@virtuozzo.com> wrote:
> There are 6 test cases, covered in the new test:
> 1. Simple mount accouting correctness, just mount/umount.
> 2. Verification of correct limit hits and changes, including
>    negative values.
> 3. Patial mounts test, when mount limit is hit in the middle
>    of creation.

nit: Partial

> 4. Test that enabled pseudosuper allows overuse.
> 5. Test that pseudosuper doesn't affect mount accoutning.

nit: accounting

>
>
> diff --git a/tools/testing/selftests/ve/ve_mount_accounting_test.c b/tools/testing/selftests/ve/ve_mount_accounting_test.c
> new file mode 100644
> index 000000000000..b295290ec6e8
> --- /dev/null
> +++ b/tools/testing/selftests/ve/ve_mount_accounting_test.c
> @@ -0,0 +1,419 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * ve_mount_accounting selftests
> + *
> + * Tests to check the correctness of mount accounting.
> + */
> +#define _GNU_SOURCE
> +#include <linux/sched.h>
> +#include <linux/mount.h>

Is linux/mount needed here? You include both linux/mount and sys/mount.
You only call mount,umount so dropping it looks generally safe.

> [ ... skip 181 lines ... ]
> +	ASSERT_EQ(read_u64_at(self->cgv2_fd, path, &initial_mnt_avail_nr), 0);
> +	ASSERT_EQ(initial_mnt_avail_nr, VE_MOUNTS_MAX);
> +};
> +
> +FIXTURE_TEARDOWN(ve_mnt_acc)
> +{

Since we mount tmpfs on host mount namespace (we dont pass CLONE_NEWNS), 
should we iterate here and umount all the ids that remain mounted after 
tests bail? There are maybe places where create_mount is tried and if 
assertion fails the mount remains and leaks to the host possibly also 
pinning the ve namespace since in ve_try_reserve_mount we get a refcount 
on it.

-- 
Vasileios Almpanis <vasileios.almpanis@virtuozzo.com>

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [Devel] [PATCH VZ10 v5 0/9] Add per-VE failcount support
  2026-08-17 11:54 ` [Devel] [PATCH VZ10 v5 0/9] Add per-VE failcount support Vasileios Almpanis
@ 2026-08-17 14:09   ` Vasileios Almpanis
  0 siblings, 0 replies; 16+ messages in thread
From: Vasileios Almpanis @ 2026-08-17 14:09 UTC (permalink / raw)


JFYI this is from presentation please disregard. Series needs rebasing 
and small requested fixes

On 8/17/26 1:54 PM, Vasileios Almpanis wrote:
> On Sun, 02 Aug 2026 11:40:30 +0000, Vladimir Riabchun <vladimir.riabchun@virtuozzo.com> wrote:
>> Add per-VE failcount support
>>
>> This patchset introduces per-VE failcount support.
>> We have several resources, that have per-VE limits,
>> the users might want to know what limit hits make
>> operations fail.
>>
>> [...]
> Reviewed-by: Vasileios Almpanis <vasileios.almpanis@virtuozzo.com>
>
-- 
Best regards, Vasileios Almpanis
Software Developer, Virtuozzo.


^ permalink raw reply	[flat|nested] 16+ messages in thread

end of thread, other threads:[~2026-08-17 14:09 UTC | newest]

Thread overview: 16+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-08-02 11:40 [Devel] [PATCH VZ10 v5 0/9] Add per-VE failcount support Vladimir Riabchun
2026-08-02 11:40 ` [Devel] [PATCH VZ10 v5 1/9] ve/ve.{h, c}: Farewell to spaces as indents Vladimir Riabchun
2026-08-02 11:40 ` [Devel] [PATCH VZ10 v5 2/9] ve/namespace: Fix UAF in alloc_mnt_ns Vladimir Riabchun
2026-08-02 11:40 ` [Devel] [PATCH VZ10 v5 3/9] ve/fs: Rework per-ve mount count Vladimir Riabchun
2026-08-02 11:40 ` [Devel] [PATCH VZ10 v5 4/9] selftests/ve: Update ve_ns_owner_test Vladimir Riabchun
2026-08-02 11:40 ` [Devel] [PATCH VZ10 v5 5/9] ve: Move from global VE mounts limit to per-VE limit Vladimir Riabchun
2026-08-02 11:40 ` [Devel] [PATCH VZ10 v5 6/9] ve/ve.c: Generate VE resource accessors using macros Vladimir Riabchun
2026-08-02 11:40 ` [Devel] [PATCH VZ10 v5 7/9] ve: Introduce per-VE failcount Vladimir Riabchun
2026-08-07  9:25   ` Vasileios Almpanis
2026-08-17 11:54   ` Vasileios Almpanis
2026-08-02 11:40 ` [Devel] [PATCH VZ10 v5 8/9] selftests/ve: Add more helpers Vladimir Riabchun
2026-08-02 11:40 ` [Devel] [PATCH VZ10 v5 9/9] selftests/ve: Add mount accounting selftest Vladimir Riabchun
2026-08-07 10:01   ` Vasileios Almpanis
2026-08-17 11:54   ` Vasileios Almpanis
2026-08-17 11:54 ` [Devel] [PATCH VZ10 v5 0/9] Add per-VE failcount support Vasileios Almpanis
2026-08-17 14:09   ` Vasileios Almpanis

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox