linux-fsdevel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v15 0/9] FUSE passthrough for file io
@ 2024-02-06 14:24 Amir Goldstein
  2024-02-06 14:24 ` [PATCH v15 1/9] fuse: factor out helper for FUSE_DEV_IOC_CLONE Amir Goldstein
                   ` (8 more replies)
  0 siblings, 9 replies; 27+ messages in thread
From: Amir Goldstein @ 2024-02-06 14:24 UTC (permalink / raw)
  To: Miklos Szeredi; +Cc: Bernd Schubert, linux-fsdevel

Miklos,

The fuse passthrough code of v14 patches [1] was failing some tests
that did mmaped write and failed due to stale attribute cache.

To resolve that issue and to clarify semantics, I worked together with
Bernd to implement inode io modes [2].

When a FUSE inode is open in passthrough mode, it cannot be opened
in caching mode and that inode has an associated backing file, which
is like an O_PATH file to hold a reference to the backing inode.

That backing inode is used by the last patch to auto-invalidate inode
attributes in passthrough mode to fix the failing tests.
In theory, the backing inode could be used to copy the attributes to
FUSE inode (overlayfs style), but the code leaves GETATTR in the hands
of the server, in case backing inode and FUSE inode attributes differ.

These patches are based on my latest fuse_io_mode branch [3], where
I have addressed your comments on my patch in Bernd's v2 patches,
including all requested cleanups.

I was going to wait for Bernd to test and re-post the io mode patches,
but since you said that you really hope we can get fuse-backing-fd [4]
into shape for the next merge window, I decided to post the patches.

Thanks,
Amir.

Changes since v14:
- backing_file helpers already merged
- Remove readdir passthrough
- Associate backing file to inode
- Use io modes to prevent open of inode in conflicting modes
- Prevent open of inodes with conflicting backing files
- Auto-invalidate inode attributes in passthrough mode

[1] https://lore.kernel.org/linux-fsdevel/20231016160902.2316986-1-amir73il@gmail.com/
[2] https://lore.kernel.org/linux-fsdevel/20240131230827.207552-1-bschubert@ddn.com/
[3] https://github.com/amir73il/linux/commits/fuse_io_mode-060224
[4] https://github.com/amir73il/linux/commits/fuse-backing-fd-v15

Amir Goldstein (9):
  fuse: factor out helper for FUSE_DEV_IOC_CLONE
  fuse: introduce FUSE_PASSTHROUGH capability
  fuse: implement ioctls to manage backing files
  fuse: prepare for opening file in passthrough mode
  fuse: implement open in passthrough mode
  fuse: implement read/write passthrough
  fuse: implement splice read/write passthrough
  fuse: implement passthrough for mmap
  fuse: auto-invalidate inode attributes in passthrough mode

 fs/fuse/Kconfig           |  11 +
 fs/fuse/Makefile          |   1 +
 fs/fuse/dev.c             |  98 ++++++---
 fs/fuse/dir.c             |  16 +-
 fs/fuse/file.c            |  99 ++++++---
 fs/fuse/fuse_i.h          | 118 ++++++++++-
 fs/fuse/inode.c           |  35 ++++
 fs/fuse/iomode.c          | 105 ++++++++--
 fs/fuse/passthrough.c     | 410 ++++++++++++++++++++++++++++++++++++++
 include/uapi/linux/fuse.h |  23 ++-
 10 files changed, 838 insertions(+), 78 deletions(-)
 create mode 100644 fs/fuse/passthrough.c

-- 
2.34.1


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

* [PATCH v15 1/9] fuse: factor out helper for FUSE_DEV_IOC_CLONE
  2024-02-06 14:24 [PATCH v15 0/9] FUSE passthrough for file io Amir Goldstein
@ 2024-02-06 14:24 ` Amir Goldstein
  2024-02-06 14:24 ` [PATCH v15 2/9] fuse: introduce FUSE_PASSTHROUGH capability Amir Goldstein
                   ` (7 subsequent siblings)
  8 siblings, 0 replies; 27+ messages in thread
From: Amir Goldstein @ 2024-02-06 14:24 UTC (permalink / raw)
  To: Miklos Szeredi; +Cc: Bernd Schubert, linux-fsdevel

In preparation to adding more fuse dev ioctls.

Signed-off-by: Amir Goldstein <amir73il@gmail.com>
---
 fs/fuse/dev.c | 59 ++++++++++++++++++++++++++++-----------------------
 1 file changed, 33 insertions(+), 26 deletions(-)

diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c
index 1a8f82f478cb..eba68b57bd7c 100644
--- a/fs/fuse/dev.c
+++ b/fs/fuse/dev.c
@@ -2251,43 +2251,50 @@ static int fuse_device_clone(struct fuse_conn *fc, struct file *new)
 	return 0;
 }
 
-static long fuse_dev_ioctl(struct file *file, unsigned int cmd,
-			   unsigned long arg)
+static long fuse_dev_ioctl_clone(struct file *file, __u32 __user *argp)
 {
 	int res;
 	int oldfd;
 	struct fuse_dev *fud = NULL;
 	struct fd f;
 
+	if (get_user(oldfd, argp))
+		return -EFAULT;
+
+	f = fdget(oldfd);
+	if (!f.file)
+		return -EINVAL;
+
+	/*
+	 * Check against file->f_op because CUSE
+	 * uses the same ioctl handler.
+	 */
+	if (f.file->f_op == file->f_op)
+		fud = fuse_get_dev(f.file);
+
+	res = -EINVAL;
+	if (fud) {
+		mutex_lock(&fuse_mutex);
+		res = fuse_device_clone(fud->fc, file);
+		mutex_unlock(&fuse_mutex);
+	}
+
+	fdput(f);
+	return res;
+}
+
+static long fuse_dev_ioctl(struct file *file, unsigned int cmd,
+			   unsigned long arg)
+{
+	void __user *argp = (void __user *)arg;
+
 	switch (cmd) {
 	case FUSE_DEV_IOC_CLONE:
-		if (get_user(oldfd, (__u32 __user *)arg))
-			return -EFAULT;
+		return fuse_dev_ioctl_clone(file, argp);
 
-		f = fdget(oldfd);
-		if (!f.file)
-			return -EINVAL;
-
-		/*
-		 * Check against file->f_op because CUSE
-		 * uses the same ioctl handler.
-		 */
-		if (f.file->f_op == file->f_op)
-			fud = fuse_get_dev(f.file);
-
-		res = -EINVAL;
-		if (fud) {
-			mutex_lock(&fuse_mutex);
-			res = fuse_device_clone(fud->fc, file);
-			mutex_unlock(&fuse_mutex);
-		}
-		fdput(f);
-		break;
 	default:
-		res = -ENOTTY;
-		break;
+		return -ENOTTY;
 	}
-	return res;
 }
 
 const struct file_operations fuse_dev_operations = {
-- 
2.34.1


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

* [PATCH v15 2/9] fuse: introduce FUSE_PASSTHROUGH capability
  2024-02-06 14:24 [PATCH v15 0/9] FUSE passthrough for file io Amir Goldstein
  2024-02-06 14:24 ` [PATCH v15 1/9] fuse: factor out helper for FUSE_DEV_IOC_CLONE Amir Goldstein
@ 2024-02-06 14:24 ` Amir Goldstein
  2024-02-06 14:24 ` [PATCH v15 3/9] fuse: implement ioctls to manage backing files Amir Goldstein
                   ` (6 subsequent siblings)
  8 siblings, 0 replies; 27+ messages in thread
From: Amir Goldstein @ 2024-02-06 14:24 UTC (permalink / raw)
  To: Miklos Szeredi; +Cc: Bernd Schubert, linux-fsdevel

FUSE_PASSTHROUGH capability to passthrough FUSE operations to backing
files will be made available with kernel config CONFIG_FUSE_PASSTHROUGH.

When requesting FUSE_PASSTHROUGH, userspace needs to specify the
max_stack_depth that is allowed for FUSE on top of backing files.

Introduce the flag FOPEN_PASSTHROUGH and backing_id to fuse_open_out
argument that can be used when replying to OPEN request, to setup
passthrough of io operations on the fuse inode to a backing file.

Introduce a refcounted fuse_backing object that will be used to
associate an open backing file with a fuse inode.

Signed-off-by: Amir Goldstein <amir73il@gmail.com>
---
 fs/fuse/Kconfig           | 11 ++++++++++
 fs/fuse/Makefile          |  1 +
 fs/fuse/fuse_i.h          | 42 +++++++++++++++++++++++++++++++++++++++
 fs/fuse/inode.c           | 26 ++++++++++++++++++++++++
 fs/fuse/passthrough.c     | 30 ++++++++++++++++++++++++++++
 include/uapi/linux/fuse.h | 14 ++++++++++---
 6 files changed, 121 insertions(+), 3 deletions(-)
 create mode 100644 fs/fuse/passthrough.c

diff --git a/fs/fuse/Kconfig b/fs/fuse/Kconfig
index 038ed0b9aaa5..8674dbfbe59d 100644
--- a/fs/fuse/Kconfig
+++ b/fs/fuse/Kconfig
@@ -52,3 +52,14 @@ config FUSE_DAX
 
 	  If you want to allow mounting a Virtio Filesystem with the "dax"
 	  option, answer Y.
+
+config FUSE_PASSTHROUGH
+	bool "FUSE passthrough operations support"
+	default y
+	depends on FUSE_FS
+	select FS_STACK
+	help
+	  This allows bypassing FUSE server by mapping specific FUSE operations
+	  to be performed directly on a backing file.
+
+	  If you want to allow passthrough operations, answer Y.
diff --git a/fs/fuse/Makefile b/fs/fuse/Makefile
index b734cc2a5e65..6e0228c6d0cb 100644
--- a/fs/fuse/Makefile
+++ b/fs/fuse/Makefile
@@ -10,5 +10,6 @@ obj-$(CONFIG_VIRTIO_FS) += virtiofs.o
 fuse-y := dev.o dir.o file.o inode.o control.o xattr.o acl.o readdir.o ioctl.o
 fuse-y += iomode.o
 fuse-$(CONFIG_FUSE_DAX) += dax.o
+fuse-$(CONFIG_FUSE_PASSTHROUGH) += passthrough.o
 
 virtiofs-y := virtio_fs.o
diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h
index dede4378c719..cae525147856 100644
--- a/fs/fuse/fuse_i.h
+++ b/fs/fuse/fuse_i.h
@@ -76,6 +76,15 @@ struct fuse_submount_lookup {
 	struct fuse_forget_link *forget;
 };
 
+/** Container for data related to mapping to backing file */
+struct fuse_backing {
+	struct file *file;
+
+	/** refcount */
+	refcount_t count;
+	struct rcu_head rcu;
+};
+
 /** FUSE inode */
 struct fuse_inode {
 	/** Inode data */
@@ -179,6 +188,10 @@ struct fuse_inode {
 #endif
 	/** Submount specific lookup tracking */
 	struct fuse_submount_lookup *submount_lookup;
+#ifdef CONFIG_FUSE_PASSTHROUGH
+	/** Reference to backing file in passthrough mode */
+	struct fuse_backing *fb;
+#endif
 };
 
 /** FUSE inode state bits */
@@ -829,6 +842,12 @@ struct fuse_conn {
 	/* Is statx not implemented by fs? */
 	unsigned int no_statx:1;
 
+	/** Passthrough support for read/write IO */
+	unsigned int passthrough:1;
+
+	/** Maximum stack depth for passthrough backing files */
+	int max_stack_depth;
+
 	/** The number of requests waiting for completion */
 	atomic_t num_waiting;
 
@@ -1373,4 +1392,27 @@ struct fuse_file *fuse_file_open(struct fuse_mount *fm, u64 nodeid,
 void fuse_file_release(struct inode *inode, struct fuse_file *ff,
 		       unsigned int open_flags, fl_owner_t id, bool isdir);
 
+/* passthrough.c */
+static inline struct fuse_backing *fuse_inode_backing(struct fuse_inode *fi)
+{
+#ifdef CONFIG_FUSE_PASSTHROUGH
+	return READ_ONCE(fi->fb);
+#else
+	return NULL;
+#endif
+}
+
+static inline struct fuse_backing *fuse_inode_backing_set(struct fuse_inode *fi,
+							  struct fuse_backing *fb)
+{
+#ifdef CONFIG_FUSE_PASSTHROUGH
+	return xchg(&fi->fb, fb);
+#else
+	return NULL;
+#endif
+}
+
+struct fuse_backing *fuse_backing_get(struct fuse_backing *fb);
+void fuse_backing_put(struct fuse_backing *fb);
+
 #endif /* _FS_FUSE_I_H */
diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c
index 2a6d44f91729..c771bd3c1336 100644
--- a/fs/fuse/inode.c
+++ b/fs/fuse/inode.c
@@ -111,6 +111,9 @@ static struct inode *fuse_alloc_inode(struct super_block *sb)
 	if (IS_ENABLED(CONFIG_FUSE_DAX) && !fuse_dax_inode_alloc(sb, fi))
 		goto out_free_forget;
 
+	if (IS_ENABLED(CONFIG_FUSE_PASSTHROUGH))
+		fuse_inode_backing_set(fi, NULL);
+
 	return &fi->inode;
 
 out_free_forget:
@@ -129,6 +132,9 @@ static void fuse_free_inode(struct inode *inode)
 #ifdef CONFIG_FUSE_DAX
 	kfree(fi->dax);
 #endif
+	if (IS_ENABLED(CONFIG_FUSE_PASSTHROUGH))
+		fuse_backing_put(fuse_inode_backing(fi));
+
 	kmem_cache_free(fuse_inode_cachep, fi);
 }
 
@@ -1284,6 +1290,24 @@ static void process_init_reply(struct fuse_mount *fm, struct fuse_args *args,
 				fc->create_supp_group = 1;
 			if (flags & FUSE_DIRECT_IO_ALLOW_MMAP)
 				fc->direct_io_allow_mmap = 1;
+			/*
+			 * max_stack_depth is the max stack depth of FUSE fs,
+			 * so it has to be at least 1 to support passthrough
+			 * to backing files.
+			 *
+			 * with max_stack_depth > 1, the backing files can be
+			 * on a stacked fs (e.g. overlayfs) themselves and with
+			 * max_stack_depth == 1, FUSE fs can be stacked as the
+			 * underlying fs of a stacked fs (e.g. overlayfs).
+			 */
+			if (IS_ENABLED(CONFIG_FUSE_PASSTHROUGH) &&
+			    (flags & FUSE_PASSTHROUGH) &&
+			    arg->max_stack_depth > 0 &&
+			    arg->max_stack_depth <= FILESYSTEM_MAX_STACK_DEPTH) {
+				fc->passthrough = 1;
+				fc->max_stack_depth = arg->max_stack_depth;
+				fm->sb->s_stack_depth = arg->max_stack_depth;
+			}
 		} else {
 			ra_pages = fc->max_read / PAGE_SIZE;
 			fc->no_lock = 1;
@@ -1339,6 +1363,8 @@ void fuse_send_init(struct fuse_mount *fm)
 #endif
 	if (fm->fc->auto_submounts)
 		flags |= FUSE_SUBMOUNTS;
+	if (IS_ENABLED(CONFIG_FUSE_PASSTHROUGH))
+		flags |= FUSE_PASSTHROUGH;
 
 	ia->in.flags = flags;
 	ia->in.flags2 = flags >> 32;
diff --git a/fs/fuse/passthrough.c b/fs/fuse/passthrough.c
new file mode 100644
index 000000000000..e8639c0a9ac6
--- /dev/null
+++ b/fs/fuse/passthrough.c
@@ -0,0 +1,30 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * FUSE passthrough to backing file.
+ *
+ * Copyright (c) 2023 CTERA Networks.
+ */
+
+#include "fuse_i.h"
+
+#include <linux/file.h>
+
+struct fuse_backing *fuse_backing_get(struct fuse_backing *fb)
+{
+	if (fb && refcount_inc_not_zero(&fb->count))
+		return fb;
+	return NULL;
+}
+
+static void fuse_backing_free(struct fuse_backing *fb)
+{
+	if (fb->file)
+		fput(fb->file);
+	kfree_rcu(fb, rcu);
+}
+
+void fuse_backing_put(struct fuse_backing *fb)
+{
+	if (fb && refcount_dec_and_test(&fb->count))
+		fuse_backing_free(fb);
+}
diff --git a/include/uapi/linux/fuse.h b/include/uapi/linux/fuse.h
index 1f452d9a5024..edfc458e5e8f 100644
--- a/include/uapi/linux/fuse.h
+++ b/include/uapi/linux/fuse.h
@@ -211,6 +211,10 @@
  *  7.39
  *  - add FUSE_DIRECT_IO_ALLOW_MMAP
  *  - add FUSE_STATX and related structures
+ *
+ *  7.40
+ *  - add max_stack_depth to fuse_init_out, add FUSE_PASSTHROUGH init flag
+ *  - add backing_id to fuse_open_out, add FOPEN_PASSTHROUGH open flag
  */
 
 #ifndef _LINUX_FUSE_H
@@ -246,7 +250,7 @@
 #define FUSE_KERNEL_VERSION 7
 
 /** Minor version number of this interface */
-#define FUSE_KERNEL_MINOR_VERSION 39
+#define FUSE_KERNEL_MINOR_VERSION 40
 
 /** The node ID of the root inode */
 #define FUSE_ROOT_ID 1
@@ -354,6 +358,7 @@ struct fuse_file_lock {
  * FOPEN_NOFLUSH: don't flush data cache on close (unless FUSE_WRITEBACK_CACHE)
  * FOPEN_PARALLEL_DIRECT_WRITES: Allow concurrent direct writes on the same inode
  * FOPEN_CACHE_IO: internal flag for mmap of direct_io (resereved for future use)
+ * FOPEN_PASSTHROUGH: passthrough read/write io for this open file
  */
 #define FOPEN_DIRECT_IO		(1 << 0)
 #define FOPEN_KEEP_CACHE	(1 << 1)
@@ -363,6 +368,7 @@ struct fuse_file_lock {
 #define FOPEN_NOFLUSH		(1 << 5)
 #define FOPEN_PARALLEL_DIRECT_WRITES	(1 << 6)
 #define FOPEN_CACHE_IO		(1 << 7)
+#define FOPEN_PASSTHROUGH	(1 << 8)
 
 /**
  * INIT request/reply flags
@@ -451,6 +457,7 @@ struct fuse_file_lock {
 #define FUSE_CREATE_SUPP_GROUP	(1ULL << 34)
 #define FUSE_HAS_EXPIRE_ONLY	(1ULL << 35)
 #define FUSE_DIRECT_IO_ALLOW_MMAP (1ULL << 36)
+#define FUSE_PASSTHROUGH	(1ULL << 37)
 
 /* Obsolete alias for FUSE_DIRECT_IO_ALLOW_MMAP */
 #define FUSE_DIRECT_IO_RELAX	FUSE_DIRECT_IO_ALLOW_MMAP
@@ -763,7 +770,7 @@ struct fuse_create_in {
 struct fuse_open_out {
 	uint64_t	fh;
 	uint32_t	open_flags;
-	uint32_t	padding;
+	int32_t		backing_id;
 };
 
 struct fuse_release_in {
@@ -879,7 +886,8 @@ struct fuse_init_out {
 	uint16_t	max_pages;
 	uint16_t	map_alignment;
 	uint32_t	flags2;
-	uint32_t	unused[7];
+	uint32_t	max_stack_depth;
+	uint32_t	unused[6];
 };
 
 #define CUSE_INIT_INFO_MAX 4096
-- 
2.34.1


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

* [PATCH v15 3/9] fuse: implement ioctls to manage backing files
  2024-02-06 14:24 [PATCH v15 0/9] FUSE passthrough for file io Amir Goldstein
  2024-02-06 14:24 ` [PATCH v15 1/9] fuse: factor out helper for FUSE_DEV_IOC_CLONE Amir Goldstein
  2024-02-06 14:24 ` [PATCH v15 2/9] fuse: introduce FUSE_PASSTHROUGH capability Amir Goldstein
@ 2024-02-06 14:24 ` Amir Goldstein
  2024-02-28 10:50   ` Jingbo Xu
  2024-02-06 14:24 ` [PATCH v15 4/9] fuse: prepare for opening file in passthrough mode Amir Goldstein
                   ` (5 subsequent siblings)
  8 siblings, 1 reply; 27+ messages in thread
From: Amir Goldstein @ 2024-02-06 14:24 UTC (permalink / raw)
  To: Miklos Szeredi; +Cc: Bernd Schubert, linux-fsdevel, Alessio Balsini

FUSE server calls the FUSE_DEV_IOC_BACKING_OPEN ioctl with a backing file
descriptor.  If the call succeeds, a backing file identifier is returned.

A later change will be using this backing file id in a reply to OPEN
request with the flag FOPEN_PASSTHROUGH to setup passthrough of file
operations on the open FUSE file to the backing file.

The FUSE server should call FUSE_DEV_IOC_BACKING_CLOSE ioctl to close the
backing file by its id.

This can be done at any time, but if an open reply with FOPEN_PASSTHROUGH
flag is still in progress, the open may fail if the backing file is
closed before the fuse file was opened.

Setting up backing files requires a server with CAP_SYS_ADMIN privileges.
For the backing file to be successfully setup, the backing file must
implement both read_iter and write_iter file operations.

The limitation on the level of filesystem stacking allowed for the
backing file is enforced before setting up the backing file.

Signed-off-by: Alessio Balsini <balsini@android.com>
Signed-off-by: Amir Goldstein <amir73il@gmail.com>
---
 fs/fuse/dev.c             |  41 ++++++++++++
 fs/fuse/fuse_i.h          |  10 +++
 fs/fuse/inode.c           |   5 ++
 fs/fuse/passthrough.c     | 135 ++++++++++++++++++++++++++++++++++++++
 include/uapi/linux/fuse.h |   9 +++
 5 files changed, 200 insertions(+)

diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c
index eba68b57bd7c..b680787bd66d 100644
--- a/fs/fuse/dev.c
+++ b/fs/fuse/dev.c
@@ -2283,6 +2283,41 @@ static long fuse_dev_ioctl_clone(struct file *file, __u32 __user *argp)
 	return res;
 }
 
+static long fuse_dev_ioctl_backing_open(struct file *file,
+					struct fuse_backing_map __user *argp)
+{
+	struct fuse_dev *fud = fuse_get_dev(file);
+	struct fuse_backing_map map;
+
+	if (!fud)
+		return -EINVAL;
+
+	if (!IS_ENABLED(CONFIG_FUSE_PASSTHROUGH))
+		return -EOPNOTSUPP;
+
+	if (copy_from_user(&map, argp, sizeof(map)))
+		return -EFAULT;
+
+	return fuse_backing_open(fud->fc, &map);
+}
+
+static long fuse_dev_ioctl_backing_close(struct file *file, __u32 __user *argp)
+{
+	struct fuse_dev *fud = fuse_get_dev(file);
+	int backing_id;
+
+	if (!fud)
+		return -EINVAL;
+
+	if (!IS_ENABLED(CONFIG_FUSE_PASSTHROUGH))
+		return -EOPNOTSUPP;
+
+	if (get_user(backing_id, argp))
+		return -EFAULT;
+
+	return fuse_backing_close(fud->fc, backing_id);
+}
+
 static long fuse_dev_ioctl(struct file *file, unsigned int cmd,
 			   unsigned long arg)
 {
@@ -2292,6 +2327,12 @@ static long fuse_dev_ioctl(struct file *file, unsigned int cmd,
 	case FUSE_DEV_IOC_CLONE:
 		return fuse_dev_ioctl_clone(file, argp);
 
+	case FUSE_DEV_IOC_BACKING_OPEN:
+		return fuse_dev_ioctl_backing_open(file, argp);
+
+	case FUSE_DEV_IOC_BACKING_CLOSE:
+		return fuse_dev_ioctl_backing_close(file, argp);
+
 	default:
 		return -ENOTTY;
 	}
diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h
index cae525147856..fb9ef02cbf45 100644
--- a/fs/fuse/fuse_i.h
+++ b/fs/fuse/fuse_i.h
@@ -79,6 +79,7 @@ struct fuse_submount_lookup {
 /** Container for data related to mapping to backing file */
 struct fuse_backing {
 	struct file *file;
+	struct cred *cred;
 
 	/** refcount */
 	refcount_t count;
@@ -897,6 +898,11 @@ struct fuse_conn {
 
 	/* New writepages go into this bucket */
 	struct fuse_sync_bucket __rcu *curr_bucket;
+
+#ifdef CONFIG_FUSE_PASSTHROUGH
+	/** IDR for backing files ids */
+	struct idr backing_files_map;
+#endif
 };
 
 /*
@@ -1414,5 +1420,9 @@ static inline struct fuse_backing *fuse_inode_backing_set(struct fuse_inode *fi,
 
 struct fuse_backing *fuse_backing_get(struct fuse_backing *fb);
 void fuse_backing_put(struct fuse_backing *fb);
+void fuse_backing_files_init(struct fuse_conn *fc);
+void fuse_backing_files_free(struct fuse_conn *fc);
+int fuse_backing_open(struct fuse_conn *fc, struct fuse_backing_map *map);
+int fuse_backing_close(struct fuse_conn *fc, int backing_id);
 
 #endif /* _FS_FUSE_I_H */
diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c
index c771bd3c1336..c26a84439934 100644
--- a/fs/fuse/inode.c
+++ b/fs/fuse/inode.c
@@ -930,6 +930,9 @@ void fuse_conn_init(struct fuse_conn *fc, struct fuse_mount *fm,
 	fc->max_pages = FUSE_DEFAULT_MAX_PAGES_PER_REQ;
 	fc->max_pages_limit = FUSE_MAX_MAX_PAGES;
 
+	if (IS_ENABLED(CONFIG_FUSE_PASSTHROUGH))
+		fuse_backing_files_init(fc);
+
 	INIT_LIST_HEAD(&fc->mounts);
 	list_add(&fm->fc_entry, &fc->mounts);
 	fm->fc = fc;
@@ -1392,6 +1395,8 @@ EXPORT_SYMBOL_GPL(fuse_send_init);
 void fuse_free_conn(struct fuse_conn *fc)
 {
 	WARN_ON(!list_empty(&fc->devices));
+	if (IS_ENABLED(CONFIG_FUSE_PASSTHROUGH))
+		fuse_backing_files_free(fc);
 	kfree_rcu(fc, rcu);
 }
 EXPORT_SYMBOL_GPL(fuse_free_conn);
diff --git a/fs/fuse/passthrough.c b/fs/fuse/passthrough.c
index e8639c0a9ac6..6604d414adb5 100644
--- a/fs/fuse/passthrough.c
+++ b/fs/fuse/passthrough.c
@@ -18,8 +18,11 @@ struct fuse_backing *fuse_backing_get(struct fuse_backing *fb)
 
 static void fuse_backing_free(struct fuse_backing *fb)
 {
+	pr_debug("%s: fb=0x%p\n", __func__, fb);
+
 	if (fb->file)
 		fput(fb->file);
+	put_cred(fb->cred);
 	kfree_rcu(fb, rcu);
 }
 
@@ -28,3 +31,135 @@ void fuse_backing_put(struct fuse_backing *fb)
 	if (fb && refcount_dec_and_test(&fb->count))
 		fuse_backing_free(fb);
 }
+
+void fuse_backing_files_init(struct fuse_conn *fc)
+{
+	idr_init(&fc->backing_files_map);
+}
+
+static int fuse_backing_id_alloc(struct fuse_conn *fc, struct fuse_backing *fb)
+{
+	int id;
+
+	idr_preload(GFP_KERNEL);
+	spin_lock(&fc->lock);
+	id = idr_alloc_cyclic(&fc->backing_files_map, fb, 1, 0, GFP_ATOMIC);
+	spin_unlock(&fc->lock);
+	idr_preload_end();
+
+	WARN_ON_ONCE(id == 0);
+	return id;
+}
+
+static struct fuse_backing *fuse_backing_id_remove(struct fuse_conn *fc,
+						   int id)
+{
+	struct fuse_backing *fb;
+
+	spin_lock(&fc->lock);
+	fb = idr_remove(&fc->backing_files_map, id);
+	spin_unlock(&fc->lock);
+
+	return fb;
+}
+
+static int fuse_backing_id_free(int id, void *p, void *data)
+{
+	struct fuse_backing *fb = p;
+
+	WARN_ON_ONCE(refcount_read(&fb->count) != 1);
+	fuse_backing_free(fb);
+	return 0;
+}
+
+void fuse_backing_files_free(struct fuse_conn *fc)
+{
+	idr_for_each(&fc->backing_files_map, fuse_backing_id_free, NULL);
+	idr_destroy(&fc->backing_files_map);
+}
+
+int fuse_backing_open(struct fuse_conn *fc, struct fuse_backing_map *map)
+{
+	struct file *file;
+	struct super_block *backing_sb;
+	struct fuse_backing *fb = NULL;
+	int res;
+
+	pr_debug("%s: fd=%d flags=0x%x\n", __func__, map->fd, map->flags);
+
+	/* TODO: relax CAP_SYS_ADMIN once backing files are visible to lsof */
+	res = -EPERM;
+	if (!fc->passthrough || !capable(CAP_SYS_ADMIN))
+		goto out;
+
+	res = -EINVAL;
+	if (map->flags)
+		goto out;
+
+	file = fget(map->fd);
+	res = -EBADF;
+	if (!file)
+		goto out;
+
+	res = -EOPNOTSUPP;
+	if (!file->f_op->read_iter || !file->f_op->write_iter)
+		goto out_fput;
+
+	backing_sb = file_inode(file)->i_sb;
+	res = -ELOOP;
+	if (backing_sb->s_stack_depth >= fc->max_stack_depth)
+		goto out_fput;
+
+	fb = kmalloc(sizeof(struct fuse_backing), GFP_KERNEL);
+	res = -ENOMEM;
+	if (!fb)
+		goto out_fput;
+
+	fb->file = file;
+	fb->cred = prepare_creds();
+	refcount_set(&fb->count, 1);
+
+	res = fuse_backing_id_alloc(fc, fb);
+	if (res < 0) {
+		fuse_backing_free(fb);
+		fb = NULL;
+	}
+
+out:
+	pr_debug("%s: fb=0x%p, ret=%i\n", __func__, fb, res);
+
+	return res;
+
+out_fput:
+	fput(file);
+	goto out;
+}
+
+int fuse_backing_close(struct fuse_conn *fc, int backing_id)
+{
+	struct fuse_backing *fb = NULL;
+	int err;
+
+	pr_debug("%s: backing_id=%d\n", __func__, backing_id);
+
+	/* TODO: relax CAP_SYS_ADMIN once backing files are visible to lsof */
+	err = -EPERM;
+	if (!fc->passthrough || !capable(CAP_SYS_ADMIN))
+		goto out;
+
+	err = -EINVAL;
+	if (backing_id <= 0)
+		goto out;
+
+	err = -ENOENT;
+	fb = fuse_backing_id_remove(fc, backing_id);
+	if (!fb)
+		goto out;
+
+	fuse_backing_put(fb);
+	err = 0;
+out:
+	pr_debug("%s: fb=0x%p, err=%i\n", __func__, fb, err);
+
+	return err;
+}
diff --git a/include/uapi/linux/fuse.h b/include/uapi/linux/fuse.h
index edfc458e5e8f..af0fe3aec329 100644
--- a/include/uapi/linux/fuse.h
+++ b/include/uapi/linux/fuse.h
@@ -1059,9 +1059,18 @@ struct fuse_notify_retrieve_in {
 	uint64_t	dummy4;
 };
 
+struct fuse_backing_map {
+	int32_t		fd;
+	uint32_t	flags;
+	uint64_t	padding;
+};
+
 /* Device ioctls: */
 #define FUSE_DEV_IOC_MAGIC		229
 #define FUSE_DEV_IOC_CLONE		_IOR(FUSE_DEV_IOC_MAGIC, 0, uint32_t)
+#define FUSE_DEV_IOC_BACKING_OPEN	_IOW(FUSE_DEV_IOC_MAGIC, 1, \
+					     struct fuse_backing_map)
+#define FUSE_DEV_IOC_BACKING_CLOSE	_IOW(FUSE_DEV_IOC_MAGIC, 2, uint32_t)
 
 struct fuse_lseek_in {
 	uint64_t	fh;
-- 
2.34.1


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

* [PATCH v15 4/9] fuse: prepare for opening file in passthrough mode
  2024-02-06 14:24 [PATCH v15 0/9] FUSE passthrough for file io Amir Goldstein
                   ` (2 preceding siblings ...)
  2024-02-06 14:24 ` [PATCH v15 3/9] fuse: implement ioctls to manage backing files Amir Goldstein
@ 2024-02-06 14:24 ` Amir Goldstein
  2024-02-06 14:24 ` [PATCH v15 5/9] fuse: implement open " Amir Goldstein
                   ` (4 subsequent siblings)
  8 siblings, 0 replies; 27+ messages in thread
From: Amir Goldstein @ 2024-02-06 14:24 UTC (permalink / raw)
  To: Miklos Szeredi; +Cc: Bernd Schubert, linux-fsdevel

In preparation for opening file in passthrough mode, store the
fuse_open_out argument in ff->args to be passed into fuse_file_io_open()
with the optional backing_id member.

This will be used for setting up passthrough to backing file on open
reply with FOPEN_PASSTHROUGH flag and a valid backing_id.

Opening a file in passthrough mode may fail for several reasons, such as
missing capability, conflicting open flags or inode in caching mode.
Return EIO from fuse_file_io_open() in those cases.

The combination of FOPEN_PASSTHROUGH and FOPEN_DIRECT_IO is allowed -
it mean that read/write operations will go directly to the server,
but mmap will be done to the backing file.

Signed-off-by: Amir Goldstein <amir73il@gmail.com>
---
 fs/fuse/dir.c    | 12 +++++++-----
 fs/fuse/file.c   | 34 +++++++++++++++-------------------
 fs/fuse/fuse_i.h | 19 ++++++++++++++++---
 fs/fuse/iomode.c | 48 +++++++++++++++++++++++++++++++++++++++++-------
 4 files changed, 79 insertions(+), 34 deletions(-)

diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c
index ea635c17572a..95330c2ca3d8 100644
--- a/fs/fuse/dir.c
+++ b/fs/fuse/dir.c
@@ -615,7 +615,7 @@ static int fuse_create_open(struct inode *dir, struct dentry *entry,
 	FUSE_ARGS(args);
 	struct fuse_forget_link *forget;
 	struct fuse_create_in inarg;
-	struct fuse_open_out outopen;
+	struct fuse_open_out *outopenp;
 	struct fuse_entry_out outentry;
 	struct fuse_inode *fi;
 	struct fuse_file *ff;
@@ -659,8 +659,10 @@ static int fuse_create_open(struct inode *dir, struct dentry *entry,
 	args.out_numargs = 2;
 	args.out_args[0].size = sizeof(outentry);
 	args.out_args[0].value = &outentry;
-	args.out_args[1].size = sizeof(outopen);
-	args.out_args[1].value = &outopen;
+	/* Store outarg for fuse_finish_open() */
+	outopenp = &ff->args->open_outarg;
+	args.out_args[1].size = sizeof(*outopenp);
+	args.out_args[1].value = outopenp;
 
 	err = get_create_ext(&args, dir, entry, mode);
 	if (err)
@@ -676,9 +678,9 @@ static int fuse_create_open(struct inode *dir, struct dentry *entry,
 	    fuse_invalid_attr(&outentry.attr))
 		goto out_free_ff;
 
-	ff->fh = outopen.fh;
+	ff->fh = outopenp->fh;
 	ff->nodeid = outentry.nodeid;
-	ff->open_flags = outopen.open_flags;
+	ff->open_flags = outopenp->open_flags;
 	inode = fuse_iget(dir->i_sb, outentry.nodeid, outentry.generation,
 			  &outentry.attr, ATTR_TIMEOUT(&outentry), 0);
 	if (!inode) {
diff --git a/fs/fuse/file.c b/fs/fuse/file.c
index eb226457c4bd..04be04b6b2af 100644
--- a/fs/fuse/file.c
+++ b/fs/fuse/file.c
@@ -50,12 +50,6 @@ static int fuse_send_open(struct fuse_mount *fm, u64 nodeid,
 	return fuse_simple_request(fm, &args);
 }
 
-struct fuse_release_args {
-	struct fuse_args args;
-	struct fuse_release_in inarg;
-	struct inode *inode;
-};
-
 struct fuse_file *fuse_file_alloc(struct fuse_mount *fm, bool release)
 {
 	struct fuse_file *ff;
@@ -66,9 +60,8 @@ struct fuse_file *fuse_file_alloc(struct fuse_mount *fm, bool release)
 
 	ff->fm = fm;
 	if (release) {
-		ff->release_args = kzalloc(sizeof(*ff->release_args),
-					   GFP_KERNEL_ACCOUNT);
-		if (!ff->release_args) {
+		ff->args = kzalloc(sizeof(*ff->args), GFP_KERNEL_ACCOUNT);
+		if (!ff->args) {
 			kfree(ff);
 			return NULL;
 		}
@@ -87,7 +80,7 @@ struct fuse_file *fuse_file_alloc(struct fuse_mount *fm, bool release)
 
 void fuse_file_free(struct fuse_file *ff)
 {
-	kfree(ff->release_args);
+	kfree(ff->args);
 	mutex_destroy(&ff->readdir.lock);
 	kfree(ff);
 }
@@ -110,7 +103,7 @@ static void fuse_release_end(struct fuse_mount *fm, struct fuse_args *args,
 static void fuse_file_put(struct fuse_file *ff, bool sync)
 {
 	if (refcount_dec_and_test(&ff->count)) {
-		struct fuse_release_args *ra = ff->release_args;
+		struct fuse_release_args *ra = &ff->args->release_args;
 		struct fuse_args *args = (ra ? &ra->args : NULL);
 
 		if (ra && ra->inode)
@@ -147,20 +140,21 @@ struct fuse_file *fuse_file_open(struct fuse_mount *fm, u64 nodeid,
 	/* Default for no-open */
 	ff->open_flags = FOPEN_KEEP_CACHE | (isdir ? FOPEN_CACHE_DIR : 0);
 	if (!noopen) {
-		struct fuse_open_out outarg;
+		/* Store outarg for fuse_finish_open() */
+		struct fuse_open_out *outargp = &ff->args->open_outarg;
 		int err;
 
-		err = fuse_send_open(fm, nodeid, open_flags, opcode, &outarg);
+		err = fuse_send_open(fm, nodeid, open_flags, opcode, outargp);
 		if (!err) {
-			ff->fh = outarg.fh;
-			ff->open_flags = outarg.open_flags;
+			ff->fh = outargp->fh;
+			ff->open_flags = outargp->open_flags;
 		} else if (err != -ENOSYS) {
 			fuse_file_free(ff);
 			return ERR_PTR(err);
 		} else {
 			/* No release needed */
-			kfree(ff->release_args);
-			ff->release_args = NULL;
+			kfree(ff->args);
+			ff->args = NULL;
 			if (isdir)
 				fc->no_opendir = 1;
 			else
@@ -299,7 +293,7 @@ static void fuse_prepare_release(struct fuse_inode *fi, struct fuse_file *ff,
 				 unsigned int flags, int opcode, bool sync)
 {
 	struct fuse_conn *fc = ff->fm->fc;
-	struct fuse_release_args *ra = ff->release_args;
+	struct fuse_release_args *ra = &ff->args->release_args;
 
 	/* Inode is NULL on error path of fuse_create_open() */
 	if (likely(fi)) {
@@ -317,6 +311,8 @@ static void fuse_prepare_release(struct fuse_inode *fi, struct fuse_file *ff,
 	if (!ra)
 		return;
 
+	/* ff->args was used for open outarg */
+	memset(ff->args, 0, sizeof(*ff->args));
 	ra->inarg.fh = ff->fh;
 	ra->inarg.flags = flags;
 	ra->args.in_numargs = 1;
@@ -339,7 +335,7 @@ void fuse_file_release(struct inode *inode, struct fuse_file *ff,
 		       unsigned int open_flags, fl_owner_t id, bool isdir)
 {
 	struct fuse_inode *fi = get_fuse_inode(inode);
-	struct fuse_release_args *ra = ff->release_args;
+	struct fuse_release_args *ra = &ff->args->release_args;
 	int opcode = isdir ? FUSE_RELEASEDIR : FUSE_RELEASE;
 
 	fuse_prepare_release(fi, ff, open_flags, opcode, false);
diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h
index fb9ef02cbf45..eea8f1ffc766 100644
--- a/fs/fuse/fuse_i.h
+++ b/fs/fuse/fuse_i.h
@@ -213,15 +213,15 @@ enum {
 
 struct fuse_conn;
 struct fuse_mount;
-struct fuse_release_args;
+union fuse_file_args;
 
 /** FUSE specific file data */
 struct fuse_file {
 	/** Fuse connection for this file */
 	struct fuse_mount *fm;
 
-	/* Argument space reserved for release */
-	struct fuse_release_args *release_args;
+	/* Argument space reserved for open/release */
+	union fuse_file_args *args;
 
 	/** Kernel file handle guaranteed to be unique */
 	u64 kh;
@@ -320,6 +320,19 @@ struct fuse_args_pages {
 	unsigned int num_pages;
 };
 
+struct fuse_release_args {
+	struct fuse_args args;
+	struct fuse_release_in inarg;
+	struct inode *inode;
+};
+
+union fuse_file_args {
+	/* Used during open() */
+	struct fuse_open_out open_outarg;
+	/* Used during release() */
+	struct fuse_release_args release_args;
+};
+
 #define FUSE_ARGS(args) struct fuse_args args = {}
 
 /** The request IO state (for asynchronous processing) */
diff --git a/fs/fuse/iomode.c b/fs/fuse/iomode.c
index acd0833ae873..48105f3c00f6 100644
--- a/fs/fuse/iomode.c
+++ b/fs/fuse/iomode.c
@@ -138,9 +138,40 @@ void fuse_file_uncached_io_end(struct inode *inode)
 		wake_up(&fi->direct_io_waitq);
 }
 
+/*
+ * Open flags that are allowed in combination with FOPEN_PASSTHROUGH.
+ * A combination of FOPEN_PASSTHROUGH and FOPEN_DIRECT_IO means that read/write
+ * operations go directly to the server, but mmap is done on the backing file.
+ * FOPEN_PASSTHROUGH mode should not co-exist with any users of the fuse inode
+ * page cache, so FOPEN_KEEP_CACHE is a strange and undesired combination.
+ */
+#define FOPEN_PASSTHROUGH_MASK \
+	(FOPEN_PASSTHROUGH | FOPEN_DIRECT_IO | FOPEN_PARALLEL_DIRECT_WRITES | \
+	 FOPEN_NOFLUSH)
+
+static int fuse_file_passthrough_open(struct file *file, struct inode *inode)
+{
+	struct fuse_file *ff = file->private_data;
+	struct fuse_conn *fc = get_fuse_conn(inode);
+	int err;
+
+	/* Check allowed conditions for file open in passthrough mode */
+	if (!IS_ENABLED(CONFIG_FUSE_PASSTHROUGH) || !fc->passthrough ||
+	    (ff->open_flags & ~FOPEN_PASSTHROUGH_MASK))
+		return -EINVAL;
+
+	/* TODO: implement backing file open */
+	return -EOPNOTSUPP;
+
+	/* First passthrough file open denies caching inode io mode */
+	err = fuse_file_uncached_io_start(inode);
+
+	return err;
+}
+
 /* Open flags to determine regular file io mode */
 #define FOPEN_IO_MODE_MASK \
-	(FOPEN_DIRECT_IO | FOPEN_CACHE_IO)
+	(FOPEN_DIRECT_IO | FOPEN_CACHE_IO | FOPEN_PASSTHROUGH)
 
 /* Request access to submit new io to inode via open file */
 int fuse_file_io_open(struct file *file, struct inode *inode)
@@ -162,7 +193,7 @@ int fuse_file_io_open(struct file *file, struct inode *inode)
 	 * implement open.
 	 */
 	err = -EINVAL;
-	if (FUSE_IS_DAX(inode) || !ff->release_args) {
+	if (FUSE_IS_DAX(inode) || !ff->args) {
 		if (iomode_flags)
 			goto fail;
 		return 0;
@@ -170,7 +201,7 @@ int fuse_file_io_open(struct file *file, struct inode *inode)
 
 	/*
 	 * FOPEN_CACHE_IO is an internal flag that is set on file not open in
-	 * direct io mode and it cannot be set explicitly by the server.
+	 * direct io or passthrough mode and it cannot be set by the server.
 	 * This includes a file open with O_DIRECT, but server did not specify
 	 * FOPEN_DIRECT_IO. In this case, a later fcntl() could remove O_DIRECT,
 	 * so we put the inode in caching mode to prevent parallel dio.
@@ -178,7 +209,7 @@ int fuse_file_io_open(struct file *file, struct inode *inode)
 	 */
 	if (ff->open_flags & FOPEN_CACHE_IO) {
 		goto fail;
-	} else if (!(ff->open_flags & FOPEN_DIRECT_IO)) {
+	} else if (!(ff->open_flags & FOPEN_IO_MODE_MASK)) {
 		ff->open_flags |= FOPEN_CACHE_IO;
 		ff->open_flags &= ~FOPEN_PARALLEL_DIRECT_WRITES;
 	}
@@ -189,6 +220,8 @@ int fuse_file_io_open(struct file *file, struct inode *inode)
 	err = 0;
 	if (ff->open_flags & FOPEN_CACHE_IO)
 		err = fuse_file_cached_io_start(inode);
+	else if (ff->open_flags & FOPEN_PASSTHROUGH)
+		err = fuse_file_passthrough_open(file, inode);
 	if (err)
 		goto fail;
 
@@ -206,17 +239,18 @@ int fuse_file_io_open(struct file *file, struct inode *inode)
 	return -EIO;
 }
 
-/* Request access to submit new io to inode via mmap */
+/* Request access to submit cached io to inode via mmap */
 int fuse_file_io_mmap(struct fuse_file *ff, struct inode *inode)
 {
 	struct fuse_inode *fi = get_fuse_inode(inode);
 	int err = 0;
 
 	/* There are no io modes if server does not implement open */
-	if (!ff->release_args)
+	if (!ff->args)
 		return 0;
 
-	if (WARN_ON(!ff->io_opened))
+	if (WARN_ON(ff->open_flags & FOPEN_PASSTHROUGH) ||
+	    WARN_ON(!ff->io_opened))
 		return -ENODEV;
 
 	spin_lock(&fi->lock);
-- 
2.34.1


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

* [PATCH v15 5/9] fuse: implement open in passthrough mode
  2024-02-06 14:24 [PATCH v15 0/9] FUSE passthrough for file io Amir Goldstein
                   ` (3 preceding siblings ...)
  2024-02-06 14:24 ` [PATCH v15 4/9] fuse: prepare for opening file in passthrough mode Amir Goldstein
@ 2024-02-06 14:24 ` Amir Goldstein
  2024-02-06 14:24 ` [PATCH v15 6/9] fuse: implement read/write passthrough Amir Goldstein
                   ` (3 subsequent siblings)
  8 siblings, 0 replies; 27+ messages in thread
From: Amir Goldstein @ 2024-02-06 14:24 UTC (permalink / raw)
  To: Miklos Szeredi; +Cc: Bernd Schubert, linux-fsdevel

After getting a backing file id with FUSE_DEV_IOC_BACKING_OPEN ioctl,
a FUSE server can reply to an OPEN request with flag FOPEN_PASSTHROUGH
and the backing file id.

The FUSE server should reuse the same backing file id for all the open
replies of the same FUSE inode and open will fail (with -EIO) if a the
server attempts to open the same inode with conflicting io modes or to
setup passthrough to two different backing files for the same FUSE inode.
Using the same backing file id for several different inodes is allowed.

Opening a new file with FOPEN_DIRECT_IO for an inode that is already
open for passthrough is allowed, but only if the FOPEN_PASSTHROUGH flag
and correct backing file id are specified as well.

The read/write IO of such files will not use passthrough operations to
the backing file, but mmap, which does not support direct_io, will use
the backing file insead of using the page cache as it always did.

Even though all FUSE passthrough files of the same inode use the same
backing file as a backing inode reference, each FUSE file opens a unique
instance of a backing_file object to store the FUSE path that was used
to open the inode and the open flags of the specific open file.

The per-file, backing_file object is released along with the FUSE file.
The inode associated fuse_backing object is released when the last FUSE
passthrough file of that inode is released AND when the backing file id
is closed by the server using the FUSE_DEV_IOC_BACKING_CLOSE ioctl.

Signed-off-by: Amir Goldstein <amir73il@gmail.com>
---
 fs/fuse/file.c        |  9 ++++++-
 fs/fuse/fuse_i.h      | 35 ++++++++++++++++++++++++-
 fs/fuse/iomode.c      | 51 ++++++++++++++++++++++++++++++++-----
 fs/fuse/passthrough.c | 59 +++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 145 insertions(+), 9 deletions(-)

diff --git a/fs/fuse/file.c b/fs/fuse/file.c
index 04be04b6b2af..bdcee82fef9a 100644
--- a/fs/fuse/file.c
+++ b/fs/fuse/file.c
@@ -295,6 +295,9 @@ static void fuse_prepare_release(struct fuse_inode *fi, struct fuse_file *ff,
 	struct fuse_conn *fc = ff->fm->fc;
 	struct fuse_release_args *ra = &ff->args->release_args;
 
+	if (fuse_file_passthrough(ff))
+		fuse_passthrough_release(ff, fuse_inode_backing(fi));
+
 	/* Inode is NULL on error path of fuse_create_open() */
 	if (likely(fi)) {
 		spin_lock(&fi->lock);
@@ -1372,7 +1375,7 @@ static void fuse_dio_lock(struct kiocb *iocb, struct iov_iter *from,
 		 * have raced, so check it again.
 		 */
 		if (fuse_io_past_eof(iocb, from) ||
-		    fuse_file_uncached_io_start(inode) != 0) {
+		    fuse_file_uncached_io_start(inode, NULL) != 0) {
 			inode_unlock_shared(inode);
 			inode_lock(inode);
 			*exclusive = true;
@@ -2522,6 +2525,10 @@ static int fuse_file_mmap(struct file *file, struct vm_area_struct *vma)
 	if (FUSE_IS_DAX(file_inode(file)))
 		return fuse_dax_mmap(file, vma);
 
+	/* TODO: implement mmap to backing file */
+	if (fuse_file_passthrough(ff))
+		return -ENODEV;
+
 	/*
 	 * FOPEN_DIRECT_IO handling is special compared to O_DIRECT,
 	 * as does not allow MAP_SHARED mmap without FUSE_DIRECT_IO_ALLOW_MMAP.
diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h
index eea8f1ffc766..407b24c79ebb 100644
--- a/fs/fuse/fuse_i.h
+++ b/fs/fuse/fuse_i.h
@@ -266,6 +266,12 @@ struct fuse_file {
 	/** Wait queue head for poll */
 	wait_queue_head_t poll_wait;
 
+#ifdef CONFIG_FUSE_PASSTHROUGH
+	/** Reference to backing file in passthrough mode */
+	struct file *passthrough;
+	const struct cred *cred;
+#endif
+
 	/** Has flock been performed on this file? */
 	bool flock:1;
 
@@ -1398,7 +1404,7 @@ int fuse_fileattr_set(struct mnt_idmap *idmap,
 /* iomode.c */
 int fuse_file_cached_io_start(struct inode *inode);
 void fuse_file_cached_io_end(struct inode *inode);
-int fuse_file_uncached_io_start(struct inode *inode);
+int fuse_file_uncached_io_start(struct inode *inode, struct fuse_backing *fb);
 void fuse_file_uncached_io_end(struct inode *inode);
 
 int fuse_file_io_open(struct file *file, struct inode *inode);
@@ -1431,11 +1437,38 @@ static inline struct fuse_backing *fuse_inode_backing_set(struct fuse_inode *fi,
 #endif
 }
 
+#ifdef CONFIG_FUSE_PASSTHROUGH
 struct fuse_backing *fuse_backing_get(struct fuse_backing *fb);
 void fuse_backing_put(struct fuse_backing *fb);
+#else
+
+static inline struct fuse_backing *fuse_backing_get(struct fuse_backing *fb)
+{
+	return NULL;
+}
+
+static inline void fuse_backing_put(struct fuse_backing *fb)
+{
+}
+#endif
+
 void fuse_backing_files_init(struct fuse_conn *fc);
 void fuse_backing_files_free(struct fuse_conn *fc);
 int fuse_backing_open(struct fuse_conn *fc, struct fuse_backing_map *map);
 int fuse_backing_close(struct fuse_conn *fc, int backing_id);
 
+struct fuse_backing *fuse_passthrough_open(struct file *file,
+					   struct inode *inode,
+					   int backing_id);
+void fuse_passthrough_release(struct fuse_file *ff, struct fuse_backing *fb);
+
+static inline struct file *fuse_file_passthrough(struct fuse_file *ff)
+{
+#ifdef CONFIG_FUSE_PASSTHROUGH
+	return ff->passthrough;
+#else
+	return NULL;
+#endif
+}
+
 #endif /* _FS_FUSE_I_H */
diff --git a/fs/fuse/iomode.c b/fs/fuse/iomode.c
index 48105f3c00f6..dce369f3b201 100644
--- a/fs/fuse/iomode.c
+++ b/fs/fuse/iomode.c
@@ -115,13 +115,24 @@ void fuse_file_cached_io_end(struct inode *inode)
 }
 
 /* Start strictly uncached io mode where cache access is not allowed */
-int fuse_file_uncached_io_start(struct inode *inode)
+int fuse_file_uncached_io_start(struct inode *inode, struct fuse_backing *fb)
 {
 	struct fuse_inode *fi = get_fuse_inode(inode);
-	int err;
+	struct fuse_backing *oldfb;
+	int err = -EBUSY;
 
 	spin_lock(&fi->lock);
-	err = fuse_inode_deny_io_cache(fi);
+	/* deny conflicting backing files on same fuse inode */
+	oldfb = fuse_inode_backing(fi);
+	if (!oldfb || oldfb == fb)
+		err = fuse_inode_deny_io_cache(fi);
+	/* fuse inode holds a single refcount of backing file */
+	if (!oldfb && !err) {
+		oldfb = fuse_inode_backing_set(fi, fb);
+		WARN_ON_ONCE(oldfb != NULL);
+	} else if (!err) {
+		fuse_backing_put(fb);
+	}
 	spin_unlock(&fi->lock);
 	return err;
 }
@@ -129,13 +140,18 @@ int fuse_file_uncached_io_start(struct inode *inode)
 void fuse_file_uncached_io_end(struct inode *inode)
 {
 	struct fuse_inode *fi = get_fuse_inode(inode);
+	struct fuse_backing *oldfb = NULL;
 	int uncached_io;
 
 	spin_lock(&fi->lock);
 	uncached_io = fuse_inode_allow_io_cache(fi);
+	if (!uncached_io)
+		oldfb = fuse_inode_backing_set(fi, NULL);
 	spin_unlock(&fi->lock);
 	if (!uncached_io)
 		wake_up(&fi->direct_io_waitq);
+	if (oldfb)
+		fuse_backing_put(oldfb);
 }
 
 /*
@@ -153,6 +169,7 @@ static int fuse_file_passthrough_open(struct file *file, struct inode *inode)
 {
 	struct fuse_file *ff = file->private_data;
 	struct fuse_conn *fc = get_fuse_conn(inode);
+	struct fuse_backing *fb;
 	int err;
 
 	/* Check allowed conditions for file open in passthrough mode */
@@ -160,11 +177,18 @@ static int fuse_file_passthrough_open(struct file *file, struct inode *inode)
 	    (ff->open_flags & ~FOPEN_PASSTHROUGH_MASK))
 		return -EINVAL;
 
-	/* TODO: implement backing file open */
-	return -EOPNOTSUPP;
+	fb = fuse_passthrough_open(file, inode,
+				   ff->args->open_outarg.backing_id);
+	if (IS_ERR(fb))
+		return PTR_ERR(fb);
 
 	/* First passthrough file open denies caching inode io mode */
-	err = fuse_file_uncached_io_start(inode);
+	err = fuse_file_uncached_io_start(inode, fb);
+	if (!err)
+		return 0;
+
+	fuse_passthrough_release(ff, fb);
+	fuse_backing_put(fb);
 
 	return err;
 }
@@ -177,6 +201,7 @@ static int fuse_file_passthrough_open(struct file *file, struct inode *inode)
 int fuse_file_io_open(struct file *file, struct inode *inode)
 {
 	struct fuse_file *ff = file->private_data;
+	struct fuse_inode *fi = get_fuse_inode(inode);
 	int iomode_flags = ff->open_flags & FOPEN_IO_MODE_MASK;
 	int err;
 
@@ -199,6 +224,13 @@ int fuse_file_io_open(struct file *file, struct inode *inode)
 		return 0;
 	}
 
+	/*
+	 * Server is expected to use FOPEN_PASSTHROUGH for all opens of an inode
+	 * which is already open for passthrough.
+	 */
+	if (fuse_inode_backing(fi) && !(ff->open_flags & FOPEN_PASSTHROUGH))
+		goto fail;
+
 	/*
 	 * FOPEN_CACHE_IO is an internal flag that is set on file not open in
 	 * direct io or passthrough mode and it cannot be set by the server.
@@ -271,9 +303,14 @@ void fuse_file_io_release(struct fuse_file *ff, struct inode *inode)
 	if (!ff->io_opened)
 		return;
 
-	/* Last caching file close exits caching inode io mode */
+	/*
+	 * Last caching file close allows passthrough open of inode and
+	 * Last passthrough file close allows caching open of inode.
+	 */
 	if (ff->open_flags & FOPEN_CACHE_IO)
 		fuse_file_cached_io_end(inode);
+	else if (ff->open_flags & FOPEN_PASSTHROUGH)
+		fuse_file_uncached_io_end(inode);
 
 	ff->io_opened = false;
 }
diff --git a/fs/fuse/passthrough.c b/fs/fuse/passthrough.c
index 6604d414adb5..098a1f765e99 100644
--- a/fs/fuse/passthrough.c
+++ b/fs/fuse/passthrough.c
@@ -8,6 +8,7 @@
 #include "fuse_i.h"
 
 #include <linux/file.h>
+#include <linux/backing-file.h>
 
 struct fuse_backing *fuse_backing_get(struct fuse_backing *fb)
 {
@@ -163,3 +164,61 @@ int fuse_backing_close(struct fuse_conn *fc, int backing_id)
 
 	return err;
 }
+
+/*
+ * Setup passthrough to a backing file.
+ *
+ * Returns an fb object with elevated refcount to be stored in fuse inode.
+ */
+struct fuse_backing *fuse_passthrough_open(struct file *file,
+					   struct inode *inode,
+					   int backing_id)
+{
+	struct fuse_file *ff = file->private_data;
+	struct fuse_conn *fc = ff->fm->fc;
+	struct fuse_backing *fb = NULL;
+	struct file *backing_file;
+	int err;
+
+	err = -EINVAL;
+	if (backing_id <= 0)
+		goto out;
+
+	rcu_read_lock();
+	fb = idr_find(&fc->backing_files_map, backing_id);
+	fb = fuse_backing_get(fb);
+	rcu_read_unlock();
+
+	err = -ENOENT;
+	if (!fb)
+		goto out;
+
+	/* Allocate backing file per fuse file to store fuse path */
+	backing_file = backing_file_open(&file->f_path, file->f_flags,
+					 &fb->file->f_path, fb->cred);
+	err = PTR_ERR(backing_file);
+	if (IS_ERR(backing_file)) {
+		fuse_backing_put(fb);
+		goto out;
+	}
+
+	err = 0;
+	ff->passthrough = backing_file;
+	ff->cred = get_cred(fb->cred);
+out:
+	pr_debug("%s: backing_id=%d, fb=0x%p, backing_file=0x%p, err=%i\n", __func__,
+		 backing_id, fb, ff->passthrough, err);
+
+	return err ? ERR_PTR(err) : fb;
+}
+
+void fuse_passthrough_release(struct fuse_file *ff, struct fuse_backing *fb)
+{
+	pr_debug("%s: fb=0x%p, backing_file=0x%p\n", __func__,
+		 fb, ff->passthrough);
+
+	fput(ff->passthrough);
+	ff->passthrough = NULL;
+	put_cred(ff->cred);
+	ff->cred = NULL;
+}
-- 
2.34.1


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

* [PATCH v15 6/9] fuse: implement read/write passthrough
  2024-02-06 14:24 [PATCH v15 0/9] FUSE passthrough for file io Amir Goldstein
                   ` (4 preceding siblings ...)
  2024-02-06 14:24 ` [PATCH v15 5/9] fuse: implement open " Amir Goldstein
@ 2024-02-06 14:24 ` Amir Goldstein
  2024-02-06 14:24 ` [PATCH v15 7/9] fuse: implement splice " Amir Goldstein
                   ` (2 subsequent siblings)
  8 siblings, 0 replies; 27+ messages in thread
From: Amir Goldstein @ 2024-02-06 14:24 UTC (permalink / raw)
  To: Miklos Szeredi; +Cc: Bernd Schubert, linux-fsdevel

Use the backing file read/write helpers to implement read/write
passthrough to a backing file.

After read/write, we invalidate a/c/mtime/size attributes if the
backing inode attributes differ from FUSE inode attributes.

Signed-off-by: Amir Goldstein <amir73il@gmail.com>
---
 fs/fuse/file.c        | 18 ++++++---
 fs/fuse/fuse_i.h      |  3 ++
 fs/fuse/passthrough.c | 86 +++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 101 insertions(+), 6 deletions(-)

diff --git a/fs/fuse/file.c b/fs/fuse/file.c
index bdcee82fef9a..0f7c53ea2f13 100644
--- a/fs/fuse/file.c
+++ b/fs/fuse/file.c
@@ -1688,10 +1688,13 @@ static ssize_t fuse_file_read_iter(struct kiocb *iocb, struct iov_iter *to)
 	if (FUSE_IS_DAX(inode))
 		return fuse_dax_read_iter(iocb, to);
 
-	if (!(ff->open_flags & FOPEN_DIRECT_IO))
-		return fuse_cache_read_iter(iocb, to);
-	else
+	/* FOPEN_DIRECT_IO overrides FOPEN_PASSTHROUGH */
+	if (ff->open_flags & FOPEN_DIRECT_IO)
 		return fuse_direct_read_iter(iocb, to);
+	else if (fuse_file_passthrough(ff))
+		return fuse_passthrough_read_iter(iocb, to);
+	else
+		return fuse_cache_read_iter(iocb, to);
 }
 
 static ssize_t fuse_file_write_iter(struct kiocb *iocb, struct iov_iter *from)
@@ -1706,10 +1709,13 @@ static ssize_t fuse_file_write_iter(struct kiocb *iocb, struct iov_iter *from)
 	if (FUSE_IS_DAX(inode))
 		return fuse_dax_write_iter(iocb, from);
 
-	if (!(ff->open_flags & FOPEN_DIRECT_IO))
-		return fuse_cache_write_iter(iocb, from);
-	else
+	/* FOPEN_DIRECT_IO overrides FOPEN_PASSTHROUGH */
+	if (ff->open_flags & FOPEN_DIRECT_IO)
 		return fuse_direct_write_iter(iocb, from);
+	else if (fuse_file_passthrough(ff))
+		return fuse_passthrough_write_iter(iocb, from);
+	else
+		return fuse_cache_write_iter(iocb, from);
 }
 
 static void fuse_writepage_free(struct fuse_writepage_args *wpa)
diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h
index 407b24c79ebb..e47d7e8e4285 100644
--- a/fs/fuse/fuse_i.h
+++ b/fs/fuse/fuse_i.h
@@ -1471,4 +1471,7 @@ static inline struct file *fuse_file_passthrough(struct fuse_file *ff)
 #endif
 }
 
+ssize_t fuse_passthrough_read_iter(struct kiocb *iocb, struct iov_iter *iter);
+ssize_t fuse_passthrough_write_iter(struct kiocb *iocb, struct iov_iter *iter);
+
 #endif /* _FS_FUSE_I_H */
diff --git a/fs/fuse/passthrough.c b/fs/fuse/passthrough.c
index 098a1f765e99..206eedbf6bf8 100644
--- a/fs/fuse/passthrough.c
+++ b/fs/fuse/passthrough.c
@@ -10,6 +10,92 @@
 #include <linux/file.h>
 #include <linux/backing-file.h>
 
+static void fuse_file_accessed(struct file *file)
+{
+	struct inode *inode = file_inode(file);
+	struct fuse_inode *fi = get_fuse_inode(inode);
+	struct fuse_backing *fb = fuse_inode_backing(fi);
+	struct inode *backing_inode = file_inode(fb->file);
+	struct timespec64 atime = inode_get_atime(inode);
+	struct timespec64 batime = inode_get_atime(backing_inode);
+
+	/* Mimic atime update policy of backing inode, not the actual value */
+	if (!timespec64_equal(&batime, &atime))
+		fuse_invalidate_atime(inode);
+}
+
+static void fuse_file_modified(struct file *file)
+{
+	struct inode *inode = file_inode(file);
+	struct fuse_inode *fi = get_fuse_inode(inode);
+	struct fuse_backing *fb = fuse_inode_backing(fi);
+	struct inode *backing_inode = file_inode(fb->file);
+	struct timespec64 ctime = inode_get_ctime(inode);
+	struct timespec64 mtime = inode_get_mtime(inode);
+	struct timespec64 bctime = inode_get_ctime(backing_inode);
+	struct timespec64 bmtime = inode_get_mtime(backing_inode);
+
+	if (!timespec64_equal(&bctime, &ctime) ||
+	    !timespec64_equal(&bmtime, &mtime) ||
+	    i_size_read(backing_inode) != i_size_read(inode))
+		fuse_invalidate_attr_mask(inode, FUSE_STATX_MODSIZE);
+}
+
+ssize_t fuse_passthrough_read_iter(struct kiocb *iocb, struct iov_iter *iter)
+{
+	struct file *file = iocb->ki_filp;
+	struct fuse_file *ff = file->private_data;
+	struct file *backing_file = fuse_file_passthrough(ff);
+	size_t count = iov_iter_count(iter);
+	ssize_t ret;
+	struct backing_file_ctx ctx = {
+		.cred = ff->cred,
+		.user_file = file,
+		.accessed = fuse_file_accessed,
+	};
+
+
+	pr_debug("%s: backing_file=0x%p, pos=%lld, len=%zu\n", __func__,
+		 backing_file, iocb->ki_pos, count);
+
+	if (!count)
+		return 0;
+
+	ret = backing_file_read_iter(backing_file, iter, iocb, iocb->ki_flags,
+				     &ctx);
+
+	return ret;
+}
+
+ssize_t fuse_passthrough_write_iter(struct kiocb *iocb,
+				    struct iov_iter *iter)
+{
+	struct file *file = iocb->ki_filp;
+	struct inode *inode = file_inode(file);
+	struct fuse_file *ff = file->private_data;
+	struct file *backing_file = fuse_file_passthrough(ff);
+	size_t count = iov_iter_count(iter);
+	ssize_t ret;
+	struct backing_file_ctx ctx = {
+		.cred = ff->cred,
+		.user_file = file,
+		.end_write = fuse_file_modified,
+	};
+
+	pr_debug("%s: backing_file=0x%p, pos=%lld, len=%zu\n", __func__,
+		 backing_file, iocb->ki_pos, count);
+
+	if (!count)
+		return 0;
+
+	inode_lock(inode);
+	ret = backing_file_write_iter(backing_file, iter, iocb, iocb->ki_flags,
+				      &ctx);
+	inode_unlock(inode);
+
+	return ret;
+}
+
 struct fuse_backing *fuse_backing_get(struct fuse_backing *fb)
 {
 	if (fb && refcount_inc_not_zero(&fb->count))
-- 
2.34.1


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

* [PATCH v15 7/9] fuse: implement splice read/write passthrough
  2024-02-06 14:24 [PATCH v15 0/9] FUSE passthrough for file io Amir Goldstein
                   ` (5 preceding siblings ...)
  2024-02-06 14:24 ` [PATCH v15 6/9] fuse: implement read/write passthrough Amir Goldstein
@ 2024-02-06 14:24 ` Amir Goldstein
  2024-02-06 14:24 ` [PATCH v15 8/9] fuse: implement passthrough for mmap Amir Goldstein
  2024-02-06 14:24 ` [PATCH v15 9/9] fuse: auto-invalidate inode attributes in passthrough mode Amir Goldstein
  8 siblings, 0 replies; 27+ messages in thread
From: Amir Goldstein @ 2024-02-06 14:24 UTC (permalink / raw)
  To: Miklos Szeredi; +Cc: Bernd Schubert, linux-fsdevel

This allows passing fstests generic/249 and generic/591.

Signed-off-by: Amir Goldstein <amir73il@gmail.com>
---
 fs/fuse/file.c        | 29 ++++++++++++++++++++++++++--
 fs/fuse/fuse_i.h      |  6 ++++++
 fs/fuse/passthrough.c | 45 +++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 78 insertions(+), 2 deletions(-)

diff --git a/fs/fuse/file.c b/fs/fuse/file.c
index 0f7c53ea2f13..80f98affbe7d 100644
--- a/fs/fuse/file.c
+++ b/fs/fuse/file.c
@@ -1718,6 +1718,31 @@ static ssize_t fuse_file_write_iter(struct kiocb *iocb, struct iov_iter *from)
 		return fuse_cache_write_iter(iocb, from);
 }
 
+static ssize_t fuse_splice_read(struct file *in, loff_t *ppos,
+				struct pipe_inode_info *pipe, size_t len,
+				unsigned int flags)
+{
+	struct fuse_file *ff = in->private_data;
+
+	/* FOPEN_DIRECT_IO overrides FOPEN_PASSTHROUGH */
+	if (fuse_file_passthrough(ff) && !(ff->open_flags & FOPEN_DIRECT_IO))
+		return fuse_passthrough_splice_read(in, ppos, pipe, len, flags);
+	else
+		return filemap_splice_read(in, ppos, pipe, len, flags);
+}
+
+static ssize_t fuse_splice_write(struct pipe_inode_info *pipe, struct file *out,
+				 loff_t *ppos, size_t len, unsigned int flags)
+{
+	struct fuse_file *ff = out->private_data;
+
+	/* FOPEN_DIRECT_IO overrides FOPEN_PASSTHROUGH */
+	if (fuse_file_passthrough(ff) && !(ff->open_flags & FOPEN_DIRECT_IO))
+		return fuse_passthrough_splice_write(pipe, out, ppos, len, flags);
+	else
+		return iter_file_splice_write(pipe, out, ppos, len, flags);
+}
+
 static void fuse_writepage_free(struct fuse_writepage_args *wpa)
 {
 	struct fuse_args_pages *ap = &wpa->ia.ap;
@@ -3298,8 +3323,8 @@ static const struct file_operations fuse_file_operations = {
 	.lock		= fuse_file_lock,
 	.get_unmapped_area = thp_get_unmapped_area,
 	.flock		= fuse_file_flock,
-	.splice_read	= filemap_splice_read,
-	.splice_write	= iter_file_splice_write,
+	.splice_read	= fuse_splice_read,
+	.splice_write	= fuse_splice_write,
 	.unlocked_ioctl	= fuse_file_ioctl,
 	.compat_ioctl	= fuse_file_compat_ioctl,
 	.poll		= fuse_file_poll,
diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h
index e47d7e8e4285..c495eaa11b49 100644
--- a/fs/fuse/fuse_i.h
+++ b/fs/fuse/fuse_i.h
@@ -1473,5 +1473,11 @@ static inline struct file *fuse_file_passthrough(struct fuse_file *ff)
 
 ssize_t fuse_passthrough_read_iter(struct kiocb *iocb, struct iov_iter *iter);
 ssize_t fuse_passthrough_write_iter(struct kiocb *iocb, struct iov_iter *iter);
+ssize_t fuse_passthrough_splice_read(struct file *in, loff_t *ppos,
+				     struct pipe_inode_info *pipe,
+				     size_t len, unsigned int flags);
+ssize_t fuse_passthrough_splice_write(struct pipe_inode_info *pipe,
+				      struct file *out, loff_t *ppos,
+				      size_t len, unsigned int flags);
 
 #endif /* _FS_FUSE_I_H */
diff --git a/fs/fuse/passthrough.c b/fs/fuse/passthrough.c
index 206eedbf6bf8..df8343cebd0a 100644
--- a/fs/fuse/passthrough.c
+++ b/fs/fuse/passthrough.c
@@ -9,6 +9,7 @@
 
 #include <linux/file.h>
 #include <linux/backing-file.h>
+#include <linux/splice.h>
 
 static void fuse_file_accessed(struct file *file)
 {
@@ -96,6 +97,50 @@ ssize_t fuse_passthrough_write_iter(struct kiocb *iocb,
 	return ret;
 }
 
+ssize_t fuse_passthrough_splice_read(struct file *in, loff_t *ppos,
+				     struct pipe_inode_info *pipe,
+				     size_t len, unsigned int flags)
+{
+	struct fuse_file *ff = in->private_data;
+	struct file *backing_file = fuse_file_passthrough(ff);
+	struct backing_file_ctx ctx = {
+		.cred = ff->cred,
+		.user_file = in,
+		.accessed = fuse_file_accessed,
+	};
+
+	pr_debug("%s: backing_file=0x%p, pos=%lld, len=%zu, flags=0x%x\n", __func__,
+		 backing_file, ppos ? *ppos : 0, len, flags);
+
+	return backing_file_splice_read(backing_file, ppos, pipe, len, flags,
+					&ctx);
+}
+
+ssize_t fuse_passthrough_splice_write(struct pipe_inode_info *pipe,
+				      struct file *out, loff_t *ppos,
+				      size_t len, unsigned int flags)
+{
+	struct fuse_file *ff = out->private_data;
+	struct file *backing_file = fuse_file_passthrough(ff);
+	struct inode *inode = file_inode(out);
+	ssize_t ret;
+	struct backing_file_ctx ctx = {
+		.cred = ff->cred,
+		.user_file = out,
+		.end_write = fuse_file_modified,
+	};
+
+	pr_debug("%s: backing_file=0x%p, pos=%lld, len=%zu, flags=0x%x\n", __func__,
+		 backing_file, ppos ? *ppos : 0, len, flags);
+
+	inode_lock(inode);
+	ret = backing_file_splice_write(pipe, backing_file, ppos, len, flags,
+					&ctx);
+	inode_unlock(inode);
+
+	return ret;
+}
+
 struct fuse_backing *fuse_backing_get(struct fuse_backing *fb)
 {
 	if (fb && refcount_inc_not_zero(&fb->count))
-- 
2.34.1


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

* [PATCH v15 8/9] fuse: implement passthrough for mmap
  2024-02-06 14:24 [PATCH v15 0/9] FUSE passthrough for file io Amir Goldstein
                   ` (6 preceding siblings ...)
  2024-02-06 14:24 ` [PATCH v15 7/9] fuse: implement splice " Amir Goldstein
@ 2024-02-06 14:24 ` Amir Goldstein
  2024-02-06 14:24 ` [PATCH v15 9/9] fuse: auto-invalidate inode attributes in passthrough mode Amir Goldstein
  8 siblings, 0 replies; 27+ messages in thread
From: Amir Goldstein @ 2024-02-06 14:24 UTC (permalink / raw)
  To: Miklos Szeredi; +Cc: Bernd Schubert, linux-fsdevel

An mmap request for a file open in passthrough mode, maps the memory
directly to the backing file.

An mmap of a file in direct io mode, usually uses cached mmap and puts
the inode in caching io mode, which denies new passthrough opens of that
inode, because caching io mode is conflicting with passthrough io mode.

For the same reason, trying to mmap a direct io file, while there is
a passthrough file open on the same inode will fail with -ENODEV.

An mmap of a file in direct io mode, also needs to wait for parallel
dio writes in-progress to complete.

If a passthrough file is opened, while an mmap of another direct io
file is waiting for parallel dio writes to complete, the wait is aborted
and mmap fails with -ENODEV.

A FUSE server that uses passthrough and direct io opens on the same inode
that may also be mmaped, is advised to provide a backing fd also for the
files that are open in direct io mode (i.e. use the flags combination
FOPEN_DIRECT_IO | FOPEN_PASSTHROUGH), so that mmap will always use the
backing file, even if read/write do not passthrough.

Signed-off-by: Amir Goldstein <amir73il@gmail.com>
---
 fs/fuse/file.c        | 13 ++++++++++---
 fs/fuse/fuse_i.h      |  1 +
 fs/fuse/iomode.c      |  9 ++++++++-
 fs/fuse/passthrough.c | 16 ++++++++++++++++
 4 files changed, 35 insertions(+), 4 deletions(-)

diff --git a/fs/fuse/file.c b/fs/fuse/file.c
index 80f98affbe7d..edbcb9ceb7e7 100644
--- a/fs/fuse/file.c
+++ b/fs/fuse/file.c
@@ -2550,14 +2550,21 @@ static int fuse_file_mmap(struct file *file, struct vm_area_struct *vma)
 {
 	struct fuse_file *ff = file->private_data;
 	struct fuse_conn *fc = ff->fm->fc;
+	struct inode *inode = file_inode(file);
 	int rc;
 
 	/* DAX mmap is superior to direct_io mmap */
-	if (FUSE_IS_DAX(file_inode(file)))
+	if (FUSE_IS_DAX(inode))
 		return fuse_dax_mmap(file, vma);
 
-	/* TODO: implement mmap to backing file */
+	/*
+	 * If inode is in passthrough io mode, because it has some file open
+	 * in passthrough mode, either mmap to backing file or fail mmap,
+	 * because mixing cached mmap and passthrough io mode is not allowed.
+	 */
 	if (fuse_file_passthrough(ff))
+		return fuse_passthrough_mmap(file, vma);
+	else if (fuse_inode_backing(get_fuse_inode(inode)))
 		return -ENODEV;
 
 	/*
@@ -2579,7 +2586,7 @@ static int fuse_file_mmap(struct file *file, struct vm_area_struct *vma)
 		 * Also waits for parallel dio writers to go into serial mode
 		 * (exclusive instead of shared lock).
 		 */
-		rc = fuse_file_io_mmap(ff, file_inode(file));
+		rc = fuse_file_io_mmap(ff, inode);
 		if (rc)
 			return rc;
 
diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h
index c495eaa11b49..98f878a52af1 100644
--- a/fs/fuse/fuse_i.h
+++ b/fs/fuse/fuse_i.h
@@ -1479,5 +1479,6 @@ ssize_t fuse_passthrough_splice_read(struct file *in, loff_t *ppos,
 ssize_t fuse_passthrough_splice_write(struct pipe_inode_info *pipe,
 				      struct file *out, loff_t *ppos,
 				      size_t len, unsigned int flags);
+ssize_t fuse_passthrough_mmap(struct file *file, struct vm_area_struct *vma);
 
 #endif /* _FS_FUSE_I_H */
diff --git a/fs/fuse/iomode.c b/fs/fuse/iomode.c
index dce369f3b201..c545058a01e1 100644
--- a/fs/fuse/iomode.c
+++ b/fs/fuse/iomode.c
@@ -17,7 +17,7 @@
  */
 static inline bool fuse_is_io_cache_wait(struct fuse_inode *fi)
 {
-	return READ_ONCE(fi->iocachectr) < 0;
+	return READ_ONCE(fi->iocachectr) < 0 && !fuse_inode_backing(fi);
 }
 
 /*
@@ -42,6 +42,13 @@ static int fuse_inode_get_io_cache(struct fuse_inode *fi)
 					  !fuse_is_io_cache_wait(fi));
 		spin_lock(&fi->lock);
 	}
+	/*
+	 * Check if inode entered passthrough io mode while waiting for parallel
+	 * dio write completion.
+	 */
+	if (fuse_inode_backing(fi))
+		err = -ETXTBSY;
+
 	/*
 	 * Enter caching mode or clear the FUSE_I_CACHE_IO_MODE bit if we
 	 * failed to enter caching mode and no other caching open exists.
diff --git a/fs/fuse/passthrough.c b/fs/fuse/passthrough.c
index df8343cebd0a..260e76fc72d5 100644
--- a/fs/fuse/passthrough.c
+++ b/fs/fuse/passthrough.c
@@ -141,6 +141,22 @@ ssize_t fuse_passthrough_splice_write(struct pipe_inode_info *pipe,
 	return ret;
 }
 
+ssize_t fuse_passthrough_mmap(struct file *file, struct vm_area_struct *vma)
+{
+	struct fuse_file *ff = file->private_data;
+	struct file *backing_file = fuse_file_passthrough(ff);
+	struct backing_file_ctx ctx = {
+		.cred = ff->cred,
+		.user_file = file,
+		.accessed = fuse_file_accessed,
+	};
+
+	pr_debug("%s: backing_file=0x%p, start=%lu, end=%lu\n", __func__,
+		 backing_file, vma->vm_start, vma->vm_end);
+
+	return backing_file_mmap(backing_file, vma, &ctx);
+}
+
 struct fuse_backing *fuse_backing_get(struct fuse_backing *fb)
 {
 	if (fb && refcount_inc_not_zero(&fb->count))
-- 
2.34.1


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

* [PATCH v15 9/9] fuse: auto-invalidate inode attributes in passthrough mode
  2024-02-06 14:24 [PATCH v15 0/9] FUSE passthrough for file io Amir Goldstein
                   ` (7 preceding siblings ...)
  2024-02-06 14:24 ` [PATCH v15 8/9] fuse: implement passthrough for mmap Amir Goldstein
@ 2024-02-06 14:24 ` Amir Goldstein
  2024-04-02 20:13   ` Sweet Tea Dorminy
  8 siblings, 1 reply; 27+ messages in thread
From: Amir Goldstein @ 2024-02-06 14:24 UTC (permalink / raw)
  To: Miklos Szeredi; +Cc: Bernd Schubert, linux-fsdevel

After passthrough read/write, we invalidate a/c/mtime/size attributes
if the backing inode attributes differ from FUSE inode attributes.

Do the same in fuse_getattr() and after detach of backing inode, so that
passthrough mmap read/write changes to a/c/mtime/size attribute of the
backing inode will be propagated to the FUSE inode.

The rules of invalidating a/c/mtime/size attributes with writeback cache
are more complicated, so for now, writeback cache and passthrough cannot
be enabled on the same filesystem.

Signed-off-by: Amir Goldstein <amir73il@gmail.com>
---
 fs/fuse/dir.c         |  4 ++++
 fs/fuse/fuse_i.h      |  2 ++
 fs/fuse/inode.c       |  4 ++++
 fs/fuse/iomode.c      |  5 +++-
 fs/fuse/passthrough.c | 55 ++++++++++++++++++++++++++++++++++++-------
 5 files changed, 61 insertions(+), 9 deletions(-)

diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c
index 95330c2ca3d8..7f9d002b8f23 100644
--- a/fs/fuse/dir.c
+++ b/fs/fuse/dir.c
@@ -2118,6 +2118,10 @@ static int fuse_getattr(struct mnt_idmap *idmap,
 		return -EACCES;
 	}
 
+	/* Maybe update/invalidate attributes from backing inode */
+	if (fuse_inode_backing(get_fuse_inode(inode)))
+		fuse_backing_update_attr_mask(inode, request_mask);
+
 	return fuse_update_get_attr(inode, NULL, stat, request_mask, flags);
 }
 
diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h
index 98f878a52af1..4b011d31012f 100644
--- a/fs/fuse/fuse_i.h
+++ b/fs/fuse/fuse_i.h
@@ -1456,6 +1456,8 @@ void fuse_backing_files_init(struct fuse_conn *fc);
 void fuse_backing_files_free(struct fuse_conn *fc);
 int fuse_backing_open(struct fuse_conn *fc, struct fuse_backing_map *map);
 int fuse_backing_close(struct fuse_conn *fc, int backing_id);
+void fuse_backing_update_attr(struct inode *inode, struct fuse_backing *fb);
+void fuse_backing_update_attr_mask(struct inode *inode, u32 request_mask);
 
 struct fuse_backing *fuse_passthrough_open(struct file *file,
 					   struct inode *inode,
diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c
index c26a84439934..c68f005b6e86 100644
--- a/fs/fuse/inode.c
+++ b/fs/fuse/inode.c
@@ -1302,9 +1302,13 @@ static void process_init_reply(struct fuse_mount *fm, struct fuse_args *args,
 			 * on a stacked fs (e.g. overlayfs) themselves and with
 			 * max_stack_depth == 1, FUSE fs can be stacked as the
 			 * underlying fs of a stacked fs (e.g. overlayfs).
+			 *
+			 * For now, writeback cache and passthrough cannot be
+			 * enabled on the same filesystem.
 			 */
 			if (IS_ENABLED(CONFIG_FUSE_PASSTHROUGH) &&
 			    (flags & FUSE_PASSTHROUGH) &&
+			    !fc->writeback_cache &&
 			    arg->max_stack_depth > 0 &&
 			    arg->max_stack_depth <= FILESYSTEM_MAX_STACK_DEPTH) {
 				fc->passthrough = 1;
diff --git a/fs/fuse/iomode.c b/fs/fuse/iomode.c
index c545058a01e1..96eb311fe7bd 100644
--- a/fs/fuse/iomode.c
+++ b/fs/fuse/iomode.c
@@ -157,8 +157,11 @@ void fuse_file_uncached_io_end(struct inode *inode)
 	spin_unlock(&fi->lock);
 	if (!uncached_io)
 		wake_up(&fi->direct_io_waitq);
-	if (oldfb)
+	if (oldfb) {
+		/* Maybe update attributes after detaching backing inode */
+		fuse_backing_update_attr(inode, oldfb);
 		fuse_backing_put(oldfb);
+	}
 }
 
 /*
diff --git a/fs/fuse/passthrough.c b/fs/fuse/passthrough.c
index 260e76fc72d5..c1bb80a6e536 100644
--- a/fs/fuse/passthrough.c
+++ b/fs/fuse/passthrough.c
@@ -11,11 +11,8 @@
 #include <linux/backing-file.h>
 #include <linux/splice.h>
 
-static void fuse_file_accessed(struct file *file)
+static void fuse_backing_accessed(struct inode *inode, struct fuse_backing *fb)
 {
-	struct inode *inode = file_inode(file);
-	struct fuse_inode *fi = get_fuse_inode(inode);
-	struct fuse_backing *fb = fuse_inode_backing(fi);
 	struct inode *backing_inode = file_inode(fb->file);
 	struct timespec64 atime = inode_get_atime(inode);
 	struct timespec64 batime = inode_get_atime(backing_inode);
@@ -25,11 +22,8 @@ static void fuse_file_accessed(struct file *file)
 		fuse_invalidate_atime(inode);
 }
 
-static void fuse_file_modified(struct file *file)
+static void fuse_backing_modified(struct inode *inode, struct fuse_backing *fb)
 {
-	struct inode *inode = file_inode(file);
-	struct fuse_inode *fi = get_fuse_inode(inode);
-	struct fuse_backing *fb = fuse_inode_backing(fi);
 	struct inode *backing_inode = file_inode(fb->file);
 	struct timespec64 ctime = inode_get_ctime(inode);
 	struct timespec64 mtime = inode_get_mtime(inode);
@@ -42,6 +36,51 @@ static void fuse_file_modified(struct file *file)
 		fuse_invalidate_attr_mask(inode, FUSE_STATX_MODSIZE);
 }
 
+/* Called from fuse_file_uncached_io_end() after detach of backing inode */
+void fuse_backing_update_attr(struct inode *inode, struct fuse_backing *fb)
+{
+	fuse_backing_modified(inode, fb);
+	fuse_backing_accessed(inode, fb);
+}
+
+/* Called from fuse_getattr() - may race with detach of backing inode */
+void fuse_backing_update_attr_mask(struct inode *inode, u32 request_mask)
+{
+	struct fuse_inode *fi = get_fuse_inode(inode);
+	struct fuse_backing *fb;
+
+	rcu_read_lock();
+	fb = fuse_backing_get(fuse_inode_backing(fi));
+	rcu_read_unlock();
+	if (!fb)
+		return;
+
+	if (request_mask & FUSE_STATX_MODSIZE)
+		fuse_backing_modified(inode, fb);
+	if (request_mask & STATX_ATIME)
+		fuse_backing_accessed(inode, fb);
+
+	fuse_backing_put(fb);
+}
+
+static void fuse_file_accessed(struct file *file)
+{
+	struct inode *inode = file_inode(file);
+	struct fuse_inode *fi = get_fuse_inode(inode);
+	struct fuse_backing *fb = fuse_inode_backing(fi);
+
+	fuse_backing_accessed(inode, fb);
+}
+
+static void fuse_file_modified(struct file *file)
+{
+	struct inode *inode = file_inode(file);
+	struct fuse_inode *fi = get_fuse_inode(inode);
+	struct fuse_backing *fb = fuse_inode_backing(fi);
+
+	fuse_backing_modified(inode, fb);
+}
+
 ssize_t fuse_passthrough_read_iter(struct kiocb *iocb, struct iov_iter *iter)
 {
 	struct file *file = iocb->ki_filp;
-- 
2.34.1


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

* Re: [PATCH v15 3/9] fuse: implement ioctls to manage backing files
  2024-02-06 14:24 ` [PATCH v15 3/9] fuse: implement ioctls to manage backing files Amir Goldstein
@ 2024-02-28 10:50   ` Jingbo Xu
  2024-02-28 11:07     ` Amir Goldstein
  0 siblings, 1 reply; 27+ messages in thread
From: Jingbo Xu @ 2024-02-28 10:50 UTC (permalink / raw)
  To: Amir Goldstein, Miklos Szeredi
  Cc: Bernd Schubert, linux-fsdevel, Alessio Balsini

Hi Amir,

On 2/6/24 10:24 PM, Amir Goldstein wrote:
> FUSE server calls the FUSE_DEV_IOC_BACKING_OPEN ioctl with a backing file
> descriptor.  If the call succeeds, a backing file identifier is returned.
> 
> A later change will be using this backing file id in a reply to OPEN
> request with the flag FOPEN_PASSTHROUGH to setup passthrough of file
> operations on the open FUSE file to the backing file.
> 
> The FUSE server should call FUSE_DEV_IOC_BACKING_CLOSE ioctl to close the
> backing file by its id.
> 
> This can be done at any time, but if an open reply with FOPEN_PASSTHROUGH
> flag is still in progress, the open may fail if the backing file is
> closed before the fuse file was opened.
> 
> Setting up backing files requires a server with CAP_SYS_ADMIN privileges.
> For the backing file to be successfully setup, the backing file must
> implement both read_iter and write_iter file operations.
> 
> The limitation on the level of filesystem stacking allowed for the
> backing file is enforced before setting up the backing file.
> 
> Signed-off-by: Alessio Balsini <balsini@android.com>
> Signed-off-by: Amir Goldstein <amir73il@gmail.com>
> ---

[...]

> +int fuse_backing_close(struct fuse_conn *fc, int backing_id)
> +{
> +	struct fuse_backing *fb = NULL;
> +	int err;
> +
> +	pr_debug("%s: backing_id=%d\n", __func__, backing_id);
> +
> +	/* TODO: relax CAP_SYS_ADMIN once backing files are visible to lsof */
> +	err = -EPERM;
> +	if (!fc->passthrough || !capable(CAP_SYS_ADMIN))
> +		goto out;

Sorry for the late comment as I started reading this series these days.

I don't understand why CAP_SYS_ADMIN is required for the fuse server,
though I can understand it's a security constraint.  I can only find
that this constraint is newly added since v14, but failed to find any
related discussion or hint.

Besides, is there any chance relaxing the constraint to
ns_capable(CAP_SYS_ADMIN), as FUSE supports FS_USERNS_MOUNT, i.e.
support passthrough mode in user namespace?



-- 
Thanks,
Jingbo

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

* Re: [PATCH v15 3/9] fuse: implement ioctls to manage backing files
  2024-02-28 10:50   ` Jingbo Xu
@ 2024-02-28 11:07     ` Amir Goldstein
  2024-02-28 11:14       ` Miklos Szeredi
  0 siblings, 1 reply; 27+ messages in thread
From: Amir Goldstein @ 2024-02-28 11:07 UTC (permalink / raw)
  To: Jingbo Xu
  Cc: Miklos Szeredi, Bernd Schubert, linux-fsdevel, Alessio Balsini,
	Christian Brauner

On Wed, Feb 28, 2024 at 12:50 PM Jingbo Xu <jefflexu@linux.alibaba.com> wrote:
>
> Hi Amir,
>
> On 2/6/24 10:24 PM, Amir Goldstein wrote:
> > FUSE server calls the FUSE_DEV_IOC_BACKING_OPEN ioctl with a backing file
> > descriptor.  If the call succeeds, a backing file identifier is returned.
> >
> > A later change will be using this backing file id in a reply to OPEN
> > request with the flag FOPEN_PASSTHROUGH to setup passthrough of file
> > operations on the open FUSE file to the backing file.
> >
> > The FUSE server should call FUSE_DEV_IOC_BACKING_CLOSE ioctl to close the
> > backing file by its id.
> >
> > This can be done at any time, but if an open reply with FOPEN_PASSTHROUGH
> > flag is still in progress, the open may fail if the backing file is
> > closed before the fuse file was opened.
> >
> > Setting up backing files requires a server with CAP_SYS_ADMIN privileges.
> > For the backing file to be successfully setup, the backing file must
> > implement both read_iter and write_iter file operations.
> >
> > The limitation on the level of filesystem stacking allowed for the
> > backing file is enforced before setting up the backing file.
> >
> > Signed-off-by: Alessio Balsini <balsini@android.com>
> > Signed-off-by: Amir Goldstein <amir73il@gmail.com>
> > ---
>
> [...]
>
> > +int fuse_backing_close(struct fuse_conn *fc, int backing_id)
> > +{
> > +     struct fuse_backing *fb = NULL;
> > +     int err;
> > +
> > +     pr_debug("%s: backing_id=%d\n", __func__, backing_id);
> > +
> > +     /* TODO: relax CAP_SYS_ADMIN once backing files are visible to lsof */
> > +     err = -EPERM;
> > +     if (!fc->passthrough || !capable(CAP_SYS_ADMIN))
> > +             goto out;
>
> Sorry for the late comment as I started reading this series these days.
>
> I don't understand why CAP_SYS_ADMIN is required for the fuse server,
> though I can understand it's a security constraint.  I can only find
> that this constraint is newly added since v14, but failed to find any
> related discussion or hint.
>

This requirement is from Miklos.
The concern is that FUSE_DEV_IOC_BACKING_OPEN opens a file,
which then prevent clean unmount of fs, is not accounted in the user's rlimit
and is not visible in lsof, because it is not in any process file
descriptors table.
Miklos suggested that every FUSE connection will have a kernel thread
that those open fds could be associated with.
Hence the comment:
/* TODO: relax CAP_SYS_ADMIN once backing files are visible to lsof */

But since then, Christian has made some changes to the lifetime of file objects,
which require that backing_file must never be installed in files table, so this
solution is not as straightforward to implement.

In any case, we decided to defer this problem to the future.

> Besides, is there any chance relaxing the constraint to
> ns_capable(CAP_SYS_ADMIN), as FUSE supports FS_USERNS_MOUNT, i.e.
> support passthrough mode in user namespace?
>

I don't think so, because it will allow unprivileged user to exceed its
nested rlimits and hide open files that are invisble to lsof.

Thanks,
Amir.

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

* Re: [PATCH v15 3/9] fuse: implement ioctls to manage backing files
  2024-02-28 11:07     ` Amir Goldstein
@ 2024-02-28 11:14       ` Miklos Szeredi
  2024-02-28 11:28         ` Amir Goldstein
  2024-02-28 13:22         ` Bernd Schubert
  0 siblings, 2 replies; 27+ messages in thread
From: Miklos Szeredi @ 2024-02-28 11:14 UTC (permalink / raw)
  To: Amir Goldstein
  Cc: Jingbo Xu, Bernd Schubert, linux-fsdevel, Alessio Balsini,
	Christian Brauner

On Wed, 28 Feb 2024 at 12:08, Amir Goldstein <amir73il@gmail.com> wrote:

> I don't think so, because it will allow unprivileged user to exceed its
> nested rlimits and hide open files that are invisble to lsof.

How does io_uring deal with the similar problem of "fixed files"?

Thanks,
Miklos

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

* Re: [PATCH v15 3/9] fuse: implement ioctls to manage backing files
  2024-02-28 11:14       ` Miklos Szeredi
@ 2024-02-28 11:28         ` Amir Goldstein
  2024-02-28 14:32           ` Jens Axboe
  2024-02-28 13:22         ` Bernd Schubert
  1 sibling, 1 reply; 27+ messages in thread
From: Amir Goldstein @ 2024-02-28 11:28 UTC (permalink / raw)
  To: Miklos Szeredi, Jens Axboe
  Cc: Jingbo Xu, Bernd Schubert, linux-fsdevel, Alessio Balsini,
	Christian Brauner

On Wed, Feb 28, 2024 at 1:14 PM Miklos Szeredi <miklos@szeredi.hu> wrote:
>
> On Wed, 28 Feb 2024 at 12:08, Amir Goldstein <amir73il@gmail.com> wrote:
>
> > I don't think so, because it will allow unprivileged user to exceed its
> > nested rlimits and hide open files that are invisble to lsof.
>
> How does io_uring deal with the similar problem of "fixed files"?
>

Good question.

Jens, Chritian,
Are fixed files visible to lsof?
Do they have to remain open in the files table of process that set them
in addition to being registered as fixed files?
Do they get accounted in rlimit? of which user?

Thanks,
Amir.

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

* Re: [PATCH v15 3/9] fuse: implement ioctls to manage backing files
  2024-02-28 11:14       ` Miklos Szeredi
  2024-02-28 11:28         ` Amir Goldstein
@ 2024-02-28 13:22         ` Bernd Schubert
  1 sibling, 0 replies; 27+ messages in thread
From: Bernd Schubert @ 2024-02-28 13:22 UTC (permalink / raw)
  To: Miklos Szeredi, Amir Goldstein
  Cc: Jingbo Xu, linux-fsdevel, Alessio Balsini, Christian Brauner



On 2/28/24 12:14, Miklos Szeredi wrote:
> On Wed, 28 Feb 2024 at 12:08, Amir Goldstein <amir73il@gmail.com> wrote:
> 
>> I don't think so, because it will allow unprivileged user to exceed its
>> nested rlimits and hide open files that are invisble to lsof.
> 
> How does io_uring deal with the similar problem of "fixed files"?

Maybe I miss something, but with io_uring the process opens a file
descriptor and then registers that as fixed file? I.e. the open files
are not invisible?

Thanks,
Bernd

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

* Re: [PATCH v15 3/9] fuse: implement ioctls to manage backing files
  2024-02-28 11:28         ` Amir Goldstein
@ 2024-02-28 14:32           ` Jens Axboe
  2024-02-28 15:01             ` Miklos Szeredi
  0 siblings, 1 reply; 27+ messages in thread
From: Jens Axboe @ 2024-02-28 14:32 UTC (permalink / raw)
  To: Amir Goldstein, Miklos Szeredi
  Cc: Jingbo Xu, Bernd Schubert, linux-fsdevel, Alessio Balsini,
	Christian Brauner

On 2/28/24 4:28 AM, Amir Goldstein wrote:
> On Wed, Feb 28, 2024 at 1:14?PM Miklos Szeredi <miklos@szeredi.hu> wrote:
>>
>> On Wed, 28 Feb 2024 at 12:08, Amir Goldstein <amir73il@gmail.com> wrote:
>>
>>> I don't think so, because it will allow unprivileged user to exceed its
>>> nested rlimits and hide open files that are invisble to lsof.
>>
>> How does io_uring deal with the similar problem of "fixed files"?
>>
> 
> Good question.
> 
> Jens, Chritian,
> Are fixed files visible to lsof?

lsof won't show them, but you can read the fdinfo of the io_uring fd to
see them. Would probably be possible to make lsof find and show them
too, but haven't looked into that.

> Do they have to remain open in the files table of process that set them
> in addition to being registered as fixed files?

No, in fact they never have to be there in the first place. You can open
a normal file and then register it, now it's in both. Then you can close
the normal fd, and now it's not in the normal process file table
anymore, just in the direct list.

Or you can instantiate it as a direct descriptor to begin with, and then
it'll never have been in the normal file table.

> Do they get accounted in rlimit? of which user?

The fixed file table is limited in size by RLIMIT_NOFILE by the user
that registers it.

-- 
Jens Axboe


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

* Re: [PATCH v15 3/9] fuse: implement ioctls to manage backing files
  2024-02-28 14:32           ` Jens Axboe
@ 2024-02-28 15:01             ` Miklos Szeredi
  2024-02-28 15:05               ` Jens Axboe
                                 ` (2 more replies)
  0 siblings, 3 replies; 27+ messages in thread
From: Miklos Szeredi @ 2024-02-28 15:01 UTC (permalink / raw)
  To: Jens Axboe
  Cc: Amir Goldstein, Jingbo Xu, Bernd Schubert, linux-fsdevel,
	Alessio Balsini, Christian Brauner

On Wed, 28 Feb 2024 at 15:32, Jens Axboe <axboe@kernel.dk> wrote:
>
> On 2/28/24 4:28 AM, Amir Goldstein wrote:

> > Are fixed files visible to lsof?
>
> lsof won't show them, but you can read the fdinfo of the io_uring fd to
> see them. Would probably be possible to make lsof find and show them
> too, but haven't looked into that.

Okay, that approach could be used with fuse as well.  This isn't
perfect, but we can think improving the interface for both.

> > Do they get accounted in rlimit? of which user?
>
> The fixed file table is limited in size by RLIMIT_NOFILE by the user
> that registers it.

That's different for fuse as the server wouldn't register the whole
file table in one go.  The number of used slots could be limited by
RLIMIT_NOFILE, I think.

Thanks,
Miklos

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

* Re: [PATCH v15 3/9] fuse: implement ioctls to manage backing files
  2024-02-28 15:01             ` Miklos Szeredi
@ 2024-02-28 15:05               ` Jens Axboe
  2024-02-28 16:21               ` Amir Goldstein
  2024-02-29 10:15               ` Christian Brauner
  2 siblings, 0 replies; 27+ messages in thread
From: Jens Axboe @ 2024-02-28 15:05 UTC (permalink / raw)
  To: Miklos Szeredi
  Cc: Amir Goldstein, Jingbo Xu, Bernd Schubert, linux-fsdevel,
	Alessio Balsini, Christian Brauner

On 2/28/24 8:01 AM, Miklos Szeredi wrote:
> On Wed, 28 Feb 2024 at 15:32, Jens Axboe <axboe@kernel.dk> wrote:
>>
>> On 2/28/24 4:28 AM, Amir Goldstein wrote:
> 
>>> Are fixed files visible to lsof?
>>
>> lsof won't show them, but you can read the fdinfo of the io_uring fd to
>> see them. Would probably be possible to make lsof find and show them
>> too, but haven't looked into that.
> 
> Okay, that approach could be used with fuse as well.  This isn't
> perfect, but we can think improving the interface for both.

Yeah agree, would be nice to either patch lsof to show them, or come up
with an alternative way to expose it so it just works.

>>> Do they get accounted in rlimit? of which user?
>>
>> The fixed file table is limited in size by RLIMIT_NOFILE by the user
>> that registers it.
> 
> That's different for fuse as the server wouldn't register the whole
> file table in one go.  The number of used slots could be limited by
> RLIMIT_NOFILE, I think.

A normal use cases is to register an empty table of that size, which
is just the table itself. And then the table gets filled out as direct
descriptors are opened (and closed, etc).

-- 
Jens Axboe



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

* Re: [PATCH v15 3/9] fuse: implement ioctls to manage backing files
  2024-02-28 15:01             ` Miklos Szeredi
  2024-02-28 15:05               ` Jens Axboe
@ 2024-02-28 16:21               ` Amir Goldstein
  2024-02-29 10:15               ` Christian Brauner
  2 siblings, 0 replies; 27+ messages in thread
From: Amir Goldstein @ 2024-02-28 16:21 UTC (permalink / raw)
  To: Miklos Szeredi
  Cc: Jens Axboe, Jingbo Xu, Bernd Schubert, linux-fsdevel,
	Alessio Balsini, Christian Brauner

On Wed, Feb 28, 2024 at 5:01 PM Miklos Szeredi <miklos@szeredi.hu> wrote:
>
> On Wed, 28 Feb 2024 at 15:32, Jens Axboe <axboe@kernel.dk> wrote:
> >
> > On 2/28/24 4:28 AM, Amir Goldstein wrote:
>
> > > Are fixed files visible to lsof?
> >
> > lsof won't show them, but you can read the fdinfo of the io_uring fd to
> > see them. Would probably be possible to make lsof find and show them
> > too, but haven't looked into that.
>
> Okay, that approach could be used with fuse as well.  This isn't
> perfect, but we can think improving the interface for both.
>
> > > Do they get accounted in rlimit? of which user?
> >
> > The fixed file table is limited in size by RLIMIT_NOFILE by the user
> > that registers it.
>
> That's different for fuse as the server wouldn't register the whole
> file table in one go.  The number of used slots could be limited by
> RLIMIT_NOFILE, I think.
>

Yes. We can limit the number of backing id slots, which is the
number of FUSE_DEV_IOC_BACKING_OPEN for whom the server
did not call FUSE_DEV_IOC_BACKING_CLOSE, and we can easily
display the backing files in backing_files_map, but we also have
inode attached backing file, whose lifetime is that of the fuse files.

Those all harder to iterate and harder to limit, because the server
cannot close/revoke them.
OTOH, we could treat the inode attached backing files same as the
overlayfs backing files - worst case they only double the number of
files in the system.

We can probably keep them also in backing_files_map so we can iterate
them, but maybe let's see how the basic feature works first...

Thanks,
Amir.

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

* Re: [PATCH v15 3/9] fuse: implement ioctls to manage backing files
  2024-02-28 15:01             ` Miklos Szeredi
  2024-02-28 15:05               ` Jens Axboe
  2024-02-28 16:21               ` Amir Goldstein
@ 2024-02-29 10:15               ` Christian Brauner
  2024-02-29 10:17                 ` Christian Brauner
  2 siblings, 1 reply; 27+ messages in thread
From: Christian Brauner @ 2024-02-29 10:15 UTC (permalink / raw)
  To: Miklos Szeredi
  Cc: Jens Axboe, Amir Goldstein, Jingbo Xu, Bernd Schubert,
	linux-fsdevel, Alessio Balsini

On Wed, Feb 28, 2024 at 04:01:17PM +0100, Miklos Szeredi wrote:
> On Wed, 28 Feb 2024 at 15:32, Jens Axboe <axboe@kernel.dk> wrote:
> >
> > On 2/28/24 4:28 AM, Amir Goldstein wrote:
> 
> > > Are fixed files visible to lsof?
> >
> > lsof won't show them, but you can read the fdinfo of the io_uring fd to
> > see them. Would probably be possible to make lsof find and show them
> > too, but haven't looked into that.

I actually wrote about this before when I suggested IORING_OP_FIXED_FD_INSTALL:
https://patchwork.kernel.org/project/io-uring/patch/df0e24ff-f3a0-4818-8282-2a4e03b7b5a6@kernel.dk/#25629935

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

* Re: [PATCH v15 3/9] fuse: implement ioctls to manage backing files
  2024-02-29 10:15               ` Christian Brauner
@ 2024-02-29 10:17                 ` Christian Brauner
  2024-03-05 10:57                   ` Miklos Szeredi
  0 siblings, 1 reply; 27+ messages in thread
From: Christian Brauner @ 2024-02-29 10:17 UTC (permalink / raw)
  To: Miklos Szeredi
  Cc: Jens Axboe, Amir Goldstein, Jingbo Xu, Bernd Schubert,
	linux-fsdevel, Alessio Balsini

On Thu, Feb 29, 2024 at 11:15:35AM +0100, Christian Brauner wrote:
> On Wed, Feb 28, 2024 at 04:01:17PM +0100, Miklos Szeredi wrote:
> > On Wed, 28 Feb 2024 at 15:32, Jens Axboe <axboe@kernel.dk> wrote:
> > >
> > > On 2/28/24 4:28 AM, Amir Goldstein wrote:
> > 
> > > > Are fixed files visible to lsof?
> > >
> > > lsof won't show them, but you can read the fdinfo of the io_uring fd to
> > > see them. Would probably be possible to make lsof find and show them
> > > too, but haven't looked into that.
> 
> I actually wrote about this before when I suggested IORING_OP_FIXED_FD_INSTALL:
> https://patchwork.kernel.org/project/io-uring/patch/df0e24ff-f3a0-4818-8282-2a4e03b7b5a6@kernel.dk/#25629935

I think that it shouldn't be a problem as long as userspace has some way
of figuring this out. So extending lsof might just be enough for this.

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

* Re: [PATCH v15 3/9] fuse: implement ioctls to manage backing files
  2024-02-29 10:17                 ` Christian Brauner
@ 2024-03-05 10:57                   ` Miklos Szeredi
  0 siblings, 0 replies; 27+ messages in thread
From: Miklos Szeredi @ 2024-03-05 10:57 UTC (permalink / raw)
  To: Christian Brauner
  Cc: Jens Axboe, Amir Goldstein, Jingbo Xu, Bernd Schubert,
	linux-fsdevel, Alessio Balsini

On Thu, 29 Feb 2024 at 11:17, Christian Brauner <brauner@kernel.org> wrote:
>
> On Thu, Feb 29, 2024 at 11:15:35AM +0100, Christian Brauner wrote:
> > On Wed, Feb 28, 2024 at 04:01:17PM +0100, Miklos Szeredi wrote:
> > > On Wed, 28 Feb 2024 at 15:32, Jens Axboe <axboe@kernel.dk> wrote:
> > > >
> > > > On 2/28/24 4:28 AM, Amir Goldstein wrote:
> > >
> > > > > Are fixed files visible to lsof?
> > > >
> > > > lsof won't show them, but you can read the fdinfo of the io_uring fd to
> > > > see them. Would probably be possible to make lsof find and show them
> > > > too, but haven't looked into that.
> >
> > I actually wrote about this before when I suggested IORING_OP_FIXED_FD_INSTALL:
> > https://patchwork.kernel.org/project/io-uring/patch/df0e24ff-f3a0-4818-8282-2a4e03b7b5a6@kernel.dk/#25629935
>
> I think that it shouldn't be a problem as long as userspace has some way
> of figuring this out. So extending lsof might just be enough for this.

Problem is fdinfo on io_uring fd just contains the last component names.

Do we want full "magic symlink" semantics for these?  I'm not sure.
But just the last component does seem too little.

I've advocated using xattr for querying virtual attributes like these.
So I'll advocate again.   Does anyone see a problem with adding

getxattr("/proc/$PID/fdinfo/$IO_URING_FD",
"io_uring:fixed_files:$SLOT:path", buf, buflen);

?

Thanks,
Miklos

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

* Re: [PATCH v15 9/9] fuse: auto-invalidate inode attributes in passthrough mode
  2024-02-06 14:24 ` [PATCH v15 9/9] fuse: auto-invalidate inode attributes in passthrough mode Amir Goldstein
@ 2024-04-02 20:13   ` Sweet Tea Dorminy
  2024-04-02 21:18     ` Bernd Schubert
  0 siblings, 1 reply; 27+ messages in thread
From: Sweet Tea Dorminy @ 2024-04-02 20:13 UTC (permalink / raw)
  To: Amir Goldstein, Miklos Szeredi; +Cc: Bernd Schubert, linux-fsdevel



On 2/6/24 09:24, Amir Goldstein wrote:
> After passthrough read/write, we invalidate a/c/mtime/size attributes
> if the backing inode attributes differ from FUSE inode attributes.
> 
> Do the same in fuse_getattr() and after detach of backing inode, so that
> passthrough mmap read/write changes to a/c/mtime/size attribute of the
> backing inode will be propagated to the FUSE inode.
> 
> The rules of invalidating a/c/mtime/size attributes with writeback cache
> are more complicated, so for now, writeback cache and passthrough cannot
> be enabled on the same filesystem.
> 
> Signed-off-by: Amir Goldstein <amir73il@gmail.com>
> ---
>   fs/fuse/dir.c         |  4 ++++
>   fs/fuse/fuse_i.h      |  2 ++
>   fs/fuse/inode.c       |  4 ++++
>   fs/fuse/iomode.c      |  5 +++-
>   fs/fuse/passthrough.c | 55 ++++++++++++++++++++++++++++++++++++-------
>   5 files changed, 61 insertions(+), 9 deletions(-)
> 
> diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c
> index 95330c2ca3d8..7f9d002b8f23 100644
> --- a/fs/fuse/dir.c
> +++ b/fs/fuse/dir.c
> @@ -2118,6 +2118,10 @@ static int fuse_getattr(struct mnt_idmap *idmap,
>   		return -EACCES;
>   	}
>   
> +	/* Maybe update/invalidate attributes from backing inode */
> +	if (fuse_inode_backing(get_fuse_inode(inode)))
> +		fuse_backing_update_attr_mask(inode, request_mask);
> +
>   	return fuse_update_get_attr(inode, NULL, stat, request_mask, flags);
>   }
>   
> diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h
> index 98f878a52af1..4b011d31012f 100644
> --- a/fs/fuse/fuse_i.h
> +++ b/fs/fuse/fuse_i.h
> @@ -1456,6 +1456,8 @@ void fuse_backing_files_init(struct fuse_conn *fc);
>   void fuse_backing_files_free(struct fuse_conn *fc);
>   int fuse_backing_open(struct fuse_conn *fc, struct fuse_backing_map *map);
>   int fuse_backing_close(struct fuse_conn *fc, int backing_id);
> +void fuse_backing_update_attr(struct inode *inode, struct fuse_backing *fb);
> +void fuse_backing_update_attr_mask(struct inode *inode, u32 request_mask);
>   
>   struct fuse_backing *fuse_passthrough_open(struct file *file,
>   					   struct inode *inode,
> diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c
> index c26a84439934..c68f005b6e86 100644
> --- a/fs/fuse/inode.c
> +++ b/fs/fuse/inode.c
> @@ -1302,9 +1302,13 @@ static void process_init_reply(struct fuse_mount *fm, struct fuse_args *args,
>   			 * on a stacked fs (e.g. overlayfs) themselves and with
>   			 * max_stack_depth == 1, FUSE fs can be stacked as the
>   			 * underlying fs of a stacked fs (e.g. overlayfs).
> +			 *
> +			 * For now, writeback cache and passthrough cannot be
> +			 * enabled on the same filesystem.
>   			 */
>   			if (IS_ENABLED(CONFIG_FUSE_PASSTHROUGH) &&
>   			    (flags & FUSE_PASSTHROUGH) &&
> +			    !fc->writeback_cache &&
>   			    arg->max_stack_depth > 0 &&
>   			    arg->max_stack_depth <= FILESYSTEM_MAX_STACK_DEPTH) {
>   				fc->passthrough = 1;
> diff --git a/fs/fuse/iomode.c b/fs/fuse/iomode.c
> index c545058a01e1..96eb311fe7bd 100644
> --- a/fs/fuse/iomode.c
> +++ b/fs/fuse/iomode.c
> @@ -157,8 +157,11 @@ void fuse_file_uncached_io_end(struct inode *inode)
>   	spin_unlock(&fi->lock);
>   	if (!uncached_io)
>   		wake_up(&fi->direct_io_waitq);
> -	if (oldfb)
> +	if (oldfb) {
> +		/* Maybe update attributes after detaching backing inode */
> +		fuse_backing_update_attr(inode, oldfb);
>   		fuse_backing_put(oldfb);
> +	}
>   }
>   
>   /*
> diff --git a/fs/fuse/passthrough.c b/fs/fuse/passthrough.c
> index 260e76fc72d5..c1bb80a6e536 100644
> --- a/fs/fuse/passthrough.c
> +++ b/fs/fuse/passthrough.c
> @@ -11,11 +11,8 @@
>   #include <linux/backing-file.h>
>   #include <linux/splice.h>
>   
> -static void fuse_file_accessed(struct file *file)
> +static void fuse_backing_accessed(struct inode *inode, struct fuse_backing *fb)
>   {
> -	struct inode *inode = file_inode(file);
> -	struct fuse_inode *fi = get_fuse_inode(inode);
> -	struct fuse_backing *fb = fuse_inode_backing(fi);
>   	struct inode *backing_inode = file_inode(fb->file);
>   	struct timespec64 atime = inode_get_atime(inode);
>   	struct timespec64 batime = inode_get_atime(backing_inode);
> @@ -25,11 +22,8 @@ static void fuse_file_accessed(struct file *file)
>   		fuse_invalidate_atime(inode);
>   }
>   
> -static void fuse_file_modified(struct file *file)
> +static void fuse_backing_modified(struct inode *inode, struct fuse_backing *fb)
>   {
> -	struct inode *inode = file_inode(file);
> -	struct fuse_inode *fi = get_fuse_inode(inode);
> -	struct fuse_backing *fb = fuse_inode_backing(fi);
>   	struct inode *backing_inode = file_inode(fb->file);
>   	struct timespec64 ctime = inode_get_ctime(inode);
>   	struct timespec64 mtime = inode_get_mtime(inode);
> @@ -42,6 +36,51 @@ static void fuse_file_modified(struct file *file)
>   		fuse_invalidate_attr_mask(inode, FUSE_STATX_MODSIZE);
>   }
>   
> +/* Called from fuse_file_uncached_io_end() after detach of backing inode */
> +void fuse_backing_update_attr(struct inode *inode, struct fuse_backing *fb)
> +{
> +	fuse_backing_modified(inode, fb);
> +	fuse_backing_accessed(inode, fb);
> +}
> +
> +/* Called from fuse_getattr() - may race with detach of backing inode */
> +void fuse_backing_update_attr_mask(struct inode *inode, u32 request_mask)
> +{
> +	struct fuse_inode *fi = get_fuse_inode(inode);
> +	struct fuse_backing *fb;
> +
> +	rcu_read_lock();
> +	fb = fuse_backing_get(fuse_inode_backing(fi));
> +	rcu_read_unlock();
> +	if (!fb)
> +		return;
> +
> +	if (request_mask & FUSE_STATX_MODSIZE)
> +		fuse_backing_modified(inode, fb);
> +	if (request_mask & STATX_ATIME)
> +		fuse_backing_accessed(inode, fb);
> +
> +	fuse_backing_put(fb);
> +}
> +
> +static void fuse_file_accessed(struct file *file)
> +{
> +	struct inode *inode = file_inode(file);
> +	struct fuse_inode *fi = get_fuse_inode(inode);
> +	struct fuse_backing *fb = fuse_inode_backing(fi);
> +
> +	fuse_backing_accessed(inode, fb);
> +}
> +
> +static void fuse_file_modified(struct file *file)
> +{
> +	struct inode *inode = file_inode(file);
> +	struct fuse_inode *fi = get_fuse_inode(inode);
> +	struct fuse_backing *fb = fuse_inode_backing(fi);
> +
> +	fuse_backing_modified(inode, fb);
> +}
> +
>   ssize_t fuse_passthrough_read_iter(struct kiocb *iocb, struct iov_iter *iter)
>   {
>   	struct file *file = iocb->ki_filp;

I noticed this patch doesn't seem to have made it into 6.9 like the rest 
of these passthrough patches -- may I ask why it didn't make it? I think 
it still makes sense but I might be missing some change between what's 
in 6.9 and this version of the patches.

Thanks!

Sweet Tea

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

* Re: [PATCH v15 9/9] fuse: auto-invalidate inode attributes in passthrough mode
  2024-04-02 20:13   ` Sweet Tea Dorminy
@ 2024-04-02 21:18     ` Bernd Schubert
  2024-04-03  8:18       ` Amir Goldstein
  0 siblings, 1 reply; 27+ messages in thread
From: Bernd Schubert @ 2024-04-02 21:18 UTC (permalink / raw)
  To: Sweet Tea Dorminy, Amir Goldstein, Miklos Szeredi; +Cc: linux-fsdevel



On 4/2/24 22:13, Sweet Tea Dorminy wrote:
> 
> 
> On 2/6/24 09:24, Amir Goldstein wrote:
>> After passthrough read/write, we invalidate a/c/mtime/size attributes
>> if the backing inode attributes differ from FUSE inode attributes.
>>
>> Do the same in fuse_getattr() and after detach of backing inode, so that
>> passthrough mmap read/write changes to a/c/mtime/size attribute of the
>> backing inode will be propagated to the FUSE inode.
>>
>> The rules of invalidating a/c/mtime/size attributes with writeback cache
>> are more complicated, so for now, writeback cache and passthrough cannot
>> be enabled on the same filesystem.
>>
>> Signed-off-by: Amir Goldstein <amir73il@gmail.com>
>> ---
>>   fs/fuse/dir.c         |  4 ++++
>>   fs/fuse/fuse_i.h      |  2 ++
>>   fs/fuse/inode.c       |  4 ++++
>>   fs/fuse/iomode.c      |  5 +++-
>>   fs/fuse/passthrough.c | 55 ++++++++++++++++++++++++++++++++++++-------
>>   5 files changed, 61 insertions(+), 9 deletions(-)
>>
>> diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c
>> index 95330c2ca3d8..7f9d002b8f23 100644
>> --- a/fs/fuse/dir.c
>> +++ b/fs/fuse/dir.c
>> @@ -2118,6 +2118,10 @@ static int fuse_getattr(struct mnt_idmap *idmap,
>>           return -EACCES;
>>       }
>>   +    /* Maybe update/invalidate attributes from backing inode */
>> +    if (fuse_inode_backing(get_fuse_inode(inode)))
>> +        fuse_backing_update_attr_mask(inode, request_mask);
>> +
>>       return fuse_update_get_attr(inode, NULL, stat, request_mask,
>> flags);
>>   }
>>   diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h
>> index 98f878a52af1..4b011d31012f 100644
>> --- a/fs/fuse/fuse_i.h
>> +++ b/fs/fuse/fuse_i.h
>> @@ -1456,6 +1456,8 @@ void fuse_backing_files_init(struct fuse_conn *fc);
>>   void fuse_backing_files_free(struct fuse_conn *fc);
>>   int fuse_backing_open(struct fuse_conn *fc, struct fuse_backing_map
>> *map);
>>   int fuse_backing_close(struct fuse_conn *fc, int backing_id);
>> +void fuse_backing_update_attr(struct inode *inode, struct
>> fuse_backing *fb);
>> +void fuse_backing_update_attr_mask(struct inode *inode, u32
>> request_mask);
>>     struct fuse_backing *fuse_passthrough_open(struct file *file,
>>                          struct inode *inode,
>> diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c
>> index c26a84439934..c68f005b6e86 100644
>> --- a/fs/fuse/inode.c
>> +++ b/fs/fuse/inode.c
>> @@ -1302,9 +1302,13 @@ static void process_init_reply(struct
>> fuse_mount *fm, struct fuse_args *args,
>>                * on a stacked fs (e.g. overlayfs) themselves and with
>>                * max_stack_depth == 1, FUSE fs can be stacked as the
>>                * underlying fs of a stacked fs (e.g. overlayfs).
>> +             *
>> +             * For now, writeback cache and passthrough cannot be
>> +             * enabled on the same filesystem.
>>                */
>>               if (IS_ENABLED(CONFIG_FUSE_PASSTHROUGH) &&
>>                   (flags & FUSE_PASSTHROUGH) &&
>> +                !fc->writeback_cache &&
>>                   arg->max_stack_depth > 0 &&
>>                   arg->max_stack_depth <= FILESYSTEM_MAX_STACK_DEPTH) {
>>                   fc->passthrough = 1;
>> diff --git a/fs/fuse/iomode.c b/fs/fuse/iomode.c
>> index c545058a01e1..96eb311fe7bd 100644
>> --- a/fs/fuse/iomode.c
>> +++ b/fs/fuse/iomode.c
>> @@ -157,8 +157,11 @@ void fuse_file_uncached_io_end(struct inode *inode)
>>       spin_unlock(&fi->lock);
>>       if (!uncached_io)
>>           wake_up(&fi->direct_io_waitq);
>> -    if (oldfb)
>> +    if (oldfb) {
>> +        /* Maybe update attributes after detaching backing inode */
>> +        fuse_backing_update_attr(inode, oldfb);
>>           fuse_backing_put(oldfb);
>> +    }
>>   }
>>     /*
>> diff --git a/fs/fuse/passthrough.c b/fs/fuse/passthrough.c
>> index 260e76fc72d5..c1bb80a6e536 100644
>> --- a/fs/fuse/passthrough.c
>> +++ b/fs/fuse/passthrough.c
>> @@ -11,11 +11,8 @@
>>   #include <linux/backing-file.h>
>>   #include <linux/splice.h>
>>   -static void fuse_file_accessed(struct file *file)
>> +static void fuse_backing_accessed(struct inode *inode, struct
>> fuse_backing *fb)
>>   {
>> -    struct inode *inode = file_inode(file);
>> -    struct fuse_inode *fi = get_fuse_inode(inode);
>> -    struct fuse_backing *fb = fuse_inode_backing(fi);
>>       struct inode *backing_inode = file_inode(fb->file);
>>       struct timespec64 atime = inode_get_atime(inode);
>>       struct timespec64 batime = inode_get_atime(backing_inode);
>> @@ -25,11 +22,8 @@ static void fuse_file_accessed(struct file *file)
>>           fuse_invalidate_atime(inode);
>>   }
>>   -static void fuse_file_modified(struct file *file)
>> +static void fuse_backing_modified(struct inode *inode, struct
>> fuse_backing *fb)
>>   {
>> -    struct inode *inode = file_inode(file);
>> -    struct fuse_inode *fi = get_fuse_inode(inode);
>> -    struct fuse_backing *fb = fuse_inode_backing(fi);
>>       struct inode *backing_inode = file_inode(fb->file);
>>       struct timespec64 ctime = inode_get_ctime(inode);
>>       struct timespec64 mtime = inode_get_mtime(inode);
>> @@ -42,6 +36,51 @@ static void fuse_file_modified(struct file *file)
>>           fuse_invalidate_attr_mask(inode, FUSE_STATX_MODSIZE);
>>   }
>>   +/* Called from fuse_file_uncached_io_end() after detach of backing
>> inode */
>> +void fuse_backing_update_attr(struct inode *inode, struct
>> fuse_backing *fb)
>> +{
>> +    fuse_backing_modified(inode, fb);
>> +    fuse_backing_accessed(inode, fb);
>> +}
>> +
>> +/* Called from fuse_getattr() - may race with detach of backing inode */
>> +void fuse_backing_update_attr_mask(struct inode *inode, u32
>> request_mask)
>> +{
>> +    struct fuse_inode *fi = get_fuse_inode(inode);
>> +    struct fuse_backing *fb;
>> +
>> +    rcu_read_lock();
>> +    fb = fuse_backing_get(fuse_inode_backing(fi));
>> +    rcu_read_unlock();
>> +    if (!fb)
>> +        return;
>> +
>> +    if (request_mask & FUSE_STATX_MODSIZE)
>> +        fuse_backing_modified(inode, fb);
>> +    if (request_mask & STATX_ATIME)
>> +        fuse_backing_accessed(inode, fb);
>> +
>> +    fuse_backing_put(fb);
>> +}
>> +
>> +static void fuse_file_accessed(struct file *file)
>> +{
>> +    struct inode *inode = file_inode(file);
>> +    struct fuse_inode *fi = get_fuse_inode(inode);
>> +    struct fuse_backing *fb = fuse_inode_backing(fi);
>> +
>> +    fuse_backing_accessed(inode, fb);
>> +}
>> +
>> +static void fuse_file_modified(struct file *file)
>> +{
>> +    struct inode *inode = file_inode(file);
>> +    struct fuse_inode *fi = get_fuse_inode(inode);
>> +    struct fuse_backing *fb = fuse_inode_backing(fi);
>> +
>> +    fuse_backing_modified(inode, fb);
>> +}
>> +
>>   ssize_t fuse_passthrough_read_iter(struct kiocb *iocb, struct
>> iov_iter *iter)
>>   {
>>       struct file *file = iocb->ki_filp;
> 
> I noticed this patch doesn't seem to have made it into 6.9 like the rest
> of these passthrough patches -- may I ask why it didn't make it? I think
> it still makes sense but I might be missing some change between what's
> in 6.9 and this version of the patches.
> 
> Thanks!
> 
> Sweet Tea

See here please
https://lore.kernel.org/all/CAOQ4uxj8Az6VEZ-Ky5gs33gc0N9hjv4XqL6XC_kc+vsVpaBCOg@mail.gmail.com/


Bernd

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

* Re: [PATCH v15 9/9] fuse: auto-invalidate inode attributes in passthrough mode
  2024-04-02 21:18     ` Bernd Schubert
@ 2024-04-03  8:18       ` Amir Goldstein
  2024-04-04 14:07         ` Sweet Tea Dorminy
  0 siblings, 1 reply; 27+ messages in thread
From: Amir Goldstein @ 2024-04-03  8:18 UTC (permalink / raw)
  To: Bernd Schubert, Sweet Tea Dorminy; +Cc: Miklos Szeredi, linux-fsdevel

On Wed, Apr 3, 2024 at 12:18 AM Bernd Schubert
<bernd.schubert@fastmail.fm> wrote:
>
>
>
> On 4/2/24 22:13, Sweet Tea Dorminy wrote:
> >
> >
> > On 2/6/24 09:24, Amir Goldstein wrote:
> >> After passthrough read/write, we invalidate a/c/mtime/size attributes
> >> if the backing inode attributes differ from FUSE inode attributes.
> >>
> >> Do the same in fuse_getattr() and after detach of backing inode, so that
> >> passthrough mmap read/write changes to a/c/mtime/size attribute of the
> >> backing inode will be propagated to the FUSE inode.
> >>
> >> The rules of invalidating a/c/mtime/size attributes with writeback cache
> >> are more complicated, so for now, writeback cache and passthrough cannot
> >> be enabled on the same filesystem.
> >>
> >> Signed-off-by: Amir Goldstein <amir73il@gmail.com>
> >> ---
> >>   fs/fuse/dir.c         |  4 ++++
> >>   fs/fuse/fuse_i.h      |  2 ++
> >>   fs/fuse/inode.c       |  4 ++++
> >>   fs/fuse/iomode.c      |  5 +++-
> >>   fs/fuse/passthrough.c | 55 ++++++++++++++++++++++++++++++++++++-------
> >>   5 files changed, 61 insertions(+), 9 deletions(-)
> >>
> >> diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c
> >> index 95330c2ca3d8..7f9d002b8f23 100644
> >> --- a/fs/fuse/dir.c
> >> +++ b/fs/fuse/dir.c
> >> @@ -2118,6 +2118,10 @@ static int fuse_getattr(struct mnt_idmap *idmap,
> >>           return -EACCES;
> >>       }
> >>   +    /* Maybe update/invalidate attributes from backing inode */
> >> +    if (fuse_inode_backing(get_fuse_inode(inode)))
> >> +        fuse_backing_update_attr_mask(inode, request_mask);
> >> +
> >>       return fuse_update_get_attr(inode, NULL, stat, request_mask,
> >> flags);
> >>   }
> >>   diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h
> >> index 98f878a52af1..4b011d31012f 100644
> >> --- a/fs/fuse/fuse_i.h
> >> +++ b/fs/fuse/fuse_i.h
> >> @@ -1456,6 +1456,8 @@ void fuse_backing_files_init(struct fuse_conn *fc);
> >>   void fuse_backing_files_free(struct fuse_conn *fc);
> >>   int fuse_backing_open(struct fuse_conn *fc, struct fuse_backing_map
> >> *map);
> >>   int fuse_backing_close(struct fuse_conn *fc, int backing_id);
> >> +void fuse_backing_update_attr(struct inode *inode, struct
> >> fuse_backing *fb);
> >> +void fuse_backing_update_attr_mask(struct inode *inode, u32
> >> request_mask);
> >>     struct fuse_backing *fuse_passthrough_open(struct file *file,
> >>                          struct inode *inode,
> >> diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c
> >> index c26a84439934..c68f005b6e86 100644
> >> --- a/fs/fuse/inode.c
> >> +++ b/fs/fuse/inode.c
> >> @@ -1302,9 +1302,13 @@ static void process_init_reply(struct
> >> fuse_mount *fm, struct fuse_args *args,
> >>                * on a stacked fs (e.g. overlayfs) themselves and with
> >>                * max_stack_depth == 1, FUSE fs can be stacked as the
> >>                * underlying fs of a stacked fs (e.g. overlayfs).
> >> +             *
> >> +             * For now, writeback cache and passthrough cannot be
> >> +             * enabled on the same filesystem.
> >>                */
> >>               if (IS_ENABLED(CONFIG_FUSE_PASSTHROUGH) &&
> >>                   (flags & FUSE_PASSTHROUGH) &&
> >> +                !fc->writeback_cache &&
> >>                   arg->max_stack_depth > 0 &&
> >>                   arg->max_stack_depth <= FILESYSTEM_MAX_STACK_DEPTH) {
> >>                   fc->passthrough = 1;
> >> diff --git a/fs/fuse/iomode.c b/fs/fuse/iomode.c
> >> index c545058a01e1..96eb311fe7bd 100644
> >> --- a/fs/fuse/iomode.c
> >> +++ b/fs/fuse/iomode.c
> >> @@ -157,8 +157,11 @@ void fuse_file_uncached_io_end(struct inode *inode)
> >>       spin_unlock(&fi->lock);
> >>       if (!uncached_io)
> >>           wake_up(&fi->direct_io_waitq);
> >> -    if (oldfb)
> >> +    if (oldfb) {
> >> +        /* Maybe update attributes after detaching backing inode */
> >> +        fuse_backing_update_attr(inode, oldfb);
> >>           fuse_backing_put(oldfb);
> >> +    }
> >>   }
> >>     /*
> >> diff --git a/fs/fuse/passthrough.c b/fs/fuse/passthrough.c
> >> index 260e76fc72d5..c1bb80a6e536 100644
> >> --- a/fs/fuse/passthrough.c
> >> +++ b/fs/fuse/passthrough.c
> >> @@ -11,11 +11,8 @@
> >>   #include <linux/backing-file.h>
> >>   #include <linux/splice.h>
> >>   -static void fuse_file_accessed(struct file *file)
> >> +static void fuse_backing_accessed(struct inode *inode, struct
> >> fuse_backing *fb)
> >>   {
> >> -    struct inode *inode = file_inode(file);
> >> -    struct fuse_inode *fi = get_fuse_inode(inode);
> >> -    struct fuse_backing *fb = fuse_inode_backing(fi);
> >>       struct inode *backing_inode = file_inode(fb->file);
> >>       struct timespec64 atime = inode_get_atime(inode);
> >>       struct timespec64 batime = inode_get_atime(backing_inode);
> >> @@ -25,11 +22,8 @@ static void fuse_file_accessed(struct file *file)
> >>           fuse_invalidate_atime(inode);
> >>   }
> >>   -static void fuse_file_modified(struct file *file)
> >> +static void fuse_backing_modified(struct inode *inode, struct
> >> fuse_backing *fb)
> >>   {
> >> -    struct inode *inode = file_inode(file);
> >> -    struct fuse_inode *fi = get_fuse_inode(inode);
> >> -    struct fuse_backing *fb = fuse_inode_backing(fi);
> >>       struct inode *backing_inode = file_inode(fb->file);
> >>       struct timespec64 ctime = inode_get_ctime(inode);
> >>       struct timespec64 mtime = inode_get_mtime(inode);
> >> @@ -42,6 +36,51 @@ static void fuse_file_modified(struct file *file)
> >>           fuse_invalidate_attr_mask(inode, FUSE_STATX_MODSIZE);
> >>   }
> >>   +/* Called from fuse_file_uncached_io_end() after detach of backing
> >> inode */
> >> +void fuse_backing_update_attr(struct inode *inode, struct
> >> fuse_backing *fb)
> >> +{
> >> +    fuse_backing_modified(inode, fb);
> >> +    fuse_backing_accessed(inode, fb);
> >> +}
> >> +
> >> +/* Called from fuse_getattr() - may race with detach of backing inode */
> >> +void fuse_backing_update_attr_mask(struct inode *inode, u32
> >> request_mask)
> >> +{
> >> +    struct fuse_inode *fi = get_fuse_inode(inode);
> >> +    struct fuse_backing *fb;
> >> +
> >> +    rcu_read_lock();
> >> +    fb = fuse_backing_get(fuse_inode_backing(fi));
> >> +    rcu_read_unlock();
> >> +    if (!fb)
> >> +        return;
> >> +
> >> +    if (request_mask & FUSE_STATX_MODSIZE)
> >> +        fuse_backing_modified(inode, fb);
> >> +    if (request_mask & STATX_ATIME)
> >> +        fuse_backing_accessed(inode, fb);
> >> +
> >> +    fuse_backing_put(fb);
> >> +}
> >> +
> >> +static void fuse_file_accessed(struct file *file)
> >> +{
> >> +    struct inode *inode = file_inode(file);
> >> +    struct fuse_inode *fi = get_fuse_inode(inode);
> >> +    struct fuse_backing *fb = fuse_inode_backing(fi);
> >> +
> >> +    fuse_backing_accessed(inode, fb);
> >> +}
> >> +
> >> +static void fuse_file_modified(struct file *file)
> >> +{
> >> +    struct inode *inode = file_inode(file);
> >> +    struct fuse_inode *fi = get_fuse_inode(inode);
> >> +    struct fuse_backing *fb = fuse_inode_backing(fi);
> >> +
> >> +    fuse_backing_modified(inode, fb);
> >> +}
> >> +
> >>   ssize_t fuse_passthrough_read_iter(struct kiocb *iocb, struct
> >> iov_iter *iter)
> >>   {
> >>       struct file *file = iocb->ki_filp;
> >
> > I noticed this patch doesn't seem to have made it into 6.9 like the rest
> > of these passthrough patches -- may I ask why it didn't make it? I think
> > it still makes sense but I might be missing some change between what's
> > in 6.9 and this version of the patches.
> >
> > Thanks!
> >
> > Sweet Tea
>
> See here please
> https://lore.kernel.org/all/CAOQ4uxj8Az6VEZ-Ky5gs33gc0N9hjv4XqL6XC_kc+vsVpaBCOg@mail.gmail.com/
>
>

FWIW, I intend to take a swing at getattr() passthrough "as soon as I can".

Sweet Tea,

Can you please explain the workload where you find that this patch is needed?
Is your workload using mmap writes? requires a long attribute cache timeout?
Does your workload involve mixing passthrough IO and direct/cached IO
on the same inode at different times or by different open fd's?

I would like to know, so I can tell you if getattr() passthrough design is
going to help your use case.

For example, my current getattr() passthrough design (in my head)
will not allow opening the inode in cached IO mode from lookup time
until evict/forget, unlike the current read/write passthrough, which is
from first open to last close.

Thanks,
Amir.

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

* Re: [PATCH v15 9/9] fuse: auto-invalidate inode attributes in passthrough mode
  2024-04-03  8:18       ` Amir Goldstein
@ 2024-04-04 14:07         ` Sweet Tea Dorminy
  2024-05-09 14:32           ` Amir Goldstein
  0 siblings, 1 reply; 27+ messages in thread
From: Sweet Tea Dorminy @ 2024-04-04 14:07 UTC (permalink / raw)
  To: Amir Goldstein, Bernd Schubert; +Cc: Miklos Szeredi, linux-fsdevel


> Sweet Tea,
> 
> Can you please explain the workload where you find that this patch is needed?

I was researching before sending out my own version of attr passthrough 
- it seemed like a step in the direction, but then the code in-tree 
wasn't the same.

> Is your workload using mmap writes? requires a long attribute cache timeout?
> Does your workload involve mixing passthrough IO and direct/cached IO
> on the same inode at different times or by different open fd's?
> 
> I would like to know, so I can tell you if getattr() passthrough design is
> going to help your use case.
> 
> For example, my current getattr() passthrough design (in my head)
> will not allow opening the inode in cached IO mode from lookup time
> until evict/forget, unlike the current read/write passthrough, which is
> from first open to last close.

I think the things I'd been working on is very similar.

Two possible HSM variants, both focused on doing passthrough IO with 
minimal involvement from the fuse server in at least some cases.

One would be using passthrough for temporary ingestion of some memory 
state for a workload, user writes files and the FUSE server can choose 
to passthrough them to local storage temporarily or to send them to 
remote storage -- as ingestion requires pausing the workload and is 
therefore very expensive, I'd like to pass through attr updates to the 
backing file so that there are minimal roundtrips to the fuse server 
during write. Later the HSM would move the files to remote storage, or 
delete them.

One would be using passthrough for binaries -- providing specific sets 
of mostly binaries with some tracking on open/close, so the HSM can 
delete unused sets. Again the goal is to avoid metadata query roundtrips 
to userspace for speed; we don't expect a file open in passthrough mode 
to be opened again for FUSE-server-mediated IO until the passthrough 
version is closed.

Thanks!

Sweet Tea

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

* Re: [PATCH v15 9/9] fuse: auto-invalidate inode attributes in passthrough mode
  2024-04-04 14:07         ` Sweet Tea Dorminy
@ 2024-05-09 14:32           ` Amir Goldstein
  0 siblings, 0 replies; 27+ messages in thread
From: Amir Goldstein @ 2024-05-09 14:32 UTC (permalink / raw)
  To: Sweet Tea Dorminy; +Cc: Bernd Schubert, Miklos Szeredi, linux-fsdevel

On Fri, Apr 5, 2024 at 11:52 PM Sweet Tea Dorminy
<sweettea-kernel@dorminy.me> wrote:
>
>
> > Sweet Tea,
> >
> > Can you please explain the workload where you find that this patch is needed?
>
> I was researching before sending out my own version of attr passthrough
> - it seemed like a step in the direction, but then the code in-tree
> wasn't the same.
>

FYI, I have pushed a WIP branch with some patches in the general direction
of getattr() passthrough:
https://github.com/amir73il/linux/commits/fuse-backing-inode-wip/

It is not at all functional and probably not working - I only verified that
it does not explode when I run xfstests, but passthrough_hp does not
yet have an API to enable getattr() passthrough.

I am posting this branch here so that we can compare notes and so
that you can learn it before we meet in LSFMM.

I wanted to give some ideas for API and implementation.
the main thing I added is the ability to declare the passthrough ops
in a mask with the backing file setup:

--- a/include/uapi/linux/fuse.h
+++ b/include/uapi/linux/fuse.h
@@ -1076,9 +1076,21 @@ struct fuse_notify_retrieve_in {
 struct fuse_backing_map {
        int32_t         fd;
        uint32_t        flags;
-       uint64_t        padding;
+       uint64_t        ops_mask;
 };

+#define FUSE_PASSTHROUGH_OP(op)        (1ULL << ((op) - 1))
+
+/* These passthrough operations are implied by FOPEN_PASSTHROUGH */
+#define FUSE_PASSTHROUGH_RW_OPS \
+       (FUSE_PASSTHROUGH_OP(FUSE_READ) | FUSE_PASSTHROUGH_OP(FUSE_WRITE))
+
+#define FUSE_BACKING_MAP_OP(map, op) \
+       ((map)->ops_mask & FUSE_PASSTHROUGH_OP(op))
+
+#define FUSE_BACKING_MAP_VALID_OPS \
+       (FUSE_PASSTHROUGH_RW_OPS)
+

Which is later extended to support also

+ /* Inode passthrough operations for backing file attached on lookup */
+ #define FUSE_PASSTHROUGH_INODE_OPS \
+       (FUSE_PASSTHROUGH_OP(FUSE_GETATTR) | \
+        FUSE_PASSTHROUGH_OP(FUSE_GETXATTR) | \
+        FUSE_PASSTHROUGH_OP(FUSE_LISTXATTR) | \
+        FUSE_PASSTHROUGH_OP(FUSE_STATX))

The idea is that these would be setup during FUSE_LOOKUP response.
Let me know what you think.

Thanks,
Amir.

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

end of thread, other threads:[~2024-05-09 14:32 UTC | newest]

Thread overview: 27+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2024-02-06 14:24 [PATCH v15 0/9] FUSE passthrough for file io Amir Goldstein
2024-02-06 14:24 ` [PATCH v15 1/9] fuse: factor out helper for FUSE_DEV_IOC_CLONE Amir Goldstein
2024-02-06 14:24 ` [PATCH v15 2/9] fuse: introduce FUSE_PASSTHROUGH capability Amir Goldstein
2024-02-06 14:24 ` [PATCH v15 3/9] fuse: implement ioctls to manage backing files Amir Goldstein
2024-02-28 10:50   ` Jingbo Xu
2024-02-28 11:07     ` Amir Goldstein
2024-02-28 11:14       ` Miklos Szeredi
2024-02-28 11:28         ` Amir Goldstein
2024-02-28 14:32           ` Jens Axboe
2024-02-28 15:01             ` Miklos Szeredi
2024-02-28 15:05               ` Jens Axboe
2024-02-28 16:21               ` Amir Goldstein
2024-02-29 10:15               ` Christian Brauner
2024-02-29 10:17                 ` Christian Brauner
2024-03-05 10:57                   ` Miklos Szeredi
2024-02-28 13:22         ` Bernd Schubert
2024-02-06 14:24 ` [PATCH v15 4/9] fuse: prepare for opening file in passthrough mode Amir Goldstein
2024-02-06 14:24 ` [PATCH v15 5/9] fuse: implement open " Amir Goldstein
2024-02-06 14:24 ` [PATCH v15 6/9] fuse: implement read/write passthrough Amir Goldstein
2024-02-06 14:24 ` [PATCH v15 7/9] fuse: implement splice " Amir Goldstein
2024-02-06 14:24 ` [PATCH v15 8/9] fuse: implement passthrough for mmap Amir Goldstein
2024-02-06 14:24 ` [PATCH v15 9/9] fuse: auto-invalidate inode attributes in passthrough mode Amir Goldstein
2024-04-02 20:13   ` Sweet Tea Dorminy
2024-04-02 21:18     ` Bernd Schubert
2024-04-03  8:18       ` Amir Goldstein
2024-04-04 14:07         ` Sweet Tea Dorminy
2024-05-09 14:32           ` Amir Goldstein

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).