[Devel] [PATCH hci-8.1 v2] fs/fuse: strictly segregate vstorage features of generic fuse

Konstantin Khorenko khorenko at virtuozzo.com
Wed Sep 23 19:13:35 MSK 2026


From: Alexey Kuznetsov <kuznet at virtuozzo.com>

1. Makes explicit use of option "close_wait" as specific to vstorage.
   The option requires some time to be fully engaged, the support
   should appear in kernel first then we use it in userspace,
   then we can get rid of original ugly way relying on special
   string name [pv]storage://

2. Option "close_wait" is privileged. It cannot be used inside
   containers and ordinary users of fusermount are not allowed.

3. Our enhancements like multiple queues, zero-copy transfers,
   lease invalidate FUSE_NOTIFY_INVAL_FILES etc. are explicitly
   prohibited w/o close_wait.

4. When close_wait is enabled, we explicitly disallow to use
   creepy bug prone fuse features like fuse interrupts, resends,
   we reject all obscure notifications except for supported ones,
   which is now only FUSE_NOTIFY_INVAL_FILES and value 4, which
   will be dropped after old user space is washed out

https://virtuozzo.atlassian.net/browse/VSTOR-144529
Feature: vStorage

Signed-off-by: Alexey Kuznetsov <kuznet at virtuozzo.com>
Reviewed-by: Konstantin Khorenko <khorenko at virtuozzo.com>
---
v1 -> v2:
 - is_close_wait_allowed() returns bool instead of int (khorenko@)

 fs/fuse/dev.c    | 32 ++++++++++++++++++--------
 fs/fuse/fuse_i.h |  5 -----
 fs/fuse/inode.c  | 58 ++++++++++++++++++++++++++++++++++++++----------
 3 files changed, 69 insertions(+), 26 deletions(-)

diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c
index a9de0f815774a..6a22fa73330b7 100644
--- a/fs/fuse/dev.c
+++ b/fs/fuse/dev.c
@@ -2019,6 +2019,8 @@ static int fuse_notify_inval_files(struct fuse_dev *fud, unsigned int size,
 	struct fuse_notify_inval_files_out outarg;
 	int err = -EINVAL;
 
+	if (!fc->close_wait)
+		goto err;
 	if (size != sizeof(outarg))
 		goto err;
 
@@ -2048,8 +2050,19 @@ static int fuse_notify(struct fuse_dev *fud, enum fuse_notify_code code,
 	cs->move_pages = 0;
 
 	/* ASSUMPTION: pstorage fused doesn't use FUSE_NOTIFY_STORE */
-	if (fc->compat_inval_files && code == 4)
-		code = FUSE_NOTIFY_INVAL_FILES;
+	if (fc->close_wait) {
+		if (code == 4)
+			code = FUSE_NOTIFY_INVAL_FILES;
+		if (code != FUSE_NOTIFY_INVAL_FILES) {
+			fuse_copy_finish(cs);
+			return -EINVAL;
+		}
+	} else {
+		if (code == FUSE_NOTIFY_INVAL_FILES) {
+			fuse_copy_finish(cs);
+			return -EINVAL;
+		}
+	}
 
 	switch (code) {
 	case FUSE_NOTIFY_POLL:
@@ -2412,10 +2425,11 @@ static ssize_t fuse_dev_do_write(struct fuse_dev *fud,
 	}
 
 	err = -EINVAL;
-	if (oh.error != FUSE_OUT_SPLICES &&
-		oh.error != FUSE_OUT_KRPCZC &&
-	    (oh.error <= -512 || oh.error > 0))
-		goto copy_finish;
+	if (oh.error <= -512 || oh.error > 0) {
+		if (!fc->close_wait ||
+		    (oh.error != FUSE_OUT_SPLICES && oh.error != FUSE_OUT_KRPCZC))
+			goto copy_finish;
+	}
 
 	spin_lock(&fpq->lock);
 	req = NULL;
@@ -2438,7 +2452,7 @@ static ssize_t fuse_dev_do_write(struct fuse_dev *fud,
 			err = -EINVAL;
 		else if (oh.error == -ENOSYS)
 			fc->no_interrupt = 1;
-		else if (oh.error == -EAGAIN)
+		else if (oh.error == -EAGAIN && !fc->no_interrupt)
 			err = queue_interrupt(req->args->fiq, req);
 
 		fuse_put_request(req);
@@ -2455,10 +2469,10 @@ static ssize_t fuse_dev_do_write(struct fuse_dev *fud,
 	if (!req->args->page_replace)
 		cs->move_pages = 0;
 
-	if (oh.error == FUSE_OUT_SPLICES) {
+	if (oh.error == FUSE_OUT_SPLICES && fc->close_wait) {
 		req->out.h.error = 0;
 		err = copy_out_splices(cs, req->args, nbytes);
-	} else if (oh.error == FUSE_OUT_KRPCZC) {
+	} else if (oh.error == FUSE_OUT_KRPCZC && fc->close_wait) {
 		req->out.h.error = 0;
 		err = copy_out_krpczc(cs, req->args, nbytes);
 	} else if (oh.error)
diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h
index 8d90f43f01c92..21f8733376270 100644
--- a/fs/fuse/fuse_i.h
+++ b/fs/fuse/fuse_i.h
@@ -669,8 +669,6 @@ struct fuse_fs_context {
 	bool umount_wait:1;
 	bool close_wait:1;
 	bool disable_close_wait:1;
-	bool compat_inval_files:1;
-	bool no_fiemap:1;
 	bool kdirect_io:1;
 	bool destroy:1;
 	bool no_control:1;
@@ -1056,9 +1054,6 @@ struct fuse_conn {
 	/** Does the disable synchronous close? */
 	unsigned disable_close_wait:1;
 
-	/** Handle wrong FUSE_NOTIFY_INVAL_FILES from old fused */
-	unsigned compat_inval_files:1;
-
 	/** No ioctl(FIEMAP) */
 	unsigned no_fiemap:1;
 
diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c
index ea158367a1efd..eb2e9a4f87564 100644
--- a/fs/fuse/inode.c
+++ b/fs/fuse/inode.c
@@ -912,6 +912,7 @@ enum {
 	OPT_ODIRECT,
 	OPT_UMOUNT_WAIT,
 	OPT_DISABLE_CLOSE_WAIT,
+	OPT_CLOSE_WAIT,
 	OPT_ERR,
 	OPT_KIO_NAME
 };
@@ -930,11 +931,18 @@ static const struct fs_parameter_spec fuse_fs_parameters[] = {
 	fsparam_flag	("direct_enable",	OPT_ODIRECT),
 	fsparam_flag	("umount_wait",		OPT_UMOUNT_WAIT),
 	fsparam_flag	("disable_close_wait",	OPT_DISABLE_CLOSE_WAIT),
+	fsparam_flag	("close_wait",		OPT_CLOSE_WAIT),
 	fsparam_string	("kdirect",		OPT_KIO_NAME),
 	fsparam_string	("subtype",		OPT_SUBTYPE),
 	{}
 };
 
+static bool is_close_wait_allowed(void)
+{
+	return ve_is_super(get_exec_env()) && capable(CAP_SYS_ADMIN) &&
+		uid_eq(current_uid(), GLOBAL_ROOT_UID);
+}
+
 static int fuse_parse_param(struct fs_context *fsc, struct fs_parameter *param)
 {
 	struct fs_parse_result result;
@@ -966,19 +974,22 @@ static int fuse_parse_param(struct fs_context *fsc, struct fs_parameter *param)
 		param->string = NULL;
 
 		/* Hack to distinguish pcs fuse service and to force
-		 * synchronous close for it.
-		 *
-		 * libfuse is not ready for fiemap autodetection, so disable
-		 * fiemap support check in advance for all FUSE fs except for
-		 * pStorage.
+		 * synchronous close for it. It is on way out, we are going
+		 * to switch to mount option "close_wait", but this requires
+		 * full cycle: support mount option in kernel, switch
+		 * user space, wait till old user space is gone.
 		 */
 		if (fsc->source &&
 		    (strncmp(fsc->source, "pstorage://", 11) == 0 ||
 		     strncmp(fsc->source, "vstorage://", 11) == 0)) {
-			ctx->close_wait = 1;
-			ctx->compat_inval_files = 1;
-		} else
-			ctx->no_fiemap = 1;
+			if (is_close_wait_allowed())
+				ctx->close_wait = 1;
+			/* If not allowed, just ignore. We can use vstorage mount
+			 * in restricted environment, but with disabled
+			 * features like multiple queues, precise metadata
+			 * tracking and kernel acceleration.
+			 */
+		}
 
 		break;
 
@@ -1066,6 +1077,13 @@ static int fuse_parse_param(struct fs_context *fsc, struct fs_parameter *param)
 		ctx->close_wait = 0;
 		break;
 
+	case OPT_CLOSE_WAIT:
+		if (is_close_wait_allowed())
+			ctx->close_wait = 1;
+		else
+			return -EPERM;
+		break;
+
 	case OPT_KIO_NAME: {
 		if (!ve_is_super(get_exec_env()))
 			return -EPERM;
@@ -1118,6 +1136,8 @@ static int fuse_show_options(struct seq_file *m, struct dentry *root)
 			seq_puts(m, ",umount_wait");
 		if (fc->disable_close_wait)
 			seq_puts(m, ",disable_close_wait");
+		if (fc->close_wait)
+			seq_puts(m, ",close_wait");
 		if (fc->max_read != ~0)
 			seq_printf(m, ",max_read=%u", fc->max_read);
 		if (sb->s_bdev && sb->s_blocksize != FUSE_DEFAULT_BLKSIZE)
@@ -1241,6 +1261,9 @@ int fuse_install_iq_route(struct fuse_dev *fud, struct fuse_iq_routing *req)
 		&fud->fc->rrt;
 	struct fuse_rtable rtl;
 
+	if (!fud->fc->close_wait)
+		return -EINVAL;
+
 	if (rt->type != FUSE_ROUTING_NONE && rt->type != req->type)
 		return -EINVAL;
 
@@ -2182,17 +2205,28 @@ int fuse_fill_super_common(struct super_block *sb, struct fuse_fs_context *ctx)
 	fc->umount_wait = ctx->umount_wait;
 	fc->close_wait = ctx->close_wait;
 	fc->disable_close_wait = ctx->disable_close_wait;
-	fc->compat_inval_files = ctx->compat_inval_files;
-	fc->no_fiemap = ctx->no_fiemap;
+	fc->no_fiemap = 0;
 	fc->writeback_cache = ctx->writeback_cache;
 	fc->destroy = ctx->destroy || ctx->umount_wait;
 	fc->no_control = ctx->no_control;
 	fc->no_force_umount = ctx->no_force_umount;
 	fc->kdirect_io = ctx->kdirect_io;
 
+	if (fc->close_wait) {
+		fc->no_interrupt = 1;
+	} else {
+		/* libfuse is not ready for fiemap autodetection, so disable
+		 * fiemap support check in advance for all FUSE fs except for
+		 * pStorage.
+		 * NOTE as of 260912. This might be not true now, yet this
+		 * still makes sense.
+		 */
+		fc->no_fiemap = 1;
+	}
+
 	if (fc->kdirect_io) {
 		err = -EINVAL;
-		if (!fc->writeback_cache)
+		if (!fc->writeback_cache || !fc->close_wait)
 			goto err_dev_free;
 
 		fc->kio.cached_op = fuse_kio_get(fc, ctx->kio_name);
-- 
2.43.0



More information about the Devel mailing list