[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