All Virtuozzo development lists (kernel + QEMU)
 help / color / mirror / Atom feed
* [Devel] [PATCH VZ10 v7 0/9] Add per-VE failcount support
@ 2026-08-24 13:54 Vladimir Riabchun
  2026-08-24 13:54 ` [Devel] [PATCH VZ10 v7 1/9] ve/ve.{h, c}: Farewell to spaces as indents Vladimir Riabchun
                   ` (8 more replies)
  0 siblings, 9 replies; 12+ messages in thread
From: Vladimir Riabchun @ 2026-08-24 13:54 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 v6:
 - Fixed several typos and adjusted comments.
 - Added CONFIG_VE check in free_vfsmnt to compile with
   !CONFIG_VE in patch 3.
 - Added permission check in ve_failcount_write in patch 7.

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                                |  90 ++--
 include/linux/ve.h                            |  25 +-
 kernel/bpf/syscall.c                          |   1 +
 kernel/ve/ve.c                                | 277 ++++++------
 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   | 421 ++++++++++++++++++
 tools/testing/selftests/ve/ve_ns_owner_test.c |  37 +-
 tools/testing/selftests/ve/ve_selftest.h      |  81 +++-
 14 files changed, 740 insertions(+), 221 deletions(-)
 create mode 100644 tools/testing/selftests/ve/ve_mount_accounting_test.c

-- 
2.47.1


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

* [Devel] [PATCH VZ10 v7 1/9] ve/ve.{h, c}: Farewell to spaces as indents
  2026-08-24 13:54 [Devel] [PATCH VZ10 v7 0/9] Add per-VE failcount support Vladimir Riabchun
@ 2026-08-24 13:54 ` Vladimir Riabchun
  2026-08-24 13:54 ` [Devel] [PATCH VZ10 v7 2/9] ve/namespace: Fix UAF in alloc_mnt_ns Vladimir Riabchun
                   ` (7 subsequent siblings)
  8 siblings, 0 replies; 12+ messages in thread
From: Vladimir Riabchun @ 2026-08-24 13:54 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 73d1c3b4873e..dffb35da22bd 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);
@@ -886,23 +886,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);
 
@@ -914,7 +914,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")) {
@@ -1343,7 +1343,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] 12+ messages in thread

* [Devel] [PATCH VZ10 v7 2/9] ve/namespace: Fix UAF in alloc_mnt_ns
  2026-08-24 13:54 [Devel] [PATCH VZ10 v7 0/9] Add per-VE failcount support Vladimir Riabchun
  2026-08-24 13:54 ` [Devel] [PATCH VZ10 v7 1/9] ve/ve.{h, c}: Farewell to spaces as indents Vladimir Riabchun
@ 2026-08-24 13:54 ` Vladimir Riabchun
  2026-08-24 13:54 ` [Devel] [PATCH VZ10 v7 3/9] ve/fs: Rework per-ve mount count Vladimir Riabchun
                   ` (6 subsequent siblings)
  8 siblings, 0 replies; 12+ messages in thread
From: Vladimir Riabchun @ 2026-08-24 13:54 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 4d4dc5290350..68e0efb73d7c 100644
--- a/fs/namespace.c
+++ b/fs/namespace.c
@@ -4243,17 +4243,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);
 }
@@ -4347,6 +4351,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] 12+ messages in thread

* [Devel] [PATCH VZ10 v7 3/9] ve/fs: Rework per-ve mount count
  2026-08-24 13:54 [Devel] [PATCH VZ10 v7 0/9] Add per-VE failcount support Vladimir Riabchun
  2026-08-24 13:54 ` [Devel] [PATCH VZ10 v7 1/9] ve/ve.{h, c}: Farewell to spaces as indents Vladimir Riabchun
  2026-08-24 13:54 ` [Devel] [PATCH VZ10 v7 2/9] ve/namespace: Fix UAF in alloc_mnt_ns Vladimir Riabchun
@ 2026-08-24 13:54 ` Vladimir Riabchun
  2026-08-24 13:54 ` [Devel] [PATCH VZ10 v7 4/9] selftests/ve: Update ve_ns_owner_test Vladimir Riabchun
                   ` (5 subsequent siblings)
  8 siblings, 0 replies; 12+ messages in thread
From: Vladimir Riabchun @ 2026-08-24 13:54 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>
---
 fs/namespace.c     | 73 ++++++++++++++++++++++++++++------------------
 include/linux/ve.h |  2 +-
 kernel/ve/ve.c     | 12 ++++----
 3 files changed, 51 insertions(+), 36 deletions(-)

diff --git a/fs/namespace.c b/fs/namespace.c
index 68e0efb73d7c..e97f48204617 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,9 @@ 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);
+#ifdef CONFIG_VE
+	ve_mount_put(mnt, mnt->ve_owner);
+#endif
 	kfree_const(mnt->mnt_devname);
 #ifdef CONFIG_SMP
 	free_percpu(mnt->mnt_pcp);
@@ -3205,7 +3221,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.
 			 */
@@ -3344,30 +3360,30 @@ int ve_devmnt_verify(struct ve_struct *ve, dev_t dev, char *opts, bool new_mount
 	return err;
 }
 
-static inline int ve_mount_allowed(void)
+static inline int ve_try_reserve_mount(struct ve_struct *ve)
 {
-	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)
-{
-	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)
@@ -3389,9 +3405,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 */
 
 /*
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 dffb35da22bd..42669a832993 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");
@@ -1054,9 +1054,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)
@@ -1616,8 +1616,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] 12+ messages in thread

* [Devel] [PATCH VZ10 v7 4/9] selftests/ve: Update ve_ns_owner_test
  2026-08-24 13:54 [Devel] [PATCH VZ10 v7 0/9] Add per-VE failcount support Vladimir Riabchun
                   ` (2 preceding siblings ...)
  2026-08-24 13:54 ` [Devel] [PATCH VZ10 v7 3/9] ve/fs: Rework per-ve mount count Vladimir Riabchun
@ 2026-08-24 13:54 ` Vladimir Riabchun
  2026-08-24 13:54 ` [Devel] [PATCH VZ10 v7 5/9] ve: Move from global VE mounts limit to per-VE limit Vladimir Riabchun
                   ` (4 subsequent siblings)
  8 siblings, 0 replies; 12+ messages in thread
From: Vladimir Riabchun @ 2026-08-24 13:54 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 | 33 ++++++++++---------
 1 file changed, 17 insertions(+), 16 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..c3fa7d7b7f9c 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 some value >= 1 (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
-	 * the clone/unshare populates the new mntns under this ve, i.e.
-	 * mnt_nr rises strictly above zero.
+	 * mnt_avail_nr counter should be VE_MOUNTS_MAX. Each test below
+	 * verifies that the clone/unshare populates the new mntns
+	 * under this ve, i.e. 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] 12+ messages in thread

* [Devel] [PATCH VZ10 v7 5/9] ve: Move from global VE mounts limit to per-VE limit
  2026-08-24 13:54 [Devel] [PATCH VZ10 v7 0/9] Add per-VE failcount support Vladimir Riabchun
                   ` (3 preceding siblings ...)
  2026-08-24 13:54 ` [Devel] [PATCH VZ10 v7 4/9] selftests/ve: Update ve_ns_owner_test Vladimir Riabchun
@ 2026-08-24 13:54 ` Vladimir Riabchun
  2026-08-24 13:54 ` [Devel] [PATCH VZ10 v7 6/9] ve/ve.c: Generate VE resource accessors using macros Vladimir Riabchun
                   ` (3 subsequent siblings)
  8 siblings, 0 replies; 12+ messages in thread
From: Vladimir Riabchun @ 2026-08-24 13:54 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 42669a832993..582fc17e4810 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");
@@ -1059,6 +1061,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;
@@ -1619,6 +1645,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 c3fa7d7b7f9c..dcff4d54172d 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 should be VE_MOUNTS_MAX. Each test below
-- 
2.47.1


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

* [Devel] [PATCH VZ10 v7 6/9] ve/ve.c: Generate VE resource accessors using macros
  2026-08-24 13:54 [Devel] [PATCH VZ10 v7 0/9] Add per-VE failcount support Vladimir Riabchun
                   ` (4 preceding siblings ...)
  2026-08-24 13:54 ` [Devel] [PATCH VZ10 v7 5/9] ve: Move from global VE mounts limit to per-VE limit Vladimir Riabchun
@ 2026-08-24 13:54 ` Vladimir Riabchun
  2026-08-24 13:54 ` [Devel] [PATCH VZ10 v7 7/9] ve: Introduce per-VE failcount Vladimir Riabchun
                   ` (2 subsequent siblings)
  8 siblings, 0 replies; 12+ messages in thread
From: Vladimir Riabchun @ 2026-08-24 13:54 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 582fc17e4810..0f02835765ff 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;
@@ -1027,121 +1060,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)
 {
@@ -1587,6 +1509,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[] = {
 
 	{
@@ -1631,46 +1566,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] 12+ messages in thread

* [Devel] [PATCH VZ10 v7 7/9] ve: Introduce per-VE failcount
  2026-08-24 13:54 [Devel] [PATCH VZ10 v7 0/9] Add per-VE failcount support Vladimir Riabchun
                   ` (5 preceding siblings ...)
  2026-08-24 13:54 ` [Devel] [PATCH VZ10 v7 6/9] ve/ve.c: Generate VE resource accessors using macros Vladimir Riabchun
@ 2026-08-24 13:54 ` Vladimir Riabchun
  2026-08-28 16:52   ` Pavel Tikhomirov
  2026-08-24 13:54 ` [Devel] [PATCH VZ10 v7 8/9] selftests/ve: Add more helpers Vladimir Riabchun
  2026-08-24 13:54 ` [Devel] [PATCH VZ10 v7 9/9] selftests/ve: Add mount accounting selftest Vladimir Riabchun
  8 siblings, 1 reply; 12+ messages in thread
From: Vladimir Riabchun @ 2026-08-24 13:54 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           | 70 ++++++++++++++++++++++++++++++++++++++++
 net/core/dev.c           |  2 ++
 net/core/neighbour.c     |  1 +
 net/core/net_namespace.c |  4 ++-
 8 files changed, 86 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 e97f48204617..c30bbc370f2b 100644
--- a/fs/namespace.c
+++ b/fs/namespace.c
@@ -3371,6 +3371,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 c94d4240e3d3..9d57e7999ae0 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 0f02835765ff..826f72ad0a22 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;
@@ -1065,6 +1082,53 @@ 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));
+	const 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));
+	const struct ve_failcount_entry *entry;
+	atomic_t *fc;
+
+	if (!ve_is_super(get_exec_env()) && !ve->is_pseudosuper)
+		return -EPERM;
+
+	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);
@@ -1602,6 +1666,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] 12+ messages in thread

* [Devel] [PATCH VZ10 v7 8/9] selftests/ve: Add more helpers
  2026-08-24 13:54 [Devel] [PATCH VZ10 v7 0/9] Add per-VE failcount support Vladimir Riabchun
                   ` (6 preceding siblings ...)
  2026-08-24 13:54 ` [Devel] [PATCH VZ10 v7 7/9] ve: Introduce per-VE failcount Vladimir Riabchun
@ 2026-08-24 13:54 ` Vladimir Riabchun
  2026-08-24 13:54 ` [Devel] [PATCH VZ10 v7 9/9] selftests/ve: Add mount accounting selftest Vladimir Riabchun
  8 siblings, 0 replies; 12+ messages in thread
From: Vladimir Riabchun @ 2026-08-24 13:54 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>
---
 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] 12+ messages in thread

* [Devel] [PATCH VZ10 v7 9/9] selftests/ve: Add mount accounting selftest
  2026-08-24 13:54 [Devel] [PATCH VZ10 v7 0/9] Add per-VE failcount support Vladimir Riabchun
                   ` (7 preceding siblings ...)
  2026-08-24 13:54 ` [Devel] [PATCH VZ10 v7 8/9] selftests/ve: Add more helpers Vladimir Riabchun
@ 2026-08-24 13:54 ` Vladimir Riabchun
  8 siblings, 0 replies; 12+ messages in thread
From: Vladimir Riabchun @ 2026-08-24 13:54 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. Partial 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 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>
---
 tools/testing/selftests/ve/.gitignore         |   1 +
 tools/testing/selftests/ve/Makefile           |   1 +
 .../selftests/ve/ve_mount_accounting_test.c   | 421 ++++++++++++++++++
 3 files changed, 423 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..4426ebc6a1ac
--- /dev/null
+++ b/tools/testing/selftests/ve/ve_mount_accounting_test.c
@@ -0,0 +1,421 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * ve_mount_accounting selftests
+ *
+ * Tests to check the correctness of mount accounting.
+ */
+#define _GNU_SOURCE
+#include <asm/unistd.h>
+#include <linux/sched.h>
+#include <linux/limits.h>
+#include <sys/wait.h>
+#include <sys/syscall.h>
+#include <sys/stat.h>
+#include <sys/mount.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sched.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.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)
+{
+	for (int i = 0; i < MAX_MNT_ID; i++)
+		_destroy_mount((void *)&i);
+
+	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 fails */
+	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 checks that if we run out of 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] 12+ messages in thread

* Re: [Devel] [PATCH VZ10 v7 7/9] ve: Introduce per-VE failcount
  2026-08-24 13:54 ` [Devel] [PATCH VZ10 v7 7/9] ve: Introduce per-VE failcount Vladimir Riabchun
@ 2026-08-28 16:52   ` Pavel Tikhomirov
  2026-08-28 17:32     ` Vladimir Riabchun
  0 siblings, 1 reply; 12+ messages in thread
From: Pavel Tikhomirov @ 2026-08-28 16:52 UTC (permalink / raw)
  To: Vladimir Riabchun, devel



On 8/24/26 15:54, 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           | 70 ++++++++++++++++++++++++++++++++++++++++
>  net/core/dev.c           |  2 ++
>  net/core/neighbour.c     |  1 +
>  net/core/net_namespace.c |  4 ++-
>  8 files changed, 86 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 e97f48204617..c30bbc370f2b 100644
> --- a/fs/namespace.c
> +++ b/fs/namespace.c
> @@ -3371,6 +3371,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 c94d4240e3d3..9d57e7999ae0 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 0f02835765ff..826f72ad0a22 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,

This feature is not intended to limit host aio, right? Should it
be INT_MAX or something big here for ve0?

> +	.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;
> @@ -1065,6 +1082,53 @@ 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));
> +	const 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));
> +	const struct ve_failcount_entry *entry;
> +	atomic_t *fc;
> +
> +	if (!ve_is_super(get_exec_env()) && !ve->is_pseudosuper)
> +		return -EPERM;
> +
> +	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);
> @@ -1602,6 +1666,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);

Let's add a helper for incrementing our failcounts:

#define ve_failcount_inc(ve, name)                                    \
do {                                                                  \
      struct ve_struct *__ve = (ve);                                  \
                                                                      \
      if (atomic_inc_return(&__ve->name##_failcount) == 1)            \
              pr_warn("CT%s: hits the " #name " limit, see the '"     \
                      #name "' counter in ve.failcount of the "       \
                      "container's ve cgroup\n", ve_name(__ve));      \
} while (0)

Call sites become one-liners:

--- a/fs/aio.c
-             atomic_inc(&ve->aio_failcount);
+             ve_failcount_inc(ve, aio);
--- a/fs/namespace.c
-             atomic_inc(&ve->mnt_failcount);
+             ve_failcount_inc(ve, mnt);
--- a/kernel/bpf/syscall.c
-                     atomic_inc(&load_ve->bpf_prog_failcount);
+                     ve_failcount_inc(load_ve, bpf_prog);
--- a/net/core/dev.c          (both register_netdevice() and __dev_change_net_namespace())
-             atomic_inc(&net->owner_ve->netif_failcount);
+             ve_failcount_inc(net->owner_ve, netif);
--- a/net/core/neighbour.c
-                     atomic_inc(&ve->neigh_tbl_failcount);
+                     ve_failcount_inc(ve, neigh_tbl);
--- a/net/core/net_namespace.c
-             atomic_inc(&ve->netns_failcount);
+             ve_failcount_inc(ve, netns);

The idea behind it is to show in dmesg that failcount was reached, to simpify
the detection of problematic containers for us.

For reference http://lore.virtuozzo.com/kernel/ccc3cbc8-e225-41cf-b563-699254aeb57b@virtuozzo.com/T/#t

>  		return -ENOSPC;
> +	}
>  	return 0;
>  }
>  

-- 
Best regards, Pavel Tikhomirov
Senior Software Developer, Virtuozzo.

_______________________________________________
Devel mailing list
Devel@openvz.org
https://lists.openvz.org/mailman/listinfo/devel

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

* Re: [Devel] [PATCH VZ10 v7 7/9] ve: Introduce per-VE failcount
  2026-08-28 16:52   ` Pavel Tikhomirov
@ 2026-08-28 17:32     ` Vladimir Riabchun
  0 siblings, 0 replies; 12+ messages in thread
From: Vladimir Riabchun @ 2026-08-28 17:32 UTC (permalink / raw)
  To: Pavel Tikhomirov, devel



On 8/28/26 18:52, Pavel Tikhomirov wrote:
> 
> 
> On 8/24/26 15:54, 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           | 70 ++++++++++++++++++++++++++++++++++++++++
>>   net/core/dev.c           |  2 ++
>>   net/core/neighbour.c     |  1 +
>>   net/core/net_namespace.c |  4 ++-
>>   8 files changed, 86 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 e97f48204617..c30bbc370f2b 100644
>> --- a/fs/namespace.c
>> +++ b/fs/namespace.c
>> @@ -3371,6 +3371,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 c94d4240e3d3..9d57e7999ae0 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 0f02835765ff..826f72ad0a22 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,
> 
> This feature is not intended to limit host aio, right? Should it
> be INT_MAX or something big here for ve0?

It preserves original kernel behavior. Upstream kernel has aio_max_nr
sysctl variable with the same value as AIO_MAX_NR_DEFAULT.
Commit 1f6006388601 ("ve/fs/aio: aio_nr & aio_max_nr variables virtualization")
added aio_max_nr to ve structure, but it didn't initialize it in ve0.

Here I just put the correct value in it, so no functional changes.

> 
>> +	.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;
>> @@ -1065,6 +1082,53 @@ 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));
>> +	const 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));
>> +	const struct ve_failcount_entry *entry;
>> +	atomic_t *fc;
>> +
>> +	if (!ve_is_super(get_exec_env()) && !ve->is_pseudosuper)
>> +		return -EPERM;
>> +
>> +	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);
>> @@ -1602,6 +1666,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);
> 
> Let's add a helper for incrementing our failcounts:
> 
> #define ve_failcount_inc(ve, name)                                    \
> do {                                                                  \
>        struct ve_struct *__ve = (ve);                                  \
>                                                                        \
>        if (atomic_inc_return(&__ve->name##_failcount) == 1)            \
>                pr_warn("CT%s: hits the " #name " limit, see the '"     \
>                        #name "' counter in ve.failcount of the "       \
>                        "container's ve cgroup\n", ve_name(__ve));      \
> } while (0)
> 
> Call sites become one-liners:
> 
> --- a/fs/aio.c
> -             atomic_inc(&ve->aio_failcount);
> +             ve_failcount_inc(ve, aio);
> --- a/fs/namespace.c
> -             atomic_inc(&ve->mnt_failcount);
> +             ve_failcount_inc(ve, mnt);
> --- a/kernel/bpf/syscall.c
> -                     atomic_inc(&load_ve->bpf_prog_failcount);
> +                     ve_failcount_inc(load_ve, bpf_prog);
> --- a/net/core/dev.c          (both register_netdevice() and __dev_change_net_namespace())
> -             atomic_inc(&net->owner_ve->netif_failcount);
> +             ve_failcount_inc(net->owner_ve, netif);
> --- a/net/core/neighbour.c
> -                     atomic_inc(&ve->neigh_tbl_failcount);
> +                     ve_failcount_inc(ve, neigh_tbl);
> --- a/net/core/net_namespace.c
> -             atomic_inc(&ve->netns_failcount);
> +             ve_failcount_inc(ve, netns);
> 
> The idea behind it is to show in dmesg that failcount was reached, to simpify
> the detection of problematic containers for us.
> 
> For reference http://lore.virtuozzo.com/kernel/ccc3cbc8-e225-41cf-b563-699254aeb57b@virtuozzo.com/T/#t
> 

Fair, I'll do it in next version or as a follow-up if everything else is fine here.
Just instead of pr_warn we should use pr_warn_ratelimited, as it is easy
to trigger warning from container and flood dmesg and serial console.

>>   		return -ENOSPC;
>> +	}
>>   	return 0;
>>   }
>>   
> 

-- 
Best regards, Riabchun Vladimir
Linux Kernel Developer, Virtuozzo

_______________________________________________
Devel mailing list
Devel@openvz.org
https://lists.openvz.org/mailman/listinfo/devel

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

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

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

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.