]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
fuse: introduce FUSE_PASSTHROUGH capability
authorAmir Goldstein <amir73il@gmail.com>
Fri, 9 Feb 2024 14:57:17 +0000 (16:57 +0200)
committerMiklos Szeredi <mszeredi@redhat.com>
Fri, 23 Feb 2024 16:36:32 +0000 (17:36 +0100)
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>
Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>
fs/fuse/Kconfig
fs/fuse/Makefile
fs/fuse/fuse_i.h
fs/fuse/inode.c
fs/fuse/passthrough.c [new file with mode: 0644]
include/uapi/linux/fuse.h

index 038ed0b9aaa5d619cf498c55ffd0069dce2abf14..8674dbfbe59dbf79c304c587b08ebba3cfe405be 100644 (file)
@@ -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.
index b734cc2a5e65e796393927b615360b64f8d047a5..6e0228c6d0cba9541c8668efb86b83094751d469 100644 (file)
@@ -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
index 2e7af883e4b360be4550595c9cece9a218e60051..19a86acc9dd03b8a68e395e26bf4d5d7a3114cb6 100644 (file)
@@ -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;
 
@@ -1368,4 +1387,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 */
index 2a6d44f91729bbd7e3bf1c955a952ecdd695bd0f..c771bd3c13361809344bc99c4057c7f31b2c0339 100644 (file)
@@ -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 (file)
index 0000000..e8639c0
--- /dev/null
@@ -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);
+}
index e7418d15fe3906507827025545dcd5348202ff30..7bb6219cfda0e1bac803f0e11425d7fbe75d43bd 100644 (file)
  *  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
 #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
@@ -353,6 +357,7 @@ struct fuse_file_lock {
  * FOPEN_STREAM: the file is stream-like (no file position at all)
  * 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_PASSTHROUGH: passthrough read/write io for this open file
  */
 #define FOPEN_DIRECT_IO                (1 << 0)
 #define FOPEN_KEEP_CACHE       (1 << 1)
@@ -361,6 +366,7 @@ struct fuse_file_lock {
 #define FOPEN_STREAM           (1 << 4)
 #define FOPEN_NOFLUSH          (1 << 5)
 #define FOPEN_PARALLEL_DIRECT_WRITES   (1 << 6)
+#define FOPEN_PASSTHROUGH      (1 << 7)
 
 /**
  * INIT request/reply flags
@@ -449,6 +455,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
@@ -761,7 +768,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 {
@@ -877,7 +884,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