This is only possible with FUSE_PASSTHROUGH_V2 enabled.

Mark the inode with S_DAX if FUSE_LOOKUP returns with FUSE_ATTR_DAX set.

This patch does not yet provide a way actually use the dax dev backing:
when such a backing ID is provided in reply to FUSE_OPEN with
FOPEN_PASSTHROUGH flag set, an error will be returned.

Move fuse_backing_put() from fuse_free_inode() to fuse_evict_inode() to
avoid sleeping in atomic context.

Originally-by: John Groves <[email protected]>
Signed-off-by: Miklos Szeredi <[email protected]>
---
 fs/fuse/backing.c     | 108 +++++++++++++++++++++++++++++++++---------
 fs/fuse/file.c        |   2 +-
 fs/fuse/fuse_i.h      |  33 +++++++++++--
 fs/fuse/inode.c       |  17 +++++--
 fs/fuse/iomode.c      |  10 ++--
 fs/fuse/passthrough.c |   6 ++-
 6 files changed, 140 insertions(+), 36 deletions(-)

diff --git a/fs/fuse/backing.c b/fs/fuse/backing.c
index 15132150535a..9ded41a94fcc 100644
--- a/fs/fuse/backing.c
+++ b/fs/fuse/backing.c
@@ -9,6 +9,7 @@
 #include "fuse_i.h"
 
 #include <linux/file.h>
+#include <linux/dax.h>
 #include <linux/rhashtable.h>
 
 static struct fuse_backing *fuse_backing_get(struct fuse_backing *fb)
@@ -22,9 +23,16 @@ 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);
+       switch (fb->type) {
+       case FUSE_BACKING_PATH:
+               path_put(&fb->path);
+               put_cred(fb->cred);
+               break;
+
+       case FUSE_BACKING_DAXDEV:
+               fs_put_dax(fb->dax_dev, fb);
+               break;
+       }
        kfree_rcu(fb, rcu);
 }
 
@@ -103,39 +111,93 @@ int fuse_backing_close_64(struct fuse_conn *fc, u64 
backing_id)
        return 0;
 }
 
-static struct fuse_backing *fuse_backing_new(struct fuse_conn *fc, int fd)
+static int fuse_dax_notify_failure(struct dax_device *daxdev, u64 offset, u64 
len, int mf_flags)
 {
        struct fuse_backing *fb;
-       struct super_block *backing_sb;
-       struct file *file;
 
-       /* TODO: relax CAP_SYS_ADMIN once backing files are visible to lsof */
-       if (!fc->passthrough || !capable(CAP_SYS_ADMIN))
-               return ERR_PTR(-EPERM);
+       guard(rcu)();
 
-       CLASS(fd_raw, f)(fd);
-       if (fd_empty(f))
-               return ERR_PTR(-EBADF);
+       fb = dax_holder(daxdev);
+       if (fb)
+               fb->dax_error = true;
+
+       return 0;
+}
+
+static const struct dax_holder_operations fuse_dax_holder_ops = {
+       .notify_failure         = fuse_dax_notify_failure,
+};
+
+static int fuse_backing_open_file(struct fuse_conn *fc, struct fuse_backing 
*fb, struct file *file)
+{
+       struct inode *inode = file_inode(file);
+       struct dax_device *daxdev;
+       int err;
+
+       switch (inode->i_mode & S_IFMT) {
+       case S_IFREG:
+               /* TODO: relax CAP_SYS_ADMIN once backing files are visible to 
lsof */
+               if (!fc->passthrough || !capable(CAP_SYS_ADMIN))
+                       return -EPERM;
+
+               if (inode->i_sb->s_stack_depth >= fc->max_stack_depth)
+                       return -ELOOP;
+
+               fb->type = FUSE_BACKING_PATH;
+               fb->path = file->f_path;
+               path_get(&fb->path);
+               fb->cred = get_current_cred();
+               return 0;
+
+       case S_IFCHR:
+               if (!fc->passthrough || !fc->backing_id_64)
+                       return -EINVAL;
+
+               if (!IS_ENABLED(CONFIG_DEV_DAX_FSDEV))
+                       return -EINVAL;
+
+               daxdev = fsdev_dax_from_file(file);
+               if (!daxdev)
+                       return -EINVAL;
+
+               err = -EPERM;
+               if (capable(CAP_SYS_RAWIO)) {
+                       err = fs_dax_get(daxdev, fb, &fuse_dax_holder_ops);
+                       if (!err) {
+                               fb->type = FUSE_BACKING_DAXDEV;
+                               fb->dax_dev = daxdev;
+                       }
+               }
+               put_dax(daxdev);
+               return err;
 
-       file = fd_file(f);
+       case S_IFDIR:
+               return -EISDIR;
 
-       /* read/write/splice/mmap passthrough only relevant for regular files */
-       if (!d_is_reg(file->f_path.dentry))
-               return d_is_dir(file->f_path.dentry) ? ERR_PTR(-EISDIR) : 
ERR_PTR(-EINVAL);
+       default:
+               return -EINVAL;
+       }
+}
 
-       backing_sb = file_inode(file)->i_sb;
-       if (backing_sb->s_stack_depth >= fc->max_stack_depth)
-               return ERR_PTR(-ELOOP);
+static struct fuse_backing *fuse_backing_new(struct fuse_conn *fc, int fd)
+{
+       struct fuse_backing *fb __free(kfree) = kzalloc_obj(*fb);
+       int err;
 
-       fb = kmalloc_obj(struct fuse_backing);
        if (!fb)
                return ERR_PTR(-ENOMEM);
 
-       fb->file = get_file(file);
-       fb->cred = get_current_cred();
+       CLASS(fd_raw, f)(fd);
+       if (fd_empty(f))
+               return ERR_PTR(-EBADF);
+
+       err = fuse_backing_open_file(fc, fb, fd_file(f));
+       if (err)
+               return ERR_PTR(err);
+
        refcount_set(&fb->count, 1);
 
-       return fb;
+       return_ptr(fb);
 }
 
 int fuse_backing_open_64(struct fuse_conn *fc, struct fuse_backing_create_in 
*map)
diff --git a/fs/fuse/file.c b/fs/fuse/file.c
index 5273957f0399..e0d72b9d3c4a 100644
--- a/fs/fuse/file.c
+++ b/fs/fuse/file.c
@@ -297,7 +297,7 @@ static int fuse_open(struct inode *inode, struct file *file)
        if (!err) {
                if (is_truncate)
                        truncate_pagecache(inode, 0);
-               else if (!(ff->open_flags & FOPEN_KEEP_CACHE))
+               else if (!(ff->open_flags & FOPEN_KEEP_CACHE) && !IS_DAX(inode))
                        invalidate_inode_pages2(inode->i_mapping);
        }
 out_unlock:
diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h
index 89dedcb54ba7..8c65b6b8c840 100644
--- a/fs/fuse/fuse_i.h
+++ b/fs/fuse/fuse_i.h
@@ -89,10 +89,25 @@ struct fuse_submount_lookup {
        struct fuse_forget_link *forget;
 };
 
+enum fuse_backing_type {
+       FUSE_BACKING_PATH,
+       FUSE_BACKING_DAXDEV,
+};
+
 /* Container for data related to mapping to backing file */
 struct fuse_backing {
-       struct file *file;
-       const struct cred *cred;
+       enum fuse_backing_type type;
+
+       union {
+               struct {
+                       struct path path;
+                       const struct cred *cred;
+               };
+               struct {
+                       struct dax_device *dax_dev;
+                       bool dax_error;
+               };
+       };
        u64 backing_id;
        struct rhash_head hash_node;
        /* refcount */
@@ -1239,7 +1254,19 @@ void fuse_free_conn(struct fuse_conn *fc);
 
 /* dax.c */
 
-#define FUSE_IS_VDAX(inode) (IS_ENABLED(CONFIG_FUSE_VDAX) && IS_DAX(inode))
+static inline bool fuse_inode_vdax(struct inode *inode)
+{
+#ifdef CONFIG_FUSE_VDAX
+       return get_fuse_inode(inode)->vdax;
+#else
+       return false;
+#endif
+}
+
+static inline bool FUSE_IS_VDAX(struct inode *inode)
+{
+       return fuse_inode_vdax(inode) && IS_DAX(inode);
+}
 
 ssize_t fuse_vdax_read_iter(struct kiocb *iocb, struct iov_iter *to);
 ssize_t fuse_vdax_write_iter(struct kiocb *iocb, struct iov_iter *from);
diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c
index b69c95ebb1ad..330655af3bd0 100644
--- a/fs/fuse/inode.c
+++ b/fs/fuse/inode.c
@@ -124,9 +124,6 @@ static void fuse_free_inode(struct inode *inode)
 #ifdef CONFIG_FUSE_VDAX
        kfree(fi->vdax);
 #endif
-       if (IS_ENABLED(CONFIG_FUSE_PASSTHROUGH))
-               fuse_backing_put(fuse_inode_backing(fi));
-
        kmem_cache_free(fuse_inode_cachep, fi);
 }
 
@@ -148,7 +145,7 @@ static void fuse_evict_inode(struct inode *inode)
        /* Will write inode on close/munmap and in all other dirtiers */
        WARN_ON(inode_state_read_once(inode) & I_DIRTY_INODE);
 
-       if (FUSE_IS_VDAX(inode))
+       if (IS_DAX(inode))
                dax_break_layout_final(inode);
 
        truncate_inode_pages_final(&inode->i_data);
@@ -177,6 +174,9 @@ static void fuse_evict_inode(struct inode *inode)
                if (inode->i_nlink > 0)
                        atomic64_inc(&fc->evict_ctr);
        }
+       if (IS_ENABLED(CONFIG_FUSE_PASSTHROUGH))
+               fuse_backing_put(fuse_inode_backing(fi));
+
        if (S_ISREG(inode->i_mode) && !fuse_is_bad(inode)) {
                WARN_ON(fi->iocachectr != 0);
                WARN_ON(!list_empty(&fi->write_files));
@@ -403,6 +403,10 @@ static void fuse_init_submount_lookup(struct 
fuse_submount_lookup *sl,
        refcount_set(&sl->count, 1);
 }
 
+static const struct address_space_operations fuse_dax_aops = {
+       .dirty_folio    = noop_dirty_folio,
+};
+
 static void fuse_init_inode(struct inode *inode, struct fuse_attr *attr,
                            struct fuse_conn *fc)
 {
@@ -413,6 +417,11 @@ static void fuse_init_inode(struct inode *inode, struct 
fuse_attr *attr,
        if (S_ISREG(inode->i_mode)) {
                fuse_init_common(inode);
                fuse_init_file_inode(inode, attr->flags);
+
+               if ((attr->flags & FUSE_ATTR_DAX) && !fuse_inode_vdax(inode)) {
+                       inode->i_flags |= S_DAX;
+                       inode->i_data.a_ops = &fuse_dax_aops;
+               }
        } else if (S_ISDIR(inode->i_mode))
                fuse_init_dir(inode);
        else if (S_ISLNK(inode->i_mode))
diff --git a/fs/fuse/iomode.c b/fs/fuse/iomode.c
index 8b4774c80b11..38afe1f238ef 100644
--- a/fs/fuse/iomode.c
+++ b/fs/fuse/iomode.c
@@ -230,10 +230,14 @@ int fuse_file_io_open(struct file *file, struct inode 
*inode)
         * Server is expected to use FOPEN_PASSTHROUGH for all opens of an inode
         * which is already open for passthrough.  Using incorrect open mode is
         * a server mistake, which results in user visible failure of open()
-        * with EIO error.
+        * with EIO error.  Same with DAX inodes.
         */
-       if (fuse_inode_backing(fi) && !(ff->open_flags & FOPEN_PASSTHROUGH))
-               return fuse_EIO("FOPEN_PASSTHROUGH expected");
+       if (!(ff->open_flags & FOPEN_PASSTHROUGH)) {
+               if (fuse_inode_backing(fi))
+                       return fuse_EIO("FOPEN_PASSTHROUGH expected");
+               if (IS_DAX(inode))
+                       return fuse_EIO("DAX inode without FOPEN_PASSTHROUGH");
+       }
 
        /*
         * FOPEN_PARALLEL_DIRECT_WRITES requires FOPEN_DIRECT_IO.
diff --git a/fs/fuse/passthrough.c b/fs/fuse/passthrough.c
index 4894842ad6d0..e9ab1aea34e2 100644
--- a/fs/fuse/passthrough.c
+++ b/fs/fuse/passthrough.c
@@ -156,9 +156,11 @@ int fuse_passthrough_open(struct file *file, struct 
fuse_backing *fb)
        struct fuse_file *ff = file->private_data;
        struct file *backing_file;
 
+       if (fb->type != FUSE_BACKING_PATH)
+               return fuse_EIO("invalid backing type");
+
        /* Allocate backing file per fuse file to store fuse path */
-       backing_file = backing_file_open(file, file->f_flags,
-                                        &fb->file->f_path, fb->cred);
+       backing_file = backing_file_open(file, file->f_flags, &fb->path, 
fb->cred);
        if (IS_ERR(backing_file))
                return fuse_EIO("failed to open backing file (%ld)", 
PTR_ERR(backing_file));
 
-- 
2.54.0


Reply via email to