On Thu, Oct 1, 2026 at 5:09 PM Miklos Szeredi <[email protected]> wrote:
>
> 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.
>
> Signed-off-by: Miklos Szeredi <[email protected]>
> ---
> fs/fuse/backing.c | 121 ++++++++++++++++++++++++++++++------------
> fs/fuse/file.c | 2 +-
> fs/fuse/fuse_i.h | 28 ++++++++--
> fs/fuse/inode.c | 11 +++-
> fs/fuse/passthrough.c | 6 ++-
> 5 files changed, 126 insertions(+), 42 deletions(-)
>
> diff --git a/fs/fuse/backing.c b/fs/fuse/backing.c
> index 3c879df7989c..c852f0498961 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,83 @@ 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;
> + struct fuse_backing *fb = dax_holder(daxdev);
>
> - /* TODO: relax CAP_SYS_ADMIN once backing files are visible to lsof */
> - if (!fc->passthrough || !capable(CAP_SYS_ADMIN))
> - return ERR_PTR(-EPERM);
> + fb->dax_error = true;
>
> - CLASS(fd_raw, f)(fd);
> - if (fd_empty(f))
> - return ERR_PTR(-EBADF);
> + return 0;
> +}
>
> - file = fd_file(f);
> +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;
>
> - /* 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);
> + 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:
> + daxdev = dax_dev_find(inode->i_rdev);
> + 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;
>
> - backing_sb = file_inode(file)->i_sb;
> - if (backing_sb->s_stack_depth >= fc->max_stack_depth)
> - return ERR_PTR(-ELOOP);
> + case S_IFDIR:
> + return -EISDIR;
> +
> + default:
> + return -EINVAL;
> + }
> +}
> +
> +static struct fuse_backing *fuse_backing_new(struct fuse_conn *fc, int fd)
> +{
> + struct fuse_backing *fb __free(kfree) = kmalloc_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)
> @@ -143,22 +195,21 @@ int fuse_backing_open_64(struct fuse_conn *fc, struct
> fuse_backing_create_in *ma
> struct fuse_backing *fb;
> int res;
>
> - res = -EINVAL;
> if (map->padding || map->spare[0] || map->spare[1])
> - goto out;
> + return -EINVAL;
>
> if (!fc->backing_id_64)
> - goto out;
> + return -EINVAL;
>
> fb = fuse_backing_new(fc, map->fd);
> - res = PTR_ERR(fb);
> - if (!IS_ERR(fb)) {
> - fb->backing_id = map->backing_id;
> - res = fuse_backing_add_64(fc, fb);
> - if (res < 0)
> - fuse_backing_free(fb);
> - }
> -out:
> + if (IS_ERR(fb))
> + return PTR_ERR(fb);
> +
> + fb->backing_id = map->backing_id;
> + res = fuse_backing_add_64(fc, fb);
> + if (res < 0)
> + fuse_backing_free(fb);
> +
> return res;
> }
>
> diff --git a/fs/fuse/file.c b/fs/fuse/file.c
> index 1036c06fe35a..fb8e0a9fa4f6 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 59afebc3dd2b..3e7ffb9bb2c3 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,14 @@ 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_IS_VDAX(struct inode *inode)
> +{
> +#ifdef CONFIG_FUSE_VDAX
> + return get_fuse_inode(inode)->vdax && IS_DAX(inode);
> +#else
> + return false;
> +#endif
> +}
>
> 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 bb76bddfc241..d42c824fd6af 100644
> --- a/fs/fuse/inode.c
> +++ b/fs/fuse/inode.c
> @@ -148,7 +148,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);
> @@ -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)
> {
> @@ -430,6 +434,11 @@ static void fuse_init_inode(struct inode *inode, struct
> fuse_attr *attr,
> */
> if (!fc->posix_acl)
> inode->i_acl = inode->i_default_acl = ACL_DONT_CACHE;
> +
> + if ((attr->flags & FUSE_ATTR_DAX) && !fc->vdax) {
This (fc->vdax) fails build without CONFIG_FUSE_DAX
> + inode->i_flags |= S_DAX;
> + inode->i_data.a_ops = &fuse_dax_aops;
> + }
> }
>
> static int fuse_inode_eq(struct inode *inode, void *_nodeidp)
> 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
>