[Devel] [PATCH vz10 6/7] fs: factor per-VE permission core into ve_perms helpers

Mirian Shilakadze mirian.shilakadze at virtuozzo.com
Sun Jun 28 12:26:04 MSK 2026


The per-VE r/w/x permission logic (line parse, kmapset apply, access check,
visibility, listing predicate, mask output) lives in fs/kernfs/ve.c and is
needed verbatim by an upcoming procfs implementation. Move the filesystem
agnostic parts into fs/ve_perms.c plus include/linux/ve-perms.h, operating
purely on a kmapset map, key and mask, and convert fs/kernfs/ve.c to call
them. The kernfs function signatures are unchanged so fs/sysfs is
untouched.

ve_perms_apply() takes the node map slot as kmapset_map __rcu ** and keeps
the rcu_assign_pointer publish, matching the __rcu annotation added in the
previous patch. The rcu_read_lock / rcu_dereference wrapping stays in the
kernfs readers.

No functional change.

Signed-off-by: Mirian Shilakadze <mirian.shilakadze at virtuozzo.com>
---
 fs/Makefile              |   1 +
 fs/kernfs/ve.c           |  99 ++++++----------------------
 fs/ve_perms.c            | 136 +++++++++++++++++++++++++++++++++++++++
 include/linux/ve-perms.h |  28 ++++++++
 4 files changed, 183 insertions(+), 81 deletions(-)
 create mode 100644 fs/ve_perms.c
 create mode 100644 include/linux/ve-perms.h

diff --git a/fs/Makefile b/fs/Makefile
index 73512f13e969..89242a58f1af 100644
--- a/fs/Makefile
+++ b/fs/Makefile
@@ -17,6 +17,7 @@ obj-y :=	open.o read_write.o file_table.o super.o \
 		fs_types.o fs_context.o fs_parser.o fsopen.o init.o \
 		kernel_read_file.o mnt_idmapping.o remap_range.o pidfs.o
 
+obj-$(CONFIG_VE)		+= ve_perms.o
 obj-$(CONFIG_BUFFER_HEAD)	+= buffer.o mpage.o
 obj-$(CONFIG_PROC_FS)		+= proc_namespace.o
 obj-$(CONFIG_LEGACY_DIRECT_IO)	+= direct-io.o
diff --git a/fs/kernfs/ve.c b/fs/kernfs/ve.c
index 94f3533bbdd9..d0e976e79e8b 100644
--- a/fs/kernfs/ve.c
+++ b/fs/kernfs/ve.c
@@ -18,6 +18,7 @@
 #include <linux/kmapset.h>
 #include <linux/rcupdate.h>
 #include <linux/kernfs-ve.h>
+#include <linux/ve-perms.h>
 
 #include "kernfs-internal.h"
 #include "kernfs-ve.h"
@@ -62,7 +63,7 @@ int kernfs_ve_permission(struct kernfs_node *kn,
 			 struct kernfs_super_info *info, int mask)
 {
 	struct kernfs_node *tmp_kn = kn;
-	int perm;
+	int ret;
 
 	if (kernfs_ve_allowed(kn))
 		return 0;
@@ -80,13 +81,10 @@ int kernfs_ve_permission(struct kernfs_node *kn,
 	if (kernfs_type(kn) == KERNFS_LINK)
 		kn = kn->symlink.target_kn;
 
-	perm = kmapset_get_value(rcu_dereference(kn->ve_perms_map),
-				 kernfs_info_perms_key(info));
+	ret = ve_perms_check(rcu_dereference(kn->ve_perms_map),
+			     kernfs_info_perms_key(info), mask);
 	rcu_read_unlock();
-	if ((mask & ~perm & (MAY_READ | MAY_WRITE | MAY_EXEC)) == 0)
-		return 0;
-
-	return -EACCES;
+	return ret;
 }
 
 void kernfs_get_ve_perms(struct kernfs_node *kn)
@@ -135,8 +133,8 @@ bool kernfs_d_visible(struct kernfs_node *kn, struct kernfs_super_info *info)
 	if (kernfs_type(kn) == KERNFS_LINK)
 		kn = kn->symlink.target_kn;
 
-	visible = !!kmapset_get_value(rcu_dereference(kn->ve_perms_map),
-				      kernfs_info_perms_key(info));
+	visible = ve_perms_visible(rcu_dereference(kn->ve_perms_map),
+				   kernfs_info_perms_key(info));
 	rcu_read_unlock();
 	return visible;
 }
@@ -176,11 +174,9 @@ static bool kernfs_perms_shown(struct ve_struct *ve, struct kernfs_node *kn,
 
 	if (!map)
 		return false;
-	if (ve_is_super(ve))
-		return map->default_value != 0;
-	/* kmapset_lookup() walks an rcu list, so keep the section to the lookup. */
+	/* ve_perms_shown() -> kmapset_lookup() walks an rcu list, so guard the call. */
 	rcu_read_lock();
-	ret = kmapset_lookup(map, key) != NULL;
+	ret = ve_perms_shown(map, key, ve_is_super(ve));
 	rcu_read_unlock();
 	return ret;
 }
@@ -260,19 +256,7 @@ int kernfs_perms_show(struct seq_file *m, void *v, struct kmapset_key *key)
 		seq_commit(m, size - off);
 	}
 
-	seq_putc(m, ' ');
-
-	if (!mask)
-		seq_putc(m, '-');
-	if (mask & MAY_READ)
-		seq_putc(m, 'r');
-	if (mask & MAY_WRITE)
-		seq_putc(m, 'w');
-	if (mask & MAY_EXEC)
-		seq_putc(m, 'x');
-
-	seq_putc(m, '\n');
-
+	ve_perms_emit(m, mask);
 	return 0;
 }
 
@@ -280,9 +264,8 @@ int kernfs_perms_set(char *path, struct ve_struct *ve, int mask,
 		     struct kernfs_node *root, struct kmapset_key *key)
 {
 	struct kernfs_node *kn = root, *nkn;
-	struct kmapset_map *map = NULL, *old;
 	char *sep = path, *dname;
-	int ret;
+	int ret = 0;
 
 	kernfs_get(kn);
 	do {
@@ -305,30 +288,7 @@ int kernfs_perms_set(char *path, struct ve_struct *ve, int mask,
 		kn = nkn;
 	} while (sep);
 
-	ret = -ENOMEM;
-	/* the caller (fs/sysfs/ve.c) holds sysfs_perms_mutex vs other writers */
-	map = kmapset_dup(rcu_dereference_protected(kn->ve_perms_map, true));
-	if (!map)
-		goto out_put;
-
-	ret = 0;
-	if (ve_is_super(ve)) {
-		kmapset_set_default(map, mask > 0 ? mask : 0);
-	} else if (mask < 0) {
-		kmapset_del_value(map, key);
-	} else {
-		ret = kmapset_set_value(map, key, mask);
-	}
-
-	if (!ret) {
-		old = rcu_dereference_protected(kn->ve_perms_map, true);
-		rcu_assign_pointer(kn->ve_perms_map, kmapset_commit(map));
-		kmapset_put(old);
-		map = NULL;
-	}
-
-out_put:
-	kmapset_put(map);
+	ret = ve_perms_apply(&kn->ve_perms_map, key, ve_is_super(ve), mask);
 out:
 	kernfs_put(kn);
 	return ret;
@@ -337,35 +297,12 @@ int kernfs_perms_set(char *path, struct ve_struct *ve, int mask,
 static int kernfs_perms_line(struct ve_struct *ve, char *line,
 			     struct kernfs_node *root, struct kmapset_key *key)
 {
-	int mask = 0;
-	char *p;
-
-	p = strpbrk(line, " \t");
-	if (!p)
-		return -EINVAL;
-	*p++ = 0;
-	p = skip_spaces(p);
-	while (1) {
-		switch (*p++) {
-			case 'r':
-				mask |= MAY_READ;
-				break;
-			case 'w':
-				mask |= MAY_WRITE;
-				break;
-			case 'x':
-				mask |= MAY_EXEC;
-				break;
-			case '-':
-				mask = -1;
-				break;
-			case 0:
-				return kernfs_perms_set(line, ve, mask,
-							root, key);
-			default:
-				return -EINVAL;
-		}
-	}
+	int mask, ret;
+
+	ret = ve_perms_parse(line, &mask);
+	if (ret)
+		return ret;
+	return kernfs_perms_set(line, ve, mask, root, key);
 }
 
 ssize_t kernfs_perms_write(struct kernfs_open_file *of,
diff --git a/fs/ve_perms.c b/fs/ve_perms.c
new file mode 100644
index 000000000000..bba08d53b205
--- /dev/null
+++ b/fs/ve_perms.c
@@ -0,0 +1,136 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ *  Filesystem-agnostic per-VE permission helpers shared by the kernfs
+ *  (sysfs) and procfs implementations. Every helper works purely on a
+ *  kmapset map, key and mask and never touches a filesystem node or a
+ *  lock, so the caller owns the tree walk and all locking.
+ *
+ *  Copyright (c) 2026 Virtuozzo International GmbH. All rights reserved.
+ */
+
+#include <linux/kernel.h>
+#include <linux/fs.h>
+#include <linux/string.h>
+#include <linux/seq_file.h>
+#include <linux/rcupdate.h>
+#include <linux/kmapset.h>
+#include <linux/ve-perms.h>
+
+/*
+ * Parse one "path mask" line in place. On success @line is left as the NUL
+ * terminated path and *@maskp holds the rwx mask, or -1 to remove the entry.
+ */
+int ve_perms_parse(char *line, int *maskp)
+{
+	int mask = 0;
+	char *p;
+
+	p = strpbrk(line, " \t");
+	if (!p)
+		return -EINVAL;
+	*p++ = 0;
+	p = skip_spaces(p);
+	while (1) {
+		switch (*p++) {
+		case 'r':
+			mask |= MAY_READ;
+			break;
+		case 'w':
+			mask |= MAY_WRITE;
+			break;
+		case 'x':
+			mask |= MAY_EXEC;
+			break;
+		case '-':
+			mask = -1;
+			break;
+		case 0:
+			*maskp = mask;
+			return 0;
+		default:
+			return -EINVAL;
+		}
+	}
+}
+
+/*
+ * Apply @mask for @key to the node map at *@slot, copy on write. A super VE
+ * sets the map default, a negative mask removes the entry. The new map is
+ * published with rcu_assign_pointer and the old one dropped, which kmapset
+ * frees via RCU, so a reader that dereferences *@slot under rcu_read_lock
+ * cannot use it after free.
+ * The caller must serialise writers and may sleep here.
+ */
+int ve_perms_apply(struct kmapset_map __rcu **slot, struct kmapset_key *key,
+		   bool super, int mask)
+{
+	struct kmapset_map *map, *old;
+	int ret = 0;
+
+	map = kmapset_dup(rcu_dereference_protected(*slot, true));
+	if (!map)
+		return -ENOMEM;
+
+	if (super)
+		kmapset_set_default(map, mask > 0 ? mask : 0);
+	else if (mask < 0)
+		kmapset_del_value(map, key);
+	else
+		ret = kmapset_set_value(map, key, mask);
+
+	if (ret) {
+		kmapset_put(map);
+		return ret;
+	}
+
+	old = rcu_dereference_protected(*slot, true);
+	rcu_assign_pointer(*slot, kmapset_commit(map));
+	kmapset_put(old);
+	return 0;
+}
+
+/*
+ * 0 if every requested rwx bit (@mask & MAY_READ|MAY_WRITE|MAY_EXEC) is
+ * granted to @key by @map, else -EACCES.
+ */
+int ve_perms_check(struct kmapset_map *map, struct kmapset_key *key, int mask)
+{
+	int perm = kmapset_get_value(map, key);
+
+	if ((mask & ~perm & (MAY_READ | MAY_WRITE | MAY_EXEC)) == 0)
+		return 0;
+	return -EACCES;
+}
+
+/* True if @key has any access on @map, used as the visibility test. */
+bool ve_perms_visible(struct kmapset_map *map, struct kmapset_key *key)
+{
+	return !!kmapset_get_value(map, key);
+}
+
+/*
+ * True if the node should be listed when dumping @key's entries. A super VE
+ * lists nodes whose default is non zero. kmapset_lookup walks an rcu list,
+ * so the caller must hold rcu_read_lock or the kmapset set mutex.
+ */
+bool ve_perms_shown(struct kmapset_map *map, struct kmapset_key *key, bool super)
+{
+	if (super)
+		return map->default_value != 0;
+	return kmapset_lookup(map, key) != NULL;
+}
+
+/* Emit " rwx\n" (or " -\n") after a path already written to @m. */
+void ve_perms_emit(struct seq_file *m, int mask)
+{
+	seq_putc(m, ' ');
+	if (!mask)
+		seq_putc(m, '-');
+	if (mask & MAY_READ)
+		seq_putc(m, 'r');
+	if (mask & MAY_WRITE)
+		seq_putc(m, 'w');
+	if (mask & MAY_EXEC)
+		seq_putc(m, 'x');
+	seq_putc(m, '\n');
+}
diff --git a/include/linux/ve-perms.h b/include/linux/ve-perms.h
new file mode 100644
index 000000000000..c9bf44b1b96b
--- /dev/null
+++ b/include/linux/ve-perms.h
@@ -0,0 +1,28 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ *  Filesystem-agnostic per-VE permission helpers, intended to be shared
+ *  by the kernfs (sysfs) and procfs per-VE permission implementations.
+ *
+ *  Copyright (c) 2026 Virtuozzo International GmbH. All rights reserved.
+ */
+#ifndef _LINUX_VE_PERMS_H
+#define _LINUX_VE_PERMS_H
+
+#include <linux/types.h>
+
+struct kmapset_map;
+struct kmapset_key;
+struct seq_file;
+
+#ifdef CONFIG_VE
+int ve_perms_parse(char *line, int *maskp);
+int ve_perms_apply(struct kmapset_map __rcu **slot, struct kmapset_key *key,
+		   bool super, int mask);
+int ve_perms_check(struct kmapset_map *map, struct kmapset_key *key, int mask);
+bool ve_perms_visible(struct kmapset_map *map, struct kmapset_key *key);
+bool ve_perms_shown(struct kmapset_map *map, struct kmapset_key *key,
+		    bool super);
+void ve_perms_emit(struct seq_file *m, int mask);
+#endif
+
+#endif /* _LINUX_VE_PERMS_H */
-- 
2.43.0



More information about the Devel mailing list