On Thu, Oct 1, 2026 at 5:09 PM Miklos Szeredi <[email protected]> wrote:
>
> Wire up read, write, splice and mmap operations for extent-mapped
> files through the iomap/DAX infrastructure.
>
> When a passthrough file is opened with an EXTMAP backing, I/O is
> dispatched to the dax devices referenced by the extent map.
>
> If an address is not mapped, EIO is returned on the I/O operation.
>
> Reviewed-by: Amir Goldstein <[email protected]>
> Signed-off-by: Miklos Szeredi <[email protected]>
> ---
> fs/fuse/backing.c | 15 +++++
> fs/fuse/ext_map.c | 149 ++++++++++++++++++++++++++++++++++++++++++
> fs/fuse/fuse_i.h | 4 ++
> fs/fuse/notify.c | 3 +
> fs/fuse/passthrough.c | 29 ++++++--
> 5 files changed, 194 insertions(+), 6 deletions(-)
>
> diff --git a/fs/fuse/backing.c b/fs/fuse/backing.c
> index e954391d696d..2708f140b94f 100644
> --- a/fs/fuse/backing.c
> +++ b/fs/fuse/backing.c
> @@ -278,6 +278,21 @@ int fuse_backing_close(struct fuse_conn *fc, int
> backing_id)
> return err;
> }
>
> +bool fuse_backing_is_dax(struct fuse_backing *fb)
> +{
> + switch (fb->type) {
> + case FUSE_BACKING_PATH:
> + return false;
> + case FUSE_BACKING_DAXDEV:
> + return true;
> + case FUSE_BACKING_EXTMAP:
> + return fuse_ext_map_is_dax(fb);
> + default:
> + WARN_ON(1);
> + return false;
> + }
> +}
> +
> struct fuse_backing *fuse_backing_lookup(struct fuse_conn *fc, u64
> backing_id)
> {
> struct fuse_backing *fb;
> diff --git a/fs/fuse/ext_map.c b/fs/fuse/ext_map.c
> index ab01d678d08e..61cb22e821b6 100644
> --- a/fs/fuse/ext_map.c
> +++ b/fs/fuse/ext_map.c
> @@ -3,6 +3,8 @@
> #include "fuse_i.h"
> #include <linux/rbtree.h>
> #include <linux/pagemap.h>
> +#include <linux/iomap.h>
> +#include <linux/dax.h>
>
> struct fuse_iext {
> struct rb_node rb;
> @@ -22,6 +24,24 @@ void fuse_ext_map_destroy(struct rb_root *extents)
> }
> }
>
> +static struct fuse_iext *fuse_find_extent(struct rb_root *extents, u64
> offset)
> +{
> + struct rb_node *node = extents->rb_node;
> +
> + while (node) {
> + struct fuse_iext *fie = rb_entry(node, typeof(*fie), rb);
> +
> + if (offset < fie->start)
> + node = node->rb_left;
> + else if (offset >= fie->end)
> + node = node->rb_right;
> + else
> + return fie;
> + }
> +
> + return NULL;
> +}
> +
> static int fuse_add_extent(struct fuse_conn *fc, struct rb_root *extents,
> struct fuse_extent *ext)
> {
> @@ -76,6 +96,135 @@ static int fuse_add_extent(struct fuse_conn *fc, struct
> rb_root *extents,
> return 0;
> }
>
> +static int fuse_ext_map_iomap_begin(struct inode *inode, loff_t offset,
> loff_t length,
> + unsigned int flags, struct iomap *iomap,
> struct iomap *srcmap)
> +{
> + struct fuse_backing *fb = fuse_inode_backing(get_fuse_inode(inode));
> + struct fuse_iext *fie;
> + loff_t ext_len;
> +
> + if (!fb || fb->type != FUSE_BACKING_EXTMAP)
> + return fuse_EIO("missing or wrong type backing");
> +
> + fie = fuse_find_extent(&fb->extents, offset);
> + if (!fie)
> + return fuse_EIO("missing mapping");
> +
> + if (WARN_ON(fie->backing->type != FUSE_BACKING_DAXDEV))
> + return fuse_EIO("wrong type backing for extent");
> +
> + if (fie->backing->dax_error) {
> + fuse_make_bad(inode);
> + return fuse_EIO("dax error");
> + }
> +
> + ext_len = fie->end - fie->start;
> +
> + iomap->offset = fie->start;
> + iomap->addr = fie->backing_offset;
> + iomap->length = ext_len;
> + iomap->dax_dev = fie->backing->dax_dev;
> + iomap->type = IOMAP_MAPPED;
> + iomap->flags = 0;
> +
> + return 0;
> +}
> +
> +static const struct iomap_ops fuse_ext_map_iomap_ops = {
> + .iomap_begin = fuse_ext_map_iomap_begin,
> +};
> +
> +static vm_fault_t fuse_ext_map_huge_fault(struct vm_fault *vmf, unsigned int
> order)
> +{
> + struct inode *inode = file_inode(vmf->vma->vm_file);
> + bool write_fault = (vmf->flags & FAULT_FLAG_WRITE) &&
> (vmf->vma->vm_flags & VM_SHARED);
> + vm_fault_t ret;
> + unsigned long pfn;
> +
> + if (WARN_ON_ONCE(!IS_DAX(inode)))
> + return VM_FAULT_SIGBUS;
> +
> + if (write_fault) {
> + sb_start_pagefault(inode->i_sb);
> + file_update_time(vmf->vma->vm_file);
> + }
> +
> + filemap_invalidate_lock_shared(inode->i_mapping);
> +
> + ret = dax_iomap_fault(vmf, order, &pfn, NULL,
> &fuse_ext_map_iomap_ops);
> + if (ret & VM_FAULT_NEEDDSYNC)
> + ret = dax_finish_sync_fault(vmf, order, pfn);
> +
> + filemap_invalidate_unlock_shared(inode->i_mapping);
> +
> + if (write_fault)
> + sb_end_pagefault(inode->i_sb);
> +
> + return ret;
> +}
> +
> +static vm_fault_t fuse_ext_map_fault(struct vm_fault *vmf)
> +{
> + return fuse_ext_map_huge_fault(vmf, 0);
> +}
> +
> +static const struct vm_operations_struct fuse_ext_map_vm_ops = {
> + .fault = fuse_ext_map_fault,
> + .huge_fault = fuse_ext_map_huge_fault,
> + .page_mkwrite = fuse_ext_map_fault,
> + .pfn_mkwrite = fuse_ext_map_fault,
> +};
> +
> +static void fuse_rw_clamp(struct kiocb *iocb, struct iov_iter *ubuf)
> +{
> + struct inode *inode = iocb->ki_filp->f_mapping->host;
> + loff_t i_size = i_size_read(inode);
> + loff_t max_count = iocb->ki_pos >= i_size ? 0 : i_size - iocb->ki_pos;
> +
> + if (iov_iter_count(ubuf) > max_count)
> + iov_iter_truncate(ubuf, max_count);
> +}
> +
> +ssize_t fuse_ext_map_read_iter(struct kiocb *iocb, struct iov_iter *to)
> +{
> + ssize_t res;
> +
> + fuse_rw_clamp(iocb, to);
> +
> + if (!iov_iter_count(to))
> + return 0;
> +
> + res = dax_iomap_rw(iocb, to, &fuse_ext_map_iomap_ops);
These fail link without CONFIG_FS_DAX
Because this file build does not depend on CONFIG_FUSE_DAX
Probably simplest to #define dax_iomap_rw() (-EIO)
locally in this case
> +
> + file_accessed(iocb->ki_filp);
> + return res;
> +}
> +
> +ssize_t fuse_ext_map_write_iter(struct kiocb *iocb, struct iov_iter *from)
> +{
> + ssize_t res;
> +
> + fuse_rw_clamp(iocb, from);
> +
> + res = generic_write_checks(iocb, from);
> + if (res <= 0)
> + return res;
> +
> + res = kiocb_modified(iocb);
> + if (res)
> + return res;
> +
> + return dax_iomap_rw(iocb, from, &fuse_ext_map_iomap_ops);
> +}
> +
> +int fuse_ext_map_mmap(struct file *file, struct vm_area_struct *vma)
> +{
> + file_accessed(file);
> + vma->vm_ops = &fuse_ext_map_vm_ops;
> + vm_flags_set(vma, VM_HUGEPAGE);
> + return 0;
> +}
> +
> bool fuse_ext_map_is_dax(struct fuse_backing *fb)
> {
> struct fuse_iext *fie;
> diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h
> index 5a20f3863ee2..7248588b45c4 100644
> --- a/fs/fuse/fuse_i.h
> +++ b/fs/fuse/fuse_i.h
> @@ -1316,6 +1316,7 @@ static inline void fuse_backing_put(struct fuse_backing
> *fb)
>
> struct fuse_backing *fuse_backing_lookup(struct fuse_conn *fc, u64
> backing_id);
> int fuse_backing_add_64(struct fuse_conn *fc, struct fuse_backing *fb);
> +bool fuse_backing_is_dax(struct fuse_backing *fb);
> void fuse_backing_files_init(struct fuse_conn *fc);
> void fuse_backing_files_init_64(struct fuse_conn *fc);
> void fuse_backing_files_free(struct fuse_conn *fc);
> @@ -1369,6 +1370,9 @@ extern void fuse_sysctl_unregister(void);
> /* ext_map.c */
>
> void fuse_ext_map_destroy(struct rb_root *extents);
> +ssize_t fuse_ext_map_write_iter(struct kiocb *iocb, struct iov_iter *from);
> +ssize_t fuse_ext_map_read_iter(struct kiocb *iocb, struct iov_iter *to);
> +int fuse_ext_map_mmap(struct file *file, struct vm_area_struct *vma);
> bool fuse_ext_map_is_dax(struct fuse_backing *fb);
> int fuse_ext_map_populate(struct fuse_conn *fc, struct
> fuse_notify_backing_map_out *arg,
> struct fuse_extent *ext);
> diff --git a/fs/fuse/notify.c b/fs/fuse/notify.c
> index 7c427f88bbc5..3bfedd0cdbdd 100644
> --- a/fs/fuse/notify.c
> +++ b/fs/fuse/notify.c
> @@ -448,6 +448,9 @@ static int fuse_notify_map(struct fuse_conn *fc, unsigned
> int size,
> if (err)
> return err;
>
> + if (!fc->backing_id_64)
> + return -EINVAL;
> +
> if (outarg.num_extents > FUSE_MAX_EXTENTS)
> return -EINVAL;
>
> diff --git a/fs/fuse/passthrough.c b/fs/fuse/passthrough.c
> index e9ab1aea34e2..040817ad76e9 100644
> --- a/fs/fuse/passthrough.c
> +++ b/fs/fuse/passthrough.c
> @@ -35,7 +35,6 @@ ssize_t fuse_passthrough_read_iter(struct kiocb *iocb,
> struct iov_iter *iter)
> 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,
> .accessed = fuse_file_accessed,
> @@ -48,10 +47,10 @@ ssize_t fuse_passthrough_read_iter(struct kiocb *iocb,
> struct iov_iter *iter)
> if (!count)
> return 0;
>
> - ret = backing_file_read_iter(backing_file, iter, iocb, iocb->ki_flags,
> - &ctx);
> + if (!backing_file)
> + return fuse_ext_map_read_iter(iocb, iter);
>
> - return ret;
> + return backing_file_read_iter(backing_file, iter, iocb,
> iocb->ki_flags, &ctx);
> }
>
> ssize_t fuse_passthrough_write_iter(struct kiocb *iocb,
> @@ -74,10 +73,13 @@ ssize_t fuse_passthrough_write_iter(struct kiocb *iocb,
> if (!count)
> return 0;
>
> - inode_lock(inode);
> + guard(rwsem_write)(&inode->i_rwsem);
> +
> + if (!backing_file)
> + return fuse_ext_map_write_iter(iocb, iter);
> +
> ret = backing_file_write_iter(backing_file, iter, iocb,
> iocb->ki_flags,
> &ctx);
> - inode_unlock(inode);
>
> return ret;
> }
> @@ -98,6 +100,9 @@ ssize_t fuse_passthrough_splice_read(struct file *in,
> loff_t *ppos,
> pr_debug("%s: backing_file=0x%p, pos=%lld, len=%zu, flags=0x%x\n",
> __func__,
> backing_file, *ppos, len, flags);
>
> + if (!backing_file)
> + return copy_splice_read(in, ppos, pipe, len, flags);
> +
> init_sync_kiocb(&iocb, in);
> iocb.ki_pos = *ppos;
> ret = backing_file_splice_read(backing_file, &iocb, pipe, len, flags,
> &ctx);
> @@ -123,6 +128,9 @@ ssize_t fuse_passthrough_splice_write(struct
> pipe_inode_info *pipe,
> pr_debug("%s: backing_file=0x%p, pos=%lld, len=%zu, flags=0x%x\n",
> __func__,
> backing_file, *ppos, len, flags);
>
> + if (!backing_file)
> + return iter_file_splice_write(pipe, out, ppos, len, flags);
> +
> inode_lock(inode);
> init_sync_kiocb(&iocb, out);
> iocb.ki_pos = *ppos;
> @@ -145,6 +153,9 @@ ssize_t fuse_passthrough_mmap(struct file *file, struct
> vm_area_struct *vma)
> pr_debug("%s: backing_file=0x%p, start=%lu, end=%lu\n", __func__,
> backing_file, vma->vm_start, vma->vm_end);
>
> + if (!backing_file)
> + return fuse_ext_map_mmap(file, vma);
> +
> return backing_file_mmap(backing_file, vma, &ctx);
> }
>
> @@ -156,6 +167,12 @@ 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_EXTMAP) {
> + if (fuse_backing_is_dax(fb) != !!IS_DAX(file_inode(file)))
> + return fuse_EIO("dax mode mismatch");
> + return 0;
> + }
> +
> if (fb->type != FUSE_BACKING_PATH)
> return fuse_EIO("invalid backing type");
>
> --
> 2.54.0
>