FILESYSTEM IN USERSPACE (FUSE) development
 help / color / mirror / Atom feed
* [PATCH 00/11] fuse: DAX device based extent maps (famfs)
@ 2026-09-22  6:10 Miklos Szeredi
  2026-09-22  6:10 ` [PATCH 01/11] dax: replace exported dax_dev_get() with non-allocating dax_dev_find() Miklos Szeredi
                   ` (10 more replies)
  0 siblings, 11 replies; 33+ messages in thread
From: Miklos Szeredi @ 2026-09-22  6:10 UTC (permalink / raw)
  To: fuse-devel; +Cc: John Groves, Amir Goldstein, Darrick J . Wong

This is the FUSIfication of the famfs-fuse patchset.

 - extent maps are assigned to a backing ID

 - 64 bit backing ID's managed by server

 - maps can be linear or cyclic (striped)

 - API supports nesting, but not implemented yet (i.e. multiple striped
   extents per inode, a-la famfs)

libfuse/famfs trees for testing:

 https://github.com/szmi/libfuse.git#extent-map
 https://github.com/szmi/famfs.git#extent-map

Passes famfs smoke test on emulated dax device.

Comments are welcome.

Thanks,
Miklos

---
John Groves (1):
  dax: replace exported dax_dev_get() with non-allocating dax_dev_find()

Miklos Szeredi (10):
  fuse: use "vdax" naming for virtiofs DAX
  fuse: don't assume ff->passthrough is set for FOPEN_PASSTHROUGH
  fuse: make fuse_backing_get() static
  fuse: add helpers for EIO return value with kernel message
  fuse: support 64 bit, server allocated backing ID
  fuse: support opening 64 bit backing ID
  fuse: add support for opening dax device as backing
  fuse: add extent map data structure
  fuse: add extent map I/O support
  fuse: add support for striped backing

 drivers/dax/super.c       |  38 ++++-
 fs/fuse/Makefile          |   2 +-
 fs/fuse/backing.c         | 270 +++++++++++++++++++++++++----------
 fs/fuse/dax.c             | 214 ++++++++++++++--------------
 fs/fuse/dev.c             |   2 +-
 fs/fuse/dev.h             |   2 +-
 fs/fuse/dir.c             |   7 +-
 fs/fuse/ext_map.c         | 288 ++++++++++++++++++++++++++++++++++++++
 fs/fuse/file.c            |  56 ++++----
 fs/fuse/fuse_i.h          | 145 +++++++++++--------
 fs/fuse/inode.c           |  47 ++++---
 fs/fuse/iomode.c          |  41 +++---
 fs/fuse/notify.c          |  64 +++++++++
 fs/fuse/passthrough.c     |  70 ++++++---
 fs/fuse/virtio_fs.c       |  18 +--
 include/linux/dax.h       |   6 +-
 include/uapi/linux/fuse.h |  56 +++++++-
 17 files changed, 984 insertions(+), 342 deletions(-)
 create mode 100644 fs/fuse/ext_map.c

-- 
2.54.0


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

* [PATCH 01/11] dax: replace exported dax_dev_get() with non-allocating dax_dev_find()
  2026-09-22  6:10 [PATCH 00/11] fuse: DAX device based extent maps (famfs) Miklos Szeredi
@ 2026-09-22  6:10 ` Miklos Szeredi
  2026-09-22  7:36   ` Amir Goldstein
  2026-09-22 19:07   ` Alison Schofield
  2026-09-22  6:10 ` [PATCH 02/11] fuse: use "vdax" naming for virtiofs DAX Miklos Szeredi
                   ` (9 subsequent siblings)
  10 siblings, 2 replies; 33+ messages in thread
From: Miklos Szeredi @ 2026-09-22  6:10 UTC (permalink / raw)
  To: fuse-devel
  Cc: John Groves, John Groves, Amir Goldstein, Darrick J . Wong,
	Dave Jiang, Alison Schofield

From: John Groves <John@Groves.net>

This fix is in response to a Sashiko review, and some subsequent
analysis.

dax_dev_get() uses iget5_locked() which creates a new inode if no
matching one exists. This is correct for the internal caller
(alloc_dax), but dangerous for external callers that look up devices
from user-supplied or metadata-supplied dev_t values:

1. A new inode is created with DAXDEV_ALIVE set but no backing driver,
   no ops, and no IDA-allocated minor number.

2. On teardown, dax_destroy_inode() warns because kill_dax() was never
   called, and dax_free_inode() calls ida_free() for a minor that was
   never ida_alloc'd -- potentially freeing the minor of a real device.

Add dax_dev_find() which uses ilookup5() for lookup-only semantics:
it returns an existing dax_device with an elevated inode reference, or
NULL if no device with the given dev_t exists. It never creates inodes.
A dax_alive() check under dax_read_lock() guards against returning a
device that is concurrently being torn down by kill_dax().

Make dax_dev_get() static again (internal to super.c for alloc_dax),
export dax_dev_find() instead, and update the two external callers
(famfs_inode.c, famfs.c). Also add the missing CONFIG_DAX=n stub.

About the 'fixes' tag: this removes the export of dax_dev_get(),
which was flawed, and replaces is with dax_dev_find(). It feels like
the fixes tag makes sense for correcting an ABI error.

Fixes: 2ae624d5a555d ("dax: export dax_dev_get()")
Reviewed-by: Dave Jiang <dave.jiang@intel.com>
Reviewed-by: Alison Schofield <alison.schofield@intel.com>
Reviewed-by: "Darrick J. Wong" <djwong@kernel.org>
Signed-off-by: John Groves <john@groves.net>
Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>
---
 drivers/dax/super.c | 38 ++++++++++++++++++++++++++++++++++++--
 include/linux/dax.h |  6 +++++-
 2 files changed, 41 insertions(+), 3 deletions(-)

diff --git a/drivers/dax/super.c b/drivers/dax/super.c
index 45f84b0eb909..824e1f6df378 100644
--- a/drivers/dax/super.c
+++ b/drivers/dax/super.c
@@ -565,7 +565,7 @@ static int dax_set(struct inode *inode, void *data)
 	return 0;
 }
 
-struct dax_device *dax_dev_get(dev_t devt)
+static struct dax_device *dax_dev_get(dev_t devt)
 {
 	struct dax_device *dax_dev;
 	struct inode *inode;
@@ -588,7 +588,41 @@ struct dax_device *dax_dev_get(dev_t devt)
 
 	return dax_dev;
 }
-EXPORT_SYMBOL_GPL(dax_dev_get);
+
+/**
+ * dax_dev_find - look up an existing dax_device by dev_t
+ * @devt: the device number to find
+ *
+ * Returns a dax_device with an elevated inode reference, or NULL if no
+ * device with the given dev_t exists. Unlike dax_dev_get(), this never
+ * allocates a new inode -- it is safe for external callers that are looking
+ * up devices from user-supplied or metadata-supplied dev_t values.
+ *
+ * Caller must put_dax() the returned device when done.
+ */
+struct dax_device *dax_dev_find(dev_t devt)
+{
+	struct dax_device *dax_dev;
+	struct inode *inode;
+	int id;
+
+	inode = ilookup5(dax_superblock, hash_32(devt + DAXFS_MAGIC, 31),
+			 dax_test, &devt);
+	if (!inode)
+		return NULL;
+
+	dax_dev = to_dax_dev(inode);
+	id = dax_read_lock();
+	if (!dax_alive(dax_dev)) {
+		dax_read_unlock(id);
+		iput(inode);
+		return NULL;
+	}
+	dax_read_unlock(id);
+
+	return dax_dev;
+}
+EXPORT_SYMBOL_GPL(dax_dev_find);
 
 struct dax_device *alloc_dax(void *private, const struct dax_operations *ops)
 {
diff --git a/include/linux/dax.h b/include/linux/dax.h
index fe6c3ded1b50..29113eb95e72 100644
--- a/include/linux/dax.h
+++ b/include/linux/dax.h
@@ -54,7 +54,7 @@ struct dax_device *alloc_dax(void *private, const struct dax_operations *ops);
 void *dax_holder(struct dax_device *dax_dev);
 void put_dax(struct dax_device *dax_dev);
 void kill_dax(struct dax_device *dax_dev);
-struct dax_device *dax_dev_get(dev_t devt);
+struct dax_device *dax_dev_find(dev_t devt);
 void dax_write_cache(struct dax_device *dax_dev, bool wc);
 bool dax_write_cache_enabled(struct dax_device *dax_dev);
 bool dax_synchronous(struct dax_device *dax_dev);
@@ -92,6 +92,10 @@ static inline void put_dax(struct dax_device *dax_dev)
 static inline void kill_dax(struct dax_device *dax_dev)
 {
 }
+static inline struct dax_device *dax_dev_find(dev_t devt)
+{
+	return NULL;
+}
 static inline void dax_write_cache(struct dax_device *dax_dev, bool wc)
 {
 }
-- 
2.54.0


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

* [PATCH 02/11] fuse: use "vdax" naming for virtiofs DAX
  2026-09-22  6:10 [PATCH 00/11] fuse: DAX device based extent maps (famfs) Miklos Szeredi
  2026-09-22  6:10 ` [PATCH 01/11] dax: replace exported dax_dev_get() with non-allocating dax_dev_find() Miklos Szeredi
@ 2026-09-22  6:10 ` Miklos Szeredi
  2026-09-22  8:43   ` Amir Goldstein
  2026-09-22  6:10 ` [PATCH 03/11] fuse: don't assume ff->passthrough is set for FOPEN_PASSTHROUGH Miklos Szeredi
                   ` (8 subsequent siblings)
  10 siblings, 1 reply; 33+ messages in thread
From: Miklos Szeredi @ 2026-09-22  6:10 UTC (permalink / raw)
  To: fuse-devel; +Cc: John Groves, Amir Goldstein, Darrick J . Wong

We are introducing DAX functionality largely unrelated to the virtiofs
code.  Rename dax -> vdax for the virtiofs case for clarity.

Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>
---
 fs/fuse/dax.c       | 214 ++++++++++++++++++++++----------------------
 fs/fuse/dir.c       |   4 +-
 fs/fuse/file.c      |  36 ++++----
 fs/fuse/fuse_i.h    |  66 +++++++-------
 fs/fuse/inode.c     |  38 ++++----
 fs/fuse/iomode.c    |   4 +-
 fs/fuse/virtio_fs.c |  18 ++--
 7 files changed, 190 insertions(+), 190 deletions(-)

diff --git a/fs/fuse/dax.c b/fs/fuse/dax.c
index 32c88ef81434..7905e7b36644 100644
--- a/fs/fuse/dax.c
+++ b/fs/fuse/dax.c
@@ -1,6 +1,6 @@
 // SPDX-License-Identifier: GPL-2.0
 /*
- * dax: direct host memory access
+ * dax: direct host memory access for virtiofs
  * Copyright (C) 2020 Red Hat, Inc.
  */
 
@@ -59,7 +59,7 @@ struct fuse_dax_mapping {
 };
 
 /* Per-inode dax map */
-struct fuse_inode_dax {
+struct fuse_inode_vdax {
 	/* Semaphore to protect modifications to the dmap tree */
 	struct rw_semaphore sem;
 
@@ -68,7 +68,7 @@ struct fuse_inode_dax {
 	unsigned long nr;
 };
 
-struct fuse_conn_dax {
+struct fuse_conn_vdax {
 	/* DAX device */
 	struct dax_device *dev;
 
@@ -102,10 +102,10 @@ node_to_dmap(struct interval_tree_node *node)
 }
 
 static struct fuse_dax_mapping *
-alloc_dax_mapping_reclaim(struct fuse_conn_dax *fcd, struct inode *inode);
+alloc_dax_mapping_reclaim(struct fuse_conn_vdax *fcd, struct inode *inode);
 
 static void
-__kick_dmap_free_worker(struct fuse_conn_dax *fcd, unsigned long delay_ms)
+__kick_dmap_free_worker(struct fuse_conn_vdax *fcd, unsigned long delay_ms)
 {
 	unsigned long free_threshold;
 
@@ -117,7 +117,7 @@ __kick_dmap_free_worker(struct fuse_conn_dax *fcd, unsigned long delay_ms)
 				   msecs_to_jiffies(delay_ms));
 }
 
-static void kick_dmap_free_worker(struct fuse_conn_dax *fcd,
+static void kick_dmap_free_worker(struct fuse_conn_vdax *fcd,
 				  unsigned long delay_ms)
 {
 	spin_lock(&fcd->lock);
@@ -125,7 +125,7 @@ static void kick_dmap_free_worker(struct fuse_conn_dax *fcd,
 	spin_unlock(&fcd->lock);
 }
 
-static struct fuse_dax_mapping *alloc_dax_mapping(struct fuse_conn_dax *fcd)
+static struct fuse_dax_mapping *alloc_dax_mapping(struct fuse_conn_vdax *fcd)
 {
 	struct fuse_dax_mapping *dmap;
 
@@ -144,7 +144,7 @@ static struct fuse_dax_mapping *alloc_dax_mapping(struct fuse_conn_dax *fcd)
 }
 
 /* This assumes fcd->lock is held */
-static void __dmap_remove_busy_list(struct fuse_conn_dax *fcd,
+static void __dmap_remove_busy_list(struct fuse_conn_vdax *fcd,
 				    struct fuse_dax_mapping *dmap)
 {
 	list_del_init(&dmap->busy_list);
@@ -152,7 +152,7 @@ static void __dmap_remove_busy_list(struct fuse_conn_dax *fcd,
 	fcd->nr_busy_ranges--;
 }
 
-static void dmap_remove_busy_list(struct fuse_conn_dax *fcd,
+static void dmap_remove_busy_list(struct fuse_conn_vdax *fcd,
 				  struct fuse_dax_mapping *dmap)
 {
 	spin_lock(&fcd->lock);
@@ -161,7 +161,7 @@ static void dmap_remove_busy_list(struct fuse_conn_dax *fcd,
 }
 
 /* This assumes fcd->lock is held */
-static void __dmap_add_to_free_pool(struct fuse_conn_dax *fcd,
+static void __dmap_add_to_free_pool(struct fuse_conn_vdax *fcd,
 				struct fuse_dax_mapping *dmap)
 {
 	list_add_tail(&dmap->list, &fcd->free_ranges);
@@ -169,7 +169,7 @@ static void __dmap_add_to_free_pool(struct fuse_conn_dax *fcd,
 	wake_up(&fcd->range_waitq);
 }
 
-static void dmap_add_to_free_pool(struct fuse_conn_dax *fcd,
+static void dmap_add_to_free_pool(struct fuse_conn_vdax *fcd,
 				struct fuse_dax_mapping *dmap)
 {
 	/* Return fuse_dax_mapping to free list */
@@ -183,7 +183,7 @@ static int fuse_setup_one_mapping(struct inode *inode, unsigned long start_idx,
 				  bool upgrade)
 {
 	struct fuse_mount *fm = get_fuse_mount(inode);
-	struct fuse_conn_dax *fcd = fm->fc->dax;
+	struct fuse_conn_vdax *fcd = fm->fc->vdax;
 	struct fuse_inode *fi = get_fuse_inode(inode);
 	struct fuse_setupmapping_in inarg;
 	loff_t offset = start_idx << FUSE_DAX_SHIFT;
@@ -218,9 +218,9 @@ static int fuse_setup_one_mapping(struct inode *inode, unsigned long start_idx,
 		 */
 		dmap->inode = inode;
 		dmap->itn.start = dmap->itn.last = start_idx;
-		/* Protected by fi->dax->sem */
-		interval_tree_insert(&dmap->itn, &fi->dax->tree);
-		fi->dax->nr++;
+		/* Protected by fi->vdax->sem */
+		interval_tree_insert(&dmap->itn, &fi->vdax->tree);
+		fi->vdax->nr++;
 		spin_lock(&fcd->lock);
 		list_add_tail(&dmap->busy_list, &fcd->busy_ranges);
 		fcd->nr_busy_ranges++;
@@ -288,7 +288,7 @@ static int dmap_removemapping_list(struct inode *inode, unsigned int num,
  * Cleanup dmap entry and add back to free list. This should be called with
  * fcd->lock held.
  */
-static void dmap_reinit_add_to_free_pool(struct fuse_conn_dax *fcd,
+static void dmap_reinit_add_to_free_pool(struct fuse_conn_vdax *fcd,
 					    struct fuse_dax_mapping *dmap)
 {
 	pr_debug("fuse: freeing memory range start_idx=0x%lx end_idx=0x%lx window_offset=0x%llx length=0x%llx\n",
@@ -306,7 +306,7 @@ static void dmap_reinit_add_to_free_pool(struct fuse_conn_dax *fcd,
  * called from evict_inode() path where we know all dmap entries can be
  * reclaimed.
  */
-static void inode_reclaim_dmap_range(struct fuse_conn_dax *fcd,
+static void inode_reclaim_dmap_range(struct fuse_conn_vdax *fcd,
 				     struct inode *inode,
 				     loff_t start, loff_t end)
 {
@@ -319,14 +319,14 @@ static void inode_reclaim_dmap_range(struct fuse_conn_dax *fcd,
 	struct interval_tree_node *node;
 
 	while (1) {
-		node = interval_tree_iter_first(&fi->dax->tree, start_idx,
+		node = interval_tree_iter_first(&fi->vdax->tree, start_idx,
 						end_idx);
 		if (!node)
 			break;
 		dmap = node_to_dmap(node);
 		/* inode is going away. There should not be any users of dmap */
 		WARN_ON(refcount_read(&dmap->refcnt) > 1);
-		interval_tree_remove(&dmap->itn, &fi->dax->tree);
+		interval_tree_remove(&dmap->itn, &fi->vdax->tree);
 		num++;
 		list_add(&dmap->list, &to_remove);
 	}
@@ -335,8 +335,8 @@ static void inode_reclaim_dmap_range(struct fuse_conn_dax *fcd,
 	if (list_empty(&to_remove))
 		return;
 
-	WARN_ON(fi->dax->nr < num);
-	fi->dax->nr -= num;
+	WARN_ON(fi->vdax->nr < num);
+	fi->vdax->nr -= num;
 	err = dmap_removemapping_list(inode, num, &to_remove);
 	if (err && err != -ENOTCONN) {
 		pr_warn("Failed to removemappings. start=0x%llx end=0x%llx\n",
@@ -367,11 +367,11 @@ static int dmap_removemapping_one(struct inode *inode,
 
 /*
  * It is called from evict_inode() and by that time inode is going away. So
- * this function does not take any locks like fi->dax->sem for traversing
+ * this function does not take any locks like fi->vdax->sem for traversing
  * that fuse inode interval tree. If that lock is taken then lock validator
  * complains of deadlock situation w.r.t fs_reclaim lock.
  */
-void fuse_dax_inode_cleanup(struct inode *inode)
+void fuse_vdax_inode_cleanup(struct inode *inode)
 {
 	struct fuse_conn *fc = get_fuse_conn(inode);
 	struct fuse_inode *fi = get_fuse_inode(inode);
@@ -381,8 +381,8 @@ void fuse_dax_inode_cleanup(struct inode *inode)
 	 * before we arrive here. So we should not have to worry about any
 	 * pages/exception entries still associated with inode.
 	 */
-	inode_reclaim_dmap_range(fc->dax, inode, 0, -1);
-	WARN_ON(fi->dax->nr);
+	inode_reclaim_dmap_range(fc->vdax, inode, 0, -1);
+	WARN_ON(fi->vdax->nr);
 }
 
 static void fuse_fill_iomap_hole(struct iomap *iomap, loff_t length)
@@ -414,7 +414,7 @@ static void fuse_fill_iomap(struct inode *inode, loff_t pos, loff_t length,
 		iomap->type = IOMAP_MAPPED;
 		/*
 		 * increace refcnt so that reclaim code knows this dmap is in
-		 * use. This assumes fi->dax->sem mutex is held either
+		 * use. This assumes fi->vdax->sem mutex is held either
 		 * shared/exclusive.
 		 */
 		refcount_inc(&dmap->refcnt);
@@ -434,7 +434,7 @@ static int fuse_setup_new_dax_mapping(struct inode *inode, loff_t pos,
 {
 	struct fuse_inode *fi = get_fuse_inode(inode);
 	struct fuse_conn *fc = get_fuse_conn(inode);
-	struct fuse_conn_dax *fcd = fc->dax;
+	struct fuse_conn_vdax *fcd = fc->vdax;
 	struct fuse_dax_mapping *dmap, *alloc_dmap = NULL;
 	int ret;
 	bool writable = flags & IOMAP_WRITE;
@@ -469,17 +469,17 @@ static int fuse_setup_new_dax_mapping(struct inode *inode, loff_t pos,
 	 * Take write lock so that only one caller can try to setup mapping
 	 * and other waits.
 	 */
-	down_write(&fi->dax->sem);
+	down_write(&fi->vdax->sem);
 	/*
 	 * We dropped lock. Check again if somebody else setup
 	 * mapping already.
 	 */
-	node = interval_tree_iter_first(&fi->dax->tree, start_idx, start_idx);
+	node = interval_tree_iter_first(&fi->vdax->tree, start_idx, start_idx);
 	if (node) {
 		dmap = node_to_dmap(node);
 		fuse_fill_iomap(inode, pos, length, iomap, dmap, flags);
 		dmap_add_to_free_pool(fcd, alloc_dmap);
-		up_write(&fi->dax->sem);
+		up_write(&fi->vdax->sem);
 		return 0;
 	}
 
@@ -488,11 +488,11 @@ static int fuse_setup_new_dax_mapping(struct inode *inode, loff_t pos,
 				     writable, false);
 	if (ret < 0) {
 		dmap_add_to_free_pool(fcd, alloc_dmap);
-		up_write(&fi->dax->sem);
+		up_write(&fi->vdax->sem);
 		return ret;
 	}
 	fuse_fill_iomap(inode, pos, length, iomap, alloc_dmap, flags);
-	up_write(&fi->dax->sem);
+	up_write(&fi->vdax->sem);
 	return 0;
 }
 
@@ -510,14 +510,14 @@ static int fuse_upgrade_dax_mapping(struct inode *inode, loff_t pos,
 	 * Take exclusive lock so that only one caller can try to setup
 	 * mapping and others wait.
 	 */
-	down_write(&fi->dax->sem);
-	node = interval_tree_iter_first(&fi->dax->tree, idx, idx);
+	down_write(&fi->vdax->sem);
+	node = interval_tree_iter_first(&fi->vdax->tree, idx, idx);
 
 	/* We are holding either inode lock or invalidate_lock, and that should
 	 * ensure that dmap can't be truncated. We are holding a reference
 	 * on dmap and that should make sure it can't be reclaimed. So dmap
 	 * should still be there in tree despite the fact we dropped and
-	 * re-acquired the fi->dax->sem lock.
+	 * re-acquired the fi->vdax->sem lock.
 	 */
 	ret = -EIO;
 	if (WARN_ON(!node))
@@ -526,7 +526,7 @@ static int fuse_upgrade_dax_mapping(struct inode *inode, loff_t pos,
 	dmap = node_to_dmap(node);
 
 	/* We took an extra reference on dmap to make sure its not reclaimd.
-	 * Now we hold fi->dax->sem lock and that reference is not needed
+	 * Now we hold fi->vdax->sem lock and that reference is not needed
 	 * anymore. Drop it.
 	 */
 	if (refcount_dec_and_test(&dmap->refcnt)) {
@@ -551,7 +551,7 @@ static int fuse_upgrade_dax_mapping(struct inode *inode, loff_t pos,
 out_fill_iomap:
 	fuse_fill_iomap(inode, pos, length, iomap, dmap, flags);
 out_err:
-	up_write(&fi->dax->sem);
+	up_write(&fi->vdax->sem);
 	return ret;
 }
 
@@ -576,7 +576,7 @@ static int fuse_iomap_begin(struct inode *inode, loff_t pos, loff_t length,
 	iomap->offset = pos;
 	iomap->flags = 0;
 	iomap->bdev = NULL;
-	iomap->dax_dev = fc->dax->dev;
+	iomap->dax_dev = fc->vdax->dev;
 
 	/*
 	 * Both read/write and mmap path can race here. So we need something
@@ -585,33 +585,33 @@ static int fuse_iomap_begin(struct inode *inode, loff_t pos, loff_t length,
 	 * For now, use a semaphore for this. It probably needs to be
 	 * optimized later.
 	 */
-	down_read(&fi->dax->sem);
-	node = interval_tree_iter_first(&fi->dax->tree, start_idx, start_idx);
+	down_read(&fi->vdax->sem);
+	node = interval_tree_iter_first(&fi->vdax->tree, start_idx, start_idx);
 	if (node) {
 		dmap = node_to_dmap(node);
 		if (writable && !dmap->writable) {
 			/* Upgrade read-only mapping to read-write. This will
-			 * require exclusive fi->dax->sem lock as we don't want
+			 * require exclusive fi->vdax->sem lock as we don't want
 			 * two threads to be trying to this simultaneously
 			 * for same dmap. So drop shared lock and acquire
 			 * exclusive lock.
 			 *
-			 * Before dropping fi->dax->sem lock, take reference
+			 * Before dropping fi->vdax->sem lock, take reference
 			 * on dmap so that its not freed by range reclaim.
 			 */
 			refcount_inc(&dmap->refcnt);
-			up_read(&fi->dax->sem);
+			up_read(&fi->vdax->sem);
 			pr_debug("%s: Upgrading mapping at offset 0x%llx length 0x%llx\n",
 				 __func__, pos, length);
 			return fuse_upgrade_dax_mapping(inode, pos, length,
 							flags, iomap);
 		} else {
 			fuse_fill_iomap(inode, pos, length, iomap, dmap, flags);
-			up_read(&fi->dax->sem);
+			up_read(&fi->vdax->sem);
 			return 0;
 		}
 	} else {
-		up_read(&fi->dax->sem);
+		up_read(&fi->vdax->sem);
 		pr_debug("%s: no mapping at offset 0x%llx length 0x%llx\n",
 				__func__, pos, length);
 		if (pos >= i_size_read(inode))
@@ -668,14 +668,14 @@ static void fuse_wait_dax_page(struct inode *inode)
 }
 
 /* Should be called with mapping->invalidate_lock held exclusively. */
-int fuse_dax_break_layouts(struct inode *inode, u64 dmap_start,
+int fuse_vdax_break_layouts(struct inode *inode, u64 dmap_start,
 				  u64 dmap_end)
 {
 	return dax_break_layout(inode, dmap_start, dmap_end,
 				fuse_wait_dax_page);
 }
 
-ssize_t fuse_dax_read_iter(struct kiocb *iocb, struct iov_iter *to)
+ssize_t fuse_vdax_read_iter(struct kiocb *iocb, struct iov_iter *to)
 {
 	struct inode *inode = file_inode(iocb->ki_filp);
 	ssize_t ret;
@@ -715,7 +715,7 @@ static ssize_t fuse_dax_direct_write(struct kiocb *iocb, struct iov_iter *from)
 	return ret;
 }
 
-ssize_t fuse_dax_write_iter(struct kiocb *iocb, struct iov_iter *from)
+ssize_t fuse_vdax_write_iter(struct kiocb *iocb, struct iov_iter *from)
 {
 	struct inode *inode = file_inode(iocb->ki_filp);
 	ssize_t ret;
@@ -761,7 +761,7 @@ static vm_fault_t __fuse_dax_fault(struct vm_fault *vmf, unsigned int order,
 	unsigned long pfn;
 	int error = 0;
 	struct fuse_conn *fc = get_fuse_conn(inode);
-	struct fuse_conn_dax *fcd = fc->dax;
+	struct fuse_conn_vdax *fcd = fc->vdax;
 	bool retry = false;
 
 	if (write)
@@ -822,7 +822,7 @@ static const struct vm_operations_struct fuse_dax_vm_ops = {
 	.pfn_mkwrite	= fuse_dax_pfn_mkwrite,
 };
 
-int fuse_dax_mmap(struct file *file, struct vm_area_struct *vma)
+int fuse_vdax_mmap(struct file *file, struct vm_area_struct *vma)
 {
 	file_accessed(file);
 	vma->vm_ops = &fuse_dax_vm_ops;
@@ -870,8 +870,8 @@ static int reclaim_one_dmap_locked(struct inode *inode,
 		return ret;
 
 	/* Remove dax mapping from inode interval tree now */
-	interval_tree_remove(&dmap->itn, &fi->dax->tree);
-	fi->dax->nr--;
+	interval_tree_remove(&dmap->itn, &fi->vdax->tree);
+	fi->vdax->nr--;
 
 	/* It is possible that umount/shutdown has killed the fuse connection
 	 * and worker thread is trying to reclaim memory in parallel.  Don't
@@ -886,7 +886,7 @@ static int reclaim_one_dmap_locked(struct inode *inode,
 }
 
 /* Find first mapped dmap for an inode and return file offset. Caller needs
- * to hold fi->dax->sem lock either shared or exclusive.
+ * to hold fi->vdax->sem lock either shared or exclusive.
  */
 static struct fuse_dax_mapping *inode_lookup_first_dmap(struct inode *inode)
 {
@@ -894,7 +894,7 @@ static struct fuse_dax_mapping *inode_lookup_first_dmap(struct inode *inode)
 	struct fuse_dax_mapping *dmap;
 	struct interval_tree_node *node;
 
-	for (node = interval_tree_iter_first(&fi->dax->tree, 0, -1); node;
+	for (node = interval_tree_iter_first(&fi->vdax->tree, 0, -1); node;
 	     node = interval_tree_iter_next(node, 0, -1)) {
 		dmap = node_to_dmap(node);
 		/* still in use. */
@@ -912,7 +912,7 @@ static struct fuse_dax_mapping *inode_lookup_first_dmap(struct inode *inode)
  * it back to free pool.
  */
 static struct fuse_dax_mapping *
-inode_inline_reclaim_one_dmap(struct fuse_conn_dax *fcd, struct inode *inode,
+inode_inline_reclaim_one_dmap(struct fuse_conn_vdax *fcd, struct inode *inode,
 			      bool *retry)
 {
 	struct fuse_inode *fi = get_fuse_inode(inode);
@@ -925,14 +925,14 @@ inode_inline_reclaim_one_dmap(struct fuse_conn_dax *fcd, struct inode *inode,
 	filemap_invalidate_lock(inode->i_mapping);
 
 	/* Lookup a dmap and corresponding file offset to reclaim. */
-	down_read(&fi->dax->sem);
+	down_read(&fi->vdax->sem);
 	dmap = inode_lookup_first_dmap(inode);
 	if (dmap) {
 		start_idx = dmap->itn.start;
 		dmap_start = start_idx << FUSE_DAX_SHIFT;
 		dmap_end = dmap_start + FUSE_DAX_SZ - 1;
 	}
-	up_read(&fi->dax->sem);
+	up_read(&fi->vdax->sem);
 
 	if (!dmap)
 		goto out_mmap_sem;
@@ -940,16 +940,16 @@ inode_inline_reclaim_one_dmap(struct fuse_conn_dax *fcd, struct inode *inode,
 	 * Make sure there are no references to inode pages using
 	 * get_user_pages()
 	 */
-	ret = fuse_dax_break_layouts(inode, dmap_start, dmap_end);
+	ret = fuse_vdax_break_layouts(inode, dmap_start, dmap_end);
 	if (ret) {
-		pr_debug("fuse: fuse_dax_break_layouts() failed. err=%d\n",
+		pr_debug("fuse: fuse_vdax_break_layouts() failed. err=%d\n",
 			 ret);
 		dmap = ERR_PTR(ret);
 		goto out_mmap_sem;
 	}
 
-	down_write(&fi->dax->sem);
-	node = interval_tree_iter_first(&fi->dax->tree, start_idx, start_idx);
+	down_write(&fi->vdax->sem);
+	node = interval_tree_iter_first(&fi->vdax->tree, start_idx, start_idx);
 	/* Range already got reclaimed by somebody else */
 	if (!node) {
 		if (retry)
@@ -981,14 +981,14 @@ inode_inline_reclaim_one_dmap(struct fuse_conn_dax *fcd, struct inode *inode,
 		 __func__, inode, dmap->window_offset, dmap->length);
 
 out_write_dmap_sem:
-	up_write(&fi->dax->sem);
+	up_write(&fi->vdax->sem);
 out_mmap_sem:
 	filemap_invalidate_unlock(inode->i_mapping);
 	return dmap;
 }
 
 static struct fuse_dax_mapping *
-alloc_dax_mapping_reclaim(struct fuse_conn_dax *fcd, struct inode *inode)
+alloc_dax_mapping_reclaim(struct fuse_conn_vdax *fcd, struct inode *inode)
 {
 	struct fuse_dax_mapping *dmap;
 	struct fuse_inode *fi = get_fuse_inode(inode);
@@ -1015,18 +1015,18 @@ alloc_dax_mapping_reclaim(struct fuse_conn_dax *fcd, struct inode *inode)
 		 * if a deadlock is possible if we sleep with
 		 * mapping->invalidate_lock held and worker to free memory
 		 * can't make progress due to unavailability of
-		 * mapping->invalidate_lock.  So sleep only if fi->dax->nr=0
+		 * mapping->invalidate_lock.  So sleep only if fi->vdax->nr=0
 		 */
 		if (retry)
 			continue;
 		/*
 		 * There are no mappings which can be reclaimed. Wait for one.
-		 * We are not holding fi->dax->sem. So it is possible
+		 * We are not holding fi->vdax->sem. So it is possible
 		 * that range gets added now. But as we are not holding
 		 * mapping->invalidate_lock, worker should still be able to
 		 * free up a range and wake us up.
 		 */
-		if (!fi->dax->nr && !(fcd->nr_free_ranges > 0)) {
+		if (!fi->vdax->nr && !(fcd->nr_free_ranges > 0)) {
 			if (wait_event_killable_exclusive(fcd->range_waitq,
 					(fcd->nr_free_ranges > 0))) {
 				return ERR_PTR(-EINTR);
@@ -1035,7 +1035,7 @@ alloc_dax_mapping_reclaim(struct fuse_conn_dax *fcd, struct inode *inode)
 	}
 }
 
-static int lookup_and_reclaim_dmap_locked(struct fuse_conn_dax *fcd,
+static int lookup_and_reclaim_dmap_locked(struct fuse_conn_vdax *fcd,
 					  struct inode *inode,
 					  unsigned long start_idx)
 {
@@ -1045,7 +1045,7 @@ static int lookup_and_reclaim_dmap_locked(struct fuse_conn_dax *fcd,
 	struct interval_tree_node *node;
 
 	/* Find fuse dax mapping at file offset inode. */
-	node = interval_tree_iter_first(&fi->dax->tree, start_idx, start_idx);
+	node = interval_tree_iter_first(&fi->vdax->tree, start_idx, start_idx);
 
 	/* Range already got cleaned up by somebody else */
 	if (!node)
@@ -1071,10 +1071,10 @@ static int lookup_and_reclaim_dmap_locked(struct fuse_conn_dax *fcd,
  * Free a range of memory.
  * Locking:
  * 1. Take mapping->invalidate_lock to block dax faults.
- * 2. Take fi->dax->sem to protect interval tree and also to make sure
+ * 2. Take fi->vdax->sem to protect interval tree and also to make sure
  *    read/write can not reuse a dmap which we might be freeing.
  */
-static int lookup_and_reclaim_dmap(struct fuse_conn_dax *fcd,
+static int lookup_and_reclaim_dmap(struct fuse_conn_vdax *fcd,
 				   struct inode *inode,
 				   unsigned long start_idx,
 				   unsigned long end_idx)
@@ -1085,22 +1085,22 @@ static int lookup_and_reclaim_dmap(struct fuse_conn_dax *fcd,
 	loff_t dmap_end = (dmap_start + FUSE_DAX_SZ) - 1;
 
 	filemap_invalidate_lock(inode->i_mapping);
-	ret = fuse_dax_break_layouts(inode, dmap_start, dmap_end);
+	ret = fuse_vdax_break_layouts(inode, dmap_start, dmap_end);
 	if (ret) {
-		pr_debug("virtio_fs: fuse_dax_break_layouts() failed. err=%d\n",
+		pr_debug("virtio_fs: fuse_vdax_break_layouts() failed. err=%d\n",
 			 ret);
 		goto out_mmap_sem;
 	}
 
-	down_write(&fi->dax->sem);
+	down_write(&fi->vdax->sem);
 	ret = lookup_and_reclaim_dmap_locked(fcd, inode, start_idx);
-	up_write(&fi->dax->sem);
+	up_write(&fi->vdax->sem);
 out_mmap_sem:
 	filemap_invalidate_unlock(inode->i_mapping);
 	return ret;
 }
 
-static int try_to_free_dmap_chunks(struct fuse_conn_dax *fcd,
+static int try_to_free_dmap_chunks(struct fuse_conn_vdax *fcd,
 				   unsigned long nr_to_free)
 {
 	struct fuse_dax_mapping *dmap, *pos, *temp;
@@ -1161,7 +1161,7 @@ static int try_to_free_dmap_chunks(struct fuse_conn_dax *fcd,
 static void fuse_dax_free_mem_worker(struct work_struct *work)
 {
 	int ret;
-	struct fuse_conn_dax *fcd = container_of(work, struct fuse_conn_dax,
+	struct fuse_conn_vdax *fcd = container_of(work, struct fuse_conn_vdax,
 						 free_work.work);
 	ret = try_to_free_dmap_chunks(fcd, FUSE_DAX_RECLAIM_CHUNK);
 	if (ret) {
@@ -1186,16 +1186,16 @@ static void fuse_free_dax_mem_ranges(struct list_head *mem_list)
 	}
 }
 
-void fuse_dax_conn_free(struct fuse_conn *fc)
+void fuse_vdax_conn_free(struct fuse_conn *fc)
 {
-	if (fc->dax) {
-		fuse_free_dax_mem_ranges(&fc->dax->free_ranges);
-		kfree(fc->dax);
-		fc->dax = NULL;
+	if (fc->vdax) {
+		fuse_free_dax_mem_ranges(&fc->vdax->free_ranges);
+		kfree(fc->vdax);
+		fc->vdax = NULL;
 	}
 }
 
-static int fuse_dax_mem_range_init(struct fuse_conn_dax *fcd)
+static int fuse_dax_mem_range_init(struct fuse_conn_vdax *fcd)
 {
 	long nr_pages, nr_ranges;
 	struct fuse_dax_mapping *range;
@@ -1247,13 +1247,13 @@ static int fuse_dax_mem_range_init(struct fuse_conn_dax *fcd)
 	return ret;
 }
 
-int fuse_dax_conn_alloc(struct fuse_conn *fc, enum fuse_dax_mode dax_mode,
+int fuse_vdax_conn_alloc(struct fuse_conn *fc, enum fuse_vdax_mode dax_mode,
 			struct dax_device *dax_dev)
 {
-	struct fuse_conn_dax *fcd;
+	struct fuse_conn_vdax *fcd;
 	int err;
 
-	fc->dax_mode = dax_mode;
+	fc->vdax_mode = dax_mode;
 
 	if (!dax_dev)
 		return 0;
@@ -1270,22 +1270,22 @@ int fuse_dax_conn_alloc(struct fuse_conn *fc, enum fuse_dax_mode dax_mode,
 		return err;
 	}
 
-	fc->dax = fcd;
+	fc->vdax = fcd;
 	return 0;
 }
 
-bool fuse_dax_inode_alloc(struct super_block *sb, struct fuse_inode *fi)
+bool fuse_vdax_inode_alloc(struct super_block *sb, struct fuse_inode *fi)
 {
 	struct fuse_conn *fc = get_fuse_conn_super(sb);
 
-	fi->dax = NULL;
-	if (fc->dax) {
-		fi->dax = kzalloc_obj(*fi->dax, GFP_KERNEL_ACCOUNT);
-		if (!fi->dax)
+	fi->vdax = NULL;
+	if (fc->vdax) {
+		fi->vdax = kzalloc_obj(*fi->vdax, GFP_KERNEL_ACCOUNT);
+		if (!fi->vdax)
 			return false;
 
-		init_rwsem(&fi->dax->sem);
-		fi->dax->tree = RB_ROOT_CACHED;
+		init_rwsem(&fi->vdax->sem);
+		fi->vdax->tree = RB_ROOT_CACHED;
 	}
 
 	return true;
@@ -1299,26 +1299,26 @@ static const struct address_space_operations fuse_dax_file_aops  = {
 static bool fuse_should_enable_dax(struct inode *inode, unsigned int flags)
 {
 	struct fuse_conn *fc = get_fuse_conn(inode);
-	enum fuse_dax_mode dax_mode = fc->dax_mode;
+	enum fuse_vdax_mode dax_mode = fc->vdax_mode;
 
-	if (dax_mode == FUSE_DAX_NEVER)
+	if (dax_mode == FUSE_VDAX_NEVER)
 		return false;
 
 	/*
-	 * fc->dax may be NULL in 'inode' mode when filesystem device doesn't
+	 * fc->vdax may be NULL in 'inode' mode when filesystem device doesn't
 	 * support DAX, in which case it will silently fallback to 'never' mode.
 	 */
-	if (!fc->dax)
+	if (!fc->vdax)
 		return false;
 
-	if (dax_mode == FUSE_DAX_ALWAYS)
+	if (dax_mode == FUSE_VDAX_ALWAYS)
 		return true;
 
 	/* dax_mode is FUSE_DAX_INODE* */
-	return fc->inode_dax && (flags & FUSE_ATTR_DAX);
+	return fc->inode_vdax && (flags & FUSE_ATTR_DAX);
 }
 
-void fuse_dax_inode_init(struct inode *inode, unsigned int flags)
+void fuse_vdax_inode_init(struct inode *inode, unsigned int flags)
 {
 	if (!fuse_should_enable_dax(inode, flags))
 		return;
@@ -1327,18 +1327,18 @@ void fuse_dax_inode_init(struct inode *inode, unsigned int flags)
 	inode->i_data.a_ops = &fuse_dax_file_aops;
 }
 
-void fuse_dax_dontcache(struct inode *inode, unsigned int flags)
+void fuse_vdax_dontcache(struct inode *inode, unsigned int flags)
 {
 	struct fuse_conn *fc = get_fuse_conn(inode);
 
-	if (fuse_is_inode_dax_mode(fc->dax_mode) &&
+	if (fuse_is_inode_vdax_mode(fc->vdax_mode) &&
 	    ((bool) IS_DAX(inode) != (bool) (flags & FUSE_ATTR_DAX)))
 		d_mark_dontcache(inode);
 }
 
-bool fuse_dax_check_alignment(struct fuse_conn *fc, unsigned int map_alignment)
+bool fuse_vdax_check_alignment(struct fuse_conn *fc, unsigned int map_alignment)
 {
-	if (fc->dax && (map_alignment > FUSE_DAX_SHIFT)) {
+	if (fc->vdax && (map_alignment > FUSE_DAX_SHIFT)) {
 		pr_warn("FUSE: map_alignment %u incompatible with dax mem range size %u\n",
 			map_alignment, FUSE_DAX_SZ);
 		return false;
@@ -1346,12 +1346,12 @@ bool fuse_dax_check_alignment(struct fuse_conn *fc, unsigned int map_alignment)
 	return true;
 }
 
-void fuse_dax_cancel_work(struct fuse_conn *fc)
+void fuse_vdax_cancel_work(struct fuse_conn *fc)
 {
-	struct fuse_conn_dax *fcd = fc->dax;
+	struct fuse_conn_vdax *fcd = fc->vdax;
 
 	if (fcd)
 		cancel_delayed_work_sync(&fcd->free_work);
 
 }
-EXPORT_SYMBOL_GPL(fuse_dax_cancel_work);
+EXPORT_SYMBOL_GPL(fuse_vdax_cancel_work);
diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c
index e49b4e874b15..f48fafccce4b 100644
--- a/fs/fuse/dir.c
+++ b/fs/fuse/dir.c
@@ -2174,10 +2174,10 @@ int fuse_do_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
 		is_truncate = true;
 	}
 
-	if (FUSE_IS_DAX(inode) && is_truncate) {
+	if (FUSE_IS_VDAX(inode) && is_truncate) {
 		filemap_invalidate_lock(mapping);
 		fault_blocked = true;
-		err = fuse_dax_break_layouts(inode, 0, -1);
+		err = fuse_vdax_break_layouts(inode, 0, -1);
 		if (err)
 			goto unlock;
 	}
diff --git a/fs/fuse/file.c b/fs/fuse/file.c
index 4bdf5dec2cb9..1eadeca0c47d 100644
--- a/fs/fuse/file.c
+++ b/fs/fuse/file.c
@@ -119,7 +119,7 @@ static void fuse_file_put(struct fuse_file *ff, bool sync)
 			 * DAX inodes may need to issue a number of synchronous
 			 * request for clearing the mappings.
 			 */
-			if (ra && ra->inode && FUSE_IS_DAX(ra->inode))
+			if (ra && ra->inode && FUSE_IS_VDAX(ra->inode))
 				args->may_block = true;
 			args->end = fuse_release_end;
 			if (fuse_simple_background(ff->fm, args,
@@ -256,7 +256,7 @@ static int fuse_open(struct inode *inode, struct file *file)
 	int err;
 	bool is_truncate = (file->f_flags & O_TRUNC) && fc->atomic_o_trunc;
 	bool is_wb_truncate = is_truncate && fc->writeback_cache;
-	bool dax_truncate = is_truncate && FUSE_IS_DAX(inode);
+	bool vdax_truncate = is_truncate && FUSE_IS_VDAX(inode);
 
 	if (fuse_is_bad(inode))
 		return -EIO;
@@ -265,17 +265,17 @@ static int fuse_open(struct inode *inode, struct file *file)
 	if (err)
 		return err;
 
-	if (is_wb_truncate || dax_truncate)
+	if (is_wb_truncate || vdax_truncate)
 		inode_lock(inode);
 
-	if (dax_truncate) {
+	if (vdax_truncate) {
 		filemap_invalidate_lock(inode->i_mapping);
-		err = fuse_dax_break_layouts(inode, 0, -1);
+		err = fuse_vdax_break_layouts(inode, 0, -1);
 		if (err)
 			goto out_unlock;
 	}
 
-	if (is_wb_truncate || dax_truncate)
+	if (is_wb_truncate || vdax_truncate)
 		fuse_set_nowrite(inode);
 
 	err = fuse_do_open(fm, get_node_id(inode), file, false);
@@ -288,7 +288,7 @@ static int fuse_open(struct inode *inode, struct file *file)
 			fuse_truncate_update_attr(inode, file);
 	}
 
-	if (is_wb_truncate || dax_truncate)
+	if (is_wb_truncate || vdax_truncate)
 		fuse_release_nowrite(inode);
 	if (!err) {
 		if (is_truncate)
@@ -297,9 +297,9 @@ static int fuse_open(struct inode *inode, struct file *file)
 			invalidate_inode_pages2(inode->i_mapping);
 	}
 out_unlock:
-	if (dax_truncate)
+	if (vdax_truncate)
 		filemap_invalidate_unlock(inode->i_mapping);
-	if (is_wb_truncate || dax_truncate)
+	if (is_wb_truncate || vdax_truncate)
 		inode_unlock(inode);
 
 	return err;
@@ -1836,8 +1836,8 @@ static ssize_t fuse_file_read_iter(struct kiocb *iocb, struct iov_iter *to)
 	if (fuse_is_bad(inode))
 		return -EIO;
 
-	if (FUSE_IS_DAX(inode))
-		return fuse_dax_read_iter(iocb, to);
+	if (FUSE_IS_VDAX(inode))
+		return fuse_vdax_read_iter(iocb, to);
 
 	/* FOPEN_DIRECT_IO overrides FOPEN_PASSTHROUGH */
 	if (ff->open_flags & FOPEN_DIRECT_IO)
@@ -1857,8 +1857,8 @@ static ssize_t fuse_file_write_iter(struct kiocb *iocb, struct iov_iter *from)
 	if (fuse_is_bad(inode))
 		return -EIO;
 
-	if (FUSE_IS_DAX(inode))
-		return fuse_dax_write_iter(iocb, from);
+	if (FUSE_IS_VDAX(inode))
+		return fuse_vdax_write_iter(iocb, from);
 
 	/* FOPEN_DIRECT_IO overrides FOPEN_PASSTHROUGH */
 	if (ff->open_flags & FOPEN_DIRECT_IO)
@@ -2398,8 +2398,8 @@ static int fuse_file_mmap(struct file *file, struct vm_area_struct *vma)
 	int rc;
 
 	/* DAX mmap is superior to direct_io mmap */
-	if (FUSE_IS_DAX(inode))
-		return fuse_dax_mmap(file, vma);
+	if (FUSE_IS_VDAX(inode))
+		return fuse_vdax_mmap(file, vma);
 
 	/*
 	 * If inode is in passthrough io mode, because it has some file open
@@ -2848,7 +2848,7 @@ static long fuse_file_fallocate(struct file *file, int mode, loff_t offset,
 		.mode = mode
 	};
 	int err;
-	bool block_faults = FUSE_IS_DAX(inode) &&
+	bool block_faults = FUSE_IS_VDAX(inode) &&
 		(!(mode & FALLOC_FL_KEEP_SIZE) ||
 		 (mode & (FALLOC_FL_PUNCH_HOLE | FALLOC_FL_ZERO_RANGE)));
 
@@ -2862,7 +2862,7 @@ static long fuse_file_fallocate(struct file *file, int mode, loff_t offset,
 	inode_lock(inode);
 	if (block_faults) {
 		filemap_invalidate_lock(inode->i_mapping);
-		err = fuse_dax_break_layouts(inode, 0, -1);
+		err = fuse_vdax_break_layouts(inode, 0, -1);
 		if (err)
 			goto out;
 	}
@@ -3131,5 +3131,5 @@ void fuse_init_file_inode(struct inode *inode, unsigned int flags)
 	init_waitqueue_head(&fi->direct_io_waitq);
 
 	if (IS_ENABLED(CONFIG_FUSE_DAX))
-		fuse_dax_inode_init(inode, flags);
+		fuse_vdax_inode_init(inode, flags);
 }
diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h
index 97d356588d00..77ccd5b2059b 100644
--- a/fs/fuse/fuse_i.h
+++ b/fs/fuse/fuse_i.h
@@ -220,9 +220,9 @@ struct fuse_inode {
 
 #ifdef CONFIG_FUSE_DAX
 	/**
-	 * @dax: Dax specific inode data
+	 * @vdax: Virtiofs DAX specific inode data
 	 */
-	struct fuse_inode_dax *dax;
+	struct fuse_inode_vdax *vdax;
 #endif
 	/** @submount_lookup: Submount specific lookup tracking */
 	struct fuse_submount_lookup *submount_lookup;
@@ -364,16 +364,16 @@ struct fuse_io_priv {
 	.iocb = i,			\
 }
 
-enum fuse_dax_mode {
-	FUSE_DAX_INODE_DEFAULT,	/* default */
-	FUSE_DAX_ALWAYS,	/* "-o dax=always" */
-	FUSE_DAX_NEVER,		/* "-o dax=never" */
-	FUSE_DAX_INODE_USER,	/* "-o dax=inode" */
+enum fuse_vdax_mode {
+	FUSE_VDAX_INODE_DEFAULT,	/* default */
+	FUSE_VDAX_ALWAYS,		/* "-o dax=always" */
+	FUSE_VDAX_NEVER,		/* "-o dax=never" */
+	FUSE_VDAX_INODE_USER,		/* "-o dax=inode" */
 };
 
-static inline bool fuse_is_inode_dax_mode(enum fuse_dax_mode mode)
+static inline bool fuse_is_inode_vdax_mode(enum fuse_vdax_mode mode)
 {
-	return mode == FUSE_DAX_INODE_DEFAULT || mode == FUSE_DAX_INODE_USER;
+	return mode == FUSE_VDAX_INODE_DEFAULT || mode == FUSE_VDAX_INODE_USER;
 }
 
 struct fuse_fs_context {
@@ -392,13 +392,13 @@ struct fuse_fs_context {
 	bool no_force_umount:1;
 	bool legacy_opts_show:1;
 	bool syncfs_capable:1;
-	enum fuse_dax_mode dax_mode;
+	enum fuse_vdax_mode vdax_mode;
 	unsigned int max_read;
 	unsigned int blksize;
 	const char *subtype;
 
-	/* DAX device, may be NULL */
-	struct dax_device *dax_dev;
+	/* Virtiofs DAX device, may be NULL */
+	struct dax_device *vdax_dev;
 };
 
 struct fuse_sync_bucket {
@@ -693,8 +693,8 @@ struct fuse_conn {
 	 */
 	unsigned int create_supp_group:1;
 
-	/** @inode_dax: Does the filesystem support per inode DAX? */
-	unsigned int inode_dax:1;
+	/** @inode_vdax: Does the filesystem support per inode virtiofs DAX? */
+	unsigned int inode_vdax:1;
 
 	/** @no_tmpfile: Is tmpfile not implemented by fs? */
 	unsigned int no_tmpfile:1;
@@ -754,11 +754,11 @@ struct fuse_conn {
 	struct rw_semaphore killsb;
 
 #ifdef CONFIG_FUSE_DAX
-	/** @dax_mode: Dax mode */
-	enum fuse_dax_mode dax_mode;
+	/** @vdax_mode: Virtiofs DAX mode */
+	enum fuse_vdax_mode vdax_mode;
 
-	/** @dax: Dax specific conn data, non-NULL if DAX is enabled */
-	struct fuse_conn_dax *dax;
+	/** @dax: Dax specific conn data, non-NULL if virtiofs DAX is enabled */
+	struct fuse_conn_vdax *vdax;
 #endif
 
 	/** @mounts: List of filesystems using this connection */
@@ -1226,21 +1226,21 @@ void fuse_free_conn(struct fuse_conn *fc);
 
 /* dax.c */
 
-#define FUSE_IS_DAX(inode) (IS_ENABLED(CONFIG_FUSE_DAX) && IS_DAX(inode))
-
-ssize_t fuse_dax_read_iter(struct kiocb *iocb, struct iov_iter *to);
-ssize_t fuse_dax_write_iter(struct kiocb *iocb, struct iov_iter *from);
-int fuse_dax_mmap(struct file *file, struct vm_area_struct *vma);
-int fuse_dax_break_layouts(struct inode *inode, u64 dmap_start, u64 dmap_end);
-int fuse_dax_conn_alloc(struct fuse_conn *fc, enum fuse_dax_mode mode,
-			struct dax_device *dax_dev);
-void fuse_dax_conn_free(struct fuse_conn *fc);
-bool fuse_dax_inode_alloc(struct super_block *sb, struct fuse_inode *fi);
-void fuse_dax_inode_init(struct inode *inode, unsigned int flags);
-void fuse_dax_inode_cleanup(struct inode *inode);
-void fuse_dax_dontcache(struct inode *inode, unsigned int flags);
-bool fuse_dax_check_alignment(struct fuse_conn *fc, unsigned int map_alignment);
-void fuse_dax_cancel_work(struct fuse_conn *fc);
+#define FUSE_IS_VDAX(inode) (IS_ENABLED(CONFIG_FUSE_DAX) && 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);
+int fuse_vdax_mmap(struct file *file, struct vm_area_struct *vma);
+int fuse_vdax_break_layouts(struct inode *inode, u64 dmap_start, u64 dmap_end);
+int fuse_vdax_conn_alloc(struct fuse_conn *fc, enum fuse_vdax_mode mode,
+			struct dax_device *vdax_dev);
+void fuse_vdax_conn_free(struct fuse_conn *fc);
+bool fuse_vdax_inode_alloc(struct super_block *sb, struct fuse_inode *fi);
+void fuse_vdax_inode_init(struct inode *inode, unsigned int flags);
+void fuse_vdax_inode_cleanup(struct inode *inode);
+void fuse_vdax_dontcache(struct inode *inode, unsigned int flags);
+bool fuse_vdax_check_alignment(struct fuse_conn *fc, unsigned int map_alignment);
+void fuse_vdax_cancel_work(struct fuse_conn *fc);
 
 /* ioctl.c */
 long fuse_file_ioctl(struct file *file, unsigned int cmd, unsigned long arg);
diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c
index c93d744b661e..e88daf88f913 100644
--- a/fs/fuse/inode.c
+++ b/fs/fuse/inode.c
@@ -100,7 +100,7 @@ static struct inode *fuse_alloc_inode(struct super_block *sb)
 	if (!fi->forget)
 		goto out_free;
 
-	if (IS_ENABLED(CONFIG_FUSE_DAX) && !fuse_dax_inode_alloc(sb, fi))
+	if (IS_ENABLED(CONFIG_FUSE_DAX) && !fuse_vdax_inode_alloc(sb, fi))
 		goto out_free_forget;
 
 	if (IS_ENABLED(CONFIG_FUSE_PASSTHROUGH))
@@ -122,7 +122,7 @@ static void fuse_free_inode(struct inode *inode)
 	mutex_destroy(&fi->mutex);
 	kfree(fi->forget);
 #ifdef CONFIG_FUSE_DAX
-	kfree(fi->dax);
+	kfree(fi->vdax);
 #endif
 	if (IS_ENABLED(CONFIG_FUSE_PASSTHROUGH))
 		fuse_backing_put(fuse_inode_backing(fi));
@@ -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_DAX(inode))
+	if (FUSE_IS_VDAX(inode))
 		dax_break_layout_final(inode);
 
 	truncate_inode_pages_final(&inode->i_data);
@@ -156,8 +156,8 @@ static void fuse_evict_inode(struct inode *inode)
 	if (inode->i_sb->s_flags & SB_ACTIVE) {
 		struct fuse_conn *fc = get_fuse_conn(inode);
 
-		if (FUSE_IS_DAX(inode))
-			fuse_dax_inode_cleanup(inode);
+		if (FUSE_IS_VDAX(inode))
+			fuse_vdax_inode_cleanup(inode);
 		if (fi->nlookup) {
 			fuse_chan_queue_forget(fc->chan, fi->forget, fi->nodeid,
 					       fi->nlookup);
@@ -386,7 +386,7 @@ static void fuse_change_attributes_i(struct inode *inode, struct fuse_attr *attr
 	}
 
 	if (IS_ENABLED(CONFIG_FUSE_DAX))
-		fuse_dax_dontcache(inode, attr->flags);
+		fuse_vdax_dontcache(inode, attr->flags);
 }
 
 void fuse_change_attributes(struct inode *inode, struct fuse_attr *attr,
@@ -954,11 +954,11 @@ static int fuse_show_options(struct seq_file *m, struct dentry *root)
 			seq_printf(m, ",blksize=%lu", sb->s_blocksize);
 	}
 #ifdef CONFIG_FUSE_DAX
-	if (fc->dax_mode == FUSE_DAX_ALWAYS)
+	if (fc->vdax_mode == FUSE_VDAX_ALWAYS)
 		seq_puts(m, ",dax=always");
-	else if (fc->dax_mode == FUSE_DAX_NEVER)
+	else if (fc->vdax_mode == FUSE_VDAX_NEVER)
 		seq_puts(m, ",dax=never");
-	else if (fc->dax_mode == FUSE_DAX_INODE_USER)
+	else if (fc->vdax_mode == FUSE_VDAX_INODE_USER)
 		seq_puts(m, ",dax=inode");
 #endif
 
@@ -1017,7 +1017,7 @@ void fuse_conn_put(struct fuse_conn *fc)
 		return;
 
 	if (IS_ENABLED(CONFIG_FUSE_DAX))
-		fuse_dax_conn_free(fc);
+		fuse_vdax_conn_free(fc);
 	cancel_work_sync(&fc->epoch_work);
 	fuse_chan_release(fc->chan);
 	put_pid_ns(fc->pid_ns);
@@ -1375,11 +1375,11 @@ static void process_init_reply(struct fuse_args *args, int error)
 			}
 			if (IS_ENABLED(CONFIG_FUSE_DAX)) {
 				if (flags & FUSE_MAP_ALIGNMENT &&
-				    !fuse_dax_check_alignment(fc, arg->map_alignment)) {
+				    !fuse_vdax_check_alignment(fc, arg->map_alignment)) {
 					ok = false;
 				}
 				if (flags & FUSE_HAS_INODE_DAX)
-					fc->inode_dax = 1;
+					fc->inode_vdax = 1;
 			}
 			if (flags & FUSE_HANDLE_KILLPRIV_V2) {
 				fc->handle_killpriv_v2 = 1;
@@ -1492,9 +1492,9 @@ static struct fuse_init_args *fuse_new_init(struct fuse_mount *fm)
 		FUSE_NO_EXPORT_SUPPORT | FUSE_HAS_RESEND | FUSE_ALLOW_IDMAP |
 		FUSE_REQUEST_TIMEOUT;
 #ifdef CONFIG_FUSE_DAX
-	if (fm->fc->dax)
+	if (fm->fc->vdax)
 		flags |= FUSE_MAP_ALIGNMENT;
-	if (fuse_is_inode_dax_mode(fm->fc->dax_mode))
+	if (fuse_is_inode_vdax_mode(fm->fc->vdax_mode))
 		flags |= FUSE_HAS_INODE_DAX;
 #endif
 	if (fm->fc->auto_submounts)
@@ -1779,7 +1779,7 @@ int fuse_fill_super_common(struct super_block *sb, struct fuse_fs_context *ctx)
 	sb->s_subtype = ctx->subtype;
 	ctx->subtype = NULL;
 	if (IS_ENABLED(CONFIG_FUSE_DAX)) {
-		err = fuse_dax_conn_alloc(fc, ctx->dax_mode, ctx->dax_dev);
+		err = fuse_vdax_conn_alloc(fc, ctx->vdax_mode, ctx->vdax_dev);
 		if (err)
 			goto err;
 	}
@@ -1788,7 +1788,7 @@ int fuse_fill_super_common(struct super_block *sb, struct fuse_fs_context *ctx)
 	fm->sb = sb;
 	err = fuse_bdi_init(fc, sb);
 	if (err)
-		goto err_free_dax;
+		goto err_free_vdax;
 
 	/* Handle umasking inside the fuse code */
 	if (sb->s_flags & SB_POSIXACL)
@@ -1811,7 +1811,7 @@ int fuse_fill_super_common(struct super_block *sb, struct fuse_fs_context *ctx)
 	set_default_d_op(sb, &fuse_dentry_operations);
 	root_dentry = d_make_root(root);
 	if (!root_dentry)
-		goto err_free_dax;
+		goto err_free_vdax;
 
 	mutex_lock(&fuse_mutex);
 	err = -EINVAL;
@@ -1837,9 +1837,9 @@ int fuse_fill_super_common(struct super_block *sb, struct fuse_fs_context *ctx)
  err_unlock:
 	mutex_unlock(&fuse_mutex);
 	dput(root_dentry);
- err_free_dax:
+ err_free_vdax:
 	if (IS_ENABLED(CONFIG_FUSE_DAX))
-		fuse_dax_conn_free(fc);
+		fuse_vdax_conn_free(fc);
  err:
 	return err;
 }
diff --git a/fs/fuse/iomode.c b/fs/fuse/iomode.c
index 3728933188f3..79637c09e883 100644
--- a/fs/fuse/iomode.c
+++ b/fs/fuse/iomode.c
@@ -200,10 +200,10 @@ int fuse_file_io_open(struct file *file, struct inode *inode)
 	int err;
 
 	/*
-	 * io modes are not relevant with DAX and with server that does not
+	 * io modes are not relevant with virtiofs DAX and with server that does not
 	 * implement open.
 	 */
-	if (FUSE_IS_DAX(inode) || !ff->args)
+	if (FUSE_IS_VDAX(inode) || !ff->args)
 		return 0;
 
 	/*
diff --git a/fs/fuse/virtio_fs.c b/fs/fuse/virtio_fs.c
index f15e516ebcb5..c82486d47a6c 100644
--- a/fs/fuse/virtio_fs.c
+++ b/fs/fuse/virtio_fs.c
@@ -102,9 +102,9 @@ static int virtio_fs_enqueue_req(struct virtio_fs_vq *fsvq,
 				 gfp_t gfp);
 
 static const struct constant_table dax_param_enums[] = {
-	{"always",	FUSE_DAX_ALWAYS },
-	{"never",	FUSE_DAX_NEVER },
-	{"inode",	FUSE_DAX_INODE_USER },
+	{"always",	FUSE_VDAX_ALWAYS },
+	{"never",	FUSE_VDAX_NEVER },
+	{"inode",	FUSE_VDAX_INODE_USER },
 	{}
 };
 
@@ -132,10 +132,10 @@ static int virtio_fs_parse_param(struct fs_context *fsc,
 
 	switch (opt) {
 	case OPT_DAX:
-		ctx->dax_mode = FUSE_DAX_ALWAYS;
+		ctx->vdax_mode = FUSE_VDAX_ALWAYS;
 		break;
 	case OPT_DAX_ENUM:
-		ctx->dax_mode = result.uint_32;
+		ctx->vdax_mode = result.uint_32;
 		break;
 	default:
 		return -EINVAL;
@@ -1592,14 +1592,14 @@ static int virtio_fs_fill_super(struct super_block *sb, struct fs_context *fsc)
 			goto err_free_fuse_devs;
 	}
 
-	if (ctx->dax_mode != FUSE_DAX_NEVER) {
-		if (ctx->dax_mode == FUSE_DAX_ALWAYS && !fs->dax_dev) {
+	if (ctx->vdax_mode != FUSE_VDAX_NEVER) {
+		if (ctx->vdax_mode == FUSE_VDAX_ALWAYS && !fs->dax_dev) {
 			err = -EINVAL;
 			pr_err("virtio-fs: dax can't be enabled as filesystem"
 			       " device does not support it.\n");
 			goto err_free_fuse_devs;
 		}
-		ctx->dax_dev = fs->dax_dev;
+		ctx->vdax_dev = fs->dax_dev;
 	}
 	err = fuse_fill_super_common(sb, ctx);
 	if (err < 0)
@@ -1634,7 +1634,7 @@ static void virtio_fs_conn_destroy(struct fuse_mount *fm)
 	 * will free all memory ranges belonging to all inodes.
 	 */
 	if (IS_ENABLED(CONFIG_FUSE_DAX))
-		fuse_dax_cancel_work(fc);
+		fuse_vdax_cancel_work(fc);
 
 	/* Stop forget queue. Soon destroy will be sent */
 	spin_lock(&fsvq->lock);
-- 
2.54.0


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

* [PATCH 03/11] fuse: don't assume ff->passthrough is set for FOPEN_PASSTHROUGH
  2026-09-22  6:10 [PATCH 00/11] fuse: DAX device based extent maps (famfs) Miklos Szeredi
  2026-09-22  6:10 ` [PATCH 01/11] dax: replace exported dax_dev_get() with non-allocating dax_dev_find() Miklos Szeredi
  2026-09-22  6:10 ` [PATCH 02/11] fuse: use "vdax" naming for virtiofs DAX Miklos Szeredi
@ 2026-09-22  6:10 ` Miklos Szeredi
  2026-09-22  8:44   ` Amir Goldstein
  2026-09-22  6:10 ` [PATCH 04/11] fuse: make fuse_backing_get() static Miklos Szeredi
                   ` (7 subsequent siblings)
  10 siblings, 1 reply; 33+ messages in thread
From: Miklos Szeredi @ 2026-09-22  6:10 UTC (permalink / raw)
  To: fuse-devel; +Cc: John Groves, Amir Goldstein, Darrick J . Wong

Following patch will introduce extent map passthrough mode, where
ff->passthrough is not set.

Check if in passthrough mode via FOPEN_PASSTHROUGH instead.

No functional change.

Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>
---
 fs/fuse/file.c        | 12 ++++++------
 fs/fuse/fuse_i.h      |  8 ++------
 fs/fuse/passthrough.c | 13 +++++++++++--
 3 files changed, 19 insertions(+), 14 deletions(-)

diff --git a/fs/fuse/file.c b/fs/fuse/file.c
index 1eadeca0c47d..af26de78a199 100644
--- a/fs/fuse/file.c
+++ b/fs/fuse/file.c
@@ -311,7 +311,7 @@ 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))
+	if (fuse_is_passthrough(ff))
 		fuse_passthrough_release(ff, fuse_inode_backing(fi));
 
 	/* Inode is NULL on error path of fuse_create_open() */
@@ -1842,7 +1842,7 @@ static ssize_t fuse_file_read_iter(struct kiocb *iocb, struct iov_iter *to)
 	/* 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))
+	else if (fuse_is_passthrough(ff))
 		return fuse_passthrough_read_iter(iocb, to);
 	else
 		return fuse_cache_read_iter(iocb, to);
@@ -1863,7 +1863,7 @@ static ssize_t fuse_file_write_iter(struct kiocb *iocb, struct iov_iter *from)
 	/* 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))
+	else if (fuse_is_passthrough(ff))
 		return fuse_passthrough_write_iter(iocb, from);
 	else
 		return fuse_cache_write_iter(iocb, from);
@@ -1879,7 +1879,7 @@ static ssize_t fuse_splice_read(struct file *in, loff_t *ppos,
 
 	if (ff->open_flags & FOPEN_DIRECT_IO)
 		return copy_splice_read(in, ppos, pipe, len, flags);
-	else if (fuse_file_passthrough(ff))
+	else if (fuse_is_passthrough(ff))
 		return fuse_passthrough_splice_read(in, ppos, pipe, len, flags);
 	else
 		return filemap_splice_read(in, ppos, pipe, len, flags);
@@ -1891,7 +1891,7 @@ static ssize_t fuse_splice_write(struct pipe_inode_info *pipe, struct file *out,
 	struct fuse_file *ff = out->private_data;
 
 	/* FOPEN_DIRECT_IO overrides FOPEN_PASSTHROUGH */
-	if (fuse_file_passthrough(ff) && !(ff->open_flags & FOPEN_DIRECT_IO))
+	if (fuse_is_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);
@@ -2406,7 +2406,7 @@ static int fuse_file_mmap(struct file *file, struct vm_area_struct *vma)
 	 * 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))
+	if (fuse_is_passthrough(ff))
 		return fuse_passthrough_mmap(file, vma);
 	else if (fuse_inode_backing(get_fuse_inode(inode)))
 		return -ENODEV;
diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h
index 77ccd5b2059b..b7d8751ba2e0 100644
--- a/fs/fuse/fuse_i.h
+++ b/fs/fuse/fuse_i.h
@@ -1313,13 +1313,9 @@ static inline struct fuse_backing *fuse_inode_backing_set(struct fuse_inode *fi,
 struct fuse_backing *fuse_passthrough_open(struct file *file, 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)
+static inline bool fuse_is_passthrough(struct fuse_file *ff)
 {
-#ifdef CONFIG_FUSE_PASSTHROUGH
-	return ff->passthrough;
-#else
-	return NULL;
-#endif
+	return IS_ENABLED(CONFIG_FUSE_PASSTHROUGH) && (ff->open_flags & FOPEN_PASSTHROUGH);
 }
 
 ssize_t fuse_passthrough_read_iter(struct kiocb *iocb, struct iov_iter *iter);
diff --git a/fs/fuse/passthrough.c b/fs/fuse/passthrough.c
index f2d08ac2459b..b43d3e0f7081 100644
--- a/fs/fuse/passthrough.c
+++ b/fs/fuse/passthrough.c
@@ -11,6 +11,10 @@
 #include <linux/backing-file.h>
 #include <linux/splice.h>
 
+static inline struct file *fuse_file_passthrough(struct fuse_file *ff)
+{
+	return ff->passthrough;
+}
 static void fuse_file_accessed(struct file *file)
 {
 	struct inode *inode = file_inode(file);
@@ -187,10 +191,15 @@ struct fuse_backing *fuse_passthrough_open(struct file *file, int backing_id)
 
 void fuse_passthrough_release(struct fuse_file *ff, struct fuse_backing *fb)
 {
+	struct file *backing_file = fuse_file_passthrough(ff);
+
 	pr_debug("%s: fb=0x%p, backing_file=0x%p\n", __func__,
-		 fb, ff->passthrough);
+		 fb, backing_file);
+
+	if (!backing_file)
+		return;
 
-	fput(ff->passthrough);
+	fput(backing_file);
 	ff->passthrough = NULL;
 	put_cred(ff->cred);
 	ff->cred = NULL;
-- 
2.54.0


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

* [PATCH 04/11] fuse: make fuse_backing_get() static
  2026-09-22  6:10 [PATCH 00/11] fuse: DAX device based extent maps (famfs) Miklos Szeredi
                   ` (2 preceding siblings ...)
  2026-09-22  6:10 ` [PATCH 03/11] fuse: don't assume ff->passthrough is set for FOPEN_PASSTHROUGH Miklos Szeredi
@ 2026-09-22  6:10 ` Miklos Szeredi
  2026-09-22  8:44   ` Amir Goldstein
  2026-09-22  6:10 ` [PATCH 05/11] fuse: add helpers for EIO return value with kernel message Miklos Szeredi
                   ` (6 subsequent siblings)
  10 siblings, 1 reply; 33+ messages in thread
From: Miklos Szeredi @ 2026-09-22  6:10 UTC (permalink / raw)
  To: fuse-devel; +Cc: John Groves, Amir Goldstein, Darrick J . Wong

Since it's not used outside of backing.c.

Also remove the fuse_backing_lookup() stub from fuse_i.h, since it's called
only from passthrough.c.

Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>
---
 fs/fuse/backing.c |  2 +-
 fs/fuse/fuse_i.h  | 11 -----------
 2 files changed, 1 insertion(+), 12 deletions(-)

diff --git a/fs/fuse/backing.c b/fs/fuse/backing.c
index 472b6afa7dff..433fa3098d71 100644
--- a/fs/fuse/backing.c
+++ b/fs/fuse/backing.c
@@ -10,7 +10,7 @@
 
 #include <linux/file.h>
 
-struct fuse_backing *fuse_backing_get(struct fuse_backing *fb)
+static struct fuse_backing *fuse_backing_get(struct fuse_backing *fb)
 {
 	if (fb && refcount_inc_not_zero(&fb->count))
 		return fb;
diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h
index b7d8751ba2e0..6cd556a0aa59 100644
--- a/fs/fuse/fuse_i.h
+++ b/fs/fuse/fuse_i.h
@@ -1267,24 +1267,13 @@ void fuse_file_release(struct inode *inode, struct fuse_file *ff,
 
 /* backing.c */
 #ifdef CONFIG_FUSE_PASSTHROUGH
-struct fuse_backing *fuse_backing_get(struct fuse_backing *fb);
 void fuse_backing_put(struct fuse_backing *fb);
 struct fuse_backing *fuse_backing_lookup(struct fuse_conn *fc, int backing_id);
 #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)
 {
 }
-static inline struct fuse_backing *fuse_backing_lookup(struct fuse_conn *fc,
-						       int backing_id)
-{
-	return NULL;
-}
 #endif
 
 void fuse_backing_files_init(struct fuse_conn *fc);
-- 
2.54.0


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

* [PATCH 05/11] fuse: add helpers for EIO return value with kernel message
  2026-09-22  6:10 [PATCH 00/11] fuse: DAX device based extent maps (famfs) Miklos Szeredi
                   ` (3 preceding siblings ...)
  2026-09-22  6:10 ` [PATCH 04/11] fuse: make fuse_backing_get() static Miklos Szeredi
@ 2026-09-22  6:10 ` Miklos Szeredi
  2026-09-22  9:58   ` Amir Goldstein
  2026-09-22  6:10 ` [PATCH 06/11] fuse: support 64 bit, server allocated backing ID Miklos Szeredi
                   ` (5 subsequent siblings)
  10 siblings, 1 reply; 33+ messages in thread
From: Miklos Szeredi @ 2026-09-22  6:10 UTC (permalink / raw)
  To: fuse-devel; +Cc: John Groves, Amir Goldstein, Darrick J . Wong

Conditions are triggered by a buggy fuse server will result in -EIO which
is hard to interpret without any context.

Add helpers that print a short message to the kernel log as well as
returning the error value.  This serves a dual purpuse:

 - documents the error condition inside the code

 - allows the implementor of the fuse server to get the context for the
   error

Some debug messages are removed in favor of this.

Currently these use the pr_notice_once() variant.  Possibly should be
changed to a ratelimit of e.g. once per minute.

Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>
---
 fs/fuse/fuse_i.h      |  6 ++++++
 fs/fuse/iomode.c      | 26 ++++++++------------------
 fs/fuse/passthrough.c | 16 ++++------------
 3 files changed, 18 insertions(+), 30 deletions(-)

diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h
index 6cd556a0aa59..b698857dfe13 100644
--- a/fs/fuse/fuse_i.h
+++ b/fs/fuse/fuse_i.h
@@ -11,6 +11,12 @@
 # define pr_fmt(fmt) "fuse: " fmt
 #endif
 
+#define fuse_EIO(msg) (pr_notice_once("%s: %s\n", __func__, msg), -EIO)
+#define fuse_err_EIO(msg, err) (pr_notice_once("%s: %s (%d)\n", __func__, msg, (int) (err)), -EIO)
+
+#define fuse_ptr_EIO(msg) ERR_PTR(fuse_EIO(msg))
+#define fuse_err_ptr_EIO(msg, err) ERR_PTR(fuse_err_EIO(msg, err))
+
 #include "args.h"
 #include <linux/fuse.h>
 #include <linux/fs.h>
diff --git a/fs/fuse/iomode.c b/fs/fuse/iomode.c
index 79637c09e883..c25ac580c811 100644
--- a/fs/fuse/iomode.c
+++ b/fs/fuse/iomode.c
@@ -122,7 +122,7 @@ static int fuse_file_uncached_io_open(struct inode *inode,
 
 	err = fuse_inode_uncached_io_start(fi, fb);
 	if (err)
-		return err;
+		return fuse_err_EIO("failed to start uncached I/O", err);
 
 	WARN_ON(ff->iomode != IOM_NONE);
 	ff->iomode = IOM_UNCACHED;
@@ -173,9 +173,11 @@ static int fuse_file_passthrough_open(struct inode *inode, struct file *file)
 	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;
+	if (!IS_ENABLED(CONFIG_FUSE_PASSTHROUGH) || !fc->passthrough)
+		return fuse_EIO("passthrough not enabled");
+
+	if (ff->open_flags & ~FOPEN_PASSTHROUGH_MASK)
+		return fuse_EIO("conflicting open flags");
 
 	fb = fuse_passthrough_open(file, ff->args->open_outarg.backing_id);
 	if (IS_ERR(fb))
@@ -212,7 +214,7 @@ int fuse_file_io_open(struct file *file, struct inode *inode)
 	 */
 	err = -EINVAL;
 	if (fuse_inode_backing(fi) && !(ff->open_flags & FOPEN_PASSTHROUGH))
-		goto fail;
+		return fuse_EIO("FOPEN_PASSTHROUGH expected");
 
 	/*
 	 * FOPEN_PARALLEL_DIRECT_WRITES requires FOPEN_DIRECT_IO.
@@ -236,20 +238,8 @@ int fuse_file_io_open(struct file *file, struct inode *inode)
 		err = fuse_file_passthrough_open(inode, file);
 	else
 		err = fuse_file_cached_io_open(inode, ff);
-	if (err)
-		goto fail;
-
-	return 0;
 
-fail:
-	pr_debug("failed to open file in requested io mode (open_flags=0x%x, err=%i).\n",
-		 ff->open_flags, err);
-	/*
-	 * The file open mode determines the inode io mode.
-	 * Using incorrect open mode is a server mistake, which results in
-	 * user visible failure of open() with EIO error.
-	 */
-	return -EIO;
+	return err;
 }
 
 /* No more pending io and no new io possible to inode via open/mmapped file */
diff --git a/fs/fuse/passthrough.c b/fs/fuse/passthrough.c
index b43d3e0f7081..0e2e1066b984 100644
--- a/fs/fuse/passthrough.c
+++ b/fs/fuse/passthrough.c
@@ -159,34 +159,26 @@ struct fuse_backing *fuse_passthrough_open(struct file *file, int backing_id)
 	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;
+		return fuse_ptr_EIO("invalid backing_id");
 
-	err = -ENOENT;
 	fb = fuse_backing_lookup(fc, backing_id);
 	if (!fb)
-		goto out;
+		return fuse_ptr_EIO("backing not found");
 
 	/* 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);
-	err = PTR_ERR(backing_file);
 	if (IS_ERR(backing_file)) {
 		fuse_backing_put(fb);
-		goto out;
+		return fuse_err_ptr_EIO("failed to open backing file", PTR_ERR(backing_file));
 	}
 
-	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;
+	return fb;
 }
 
 void fuse_passthrough_release(struct fuse_file *ff, struct fuse_backing *fb)
-- 
2.54.0


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

* [PATCH 06/11] fuse: support 64 bit, server allocated backing ID
  2026-09-22  6:10 [PATCH 00/11] fuse: DAX device based extent maps (famfs) Miklos Szeredi
                   ` (4 preceding siblings ...)
  2026-09-22  6:10 ` [PATCH 05/11] fuse: add helpers for EIO return value with kernel message Miklos Szeredi
@ 2026-09-22  6:10 ` Miklos Szeredi
  2026-09-22 10:29   ` Amir Goldstein
  2026-09-23  7:08   ` Amir Goldstein
  2026-09-22  6:10 ` [PATCH 07/11] fuse: support opening 64 bit " Miklos Szeredi
                   ` (4 subsequent siblings)
  10 siblings, 2 replies; 33+ messages in thread
From: Miklos Szeredi @ 2026-09-22  6:10 UTC (permalink / raw)
  To: fuse-devel; +Cc: John Groves, Amir Goldstein, Darrick J . Wong

Add support for server allocated 64-bit backing IDs alongside the
existing kernel allocated 32-bit IDs.

Opened with FUSE_DEV_IOC_BACKING_OPEN, but the backing ID is sent via
fuse_backing_map.backing_id, with FUSE_BACKING_ID_64 being set in .flags.

Closed with FUSE_NOTIFY_BACKING_CLOSE.  Since close only provides the
backing ID, not the file descriptor, it doesn't have to be done with an
ioctl.

All 64 bit values are valid.

Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>
---
 fs/fuse/backing.c         | 121 +++++++++++++++++++++++++-------------
 fs/fuse/dev.c             |   2 +-
 fs/fuse/dev.h             |   2 +-
 fs/fuse/fuse_i.h          |  13 +++-
 fs/fuse/notify.c          |  23 ++++++++
 fs/fuse/passthrough.c     |   2 +-
 include/uapi/linux/fuse.h |  19 +++++-
 7 files changed, 133 insertions(+), 49 deletions(-)

diff --git a/fs/fuse/backing.c b/fs/fuse/backing.c
index 433fa3098d71..58dbdd17c1ef 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/rhashtable.h>
 
 static struct fuse_backing *fuse_backing_get(struct fuse_backing *fb)
 {
@@ -33,11 +34,6 @@ void fuse_backing_put(struct fuse_backing *fb)
 		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;
@@ -53,38 +49,42 @@ static int fuse_backing_id_alloc(struct fuse_conn *fc, struct fuse_backing *fb)
 	return id;
 }
 
-static struct fuse_backing *fuse_backing_id_remove(struct fuse_conn *fc,
-						   int id)
-{
-	struct fuse_backing *fb;
+static const struct rhashtable_params fuse_backing_params = {
+	.head_offset = offsetof(struct fuse_backing, hash_node),
+	.key_offset = offsetof(struct fuse_backing, backing_id),
+	.key_len = sizeof_field(struct fuse_backing, backing_id),
+};
 
-	spin_lock(&fc->lock);
-	fb = idr_remove(&fc->backing_files_map, id);
-	spin_unlock(&fc->lock);
-
-	return fb;
+int fuse_backing_add_64(struct fuse_conn *fc, struct fuse_backing *fb)
+{
+	return rhashtable_insert_fast(&fc->backing_64_ht, &fb->hash_node, fuse_backing_params);
 }
 
-static int fuse_backing_id_free(int id, void *p, void *data)
+static struct fuse_backing *fuse_backing_id_remove(struct fuse_conn *fc, u64 id, bool is_64bit)
 {
-	struct fuse_backing *fb = p;
+	struct fuse_backing *fb;
+	int err;
 
-	WARN_ON_ONCE(refcount_read(&fb->count) != 1);
-	fuse_backing_free(fb);
-	return 0;
-}
+	guard(spinlock)(&fc->lock);
+	if (!is_64bit)
+		return idr_remove(&fc->backing_files_map, id);
 
-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);
+	fb = rhashtable_lookup_fast(&fc->backing_64_ht, &id, fuse_backing_params);
+	if (!fb)
+		return NULL;
+
+	err = rhashtable_remove_fast(&fc->backing_64_ht, &fb->hash_node, fuse_backing_params);
+	WARN_ON(err);
+
+	return fb;
 }
 
 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;
+	struct fuse_backing *fb;
+	bool is_64bit = map->flags & FUSE_BACKING_ID_64;
 	int res;
 
 	pr_debug("%s: fd=%d flags=0x%x\n", __func__, map->fd, map->flags);
@@ -95,7 +95,10 @@ int fuse_backing_open(struct fuse_conn *fc, struct fuse_backing_map *map)
 		goto out;
 
 	res = -EINVAL;
-	if (map->flags || map->padding)
+	if (map->flags & ~FUSE_BACKING_ID_64)
+		goto out;
+
+	if (!is_64bit && map->backing_id != 0)
 		goto out;
 
 	file = fget_raw(map->fd);
@@ -120,9 +123,13 @@ int fuse_backing_open(struct fuse_conn *fc, struct fuse_backing_map *map)
 
 	fb->file = file;
 	fb->cred = get_current_cred();
+	fb->backing_id = map->backing_id;
 	refcount_set(&fb->count, 1);
 
-	res = fuse_backing_id_alloc(fc, fb);
+	if (is_64bit)
+		res = fuse_backing_add_64(fc, fb);
+	else
+		res = fuse_backing_id_alloc(fc, fb);
 	if (res < 0) {
 		fuse_backing_free(fb);
 		fb = NULL;
@@ -138,24 +145,19 @@ int fuse_backing_open(struct fuse_conn *fc, struct fuse_backing_map *map)
 	goto out;
 }
 
-int fuse_backing_close(struct fuse_conn *fc, int backing_id)
+int fuse_backing_close(struct fuse_conn *fc, u64 backing_id, bool is_64bit)
 {
 	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;
+	pr_debug("%s: backing_id=%lld\n", __func__, backing_id);
 
 	err = -EINVAL;
-	if (backing_id <= 0)
+	if (!is_64bit && (backing_id == 0 || backing_id > INT_MAX))
 		goto out;
 
 	err = -ENOENT;
-	fb = fuse_backing_id_remove(fc, backing_id);
+	fb = fuse_backing_id_remove(fc, backing_id, is_64bit);
 	if (!fb)
 		goto out;
 
@@ -167,14 +169,49 @@ int fuse_backing_close(struct fuse_conn *fc, int backing_id)
 	return err;
 }
 
-struct fuse_backing *fuse_backing_lookup(struct fuse_conn *fc, int backing_id)
+struct fuse_backing *fuse_backing_lookup(struct fuse_conn *fc, u64 backing_id, bool is_64bit)
 {
 	struct fuse_backing *fb;
 
-	rcu_read_lock();
-	fb = idr_find(&fc->backing_files_map, backing_id);
-	fb = fuse_backing_get(fb);
-	rcu_read_unlock();
+	guard(rcu)();
+	if (!is_64bit)
+		fb = idr_find(&fc->backing_files_map, backing_id);
+	else
+		fb = rhashtable_lookup(&fc->backing_64_ht, &backing_id, fuse_backing_params);
 
-	return fb;
+	return fuse_backing_get(fb);
+}
+
+static void fuse_backing_check_free(struct fuse_backing *fb)
+{
+	WARN_ON_ONCE(refcount_read(&fb->count) != 1);
+	fuse_backing_free(fb);
+}
+
+static int fuse_backing_idr_free(int id, void *p, void *data)
+{
+	fuse_backing_check_free(p);
+	return 0;
+}
+
+static void fuse_backing_rht_free(void *p, void *data)
+{
+	fuse_backing_check_free(p);
+}
+
+void fuse_backing_files_free(struct fuse_conn *fc)
+{
+	idr_for_each(&fc->backing_files_map, fuse_backing_idr_free, NULL);
+	idr_destroy(&fc->backing_files_map);
+
+	rhashtable_free_and_destroy(&fc->backing_64_ht, fuse_backing_rht_free, NULL);
+}
+
+void fuse_backing_files_init(struct fuse_conn *fc)
+{
+	int err;
+
+	idr_init(&fc->backing_files_map);
+	err = rhashtable_init(&fc->backing_64_ht, &fuse_backing_params);
+	WARN_ON(err); /* fails on programming error only */
 }
diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c
index 4fec31fc0b84..c6361c914b35 100644
--- a/fs/fuse/dev.c
+++ b/fs/fuse/dev.c
@@ -2357,7 +2357,7 @@ static long fuse_dev_ioctl_backing_close(struct file *file, __u32 __user *argp)
 	if (get_user(backing_id, argp))
 		return -EFAULT;
 
-	return fuse_backing_close(fud->chan->conn, backing_id);
+	return fuse_backing_close(fud->chan->conn, backing_id, false);
 }
 
 static long fuse_dev_ioctl_sync_init(struct file *file)
diff --git a/fs/fuse/dev.h b/fs/fuse/dev.h
index 8d25378c0918..6f2f6c3822e1 100644
--- a/fs/fuse/dev.h
+++ b/fs/fuse/dev.h
@@ -86,7 +86,7 @@ int fuse_notify(struct fuse_conn *fc, enum fuse_notify_code code,
 		unsigned int size, struct fuse_copy_state *cs);
 
 int fuse_backing_open(struct fuse_conn *fc, struct fuse_backing_map *map);
-int fuse_backing_close(struct fuse_conn *fc, int backing_id);
+int fuse_backing_close(struct fuse_conn *fc, u64 backing_id, bool is_64bit);
 
 int fuse_copy_one(struct fuse_copy_state *cs, void *val, unsigned size);
 int fuse_copy_folio(struct fuse_copy_state *cs, struct folio **foliop,
diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h
index b698857dfe13..75c7997098c1 100644
--- a/fs/fuse/fuse_i.h
+++ b/fs/fuse/fuse_i.h
@@ -28,7 +28,7 @@
 #include <linux/backing-dev.h>
 #include <linux/mutex.h>
 #include <linux/rwsem.h>
-#include <linux/rbtree.h>
+#include <linux/rbtree_types.h>
 #include <linux/poll.h>
 #include <linux/workqueue.h>
 #include <linux/kref.h>
@@ -36,6 +36,7 @@
 #include <linux/pid_namespace.h>
 #include <linux/refcount.h>
 #include <linux/user_namespace.h>
+#include <linux/rhashtable-types.h>
 
 /** Default max number of pages that can be used in a single read request */
 #define FUSE_DEFAULT_MAX_PAGES_PER_REQ 32
@@ -96,7 +97,8 @@ struct fuse_submount_lookup {
 struct fuse_backing {
 	struct file *file;
 	const struct cred *cred;
-
+	u64 backing_id;
+	struct rhash_head hash_node;
 	/* refcount */
 	refcount_t count;
 	struct rcu_head rcu;
@@ -776,6 +778,9 @@ struct fuse_conn {
 #ifdef CONFIG_FUSE_PASSTHROUGH
 	/** @backing_files_map: IDR for backing files ids */
 	struct idr backing_files_map;
+
+	/** @backing_64_ht: 64 bit ID lookup hash table */
+	struct rhashtable backing_64_ht;
 #endif
 };
 
@@ -1274,7 +1279,9 @@ void fuse_file_release(struct inode *inode, struct fuse_file *ff,
 /* backing.c */
 #ifdef CONFIG_FUSE_PASSTHROUGH
 void fuse_backing_put(struct fuse_backing *fb);
-struct fuse_backing *fuse_backing_lookup(struct fuse_conn *fc, int backing_id);
+
+struct fuse_backing *fuse_backing_lookup(struct fuse_conn *fc, u64 backing_id, bool is_64bit);
+int fuse_backing_add_64(struct fuse_conn *fc, struct fuse_backing *fb);
 #else
 
 static inline void fuse_backing_put(struct fuse_backing *fb)
diff --git a/fs/fuse/notify.c b/fs/fuse/notify.c
index 1ba763705d91..7361b56bb973 100644
--- a/fs/fuse/notify.c
+++ b/fs/fuse/notify.c
@@ -409,6 +409,26 @@ static int fuse_notify_prune(struct fuse_conn *fc, unsigned int size,
 	return 0;
 }
 
+static int fuse_notify_backing_close(struct fuse_conn *fc, unsigned int size,
+				     struct fuse_copy_state *cs)
+{
+	struct fuse_notify_backing_close_out outarg;
+	int err;
+
+	if (size != sizeof(outarg))
+		return -EINVAL;
+
+	err = fuse_copy_one(cs, &outarg, sizeof(outarg));
+	if (err)
+		return err;
+
+	if (outarg.reserved)
+		return -EINVAL;
+
+	return fuse_backing_close(fc, outarg.backing_id, true);
+
+}
+
 int fuse_notify(struct fuse_conn *fc, enum fuse_notify_code code,
 		unsigned int size, struct fuse_copy_state *cs)
 {
@@ -440,6 +460,9 @@ int fuse_notify(struct fuse_conn *fc, enum fuse_notify_code code,
 	case FUSE_NOTIFY_PRUNE:
 		return fuse_notify_prune(fc, size, cs);
 
+	case FUSE_NOTIFY_BACKING_CLOSE:
+		return fuse_notify_backing_close(fc, size, cs);
+
 	default:
 		return -EINVAL;
 	}
diff --git a/fs/fuse/passthrough.c b/fs/fuse/passthrough.c
index 0e2e1066b984..56314dea0b5f 100644
--- a/fs/fuse/passthrough.c
+++ b/fs/fuse/passthrough.c
@@ -163,7 +163,7 @@ struct fuse_backing *fuse_passthrough_open(struct file *file, int backing_id)
 	if (backing_id <= 0)
 		return fuse_ptr_EIO("invalid backing_id");
 
-	fb = fuse_backing_lookup(fc, backing_id);
+	fb = fuse_backing_lookup(fc, backing_id, false);
 	if (!fb)
 		return fuse_ptr_EIO("backing not found");
 
diff --git a/include/uapi/linux/fuse.h b/include/uapi/linux/fuse.h
index 10a7f31c4bdf..f271faa60424 100644
--- a/include/uapi/linux/fuse.h
+++ b/include/uapi/linux/fuse.h
@@ -251,6 +251,10 @@
  *
  *  7.47
  *  - add FUSE_HAS_SYNCFS opt-in flag for privileged userspace servers
+ *  - add FUSE_NOTIFY_BACKING_CLOSE
+ *  - add struct fuse_notify_backing_close_out
+ *  - add backing_id to fuse_backing_map
+ *  - add FUSE_BACKING_ID_64 (multiple structs)
  */
 
 #ifndef _LINUX_FUSE_H
@@ -709,6 +713,7 @@ enum fuse_notify_code {
 	FUSE_NOTIFY_RESEND = 7,
 	FUSE_NOTIFY_INC_EPOCH = 8,
 	FUSE_NOTIFY_PRUNE = 9,
+	FUSE_NOTIFY_BACKING_CLOSE = 10,
 };
 
 /* The read buffer is required to be at least 8k, but may be much larger */
@@ -1153,10 +1158,17 @@ struct fuse_notify_prune_out {
 	uint64_t	spare;
 };
 
+/**
+ * flags for fuse_backing_map
+ *
+ * FUSE_BACKING_ID_64: backing ID is server allocated, stored in @backing_id
+ */
+#define FUSE_BACKING_ID_64	(1 << 30) /* used in multiple structs */
+
 struct fuse_backing_map {
 	int32_t		fd;
 	uint32_t	flags;
-	uint64_t	padding;
+	uint64_t	backing_id;
 };
 
 /* Device ioctls: */
@@ -1193,6 +1205,11 @@ struct fuse_copy_file_range_out {
 	uint64_t	bytes_copied;
 };
 
+struct fuse_notify_backing_close_out {
+	uint64_t	backing_id;
+	uint64_t	reserved;
+};
+
 #define FUSE_SETUPMAPPING_FLAG_WRITE (1ull << 0)
 #define FUSE_SETUPMAPPING_FLAG_READ (1ull << 1)
 struct fuse_setupmapping_in {
-- 
2.54.0


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

* [PATCH 07/11] fuse: support opening 64 bit backing ID
  2026-09-22  6:10 [PATCH 00/11] fuse: DAX device based extent maps (famfs) Miklos Szeredi
                   ` (5 preceding siblings ...)
  2026-09-22  6:10 ` [PATCH 06/11] fuse: support 64 bit, server allocated backing ID Miklos Szeredi
@ 2026-09-22  6:10 ` Miklos Szeredi
  2026-09-22 10:33   ` Amir Goldstein
  2026-09-22  6:10 ` [PATCH 08/11] fuse: add support for opening dax device as backing Miklos Szeredi
                   ` (3 subsequent siblings)
  10 siblings, 1 reply; 33+ messages in thread
From: Miklos Szeredi @ 2026-09-22  6:10 UTC (permalink / raw)
  To: fuse-devel; +Cc: John Groves, Amir Goldstein, Darrick J . Wong

Add backing_id_64 field to fuse_open_out to allow opening files with
64-bit server-allocated backing IDs.

When the server sets FUSE_BACKING_ID_64 in open_out.open_flags, the
kernel reads the backing ID from open_out.backing_id_64 instead of
open_out.backing_id.

The open reply is now variable-length for backward compatibility with
servers that don't send the new backing_id_64 field.

Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>
---
 fs/fuse/dir.c             |  3 ++-
 fs/fuse/file.c            |  6 +++++-
 fs/fuse/fuse_i.h          |  4 ++--
 fs/fuse/iomode.c          | 11 ++++++++++-
 fs/fuse/passthrough.c     |  6 +++---
 include/uapi/linux/fuse.h |  4 ++++
 6 files changed, 26 insertions(+), 8 deletions(-)

diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c
index f48fafccce4b..874ec7cfffb1 100644
--- a/fs/fuse/dir.c
+++ b/fs/fuse/dir.c
@@ -876,6 +876,7 @@ static int fuse_create_open(struct mnt_idmap *idmap, struct inode *dir,
 	args.out_args[0].value = &outentry;
 	/* Store outarg for fuse_finish_open() */
 	outopenp = &ff->args->open_outarg;
+	args.out_argvar = true; /* compat */
 	args.out_args[1].size = sizeof(*outopenp);
 	args.out_args[1].value = outopenp;
 
@@ -885,7 +886,7 @@ static int fuse_create_open(struct mnt_idmap *idmap, struct inode *dir,
 
 	err = fuse_simple_idmap_request(idmap, fm, &args);
 	free_ext_value(&args);
-	if (err)
+	if (err < 0)
 		goto out_free_ff;
 
 	err = -EIO;
diff --git a/fs/fuse/file.c b/fs/fuse/file.c
index af26de78a199..8643d9a290ac 100644
--- a/fs/fuse/file.c
+++ b/fs/fuse/file.c
@@ -28,6 +28,7 @@ static int fuse_send_open(struct fuse_mount *fm, u64 nodeid,
 {
 	struct fuse_open_in inarg;
 	FUSE_ARGS(args);
+	int res;
 
 	memset(&inarg, 0, sizeof(inarg));
 	inarg.flags = open_flags & ~(O_CREAT | O_EXCL | O_NOCTTY);
@@ -45,10 +46,13 @@ static int fuse_send_open(struct fuse_mount *fm, u64 nodeid,
 	args.in_args[0].size = sizeof(inarg);
 	args.in_args[0].value = &inarg;
 	args.out_numargs = 1;
+	args.out_argvar = true; /* compat */
 	args.out_args[0].size = sizeof(*outargp);
 	args.out_args[0].value = outargp;
 
-	return fuse_simple_request(fm, &args);
+	res = fuse_simple_request(fm, &args);
+
+	return res < 0 ? res : 0;
 }
 
 struct fuse_file *fuse_file_alloc(struct fuse_mount *fm, bool release)
diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h
index 75c7997098c1..d36459487bee 100644
--- a/fs/fuse/fuse_i.h
+++ b/fs/fuse/fuse_i.h
@@ -28,7 +28,7 @@
 #include <linux/backing-dev.h>
 #include <linux/mutex.h>
 #include <linux/rwsem.h>
-#include <linux/rbtree_types.h>
+#include <linux/rbtree.h>
 #include <linux/poll.h>
 #include <linux/workqueue.h>
 #include <linux/kref.h>
@@ -1312,7 +1312,7 @@ static inline struct fuse_backing *fuse_inode_backing_set(struct fuse_inode *fi,
 #endif
 }
 
-struct fuse_backing *fuse_passthrough_open(struct file *file, int backing_id);
+struct fuse_backing *fuse_passthrough_open(struct file *file, u64 backing_id, bool is_64bit);
 void fuse_passthrough_release(struct fuse_file *ff, struct fuse_backing *fb);
 
 static inline bool fuse_is_passthrough(struct fuse_file *ff)
diff --git a/fs/fuse/iomode.c b/fs/fuse/iomode.c
index c25ac580c811..53b0c2a32ed9 100644
--- a/fs/fuse/iomode.c
+++ b/fs/fuse/iomode.c
@@ -170,8 +170,12 @@ static int fuse_file_passthrough_open(struct inode *inode, struct file *file)
 	struct fuse_file *ff = file->private_data;
 	struct fuse_conn *fc = get_fuse_conn(inode);
 	struct fuse_backing *fb;
+	u64 backing_id;
+	bool is_64bit = ff->open_flags & FUSE_BACKING_ID_64;
 	int err;
 
+	ff->open_flags &= ~FUSE_BACKING_ID_64;
+
 	/* Check allowed conditions for file open in passthrough mode */
 	if (!IS_ENABLED(CONFIG_FUSE_PASSTHROUGH) || !fc->passthrough)
 		return fuse_EIO("passthrough not enabled");
@@ -179,7 +183,12 @@ static int fuse_file_passthrough_open(struct inode *inode, struct file *file)
 	if (ff->open_flags & ~FOPEN_PASSTHROUGH_MASK)
 		return fuse_EIO("conflicting open flags");
 
-	fb = fuse_passthrough_open(file, ff->args->open_outarg.backing_id);
+	if (!is_64bit)
+		backing_id = ff->args->open_outarg.backing_id;
+	else
+		backing_id = ff->args->open_outarg.backing_id_64;
+
+	fb = fuse_passthrough_open(file, backing_id, is_64bit);
 	if (IS_ERR(fb))
 		return PTR_ERR(fb);
 
diff --git a/fs/fuse/passthrough.c b/fs/fuse/passthrough.c
index 56314dea0b5f..49cae366180d 100644
--- a/fs/fuse/passthrough.c
+++ b/fs/fuse/passthrough.c
@@ -153,17 +153,17 @@ ssize_t fuse_passthrough_mmap(struct file *file, struct vm_area_struct *vma)
  *
  * Returns an fb object with elevated refcount to be stored in fuse inode.
  */
-struct fuse_backing *fuse_passthrough_open(struct file *file, int backing_id)
+struct fuse_backing *fuse_passthrough_open(struct file *file, u64 backing_id, bool is_64bit)
 {
 	struct fuse_file *ff = file->private_data;
 	struct fuse_conn *fc = ff->fm->fc;
 	struct fuse_backing *fb = NULL;
 	struct file *backing_file;
 
-	if (backing_id <= 0)
+	if (!is_64bit && (backing_id == 0 || backing_id > INT_MAX))
 		return fuse_ptr_EIO("invalid backing_id");
 
-	fb = fuse_backing_lookup(fc, backing_id, false);
+	fb = fuse_backing_lookup(fc, backing_id, is_64bit);
 	if (!fb)
 		return fuse_ptr_EIO("backing not found");
 
diff --git a/include/uapi/linux/fuse.h b/include/uapi/linux/fuse.h
index f271faa60424..30ec8c124c74 100644
--- a/include/uapi/linux/fuse.h
+++ b/include/uapi/linux/fuse.h
@@ -253,6 +253,7 @@
  *  - add FUSE_HAS_SYNCFS opt-in flag for privileged userspace servers
  *  - add FUSE_NOTIFY_BACKING_CLOSE
  *  - add struct fuse_notify_backing_close_out
+ *  - add backing_id_64 to fuse_open_out
  *  - add backing_id to fuse_backing_map
  *  - add FUSE_BACKING_ID_64 (multiple structs)
  */
@@ -404,6 +405,7 @@ struct fuse_file_lock {
  *                           (FUSE_URING_ZERO_COPY) and the request carries page
  *                           payload. Otherwise reads/writes fall back to
  *                           copying.
+ * FUSE_BACKING_ID_64: backing ID is server allocated, stored in open_out.backing_id_64
  */
 #define FOPEN_DIRECT_IO		(1 << 0)
 #define FOPEN_KEEP_CACHE	(1 << 1)
@@ -414,6 +416,7 @@ struct fuse_file_lock {
 #define FOPEN_PARALLEL_DIRECT_WRITES	(1 << 6)
 #define FOPEN_PASSTHROUGH	(1 << 7)
 #define FOPEN_IO_URING_ZERO_COPY (1 << 8)
+#define FUSE_BACKING_ID_64	(1 << 30) /* used in multiple structs */
 
 /**
  * INIT request/reply flags
@@ -840,6 +843,7 @@ struct fuse_open_out {
 	uint64_t	fh;
 	uint32_t	open_flags;
 	int32_t		backing_id;
+	uint64_t	backing_id_64;
 };
 
 struct fuse_release_in {
-- 
2.54.0


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

* [PATCH 08/11] fuse: add support for opening dax device as backing
  2026-09-22  6:10 [PATCH 00/11] fuse: DAX device based extent maps (famfs) Miklos Szeredi
                   ` (6 preceding siblings ...)
  2026-09-22  6:10 ` [PATCH 07/11] fuse: support opening 64 bit " Miklos Szeredi
@ 2026-09-22  6:10 ` Miklos Szeredi
  2026-09-22 11:00   ` Amir Goldstein
  2026-09-22  6:10 ` [PATCH 09/11] fuse: add extent map data structure Miklos Szeredi
                   ` (2 subsequent siblings)
  10 siblings, 1 reply; 33+ messages in thread
From: Miklos Szeredi @ 2026-09-22  6:10 UTC (permalink / raw)
  To: fuse-devel; +Cc: John Groves, Amir Goldstein, Darrick J . Wong

Add FUSE_BACKING_IS_DEV flag that allows opening a character device
(dax device) as a backing, in addition to regular files.

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 <mszeredi@redhat.com>
---
 fs/fuse/backing.c         | 131 ++++++++++++++++++++++++++------------
 fs/fuse/file.c            |   2 +-
 fs/fuse/fuse_i.h          |  28 +++++++-
 fs/fuse/inode.c           |  11 +++-
 fs/fuse/passthrough.c     |   8 ++-
 include/uapi/linux/fuse.h |   3 +
 6 files changed, 136 insertions(+), 47 deletions(-)

diff --git a/fs/fuse/backing.c b/fs/fuse/backing.c
index 58dbdd17c1ef..aa558a0c2e64 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);
 }
 
@@ -79,23 +87,88 @@ static struct fuse_backing *fuse_backing_id_remove(struct fuse_conn *fc, u64 id,
 	return fb;
 }
 
+static int fuse_dax_notify_failure(struct dax_device *daxdev, u64 offset, u64 len, int mf_flags)
+{
+	struct fuse_backing *fb = dax_holder(daxdev);
+
+	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,
+				  bool is_dev)
+{
+	struct inode *inode = file_inode(file);
+	struct dax_device *daxdev;
+	int err;
+
+	switch (inode->i_mode & S_IFMT) {
+	case S_IFREG:
+		if (is_dev)
+			return -EINVAL;
+		/* 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 (!is_dev)
+			return -EINVAL;
+		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;
+
+	case S_IFDIR:
+		if (is_dev)
+			return -EINVAL;
+		return -EISDIR;
+
+	default:
+		return -EINVAL;
+	}
+}
+
 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;
+	struct fuse_backing *fb = kzalloc_obj(struct fuse_backing);
 	bool is_64bit = map->flags & FUSE_BACKING_ID_64;
 	int res;
 
-	pr_debug("%s: fd=%d flags=0x%x\n", __func__, map->fd, map->flags);
+	if (!fb)
+		return -ENOMEM;
 
-	/* TODO: relax CAP_SYS_ADMIN once backing files are visible to lsof */
-	res = -EPERM;
-	if (!fc->passthrough || !capable(CAP_SYS_ADMIN))
-		goto out;
+	fb->backing_id = map->backing_id;
+	refcount_set(&fb->count, 1);
+
+	pr_debug("%s: fd=%d flags=0x%x\n", __func__, map->fd, map->flags);
 
 	res = -EINVAL;
-	if (map->flags & ~FUSE_BACKING_ID_64)
+	if (map->flags & ~(FUSE_BACKING_IS_DEV | FUSE_BACKING_ID_64))
 		goto out;
 
 	if (!is_64bit && map->backing_id != 0)
@@ -106,43 +179,21 @@ int fuse_backing_open(struct fuse_conn *fc, struct fuse_backing_map *map)
 	if (!file)
 		goto out;
 
-	/* read/write/splice/mmap passthrough only relevant for regular files */
-	res = d_is_dir(file->f_path.dentry) ? -EISDIR : -EINVAL;
-	if (!d_is_reg(file->f_path.dentry))
-		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_obj(struct fuse_backing);
-	res = -ENOMEM;
-	if (!fb)
-		goto out_fput;
-
-	fb->file = file;
-	fb->cred = get_current_cred();
-	fb->backing_id = map->backing_id;
-	refcount_set(&fb->count, 1);
+	res = fuse_backing_open_file(fc, fb, file, map->flags & FUSE_BACKING_IS_DEV);
+	fput(file);
+	if (res)
+		goto out;
 
 	if (is_64bit)
 		res = fuse_backing_add_64(fc, fb);
 	else
 		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);
+	pr_debug("%s: ret=%i\n", __func__, res);
+	if (res < 0)
+		fuse_backing_free(fb);
 
 	return res;
-
-out_fput:
-	fput(file);
-	goto out;
 }
 
 int fuse_backing_close(struct fuse_conn *fc, u64 backing_id, bool is_64bit)
diff --git a/fs/fuse/file.c b/fs/fuse/file.c
index 8643d9a290ac..ea33b8d60473 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 d36459487bee..00c124bcb88d 100644
--- a/fs/fuse/fuse_i.h
+++ b/fs/fuse/fuse_i.h
@@ -93,10 +93,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 */
@@ -1237,7 +1252,14 @@ void fuse_free_conn(struct fuse_conn *fc);
 
 /* dax.c */
 
-#define FUSE_IS_VDAX(inode) (IS_ENABLED(CONFIG_FUSE_DAX) && IS_DAX(inode))
+static inline bool FUSE_IS_VDAX(struct inode *inode)
+{
+#ifdef CONFIG_FUSE_DAX
+	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 e88daf88f913..e6634a660083 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) {
+		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 49cae366180d..adc9d2d8e203 100644
--- a/fs/fuse/passthrough.c
+++ b/fs/fuse/passthrough.c
@@ -167,9 +167,13 @@ struct fuse_backing *fuse_passthrough_open(struct file *file, u64 backing_id, bo
 	if (!fb)
 		return fuse_ptr_EIO("backing not found");
 
+	if (fb->type != FUSE_BACKING_PATH) {
+		fuse_backing_put(fb);
+		return fuse_ptr_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)) {
 		fuse_backing_put(fb);
 		return fuse_err_ptr_EIO("failed to open backing file", PTR_ERR(backing_file));
diff --git a/include/uapi/linux/fuse.h b/include/uapi/linux/fuse.h
index 30ec8c124c74..1ba28a2885ad 100644
--- a/include/uapi/linux/fuse.h
+++ b/include/uapi/linux/fuse.h
@@ -255,6 +255,7 @@
  *  - add struct fuse_notify_backing_close_out
  *  - add backing_id_64 to fuse_open_out
  *  - add backing_id to fuse_backing_map
+ *  - add FUSE_BACKING_IS_DEV (fuse_backing_map.flags)
  *  - add FUSE_BACKING_ID_64 (multiple structs)
  */
 
@@ -1165,8 +1166,10 @@ struct fuse_notify_prune_out {
 /**
  * flags for fuse_backing_map
  *
+ * FUSE_BACKING_IS_DEV: @fd refers to a device file
  * FUSE_BACKING_ID_64: backing ID is server allocated, stored in @backing_id
  */
+#define FUSE_BACKING_IS_DEV	(1 << 0)
 #define FUSE_BACKING_ID_64	(1 << 30) /* used in multiple structs */
 
 struct fuse_backing_map {
-- 
2.54.0


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

* [PATCH 09/11] fuse: add extent map data structure
  2026-09-22  6:10 [PATCH 00/11] fuse: DAX device based extent maps (famfs) Miklos Szeredi
                   ` (7 preceding siblings ...)
  2026-09-22  6:10 ` [PATCH 08/11] fuse: add support for opening dax device as backing Miklos Szeredi
@ 2026-09-22  6:10 ` Miklos Szeredi
  2026-09-22 12:57   ` Amir Goldstein
  2026-09-22  6:10 ` [PATCH 10/11] fuse: add extent map I/O support Miklos Szeredi
  2026-09-22  6:10 ` [PATCH 11/11] fuse: add support for striped backing Miklos Szeredi
  10 siblings, 1 reply; 33+ messages in thread
From: Miklos Szeredi @ 2026-09-22  6:10 UTC (permalink / raw)
  To: fuse-devel; +Cc: John Groves, Amir Goldstein, Darrick J . Wong

Add support for creating and managing extent maps that map file regions to
dax device regions.

Introduce FUSE_NOTIFY_MAP for populating extent maps from userspace.

Extent maps are handled as a new type of backing and are assigned a 64 bit
backing ID by the server.

One extent consists of

 - offset within the containing backing
 - length of extent
 - target backing ID
 - offset into target backing

Extents must be non-overlapping, and can only refer to dax device backings
for now.

At this point only support creating and populating the extent map in one
operation, though the interface is not limited by this and later may be
changed, so that partial population of an extent map would be possible.

Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>
---
 fs/fuse/Makefile          |   2 +-
 fs/fuse/backing.c         |  23 +++++++
 fs/fuse/ext_map.c         | 129 ++++++++++++++++++++++++++++++++++++++
 fs/fuse/fuse_i.h          |  12 +++-
 fs/fuse/notify.c          |  41 ++++++++++++
 include/uapi/linux/fuse.h |  30 ++++++++-
 6 files changed, 234 insertions(+), 3 deletions(-)
 create mode 100644 fs/fuse/ext_map.c

diff --git a/fs/fuse/Makefile b/fs/fuse/Makefile
index 245e67852b03..139f37071a78 100644
--- a/fs/fuse/Makefile
+++ b/fs/fuse/Makefile
@@ -15,7 +15,7 @@ fuse-y += dev.o dir.o file.o inode.o control.o xattr.o acl.o readdir.o ioctl.o r
 fuse-y += poll.o notify.o
 fuse-y += iomode.o
 fuse-$(CONFIG_FUSE_DAX) += dax.o
-fuse-$(CONFIG_FUSE_PASSTHROUGH) += passthrough.o backing.o
+fuse-$(CONFIG_FUSE_PASSTHROUGH) += passthrough.o backing.o ext_map.o
 fuse-$(CONFIG_SYSCTL) += sysctl.o
 fuse-$(CONFIG_FUSE_IO_URING) += dev_uring.o
 
diff --git a/fs/fuse/backing.c b/fs/fuse/backing.c
index aa558a0c2e64..484ca29e8aed 100644
--- a/fs/fuse/backing.c
+++ b/fs/fuse/backing.c
@@ -32,6 +32,10 @@ static void fuse_backing_free(struct fuse_backing *fb)
 	case FUSE_BACKING_DAXDEV:
 		fs_put_dax(fb->dax_dev, fb);
 		break;
+
+	case FUSE_BACKING_EXTMAP:
+		fuse_ext_map_destroy(&fb->extents);
+		break;
 	}
 	kfree_rcu(fb, rcu);
 }
@@ -252,9 +256,28 @@ static void fuse_backing_rht_free(void *p, void *data)
 
 void fuse_backing_files_free(struct fuse_conn *fc)
 {
+	struct rhashtable_iter iter;
+	struct fuse_backing *fb;
+
 	idr_for_each(&fc->backing_files_map, fuse_backing_idr_free, NULL);
 	idr_destroy(&fc->backing_files_map);
 
+	/*
+	 * extents are referencing other backings, put these refs before
+	 * destroying the backings themselves
+	 */
+	rhashtable_walk_enter(&fc->backing_64_ht, &iter);
+	rhashtable_walk_start(&iter);
+	while ((fb = rhashtable_walk_next(&iter))) {
+		/* Nothing changing the hash table, -EAGAIN not possible */
+		if (fb->type == FUSE_BACKING_EXTMAP) {
+			fuse_ext_map_destroy(&fb->extents);
+			fb->extents.rb_node = NULL;
+		}
+	}
+	rhashtable_walk_stop(&iter);
+	rhashtable_walk_exit(&iter);
+
 	rhashtable_free_and_destroy(&fc->backing_64_ht, fuse_backing_rht_free, NULL);
 }
 
diff --git a/fs/fuse/ext_map.c b/fs/fuse/ext_map.c
new file mode 100644
index 000000000000..e5d257fec88c
--- /dev/null
+++ b/fs/fuse/ext_map.c
@@ -0,0 +1,129 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include "fuse_i.h"
+#include <linux/rbtree.h>
+#include <linux/pagemap.h>
+
+struct fuse_iext {
+	struct rb_node rb;
+	u64 start;
+	u64 end; /* exclusive */
+	struct fuse_backing *backing;
+	u64 backing_offset;
+};
+
+void fuse_ext_map_destroy(struct rb_root *extents)
+{
+	struct fuse_iext *fie, *tmp;
+
+	rbtree_postorder_for_each_entry_safe(fie, tmp, extents, rb) {
+		fuse_backing_put(fie->backing);
+		kfree(fie);
+	}
+}
+
+static int fuse_add_extent(struct fuse_conn *fc, struct rb_root *extents,
+			   struct fuse_extent *ext)
+{
+	struct fuse_iext *new_fie __free(kfree) = kzalloc_obj(*new_fie);
+	struct rb_node *parent = NULL, **link = &extents->rb_node;
+	loff_t end;
+
+	if (!new_fie)
+		return -ENOMEM;
+
+	if (ext->reserved[0] || ext->reserved[1])
+		return fuse_EIO("reserved fields set");
+
+	if (!PAGE_ALIGNED(ext->offset) || !PAGE_ALIGNED(ext->length) || !PAGE_ALIGNED(ext->addr))
+		return fuse_EIO("not page aligned");
+
+	if (!ext->length)
+		return fuse_EIO("zero sized extent");
+
+	if (overflows_type(ext->offset, loff_t) ||
+	    check_add_overflow(ext->offset, ext->length, &end))
+		return fuse_EIO("offset overflow");
+
+	new_fie->start = ext->offset;
+	new_fie->end = end;
+	new_fie->backing_offset = ext->addr;
+
+	while (*link) {
+		struct fuse_iext *fie = rb_entry(*link, typeof(*fie), rb);
+
+		parent = *link;
+		if (new_fie->end <= fie->start)
+			link = &parent->rb_left;
+		else if (new_fie->start >= fie->end)
+			link = &parent->rb_right;
+		else
+			return fuse_EIO("overlap");
+	}
+
+	new_fie->backing = fuse_backing_lookup(fc, ext->backing_id, true);
+	if (!new_fie->backing)
+		return fuse_EIO("backing not found");
+
+	if (new_fie->backing->type != FUSE_BACKING_DAXDEV) {
+		fuse_backing_put(new_fie->backing);
+		return fuse_EIO("backing is not dax device");
+	}
+
+	rb_link_node(&new_fie->rb, parent, link);
+	rb_insert_color(&no_free_ptr(new_fie)->rb, extents);
+
+	return 0;
+}
+
+bool fuse_ext_map_is_dax(struct fuse_backing *fb)
+{
+	struct fuse_iext *fie;
+
+	if (WARN_ON(RB_EMPTY_ROOT(&fb->extents)))
+		return false;
+
+	fie = rb_entry(fb->extents.rb_node, typeof(*fie), rb);
+	return fie->backing->type == FUSE_BACKING_DAXDEV;
+}
+
+int fuse_ext_map_populate(struct fuse_conn *fc, struct fuse_notify_map_out *arg,
+			  struct fuse_extent *ext)
+{
+	struct fuse_backing *fb;
+	unsigned int i;
+	int err;
+
+	if (!arg->num_extents)
+		return fuse_EIO("no extents");
+
+	if (arg->flags & FUSE_MAP_BACKING_CREATE) {
+		fb = kzalloc_obj(*fb);
+		if (!fb)
+			return -ENOMEM;
+
+		refcount_set(&fb->count, 1);
+		fb->type = FUSE_BACKING_EXTMAP;
+		fb->backing_id = arg->backing_id;
+	} else {
+		/* Adding extents to an existing extmap backing is not yet supported */
+		return -EINVAL;
+	}
+
+	for (i = 0; i < arg->num_extents; i++) {
+		err = fuse_add_extent(fc, &fb->extents, &ext[i]);
+		if (err)
+			goto err_put;
+	}
+
+	err = 0;
+	if (arg->flags & FUSE_MAP_BACKING_CREATE) {
+		err = fuse_backing_add_64(fc, fb);
+		if (!err)
+			return 0;
+	}
+
+err_put:
+	fuse_backing_put(fb);
+	return err;
+}
diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h
index 00c124bcb88d..928e5c43e43c 100644
--- a/fs/fuse/fuse_i.h
+++ b/fs/fuse/fuse_i.h
@@ -28,7 +28,7 @@
 #include <linux/backing-dev.h>
 #include <linux/mutex.h>
 #include <linux/rwsem.h>
-#include <linux/rbtree.h>
+#include <linux/rbtree_types.h>
 #include <linux/poll.h>
 #include <linux/workqueue.h>
 #include <linux/kref.h>
@@ -96,6 +96,7 @@ struct fuse_submount_lookup {
 enum fuse_backing_type {
 	FUSE_BACKING_PATH,
 	FUSE_BACKING_DAXDEV,
+	FUSE_BACKING_EXTMAP,
 };
 
 /* Container for data related to mapping to backing file */
@@ -111,6 +112,9 @@ struct fuse_backing {
 			struct dax_device *dax_dev;
 			bool dax_error;
 		};
+		struct {
+			struct rb_root extents;
+		};
 	};
 	u64 backing_id;
 	struct rhash_head hash_node;
@@ -1360,4 +1364,10 @@ extern void fuse_sysctl_unregister(void);
 #define fuse_sysctl_unregister()	do { } while (0)
 #endif /* CONFIG_SYSCTL */
 
+/* ext_map.c */
+
+void fuse_ext_map_destroy(struct rb_root *extents);
+bool fuse_ext_map_is_dax(struct fuse_backing *fb);
+int fuse_ext_map_populate(struct fuse_conn *fc, struct fuse_notify_map_out *arg,
+			  struct fuse_extent *ext);
 #endif /* _FS_FUSE_I_H */
diff --git a/fs/fuse/notify.c b/fs/fuse/notify.c
index 7361b56bb973..ce6d85834ea5 100644
--- a/fs/fuse/notify.c
+++ b/fs/fuse/notify.c
@@ -429,6 +429,44 @@ static int fuse_notify_backing_close(struct fuse_conn *fc, unsigned int size,
 
 }
 
+static int fuse_notify_map(struct fuse_conn *fc, unsigned int size,
+			   struct fuse_copy_state *cs)
+{
+	struct fuse_notify_map_out outarg;
+	struct fuse_extent *ext __free(kvfree) = NULL;
+	int err;
+
+	if (size < sizeof(outarg))
+		return -EINVAL;
+
+	err = fuse_copy_one(cs, &outarg, sizeof(outarg));
+	if (err)
+		return err;
+
+	if (outarg.num_extents > FUSE_MAX_EXTENTS)
+		return -EINVAL;
+
+	size -= sizeof(outarg);
+	if (size != outarg.num_extents * sizeof(*ext))
+		return -EINVAL;
+
+	if (outarg.reserved[0] != 0 || outarg.reserved[1] != 0)
+		return -EINVAL;
+
+	if (outarg.flags & ~FUSE_MAP_BACKING_CREATE)
+		return -EINVAL;
+
+	ext = kvmalloc_objs(*ext, outarg.num_extents);
+	if (!ext)
+		return -ENOMEM;
+
+	err = fuse_copy_one(cs, ext, size);
+	if (err)
+		return err;
+
+	return fuse_ext_map_populate(fc, &outarg, ext);
+}
+
 int fuse_notify(struct fuse_conn *fc, enum fuse_notify_code code,
 		unsigned int size, struct fuse_copy_state *cs)
 {
@@ -463,6 +501,9 @@ int fuse_notify(struct fuse_conn *fc, enum fuse_notify_code code,
 	case FUSE_NOTIFY_BACKING_CLOSE:
 		return fuse_notify_backing_close(fc, size, cs);
 
+	case FUSE_NOTIFY_MAP:
+		return fuse_notify_map(fc, size, cs);
+
 	default:
 		return -EINVAL;
 	}
diff --git a/include/uapi/linux/fuse.h b/include/uapi/linux/fuse.h
index 1ba28a2885ad..5368889710c7 100644
--- a/include/uapi/linux/fuse.h
+++ b/include/uapi/linux/fuse.h
@@ -251,12 +251,15 @@
  *
  *  7.47
  *  - add FUSE_HAS_SYNCFS opt-in flag for privileged userspace servers
- *  - add FUSE_NOTIFY_BACKING_CLOSE
+ *  - add FUSE_NOTIFY_BACKING_CLOSE, FUSE_NOTIFY_MAP
  *  - add struct fuse_notify_backing_close_out
+ *  - add struct fuse_notify_map_out
+ *  - add struct fuse_extent
  *  - add backing_id_64 to fuse_open_out
  *  - add backing_id to fuse_backing_map
  *  - add FUSE_BACKING_IS_DEV (fuse_backing_map.flags)
  *  - add FUSE_BACKING_ID_64 (multiple structs)
+ *  - add FUSE_MAP_BACKING_CREATE (fuse_notify_map_out.flags)
  */
 
 #ifndef _LINUX_FUSE_H
@@ -718,6 +721,7 @@ enum fuse_notify_code {
 	FUSE_NOTIFY_INC_EPOCH = 8,
 	FUSE_NOTIFY_PRUNE = 9,
 	FUSE_NOTIFY_BACKING_CLOSE = 10,
+	FUSE_NOTIFY_MAP = 11,
 };
 
 /* The read buffer is required to be at least 8k, but may be much larger */
@@ -1217,6 +1221,30 @@ struct fuse_notify_backing_close_out {
 	uint64_t	reserved;
 };
 
+/**
+ * notify_map flags
+ *
+ * FUSE_MAP_BACKING_CREATE:	create backing with the supplied ID
+ */
+#define FUSE_MAP_BACKING_CREATE	(1 << 0)
+
+struct fuse_notify_map_out {
+	uint64_t	backing_id;
+	uint32_t	num_extents;
+	uint32_t	flags;
+	uint64_t	reserved[2];
+};
+
+#define FUSE_MAX_EXTENTS 1638
+
+struct fuse_extent {
+	uint64_t	offset;		/* offset of extent into file */
+	uint64_t	length;		/* extent length */
+	uint64_t	backing_id;	/* target backing */
+	uint64_t	addr;		/* target offset within backing file/device */
+	uint64_t	reserved[2];
+};
+
 #define FUSE_SETUPMAPPING_FLAG_WRITE (1ull << 0)
 #define FUSE_SETUPMAPPING_FLAG_READ (1ull << 1)
 struct fuse_setupmapping_in {
-- 
2.54.0


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

* [PATCH 10/11] fuse: add extent map I/O support
  2026-09-22  6:10 [PATCH 00/11] fuse: DAX device based extent maps (famfs) Miklos Szeredi
                   ` (8 preceding siblings ...)
  2026-09-22  6:10 ` [PATCH 09/11] fuse: add extent map data structure Miklos Szeredi
@ 2026-09-22  6:10 ` Miklos Szeredi
  2026-09-22 13:04   ` Amir Goldstein
  2026-09-22  6:10 ` [PATCH 11/11] fuse: add support for striped backing Miklos Szeredi
  10 siblings, 1 reply; 33+ messages in thread
From: Miklos Szeredi @ 2026-09-22  6:10 UTC (permalink / raw)
  To: fuse-devel; +Cc: John Groves, Amir Goldstein, Darrick J . Wong

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.

Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>
---
 fs/fuse/backing.c     |  15 +++++
 fs/fuse/ext_map.c     | 138 ++++++++++++++++++++++++++++++++++++++++++
 fs/fuse/fuse_i.h      |   4 ++
 fs/fuse/passthrough.c |  27 +++++++--
 4 files changed, 180 insertions(+), 4 deletions(-)

diff --git a/fs/fuse/backing.c b/fs/fuse/backing.c
index 484ca29e8aed..14875d48476e 100644
--- a/fs/fuse/backing.c
+++ b/fs/fuse/backing.c
@@ -224,6 +224,21 @@ int fuse_backing_close(struct fuse_conn *fc, u64 backing_id, bool is_64bit)
 	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, bool is_64bit)
 {
 	struct fuse_backing *fb;
diff --git a/fs/fuse/ext_map.c b/fs/fuse/ext_map.c
index e5d257fec88c..c752b6ef0686 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,124 @@ 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);
+	}
+
+	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);
+
+	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);
+
+	file_accessed(iocb->ki_filp);
+	return res;
+}
+
+ssize_t fuse_ext_map_write_iter(struct kiocb *iocb, struct iov_iter *from)
+{
+	fuse_rw_clamp(iocb, from);
+
+	if (!iov_iter_count(from))
+		return 0;
+
+	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 928e5c43e43c..50b61e16a15f 100644
--- a/fs/fuse/fuse_i.h
+++ b/fs/fuse/fuse_i.h
@@ -1308,6 +1308,7 @@ void fuse_backing_put(struct fuse_backing *fb);
 
 struct fuse_backing *fuse_backing_lookup(struct fuse_conn *fc, u64 backing_id, bool is_64bit);
 int fuse_backing_add_64(struct fuse_conn *fc, struct fuse_backing *fb);
+bool fuse_backing_is_dax(struct fuse_backing *fb);
 #else
 
 static inline void fuse_backing_put(struct fuse_backing *fb)
@@ -1367,6 +1368,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_map_out *arg,
 			  struct fuse_extent *ext);
diff --git a/fs/fuse/passthrough.c b/fs/fuse/passthrough.c
index adc9d2d8e203..5b0886562408 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,6 +73,9 @@ ssize_t fuse_passthrough_write_iter(struct kiocb *iocb,
 	if (!count)
 		return 0;
 
+	if (!backing_file)
+		return fuse_ext_map_write_iter(iocb, iter);
+
 	inode_lock(inode);
 	ret = backing_file_write_iter(backing_file, iter, iocb, iocb->ki_flags,
 				      &ctx);
@@ -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);
 }
 
@@ -167,6 +178,14 @@ struct fuse_backing *fuse_passthrough_open(struct file *file, u64 backing_id, bo
 	if (!fb)
 		return fuse_ptr_EIO("backing not found");
 
+	if (fb->type == FUSE_BACKING_EXTMAP) {
+		if (fuse_backing_is_dax(fb) != !!IS_DAX(file_inode(file))) {
+			fuse_backing_put(fb);
+			return fuse_ptr_EIO("dax mode mismatch");
+		}
+		return fb;
+	}
+
 	if (fb->type != FUSE_BACKING_PATH) {
 		fuse_backing_put(fb);
 		return fuse_ptr_EIO("invalid backing type");
-- 
2.54.0


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

* [PATCH 11/11] fuse: add support for striped backing
  2026-09-22  6:10 [PATCH 00/11] fuse: DAX device based extent maps (famfs) Miklos Szeredi
                   ` (9 preceding siblings ...)
  2026-09-22  6:10 ` [PATCH 10/11] fuse: add extent map I/O support Miklos Szeredi
@ 2026-09-22  6:10 ` Miklos Szeredi
  2026-09-23  5:41   ` Amir Goldstein
  10 siblings, 1 reply; 33+ messages in thread
From: Miklos Szeredi @ 2026-09-22  6:10 UTC (permalink / raw)
  To: fuse-devel; +Cc: John Groves, Amir Goldstein, Darrick J . Wong

Add the FUSE_MAP_CYCLIC flag for FUSE_NOTIFY_MAP notifications to enable
creating striped maps.

This is just a set of regular extents that repeats after the end of the
last extent using a striping pattern (i.e. on the target backings the
stripes have a contiguous footprint).

Two additional limits apply compared to non-cyclic extent map population:

 - the size of the extents (chunk size) must be equal

 - the extents must start from zero offset and follow the previous without
   one with a gap.

Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>
---
 fs/fuse/ext_map.c         | 27 ++++++++++++++++++++++++---
 fs/fuse/fuse_i.h          |  1 +
 fs/fuse/notify.c          |  2 +-
 include/uapi/linux/fuse.h |  4 +++-
 4 files changed, 29 insertions(+), 5 deletions(-)

diff --git a/fs/fuse/ext_map.c b/fs/fuse/ext_map.c
index c752b6ef0686..52f3fb0bb571 100644
--- a/fs/fuse/ext_map.c
+++ b/fs/fuse/ext_map.c
@@ -101,12 +101,18 @@ static int fuse_ext_map_iomap_begin(struct inode *inode, loff_t offset, loff_t l
 {
 	struct fuse_backing *fb = fuse_inode_backing(get_fuse_inode(inode));
 	struct fuse_iext *fie;
+	u64 ncycle = 0, seq_off = 0, addr, tmp;
 	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 (fb->cycle_length) {
+		ncycle = offset / fb->cycle_length;
+		seq_off = ncycle * fb->cycle_length;
+	}
+
+	fie = fuse_find_extent(&fb->extents, offset - seq_off);
 	if (!fie)
 		return fuse_EIO("missing mapping");
 
@@ -119,9 +125,12 @@ static int fuse_ext_map_iomap_begin(struct inode *inode, loff_t offset, loff_t l
 	}
 
 	ext_len = fie->end - fie->start;
+	addr = fie->backing_offset;
+	if (check_mul_overflow(ncycle, ext_len, &tmp) || check_add_overflow(addr, tmp, &addr))
+		return fuse_EIO("extent address overflow");
 
-	iomap->offset = fie->start;
-	iomap->addr = fie->backing_offset;
+	iomap->offset = fie->start + seq_off;
+	iomap->addr = addr;
 	iomap->length = ext_len;
 	iomap->dax_dev = fie->backing->dax_dev;
 	iomap->type = IOMAP_MAPPED;
@@ -231,6 +240,7 @@ int fuse_ext_map_populate(struct fuse_conn *fc, struct fuse_notify_map_out *arg,
 	struct fuse_backing *fb;
 	unsigned int i;
 	int err;
+	u64 chunk_size = 0;
 
 	if (!arg->num_extents)
 		return fuse_EIO("no extents");
@@ -248,7 +258,18 @@ int fuse_ext_map_populate(struct fuse_conn *fc, struct fuse_notify_map_out *arg,
 		return -EINVAL;
 	}
 
+	if (arg->flags & FUSE_MAP_CYCLIC) {
+		chunk_size = ext[0].length;
+		err = -EINVAL;
+		if (check_mul_overflow(chunk_size, arg->num_extents, &fb->cycle_length))
+			goto err_put;
+	}
+
 	for (i = 0; i < arg->num_extents; i++) {
+		err = -EINVAL;
+		if (chunk_size && (ext[i].offset != chunk_size * i || ext[i].length != chunk_size))
+			goto err_put;
+
 		err = fuse_add_extent(fc, &fb->extents, &ext[i]);
 		if (err)
 			goto err_put;
diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h
index 50b61e16a15f..a34db4ad4bc4 100644
--- a/fs/fuse/fuse_i.h
+++ b/fs/fuse/fuse_i.h
@@ -114,6 +114,7 @@ struct fuse_backing {
 		};
 		struct {
 			struct rb_root extents;
+			u64 cycle_length;
 		};
 	};
 	u64 backing_id;
diff --git a/fs/fuse/notify.c b/fs/fuse/notify.c
index ce6d85834ea5..cbaa953e9db5 100644
--- a/fs/fuse/notify.c
+++ b/fs/fuse/notify.c
@@ -453,7 +453,7 @@ static int fuse_notify_map(struct fuse_conn *fc, unsigned int size,
 	if (outarg.reserved[0] != 0 || outarg.reserved[1] != 0)
 		return -EINVAL;
 
-	if (outarg.flags & ~FUSE_MAP_BACKING_CREATE)
+	if (outarg.flags & ~(FUSE_MAP_BACKING_CREATE | FUSE_MAP_CYCLIC))
 		return -EINVAL;
 
 	ext = kvmalloc_objs(*ext, outarg.num_extents);
diff --git a/include/uapi/linux/fuse.h b/include/uapi/linux/fuse.h
index 5368889710c7..c100e6d78f7e 100644
--- a/include/uapi/linux/fuse.h
+++ b/include/uapi/linux/fuse.h
@@ -259,7 +259,7 @@
  *  - add backing_id to fuse_backing_map
  *  - add FUSE_BACKING_IS_DEV (fuse_backing_map.flags)
  *  - add FUSE_BACKING_ID_64 (multiple structs)
- *  - add FUSE_MAP_BACKING_CREATE (fuse_notify_map_out.flags)
+ *  - add FUSE_MAP_CYCLIC, FUSE_MAP_BACKING_CREATE (fuse_map_out.flags)
  */
 
 #ifndef _LINUX_FUSE_H
@@ -1225,8 +1225,10 @@ struct fuse_notify_backing_close_out {
  * notify_map flags
  *
  * FUSE_MAP_BACKING_CREATE:	create backing with the supplied ID
+ * FUSE_MAP_CYCLIC:		map repeats after last extent
  */
 #define FUSE_MAP_BACKING_CREATE	(1 << 0)
+#define FUSE_MAP_CYCLIC		(1 << 1)
 
 struct fuse_notify_map_out {
 	uint64_t	backing_id;
-- 
2.54.0


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

* Re: [PATCH 01/11] dax: replace exported dax_dev_get() with non-allocating dax_dev_find()
  2026-09-22  6:10 ` [PATCH 01/11] dax: replace exported dax_dev_get() with non-allocating dax_dev_find() Miklos Szeredi
@ 2026-09-22  7:36   ` Amir Goldstein
  2026-09-22 19:07   ` Alison Schofield
  1 sibling, 0 replies; 33+ messages in thread
From: Amir Goldstein @ 2026-09-22  7:36 UTC (permalink / raw)
  To: Miklos Szeredi
  Cc: fuse-devel, John Groves, Darrick J . Wong, Dave Jiang,
	Alison Schofield

On Tue, Sep 22, 2026 at 8:10 AM Miklos Szeredi <mszeredi@redhat.com> wrote:
>
> From: John Groves <John@Groves.net>
>
> This fix is in response to a Sashiko review, and some subsequent
> analysis.
>
> dax_dev_get() uses iget5_locked() which creates a new inode if no
> matching one exists. This is correct for the internal caller
> (alloc_dax), but dangerous for external callers that look up devices
> from user-supplied or metadata-supplied dev_t values:
>
> 1. A new inode is created with DAXDEV_ALIVE set but no backing driver,
>    no ops, and no IDA-allocated minor number.
>
> 2. On teardown, dax_destroy_inode() warns because kill_dax() was never
>    called, and dax_free_inode() calls ida_free() for a minor that was
>    never ida_alloc'd -- potentially freeing the minor of a real device.
>
> Add dax_dev_find() which uses ilookup5() for lookup-only semantics:
> it returns an existing dax_device with an elevated inode reference, or
> NULL if no device with the given dev_t exists. It never creates inodes.
> A dax_alive() check under dax_read_lock() guards against returning a
> device that is concurrently being torn down by kill_dax().
>
> Make dax_dev_get() static again (internal to super.c for alloc_dax),
> export dax_dev_find() instead

, and update the two external callers
> (famfs_inode.c, famfs.c). Also add the missing CONFIG_DAX=n stub.

prune stale text


>
> About the 'fixes' tag: this removes the export of dax_dev_get(),
> which was flawed, and replaces is with dax_dev_find(). It feels like
> the fixes tag makes sense for correcting an ABI error.
>
> Fixes: 2ae624d5a555d ("dax: export dax_dev_get()")
> Reviewed-by: Dave Jiang <dave.jiang@intel.com>
> Reviewed-by: Alison Schofield <alison.schofield@intel.com>
> Reviewed-by: "Darrick J. Wong" <djwong@kernel.org>
> Signed-off-by: John Groves <john@groves.net>
> Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>
> ---
>  drivers/dax/super.c | 38 ++++++++++++++++++++++++++++++++++++--
>  include/linux/dax.h |  6 +++++-
>  2 files changed, 41 insertions(+), 3 deletions(-)
>
> diff --git a/drivers/dax/super.c b/drivers/dax/super.c
> index 45f84b0eb909..824e1f6df378 100644
> --- a/drivers/dax/super.c
> +++ b/drivers/dax/super.c
> @@ -565,7 +565,7 @@ static int dax_set(struct inode *inode, void *data)
>         return 0;
>  }
>
> -struct dax_device *dax_dev_get(dev_t devt)
> +static struct dax_device *dax_dev_get(dev_t devt)
>  {
>         struct dax_device *dax_dev;
>         struct inode *inode;
> @@ -588,7 +588,41 @@ struct dax_device *dax_dev_get(dev_t devt)
>
>         return dax_dev;
>  }
> -EXPORT_SYMBOL_GPL(dax_dev_get);
> +
> +/**
> + * dax_dev_find - look up an existing dax_device by dev_t
> + * @devt: the device number to find
> + *
> + * Returns a dax_device with an elevated inode reference, or NULL if no
> + * device with the given dev_t exists. Unlike dax_dev_get(), this never
> + * allocates a new inode -- it is safe for external callers that are looking
> + * up devices from user-supplied or metadata-supplied dev_t values.
> + *
> + * Caller must put_dax() the returned device when done.
> + */
> +struct dax_device *dax_dev_find(dev_t devt)
> +{
> +       struct dax_device *dax_dev;
> +       struct inode *inode;
> +       int id;
> +
> +       inode = ilookup5(dax_superblock, hash_32(devt + DAXFS_MAGIC, 31),
> +                        dax_test, &devt);
> +       if (!inode)
> +               return NULL;
> +
> +       dax_dev = to_dax_dev(inode);
> +       id = dax_read_lock();
> +       if (!dax_alive(dax_dev)) {
> +               dax_read_unlock(id);
> +               iput(inode);
> +               return NULL;
> +       }
> +       dax_read_unlock(id);
> +
> +       return dax_dev;
> +}
> +EXPORT_SYMBOL_GPL(dax_dev_find);
>
>  struct dax_device *alloc_dax(void *private, const struct dax_operations *ops)
>  {
> diff --git a/include/linux/dax.h b/include/linux/dax.h
> index fe6c3ded1b50..29113eb95e72 100644
> --- a/include/linux/dax.h
> +++ b/include/linux/dax.h
> @@ -54,7 +54,7 @@ struct dax_device *alloc_dax(void *private, const struct dax_operations *ops);
>  void *dax_holder(struct dax_device *dax_dev);
>  void put_dax(struct dax_device *dax_dev);
>  void kill_dax(struct dax_device *dax_dev);
> -struct dax_device *dax_dev_get(dev_t devt);
> +struct dax_device *dax_dev_find(dev_t devt);
>  void dax_write_cache(struct dax_device *dax_dev, bool wc);
>  bool dax_write_cache_enabled(struct dax_device *dax_dev);
>  bool dax_synchronous(struct dax_device *dax_dev);
> @@ -92,6 +92,10 @@ static inline void put_dax(struct dax_device *dax_dev)
>  static inline void kill_dax(struct dax_device *dax_dev)
>  {
>  }
> +static inline struct dax_device *dax_dev_find(dev_t devt)
> +{
> +       return NULL;
> +}
>  static inline void dax_write_cache(struct dax_device *dax_dev, bool wc)
>  {
>  }
> --
> 2.54.0
>

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

* Re: [PATCH 02/11] fuse: use "vdax" naming for virtiofs DAX
  2026-09-22  6:10 ` [PATCH 02/11] fuse: use "vdax" naming for virtiofs DAX Miklos Szeredi
@ 2026-09-22  8:43   ` Amir Goldstein
  2026-09-28 10:38     ` Miklos Szeredi
  0 siblings, 1 reply; 33+ messages in thread
From: Amir Goldstein @ 2026-09-22  8:43 UTC (permalink / raw)
  To: Miklos Szeredi; +Cc: fuse-devel, John Groves, Darrick J . Wong

On Tue, Sep 22, 2026 at 8:10 AM Miklos Szeredi <mszeredi@redhat.com> wrote:
>
> We are introducing DAX functionality largely unrelated to the virtiofs
> code.  Rename dax -> vdax for the virtiofs case for clarity.
>
> Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>

I am sure it has crossed your mind that leaving CONFIG_FUSE_DAX
all over the place is ugly and just as confusing.

You may want to create an alias in Kconfig and rename the checks
in the code to FUSE_VDAX:

config FUSE_VDAX
        def_bool FUSE_DAX

This should preserve old configs.

Anyway, with or without this config alias

Reviewed-by: Amir Goldstein <amir73il@gmail.com>

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

* Re: [PATCH 03/11] fuse: don't assume ff->passthrough is set for FOPEN_PASSTHROUGH
  2026-09-22  6:10 ` [PATCH 03/11] fuse: don't assume ff->passthrough is set for FOPEN_PASSTHROUGH Miklos Szeredi
@ 2026-09-22  8:44   ` Amir Goldstein
  0 siblings, 0 replies; 33+ messages in thread
From: Amir Goldstein @ 2026-09-22  8:44 UTC (permalink / raw)
  To: Miklos Szeredi; +Cc: fuse-devel, John Groves, Darrick J . Wong

On Tue, Sep 22, 2026 at 8:10 AM Miklos Szeredi <mszeredi@redhat.com> wrote:
>
> Following patch will introduce extent map passthrough mode, where
> ff->passthrough is not set.
>
> Check if in passthrough mode via FOPEN_PASSTHROUGH instead.
>
> No functional change.
>
> Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>

Reviewed-by: Amir Goldstein <amir73il@gmail.com>

> ---
>  fs/fuse/file.c        | 12 ++++++------
>  fs/fuse/fuse_i.h      |  8 ++------
>  fs/fuse/passthrough.c | 13 +++++++++++--
>  3 files changed, 19 insertions(+), 14 deletions(-)
>
> diff --git a/fs/fuse/file.c b/fs/fuse/file.c
> index 1eadeca0c47d..af26de78a199 100644
> --- a/fs/fuse/file.c
> +++ b/fs/fuse/file.c
> @@ -311,7 +311,7 @@ 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))
> +       if (fuse_is_passthrough(ff))
>                 fuse_passthrough_release(ff, fuse_inode_backing(fi));
>
>         /* Inode is NULL on error path of fuse_create_open() */
> @@ -1842,7 +1842,7 @@ static ssize_t fuse_file_read_iter(struct kiocb *iocb, struct iov_iter *to)
>         /* 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))
> +       else if (fuse_is_passthrough(ff))
>                 return fuse_passthrough_read_iter(iocb, to);
>         else
>                 return fuse_cache_read_iter(iocb, to);
> @@ -1863,7 +1863,7 @@ static ssize_t fuse_file_write_iter(struct kiocb *iocb, struct iov_iter *from)
>         /* 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))
> +       else if (fuse_is_passthrough(ff))
>                 return fuse_passthrough_write_iter(iocb, from);
>         else
>                 return fuse_cache_write_iter(iocb, from);
> @@ -1879,7 +1879,7 @@ static ssize_t fuse_splice_read(struct file *in, loff_t *ppos,
>
>         if (ff->open_flags & FOPEN_DIRECT_IO)
>                 return copy_splice_read(in, ppos, pipe, len, flags);
> -       else if (fuse_file_passthrough(ff))
> +       else if (fuse_is_passthrough(ff))
>                 return fuse_passthrough_splice_read(in, ppos, pipe, len, flags);
>         else
>                 return filemap_splice_read(in, ppos, pipe, len, flags);
> @@ -1891,7 +1891,7 @@ static ssize_t fuse_splice_write(struct pipe_inode_info *pipe, struct file *out,
>         struct fuse_file *ff = out->private_data;
>
>         /* FOPEN_DIRECT_IO overrides FOPEN_PASSTHROUGH */
> -       if (fuse_file_passthrough(ff) && !(ff->open_flags & FOPEN_DIRECT_IO))
> +       if (fuse_is_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);
> @@ -2406,7 +2406,7 @@ static int fuse_file_mmap(struct file *file, struct vm_area_struct *vma)
>          * 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))
> +       if (fuse_is_passthrough(ff))
>                 return fuse_passthrough_mmap(file, vma);
>         else if (fuse_inode_backing(get_fuse_inode(inode)))
>                 return -ENODEV;
> diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h
> index 77ccd5b2059b..b7d8751ba2e0 100644
> --- a/fs/fuse/fuse_i.h
> +++ b/fs/fuse/fuse_i.h
> @@ -1313,13 +1313,9 @@ static inline struct fuse_backing *fuse_inode_backing_set(struct fuse_inode *fi,
>  struct fuse_backing *fuse_passthrough_open(struct file *file, 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)
> +static inline bool fuse_is_passthrough(struct fuse_file *ff)
>  {
> -#ifdef CONFIG_FUSE_PASSTHROUGH
> -       return ff->passthrough;
> -#else
> -       return NULL;
> -#endif
> +       return IS_ENABLED(CONFIG_FUSE_PASSTHROUGH) && (ff->open_flags & FOPEN_PASSTHROUGH);
>  }
>
>  ssize_t fuse_passthrough_read_iter(struct kiocb *iocb, struct iov_iter *iter);
> diff --git a/fs/fuse/passthrough.c b/fs/fuse/passthrough.c
> index f2d08ac2459b..b43d3e0f7081 100644
> --- a/fs/fuse/passthrough.c
> +++ b/fs/fuse/passthrough.c
> @@ -11,6 +11,10 @@
>  #include <linux/backing-file.h>
>  #include <linux/splice.h>
>
> +static inline struct file *fuse_file_passthrough(struct fuse_file *ff)
> +{
> +       return ff->passthrough;
> +}
>  static void fuse_file_accessed(struct file *file)
>  {
>         struct inode *inode = file_inode(file);
> @@ -187,10 +191,15 @@ struct fuse_backing *fuse_passthrough_open(struct file *file, int backing_id)
>
>  void fuse_passthrough_release(struct fuse_file *ff, struct fuse_backing *fb)
>  {
> +       struct file *backing_file = fuse_file_passthrough(ff);
> +
>         pr_debug("%s: fb=0x%p, backing_file=0x%p\n", __func__,
> -                fb, ff->passthrough);
> +                fb, backing_file);
> +
> +       if (!backing_file)
> +               return;
>
> -       fput(ff->passthrough);
> +       fput(backing_file);
>         ff->passthrough = NULL;
>         put_cred(ff->cred);
>         ff->cred = NULL;
> --
> 2.54.0
>

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

* Re: [PATCH 04/11] fuse: make fuse_backing_get() static
  2026-09-22  6:10 ` [PATCH 04/11] fuse: make fuse_backing_get() static Miklos Szeredi
@ 2026-09-22  8:44   ` Amir Goldstein
  0 siblings, 0 replies; 33+ messages in thread
From: Amir Goldstein @ 2026-09-22  8:44 UTC (permalink / raw)
  To: Miklos Szeredi; +Cc: fuse-devel, John Groves, Darrick J . Wong

On Tue, Sep 22, 2026 at 8:10 AM Miklos Szeredi <mszeredi@redhat.com> wrote:
>
> Since it's not used outside of backing.c.
>
> Also remove the fuse_backing_lookup() stub from fuse_i.h, since it's called
> only from passthrough.c.
>
> Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>

Reviewed-by: Amir Goldstein <amir73il@gmail.com>

> ---
>  fs/fuse/backing.c |  2 +-
>  fs/fuse/fuse_i.h  | 11 -----------
>  2 files changed, 1 insertion(+), 12 deletions(-)
>
> diff --git a/fs/fuse/backing.c b/fs/fuse/backing.c
> index 472b6afa7dff..433fa3098d71 100644
> --- a/fs/fuse/backing.c
> +++ b/fs/fuse/backing.c
> @@ -10,7 +10,7 @@
>
>  #include <linux/file.h>
>
> -struct fuse_backing *fuse_backing_get(struct fuse_backing *fb)
> +static struct fuse_backing *fuse_backing_get(struct fuse_backing *fb)
>  {
>         if (fb && refcount_inc_not_zero(&fb->count))
>                 return fb;
> diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h
> index b7d8751ba2e0..6cd556a0aa59 100644
> --- a/fs/fuse/fuse_i.h
> +++ b/fs/fuse/fuse_i.h
> @@ -1267,24 +1267,13 @@ void fuse_file_release(struct inode *inode, struct fuse_file *ff,
>
>  /* backing.c */
>  #ifdef CONFIG_FUSE_PASSTHROUGH
> -struct fuse_backing *fuse_backing_get(struct fuse_backing *fb);
>  void fuse_backing_put(struct fuse_backing *fb);
>  struct fuse_backing *fuse_backing_lookup(struct fuse_conn *fc, int backing_id);
>  #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)
>  {
>  }
> -static inline struct fuse_backing *fuse_backing_lookup(struct fuse_conn *fc,
> -                                                      int backing_id)
> -{
> -       return NULL;
> -}
>  #endif
>
>  void fuse_backing_files_init(struct fuse_conn *fc);
> --
> 2.54.0
>

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

* Re: [PATCH 05/11] fuse: add helpers for EIO return value with kernel message
  2026-09-22  6:10 ` [PATCH 05/11] fuse: add helpers for EIO return value with kernel message Miklos Szeredi
@ 2026-09-22  9:58   ` Amir Goldstein
  2026-09-28 10:57     ` Miklos Szeredi
  0 siblings, 1 reply; 33+ messages in thread
From: Amir Goldstein @ 2026-09-22  9:58 UTC (permalink / raw)
  To: Miklos Szeredi; +Cc: fuse-devel, John Groves, Darrick J . Wong

On Tue, Sep 22, 2026 at 8:10 AM Miklos Szeredi <mszeredi@redhat.com> wrote:
>
> Conditions are triggered by a buggy fuse server will result in -EIO which
> is hard to interpret without any context.
>
> Add helpers that print a short message to the kernel log as well as
> returning the error value.  This serves a dual purpuse:
>
>  - documents the error condition inside the code
>
>  - allows the implementor of the fuse server to get the context for the
>    error
>
> Some debug messages are removed in favor of this.
>
> Currently these use the pr_notice_once() variant.  Possibly should be
> changed to a ratelimit of e.g. once per minute.
>
> Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>
> ---
>  fs/fuse/fuse_i.h      |  6 ++++++
>  fs/fuse/iomode.c      | 26 ++++++++------------------
>  fs/fuse/passthrough.c | 16 ++++------------
>  3 files changed, 18 insertions(+), 30 deletions(-)
>
> diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h
> index 6cd556a0aa59..b698857dfe13 100644
> --- a/fs/fuse/fuse_i.h
> +++ b/fs/fuse/fuse_i.h
> @@ -11,6 +11,12 @@
>  # define pr_fmt(fmt) "fuse: " fmt
>  #endif
>
> +#define fuse_EIO(msg) (pr_notice_once("%s: %s\n", __func__, msg), -EIO)
> +#define fuse_err_EIO(msg, err) (pr_notice_once("%s: %s (%d)\n", __func__, msg, (int) (err)), -EIO)
> +
> +#define fuse_ptr_EIO(msg) ERR_PTR(fuse_EIO(msg))
> +#define fuse_err_ptr_EIO(msg, err) ERR_PTR(fuse_err_EIO(msg, err))
> +
>  #include "args.h"
>  #include <linux/fuse.h>
>  #include <linux/fs.h>
> diff --git a/fs/fuse/iomode.c b/fs/fuse/iomode.c
> index 79637c09e883..c25ac580c811 100644
> --- a/fs/fuse/iomode.c
> +++ b/fs/fuse/iomode.c
> @@ -122,7 +122,7 @@ static int fuse_file_uncached_io_open(struct inode *inode,
>
>         err = fuse_inode_uncached_io_start(fi, fb);
>         if (err)
> -               return err;
> +               return fuse_err_EIO("failed to start uncached I/O", err);
>
>         WARN_ON(ff->iomode != IOM_NONE);
>         ff->iomode = IOM_UNCACHED;
> @@ -173,9 +173,11 @@ static int fuse_file_passthrough_open(struct inode *inode, struct file *file)
>         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;
> +       if (!IS_ENABLED(CONFIG_FUSE_PASSTHROUGH) || !fc->passthrough)
> +               return fuse_EIO("passthrough not enabled");
> +
> +       if (ff->open_flags & ~FOPEN_PASSTHROUGH_MASK)
> +               return fuse_EIO("conflicting open flags");
>
>         fb = fuse_passthrough_open(file, ff->args->open_outarg.backing_id);
>         if (IS_ERR(fb))
> @@ -212,7 +214,7 @@ int fuse_file_io_open(struct file *file, struct inode *inode)
>          */
>         err = -EINVAL;

@@ -245,8 +245,9 @@ 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.
         */
-       err = -EINVAL;
        if (fuse_inode_backing(fi) && !(ff->open_flags & FOPEN_PASSTHROUGH))

>         if (fuse_inode_backing(fi) && !(ff->open_flags & FOPEN_PASSTHROUGH))
> -               goto fail;
> +               return fuse_EIO("FOPEN_PASSTHROUGH expected");
>
>         /*
>          * FOPEN_PARALLEL_DIRECT_WRITES requires FOPEN_DIRECT_IO.
> @@ -236,20 +238,8 @@ int fuse_file_io_open(struct file *file, struct inode *inode)
>                 err = fuse_file_passthrough_open(inode, file);
>         else
>                 err = fuse_file_cached_io_open(inode, ff);
> -       if (err)
> -               goto fail;
> -
> -       return 0;
>
> -fail:
> -       pr_debug("failed to open file in requested io mode (open_flags=0x%x, err=%i).\n",
> -                ff->open_flags, err);
> -       /*
> -        * The file open mode determines the inode io mode.
> -        * Using incorrect open mode is a server mistake, which results in
> -        * user visible failure of open() with EIO error.
> -        */
> -       return -EIO;

The reason for EIO is not immediately apparent so suggest to move this
comment up and remove moot err = -EINVAL;

> +       return err;
>  }
>
>  /* No more pending io and no new io possible to inode via open/mmapped file */
> diff --git a/fs/fuse/passthrough.c b/fs/fuse/passthrough.c
> index b43d3e0f7081..0e2e1066b984 100644
> --- a/fs/fuse/passthrough.c
> +++ b/fs/fuse/passthrough.c
> @@ -159,34 +159,26 @@ struct fuse_backing *fuse_passthrough_open(struct file *file, int backing_id)
>         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;
> +               return fuse_ptr_EIO("invalid backing_id");
>
> -       err = -ENOENT;
>         fb = fuse_backing_lookup(fc, backing_id);
>         if (!fb)
> -               goto out;
> +               return fuse_ptr_EIO("backing not found");
>
>         /* 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);
> -       err = PTR_ERR(backing_file);
>         if (IS_ERR(backing_file)) {
>                 fuse_backing_put(fb);
> -               goto out;
> +               return fuse_err_ptr_EIO("failed to open backing file", PTR_ERR(backing_file));
>         }
>
> -       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);

Why remove this pr_debug of the success branch?
it is used as a debugging trace for the lifetime and usage of backing files.

Thanks,
Amir.

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

* Re: [PATCH 06/11] fuse: support 64 bit, server allocated backing ID
  2026-09-22  6:10 ` [PATCH 06/11] fuse: support 64 bit, server allocated backing ID Miklos Szeredi
@ 2026-09-22 10:29   ` Amir Goldstein
  2026-09-23  7:08   ` Amir Goldstein
  1 sibling, 0 replies; 33+ messages in thread
From: Amir Goldstein @ 2026-09-22 10:29 UTC (permalink / raw)
  To: Miklos Szeredi; +Cc: fuse-devel, John Groves, Darrick J . Wong

On Tue, Sep 22, 2026 at 8:10 AM Miklos Szeredi <mszeredi@redhat.com> wrote:
>
> Add support for server allocated 64-bit backing IDs alongside the
> existing kernel allocated 32-bit IDs.
>
> Opened with FUSE_DEV_IOC_BACKING_OPEN, but the backing ID is sent via
> fuse_backing_map.backing_id, with FUSE_BACKING_ID_64 being set in .flags.
>

As I wrote you off-list, I prefer that the new implementation is gated at init
time with FUSE_PASSTHOUGH_V2 and then we assert that all API is using
either 32 or 64 bit.

> Closed with FUSE_NOTIFY_BACKING_CLOSE.  Since close only provides the
> backing ID, not the file descriptor, it doesn't have to be done with an
> ioctl.
>
> All 64 bit values are valid.

I prefer that we reserve 0 backing_id for future use as we did with
the 32bit id.
Its intended purpose was for OPEN reply to mean "passthrough to the pre
attached backing inode".

Also, my plan is for a mode PASSTHROUGH_INO which has an
identity backing_id == nodeid (and probably also == ino) so 0 value
does not fit into this plan.

>
> Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>
> ---
>  fs/fuse/backing.c         | 121 +++++++++++++++++++++++++-------------
>  fs/fuse/dev.c             |   2 +-
>  fs/fuse/dev.h             |   2 +-
>  fs/fuse/fuse_i.h          |  13 +++-
>  fs/fuse/notify.c          |  23 ++++++++
>  fs/fuse/passthrough.c     |   2 +-
>  include/uapi/linux/fuse.h |  19 +++++-
>  7 files changed, 133 insertions(+), 49 deletions(-)
>
> diff --git a/fs/fuse/backing.c b/fs/fuse/backing.c
> index 433fa3098d71..58dbdd17c1ef 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/rhashtable.h>
>
>  static struct fuse_backing *fuse_backing_get(struct fuse_backing *fb)
>  {
> @@ -33,11 +34,6 @@ void fuse_backing_put(struct fuse_backing *fb)
>                 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;
> @@ -53,38 +49,42 @@ static int fuse_backing_id_alloc(struct fuse_conn *fc, struct fuse_backing *fb)
>         return id;
>  }
>
> -static struct fuse_backing *fuse_backing_id_remove(struct fuse_conn *fc,
> -                                                  int id)
> -{
> -       struct fuse_backing *fb;
> +static const struct rhashtable_params fuse_backing_params = {
> +       .head_offset = offsetof(struct fuse_backing, hash_node),
> +       .key_offset = offsetof(struct fuse_backing, backing_id),
> +       .key_len = sizeof_field(struct fuse_backing, backing_id),
> +};
>
> -       spin_lock(&fc->lock);
> -       fb = idr_remove(&fc->backing_files_map, id);
> -       spin_unlock(&fc->lock);
> -
> -       return fb;
> +int fuse_backing_add_64(struct fuse_conn *fc, struct fuse_backing *fb)
> +{
> +       return rhashtable_insert_fast(&fc->backing_64_ht, &fb->hash_node, fuse_backing_params);
>  }
>
> -static int fuse_backing_id_free(int id, void *p, void *data)
> +static struct fuse_backing *fuse_backing_id_remove(struct fuse_conn *fc, u64 id, bool is_64bit)
>  {
> -       struct fuse_backing *fb = p;
> +       struct fuse_backing *fb;
> +       int err;
>
> -       WARN_ON_ONCE(refcount_read(&fb->count) != 1);
> -       fuse_backing_free(fb);
> -       return 0;
> -}
> +       guard(spinlock)(&fc->lock);
> +       if (!is_64bit)
> +               return idr_remove(&fc->backing_files_map, id);
>
> -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);
> +       fb = rhashtable_lookup_fast(&fc->backing_64_ht, &id, fuse_backing_params);
> +       if (!fb)
> +               return NULL;
> +
> +       err = rhashtable_remove_fast(&fc->backing_64_ht, &fb->hash_node, fuse_backing_params);
> +       WARN_ON(err);
> +
> +       return fb;
>  }
>
>  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;
> +       struct fuse_backing *fb;
> +       bool is_64bit = map->flags & FUSE_BACKING_ID_64;
>         int res;
>
>         pr_debug("%s: fd=%d flags=0x%x\n", __func__, map->fd, map->flags);
> @@ -95,7 +95,10 @@ int fuse_backing_open(struct fuse_conn *fc, struct fuse_backing_map *map)
>                 goto out;
>
>         res = -EINVAL;
> -       if (map->flags || map->padding)
> +       if (map->flags & ~FUSE_BACKING_ID_64)
> +               goto out;
> +
> +       if (!is_64bit && map->backing_id != 0)
>                 goto out;
>
>         file = fget_raw(map->fd);
> @@ -120,9 +123,13 @@ int fuse_backing_open(struct fuse_conn *fc, struct fuse_backing_map *map)
>
>         fb->file = file;
>         fb->cred = get_current_cred();
> +       fb->backing_id = map->backing_id;
>         refcount_set(&fb->count, 1);
>
> -       res = fuse_backing_id_alloc(fc, fb);
> +       if (is_64bit)
> +               res = fuse_backing_add_64(fc, fb);
> +       else
> +               res = fuse_backing_id_alloc(fc, fb);
>         if (res < 0) {
>                 fuse_backing_free(fb);
>                 fb = NULL;
> @@ -138,24 +145,19 @@ int fuse_backing_open(struct fuse_conn *fc, struct fuse_backing_map *map)
>         goto out;
>  }
>
> -int fuse_backing_close(struct fuse_conn *fc, int backing_id)
> +int fuse_backing_close(struct fuse_conn *fc, u64 backing_id, bool is_64bit)
>  {
>         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;
> +       pr_debug("%s: backing_id=%lld\n", __func__, backing_id);

%llu

>
>         err = -EINVAL;
> -       if (backing_id <= 0)
> +       if (!is_64bit && (backing_id == 0 || backing_id > INT_MAX))
>                 goto out;
>
>         err = -ENOENT;
> -       fb = fuse_backing_id_remove(fc, backing_id);
> +       fb = fuse_backing_id_remove(fc, backing_id, is_64bit);
>         if (!fb)
>                 goto out;
>

       fb->backing_id = 0; // mark orphan fb

Because ff->passthrough may still refer to this fb and tracing/debug
may be printing this backing_id which can now be reassigned.

Thanks,
Amir.

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

* Re: [PATCH 07/11] fuse: support opening 64 bit backing ID
  2026-09-22  6:10 ` [PATCH 07/11] fuse: support opening 64 bit " Miklos Szeredi
@ 2026-09-22 10:33   ` Amir Goldstein
  0 siblings, 0 replies; 33+ messages in thread
From: Amir Goldstein @ 2026-09-22 10:33 UTC (permalink / raw)
  To: Miklos Szeredi; +Cc: fuse-devel, John Groves, Darrick J . Wong

On Tue, Sep 22, 2026 at 8:10 AM Miklos Szeredi <mszeredi@redhat.com> wrote:
>
> Add backing_id_64 field to fuse_open_out to allow opening files with
> 64-bit server-allocated backing IDs.
>
> When the server sets FUSE_BACKING_ID_64 in open_out.open_flags, the
> kernel reads the backing ID from open_out.backing_id_64 instead of
> open_out.backing_id.
>
> The open reply is now variable-length for backward compatibility with
> servers that don't send the new backing_id_64 field.
>
> Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>
> ---
>  fs/fuse/dir.c             |  3 ++-
>  fs/fuse/file.c            |  6 +++++-
>  fs/fuse/fuse_i.h          |  4 ++--
>  fs/fuse/iomode.c          | 11 ++++++++++-
>  fs/fuse/passthrough.c     |  6 +++---
>  include/uapi/linux/fuse.h |  4 ++++
>  6 files changed, 26 insertions(+), 8 deletions(-)
>
> diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c
> index f48fafccce4b..874ec7cfffb1 100644
> --- a/fs/fuse/dir.c
> +++ b/fs/fuse/dir.c
> @@ -876,6 +876,7 @@ static int fuse_create_open(struct mnt_idmap *idmap, struct inode *dir,
>         args.out_args[0].value = &outentry;
>         /* Store outarg for fuse_finish_open() */
>         outopenp = &ff->args->open_outarg;
> +       args.out_argvar = true; /* compat */
>         args.out_args[1].size = sizeof(*outopenp);
>         args.out_args[1].value = outopenp;
>
> @@ -885,7 +886,7 @@ static int fuse_create_open(struct mnt_idmap *idmap, struct inode *dir,
>
>         err = fuse_simple_idmap_request(idmap, fm, &args);
>         free_ext_value(&args);
> -       if (err)
> +       if (err < 0)
>                 goto out_free_ff;
>
>         err = -EIO;
> diff --git a/fs/fuse/file.c b/fs/fuse/file.c
> index af26de78a199..8643d9a290ac 100644
> --- a/fs/fuse/file.c
> +++ b/fs/fuse/file.c
> @@ -28,6 +28,7 @@ static int fuse_send_open(struct fuse_mount *fm, u64 nodeid,
>  {
>         struct fuse_open_in inarg;
>         FUSE_ARGS(args);
> +       int res;
>
>         memset(&inarg, 0, sizeof(inarg));
>         inarg.flags = open_flags & ~(O_CREAT | O_EXCL | O_NOCTTY);
> @@ -45,10 +46,13 @@ static int fuse_send_open(struct fuse_mount *fm, u64 nodeid,
>         args.in_args[0].size = sizeof(inarg);
>         args.in_args[0].value = &inarg;
>         args.out_numargs = 1;
> +       args.out_argvar = true; /* compat */
>         args.out_args[0].size = sizeof(*outargp);
>         args.out_args[0].value = outargp;
>
> -       return fuse_simple_request(fm, &args);
> +       res = fuse_simple_request(fm, &args);
> +
> +       return res < 0 ? res : 0;
>  }
>
>  struct fuse_file *fuse_file_alloc(struct fuse_mount *fm, bool release)
> diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h
> index 75c7997098c1..d36459487bee 100644
> --- a/fs/fuse/fuse_i.h
> +++ b/fs/fuse/fuse_i.h
> @@ -28,7 +28,7 @@
>  #include <linux/backing-dev.h>
>  #include <linux/mutex.h>
>  #include <linux/rwsem.h>
> -#include <linux/rbtree_types.h>
> +#include <linux/rbtree.h>
>  #include <linux/poll.h>
>  #include <linux/workqueue.h>
>  #include <linux/kref.h>
> @@ -1312,7 +1312,7 @@ static inline struct fuse_backing *fuse_inode_backing_set(struct fuse_inode *fi,
>  #endif
>  }
>
> -struct fuse_backing *fuse_passthrough_open(struct file *file, int backing_id);
> +struct fuse_backing *fuse_passthrough_open(struct file *file, u64 backing_id, bool is_64bit);
>  void fuse_passthrough_release(struct fuse_file *ff, struct fuse_backing *fb);
>
>  static inline bool fuse_is_passthrough(struct fuse_file *ff)
> diff --git a/fs/fuse/iomode.c b/fs/fuse/iomode.c
> index c25ac580c811..53b0c2a32ed9 100644
> --- a/fs/fuse/iomode.c
> +++ b/fs/fuse/iomode.c
> @@ -170,8 +170,12 @@ static int fuse_file_passthrough_open(struct inode *inode, struct file *file)
>         struct fuse_file *ff = file->private_data;
>         struct fuse_conn *fc = get_fuse_conn(inode);
>         struct fuse_backing *fb;
> +       u64 backing_id;
> +       bool is_64bit = ff->open_flags & FUSE_BACKING_ID_64;
>         int err;
>
> +       ff->open_flags &= ~FUSE_BACKING_ID_64;
> +
>         /* Check allowed conditions for file open in passthrough mode */
>         if (!IS_ENABLED(CONFIG_FUSE_PASSTHROUGH) || !fc->passthrough)
>                 return fuse_EIO("passthrough not enabled");
> @@ -179,7 +183,12 @@ static int fuse_file_passthrough_open(struct inode *inode, struct file *file)
>         if (ff->open_flags & ~FOPEN_PASSTHROUGH_MASK)
>                 return fuse_EIO("conflicting open flags");
>
> -       fb = fuse_passthrough_open(file, ff->args->open_outarg.backing_id);
> +       if (!is_64bit)
> +               backing_id = ff->args->open_outarg.backing_id;
> +       else
> +               backing_id = ff->args->open_outarg.backing_id_64;
> +
> +       fb = fuse_passthrough_open(file, backing_id, is_64bit);
>         if (IS_ERR(fb))
>                 return PTR_ERR(fb);
>
> diff --git a/fs/fuse/passthrough.c b/fs/fuse/passthrough.c
> index 56314dea0b5f..49cae366180d 100644
> --- a/fs/fuse/passthrough.c
> +++ b/fs/fuse/passthrough.c
> @@ -153,17 +153,17 @@ ssize_t fuse_passthrough_mmap(struct file *file, struct vm_area_struct *vma)
>   *
>   * Returns an fb object with elevated refcount to be stored in fuse inode.
>   */
> -struct fuse_backing *fuse_passthrough_open(struct file *file, int backing_id)
> +struct fuse_backing *fuse_passthrough_open(struct file *file, u64 backing_id, bool is_64bit)
>  {
>         struct fuse_file *ff = file->private_data;
>         struct fuse_conn *fc = ff->fm->fc;
>         struct fuse_backing *fb = NULL;
>         struct file *backing_file;
>
> -       if (backing_id <= 0)
> +       if (!is_64bit && (backing_id == 0 || backing_id > INT_MAX))
>                 return fuse_ptr_EIO("invalid backing_id");
>
> -       fb = fuse_backing_lookup(fc, backing_id, false);
> +       fb = fuse_backing_lookup(fc, backing_id, is_64bit);
>         if (!fb)
>                 return fuse_ptr_EIO("backing not found");
>
> diff --git a/include/uapi/linux/fuse.h b/include/uapi/linux/fuse.h
> index f271faa60424..30ec8c124c74 100644
> --- a/include/uapi/linux/fuse.h
> +++ b/include/uapi/linux/fuse.h
> @@ -253,6 +253,7 @@
>   *  - add FUSE_HAS_SYNCFS opt-in flag for privileged userspace servers
>   *  - add FUSE_NOTIFY_BACKING_CLOSE
>   *  - add struct fuse_notify_backing_close_out
> + *  - add backing_id_64 to fuse_open_out
>   *  - add backing_id to fuse_backing_map
>   *  - add FUSE_BACKING_ID_64 (multiple structs)
>   */
> @@ -404,6 +405,7 @@ struct fuse_file_lock {
>   *                           (FUSE_URING_ZERO_COPY) and the request carries page
>   *                           payload. Otherwise reads/writes fall back to
>   *                           copying.
> + * FUSE_BACKING_ID_64: backing ID is server allocated, stored in open_out.backing_id_64
>   */
>  #define FOPEN_DIRECT_IO                (1 << 0)
>  #define FOPEN_KEEP_CACHE       (1 << 1)
> @@ -414,6 +416,7 @@ struct fuse_file_lock {
>  #define FOPEN_PARALLEL_DIRECT_WRITES   (1 << 6)
>  #define FOPEN_PASSTHROUGH      (1 << 7)
>  #define FOPEN_IO_URING_ZERO_COPY (1 << 8)
> +#define FUSE_BACKING_ID_64     (1 << 30) /* used in multiple structs */

This is weird. Why not FOPEN_BACKING_ID_64?

But I would much rather choose a mode at init time for backing ids and then
this flag will not be needed at all.

Thanks,
Amir.

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

* Re: [PATCH 08/11] fuse: add support for opening dax device as backing
  2026-09-22  6:10 ` [PATCH 08/11] fuse: add support for opening dax device as backing Miklos Szeredi
@ 2026-09-22 11:00   ` Amir Goldstein
  2026-10-01 15:06     ` Amir Goldstein
  0 siblings, 1 reply; 33+ messages in thread
From: Amir Goldstein @ 2026-09-22 11:00 UTC (permalink / raw)
  To: Miklos Szeredi; +Cc: fuse-devel, John Groves, Darrick J . Wong

On Tue, Sep 22, 2026 at 8:10 AM Miklos Szeredi <mszeredi@redhat.com> wrote:
>
> Add FUSE_BACKING_IS_DEV flag that allows opening a character device
> (dax device) as a backing, in addition to regular files.
>
> 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 <mszeredi@redhat.com>
> ---
>  fs/fuse/backing.c         | 131 ++++++++++++++++++++++++++------------
>  fs/fuse/file.c            |   2 +-
>  fs/fuse/fuse_i.h          |  28 +++++++-
>  fs/fuse/inode.c           |  11 +++-
>  fs/fuse/passthrough.c     |   8 ++-
>  include/uapi/linux/fuse.h |   3 +
>  6 files changed, 136 insertions(+), 47 deletions(-)
>
> diff --git a/fs/fuse/backing.c b/fs/fuse/backing.c
> index 58dbdd17c1ef..aa558a0c2e64 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);
>  }
>
> @@ -79,23 +87,88 @@ static struct fuse_backing *fuse_backing_id_remove(struct fuse_conn *fc, u64 id,
>         return fb;
>  }
>
> +static int fuse_dax_notify_failure(struct dax_device *daxdev, u64 offset, u64 len, int mf_flags)
> +{
> +       struct fuse_backing *fb = dax_holder(daxdev);
> +
> +       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,
> +                                 bool is_dev)
> +{
> +       struct inode *inode = file_inode(file);
> +       struct dax_device *daxdev;
> +       int err;
> +
> +       switch (inode->i_mode & S_IFMT) {
> +       case S_IFREG:
> +               if (is_dev)
> +                       return -EINVAL;
> +               /* 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 (!is_dev)
> +                       return -EINVAL;
> +               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;
> +
> +       case S_IFDIR:
> +               if (is_dev)
> +                       return -EINVAL;
> +               return -EISDIR;
> +
> +       default:
> +               return -EINVAL;
> +       }
> +}
> +
>  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;
> +       struct fuse_backing *fb = kzalloc_obj(struct fuse_backing);
>         bool is_64bit = map->flags & FUSE_BACKING_ID_64;
>         int res;
>
> -       pr_debug("%s: fd=%d flags=0x%x\n", __func__, map->fd, map->flags);
> +       if (!fb)
> +               return -ENOMEM;
>
> -       /* TODO: relax CAP_SYS_ADMIN once backing files are visible to lsof */
> -       res = -EPERM;
> -       if (!fc->passthrough || !capable(CAP_SYS_ADMIN))
> -               goto out;
> +       fb->backing_id = map->backing_id;
> +       refcount_set(&fb->count, 1);
> +
> +       pr_debug("%s: fd=%d flags=0x%x\n", __func__, map->fd, map->flags);
>
>         res = -EINVAL;
> -       if (map->flags & ~FUSE_BACKING_ID_64)
> +       if (map->flags & ~(FUSE_BACKING_IS_DEV | FUSE_BACKING_ID_64))
>                 goto out;
>
>         if (!is_64bit && map->backing_id != 0)
> @@ -106,43 +179,21 @@ int fuse_backing_open(struct fuse_conn *fc, struct fuse_backing_map *map)
>         if (!file)
>                 goto out;
>
> -       /* read/write/splice/mmap passthrough only relevant for regular files */
> -       res = d_is_dir(file->f_path.dentry) ? -EISDIR : -EINVAL;
> -       if (!d_is_reg(file->f_path.dentry))
> -               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_obj(struct fuse_backing);
> -       res = -ENOMEM;
> -       if (!fb)
> -               goto out_fput;
> -
> -       fb->file = file;
> -       fb->cred = get_current_cred();
> -       fb->backing_id = map->backing_id;
> -       refcount_set(&fb->count, 1);
> +       res = fuse_backing_open_file(fc, fb, file, map->flags & FUSE_BACKING_IS_DEV);
> +       fput(file);
> +       if (res)
> +               goto out;
>
>         if (is_64bit)
>                 res = fuse_backing_add_64(fc, fb);
>         else
>                 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);
> +       pr_debug("%s: ret=%i\n", __func__, res);

why remove fb? it is useful to track fb lifetimes.

> +       if (res < 0)
> +               fuse_backing_free(fb);
>
>         return res;
> -
> -out_fput:
> -       fput(file);
> -       goto out;
>  }
>
>  int fuse_backing_close(struct fuse_conn *fc, u64 backing_id, bool is_64bit)
> diff --git a/fs/fuse/file.c b/fs/fuse/file.c
> index 8643d9a290ac..ea33b8d60473 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 d36459487bee..00c124bcb88d 100644
> --- a/fs/fuse/fuse_i.h
> +++ b/fs/fuse/fuse_i.h
> @@ -93,10 +93,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 */
> @@ -1237,7 +1252,14 @@ void fuse_free_conn(struct fuse_conn *fc);
>
>  /* dax.c */
>
> -#define FUSE_IS_VDAX(inode) (IS_ENABLED(CONFIG_FUSE_DAX) && IS_DAX(inode))
> +static inline bool FUSE_IS_VDAX(struct inode *inode)
> +{
> +#ifdef CONFIG_FUSE_DAX
> +       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 e88daf88f913..e6634a660083 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) {
> +               inode->i_flags |= S_DAX;
> +               inode->i_data.a_ops = &fuse_dax_aops;
> +       }
>  }

Do we need some handling in fuse_change_attributes_i?
like vdax gets to make sure FUSE_ATTR_DAX is consistent with
inode init?

Thanks,
Amir.

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

* Re: [PATCH 09/11] fuse: add extent map data structure
  2026-09-22  6:10 ` [PATCH 09/11] fuse: add extent map data structure Miklos Szeredi
@ 2026-09-22 12:57   ` Amir Goldstein
  0 siblings, 0 replies; 33+ messages in thread
From: Amir Goldstein @ 2026-09-22 12:57 UTC (permalink / raw)
  To: Miklos Szeredi; +Cc: fuse-devel, John Groves, Darrick J . Wong

On Tue, Sep 22, 2026 at 8:10 AM Miklos Szeredi <mszeredi@redhat.com> wrote:
>
> Add support for creating and managing extent maps that map file regions to
> dax device regions.
>
> Introduce FUSE_NOTIFY_MAP for populating extent maps from userspace.
>
> Extent maps are handled as a new type of backing and are assigned a 64 bit
> backing ID by the server.
>
> One extent consists of
>
>  - offset within the containing backing
>  - length of extent
>  - target backing ID
>  - offset into target backing
>
> Extents must be non-overlapping, and can only refer to dax device backings
> for now.
>
> At this point only support creating and populating the extent map in one
> operation, though the interface is not limited by this and later may be
> changed, so that partial population of an extent map would be possible.
>
> Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>
> ---
>  fs/fuse/Makefile          |   2 +-
>  fs/fuse/backing.c         |  23 +++++++
>  fs/fuse/ext_map.c         | 129 ++++++++++++++++++++++++++++++++++++++
>  fs/fuse/fuse_i.h          |  12 +++-
>  fs/fuse/notify.c          |  41 ++++++++++++
>  include/uapi/linux/fuse.h |  30 ++++++++-
>  6 files changed, 234 insertions(+), 3 deletions(-)
>  create mode 100644 fs/fuse/ext_map.c
>
> diff --git a/fs/fuse/Makefile b/fs/fuse/Makefile
> index 245e67852b03..139f37071a78 100644
> --- a/fs/fuse/Makefile
> +++ b/fs/fuse/Makefile
> @@ -15,7 +15,7 @@ fuse-y += dev.o dir.o file.o inode.o control.o xattr.o acl.o readdir.o ioctl.o r
>  fuse-y += poll.o notify.o
>  fuse-y += iomode.o
>  fuse-$(CONFIG_FUSE_DAX) += dax.o
> -fuse-$(CONFIG_FUSE_PASSTHROUGH) += passthrough.o backing.o
> +fuse-$(CONFIG_FUSE_PASSTHROUGH) += passthrough.o backing.o ext_map.o
>  fuse-$(CONFIG_SYSCTL) += sysctl.o
>  fuse-$(CONFIG_FUSE_IO_URING) += dev_uring.o
>
> diff --git a/fs/fuse/backing.c b/fs/fuse/backing.c
> index aa558a0c2e64..484ca29e8aed 100644
> --- a/fs/fuse/backing.c
> +++ b/fs/fuse/backing.c
> @@ -32,6 +32,10 @@ static void fuse_backing_free(struct fuse_backing *fb)
>         case FUSE_BACKING_DAXDEV:
>                 fs_put_dax(fb->dax_dev, fb);
>                 break;
> +
> +       case FUSE_BACKING_EXTMAP:
> +               fuse_ext_map_destroy(&fb->extents);
> +               break;
>         }
>         kfree_rcu(fb, rcu);
>  }
> @@ -252,9 +256,28 @@ static void fuse_backing_rht_free(void *p, void *data)
>
>  void fuse_backing_files_free(struct fuse_conn *fc)
>  {
> +       struct rhashtable_iter iter;
> +       struct fuse_backing *fb;
> +
>         idr_for_each(&fc->backing_files_map, fuse_backing_idr_free, NULL);
>         idr_destroy(&fc->backing_files_map);
>
> +       /*
> +        * extents are referencing other backings, put these refs before
> +        * destroying the backings themselves
> +        */
> +       rhashtable_walk_enter(&fc->backing_64_ht, &iter);
> +       rhashtable_walk_start(&iter);
> +       while ((fb = rhashtable_walk_next(&iter))) {
> +               /* Nothing changing the hash table, -EAGAIN not possible */
> +               if (fb->type == FUSE_BACKING_EXTMAP) {
> +                       fuse_ext_map_destroy(&fb->extents);
> +                       fb->extents.rb_node = NULL;
> +               }
> +       }
> +       rhashtable_walk_stop(&iter);
> +       rhashtable_walk_exit(&iter);
> +
>         rhashtable_free_and_destroy(&fc->backing_64_ht, fuse_backing_rht_free, NULL);
>  }
>
> diff --git a/fs/fuse/ext_map.c b/fs/fuse/ext_map.c
> new file mode 100644
> index 000000000000..e5d257fec88c
> --- /dev/null
> +++ b/fs/fuse/ext_map.c
> @@ -0,0 +1,129 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +
> +#include "fuse_i.h"
> +#include <linux/rbtree.h>
> +#include <linux/pagemap.h>
> +
> +struct fuse_iext {
> +       struct rb_node rb;
> +       u64 start;
> +       u64 end; /* exclusive */
> +       struct fuse_backing *backing;
> +       u64 backing_offset;
> +};
> +
> +void fuse_ext_map_destroy(struct rb_root *extents)
> +{
> +       struct fuse_iext *fie, *tmp;
> +
> +       rbtree_postorder_for_each_entry_safe(fie, tmp, extents, rb) {
> +               fuse_backing_put(fie->backing);
> +               kfree(fie);
> +       }
> +}
> +
> +static int fuse_add_extent(struct fuse_conn *fc, struct rb_root *extents,
> +                          struct fuse_extent *ext)
> +{
> +       struct fuse_iext *new_fie __free(kfree) = kzalloc_obj(*new_fie);
> +       struct rb_node *parent = NULL, **link = &extents->rb_node;
> +       loff_t end;
> +
> +       if (!new_fie)
> +               return -ENOMEM;
> +
> +       if (ext->reserved[0] || ext->reserved[1])
> +               return fuse_EIO("reserved fields set");
> +
> +       if (!PAGE_ALIGNED(ext->offset) || !PAGE_ALIGNED(ext->length) || !PAGE_ALIGNED(ext->addr))
> +               return fuse_EIO("not page aligned");
> +
> +       if (!ext->length)
> +               return fuse_EIO("zero sized extent");
> +
> +       if (overflows_type(ext->offset, loff_t) ||
> +           check_add_overflow(ext->offset, ext->length, &end))
> +               return fuse_EIO("offset overflow");
> +
> +       new_fie->start = ext->offset;
> +       new_fie->end = end;
> +       new_fie->backing_offset = ext->addr;
> +
> +       while (*link) {
> +               struct fuse_iext *fie = rb_entry(*link, typeof(*fie), rb);
> +
> +               parent = *link;
> +               if (new_fie->end <= fie->start)
> +                       link = &parent->rb_left;
> +               else if (new_fie->start >= fie->end)
> +                       link = &parent->rb_right;
> +               else
> +                       return fuse_EIO("overlap");
> +       }
> +
> +       new_fie->backing = fuse_backing_lookup(fc, ext->backing_id, true);
> +       if (!new_fie->backing)
> +               return fuse_EIO("backing not found");
> +
> +       if (new_fie->backing->type != FUSE_BACKING_DAXDEV) {
> +               fuse_backing_put(new_fie->backing);
> +               return fuse_EIO("backing is not dax device");
> +       }
> +
> +       rb_link_node(&new_fie->rb, parent, link);
> +       rb_insert_color(&no_free_ptr(new_fie)->rb, extents);
> +
> +       return 0;
> +}
> +
> +bool fuse_ext_map_is_dax(struct fuse_backing *fb)
> +{
> +       struct fuse_iext *fie;
> +
> +       if (WARN_ON(RB_EMPTY_ROOT(&fb->extents)))
> +               return false;
> +
> +       fie = rb_entry(fb->extents.rb_node, typeof(*fie), rb);
> +       return fie->backing->type == FUSE_BACKING_DAXDEV;
> +}
> +
> +int fuse_ext_map_populate(struct fuse_conn *fc, struct fuse_notify_map_out *arg,
> +                         struct fuse_extent *ext)
> +{
> +       struct fuse_backing *fb;
> +       unsigned int i;
> +       int err;
> +
> +       if (!arg->num_extents)
> +               return fuse_EIO("no extents");
> +
> +       if (arg->flags & FUSE_MAP_BACKING_CREATE) {
> +               fb = kzalloc_obj(*fb);
> +               if (!fb)
> +                       return -ENOMEM;
> +
> +               refcount_set(&fb->count, 1);
> +               fb->type = FUSE_BACKING_EXTMAP;
> +               fb->backing_id = arg->backing_id;
> +       } else {
> +               /* Adding extents to an existing extmap backing is not yet supported */
> +               return -EINVAL;
> +       }
> +
> +       for (i = 0; i < arg->num_extents; i++) {
> +               err = fuse_add_extent(fc, &fb->extents, &ext[i]);
> +               if (err)
> +                       goto err_put;
> +       }
> +
> +       err = 0;
> +       if (arg->flags & FUSE_MAP_BACKING_CREATE) {
> +               err = fuse_backing_add_64(fc, fb);
> +               if (!err)
> +                       return 0;
> +       }
> +
> +err_put:
> +       fuse_backing_put(fb);
> +       return err;
> +}
> diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h
> index 00c124bcb88d..928e5c43e43c 100644
> --- a/fs/fuse/fuse_i.h
> +++ b/fs/fuse/fuse_i.h
> @@ -28,7 +28,7 @@
>  #include <linux/backing-dev.h>
>  #include <linux/mutex.h>
>  #include <linux/rwsem.h>
> -#include <linux/rbtree.h>
> +#include <linux/rbtree_types.h>
>  #include <linux/poll.h>
>  #include <linux/workqueue.h>
>  #include <linux/kref.h>
> @@ -96,6 +96,7 @@ struct fuse_submount_lookup {
>  enum fuse_backing_type {
>         FUSE_BACKING_PATH,
>         FUSE_BACKING_DAXDEV,
> +       FUSE_BACKING_EXTMAP,
>  };
>
>  /* Container for data related to mapping to backing file */
> @@ -111,6 +112,9 @@ struct fuse_backing {
>                         struct dax_device *dax_dev;
>                         bool dax_error;
>                 };
> +               struct {
> +                       struct rb_root extents;
> +               };
>         };
>         u64 backing_id;
>         struct rhash_head hash_node;
> @@ -1360,4 +1364,10 @@ extern void fuse_sysctl_unregister(void);
>  #define fuse_sysctl_unregister()       do { } while (0)
>  #endif /* CONFIG_SYSCTL */
>
> +/* ext_map.c */
> +
> +void fuse_ext_map_destroy(struct rb_root *extents);
> +bool fuse_ext_map_is_dax(struct fuse_backing *fb);
> +int fuse_ext_map_populate(struct fuse_conn *fc, struct fuse_notify_map_out *arg,
> +                         struct fuse_extent *ext);
>  #endif /* _FS_FUSE_I_H */
> diff --git a/fs/fuse/notify.c b/fs/fuse/notify.c
> index 7361b56bb973..ce6d85834ea5 100644
> --- a/fs/fuse/notify.c
> +++ b/fs/fuse/notify.c
> @@ -429,6 +429,44 @@ static int fuse_notify_backing_close(struct fuse_conn *fc, unsigned int size,
>
>  }
>
> +static int fuse_notify_map(struct fuse_conn *fc, unsigned int size,
> +                          struct fuse_copy_state *cs)
> +{
> +       struct fuse_notify_map_out outarg;
> +       struct fuse_extent *ext __free(kvfree) = NULL;
> +       int err;
> +
> +       if (size < sizeof(outarg))
> +               return -EINVAL;
> +
> +       err = fuse_copy_one(cs, &outarg, sizeof(outarg));
> +       if (err)
> +               return err;
> +
> +       if (outarg.num_extents > FUSE_MAX_EXTENTS)
> +               return -EINVAL;
> +
> +       size -= sizeof(outarg);
> +       if (size != outarg.num_extents * sizeof(*ext))
> +               return -EINVAL;
> +
> +       if (outarg.reserved[0] != 0 || outarg.reserved[1] != 0)
> +               return -EINVAL;
> +
> +       if (outarg.flags & ~FUSE_MAP_BACKING_CREATE)
> +               return -EINVAL;
> +
> +       ext = kvmalloc_objs(*ext, outarg.num_extents);
> +       if (!ext)
> +               return -ENOMEM;
> +
> +       err = fuse_copy_one(cs, ext, size);
> +       if (err)
> +               return err;
> +
> +       return fuse_ext_map_populate(fc, &outarg, ext);
> +}
> +
>  int fuse_notify(struct fuse_conn *fc, enum fuse_notify_code code,
>                 unsigned int size, struct fuse_copy_state *cs)
>  {
> @@ -463,6 +501,9 @@ int fuse_notify(struct fuse_conn *fc, enum fuse_notify_code code,
>         case FUSE_NOTIFY_BACKING_CLOSE:
>                 return fuse_notify_backing_close(fc, size, cs);
>
> +       case FUSE_NOTIFY_MAP:
> +               return fuse_notify_map(fc, size, cs);
> +
>         default:
>                 return -EINVAL;
>         }
> diff --git a/include/uapi/linux/fuse.h b/include/uapi/linux/fuse.h
> index 1ba28a2885ad..5368889710c7 100644
> --- a/include/uapi/linux/fuse.h
> +++ b/include/uapi/linux/fuse.h
> @@ -251,12 +251,15 @@
>   *
>   *  7.47
>   *  - add FUSE_HAS_SYNCFS opt-in flag for privileged userspace servers
> - *  - add FUSE_NOTIFY_BACKING_CLOSE
> + *  - add FUSE_NOTIFY_BACKING_CLOSE, FUSE_NOTIFY_MAP
>   *  - add struct fuse_notify_backing_close_out
> + *  - add struct fuse_notify_map_out
> + *  - add struct fuse_extent
>   *  - add backing_id_64 to fuse_open_out
>   *  - add backing_id to fuse_backing_map
>   *  - add FUSE_BACKING_IS_DEV (fuse_backing_map.flags)
>   *  - add FUSE_BACKING_ID_64 (multiple structs)
> + *  - add FUSE_MAP_BACKING_CREATE (fuse_notify_map_out.flags)
>   */
>
>  #ifndef _LINUX_FUSE_H
> @@ -718,6 +721,7 @@ enum fuse_notify_code {
>         FUSE_NOTIFY_INC_EPOCH = 8,
>         FUSE_NOTIFY_PRUNE = 9,
>         FUSE_NOTIFY_BACKING_CLOSE = 10,
> +       FUSE_NOTIFY_MAP = 11,
>  };
>
>  /* The read buffer is required to be at least 8k, but may be much larger */
> @@ -1217,6 +1221,30 @@ struct fuse_notify_backing_close_out {
>         uint64_t        reserved;
>  };
>
> +/**
> + * notify_map flags
> + *
> + * FUSE_MAP_BACKING_CREATE:    create backing with the supplied ID
> + */
> +#define FUSE_MAP_BACKING_CREATE        (1 << 0)
> +
> +struct fuse_notify_map_out {
> +       uint64_t        backing_id;
> +       uint32_t        num_extents;
> +       uint32_t        flags;
> +       uint64_t        reserved[2];
> +};
> +
> +#define FUSE_MAX_EXTENTS 1638
> +

I'm curious what's the math behind this :)

> +struct fuse_extent {
> +       uint64_t        offset;         /* offset of extent into file */
> +       uint64_t        length;         /* extent length */
> +       uint64_t        backing_id;     /* target backing */
> +       uint64_t        addr;           /* target offset within backing file/device */
> +       uint64_t        reserved[2];
> +};
> +
>  #define FUSE_SETUPMAPPING_FLAG_WRITE (1ull << 0)
>  #define FUSE_SETUPMAPPING_FLAG_READ (1ull << 1)
>  struct fuse_setupmapping_in {
> --
> 2.54.0

Reviewed-by: Amir Goldstein <amir73il@gmail.com>

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

* Re: [PATCH 10/11] fuse: add extent map I/O support
  2026-09-22  6:10 ` [PATCH 10/11] fuse: add extent map I/O support Miklos Szeredi
@ 2026-09-22 13:04   ` Amir Goldstein
  0 siblings, 0 replies; 33+ messages in thread
From: Amir Goldstein @ 2026-09-22 13:04 UTC (permalink / raw)
  To: Miklos Szeredi; +Cc: fuse-devel, John Groves, Darrick J . Wong

On Tue, Sep 22, 2026 at 8:10 AM Miklos Szeredi <mszeredi@redhat.com> 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.
>
> Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>
> ---
>  fs/fuse/backing.c     |  15 +++++
>  fs/fuse/ext_map.c     | 138 ++++++++++++++++++++++++++++++++++++++++++
>  fs/fuse/fuse_i.h      |   4 ++
>  fs/fuse/passthrough.c |  27 +++++++--
>  4 files changed, 180 insertions(+), 4 deletions(-)
>
> diff --git a/fs/fuse/backing.c b/fs/fuse/backing.c
> index 484ca29e8aed..14875d48476e 100644
> --- a/fs/fuse/backing.c
> +++ b/fs/fuse/backing.c
> @@ -224,6 +224,21 @@ int fuse_backing_close(struct fuse_conn *fc, u64 backing_id, bool is_64bit)
>         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, bool is_64bit)
>  {
>         struct fuse_backing *fb;
> diff --git a/fs/fuse/ext_map.c b/fs/fuse/ext_map.c
> index e5d257fec88c..c752b6ef0686 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,124 @@ 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);
> +       }
> +
> +       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);
> +
> +       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);
> +
> +       file_accessed(iocb->ki_filp);
> +       return res;
> +}
> +
> +ssize_t fuse_ext_map_write_iter(struct kiocb *iocb, struct iov_iter *from)
> +{
> +       fuse_rw_clamp(iocb, from);
> +
> +       if (!iov_iter_count(from))
> +               return 0;
> +
> +       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 928e5c43e43c..50b61e16a15f 100644
> --- a/fs/fuse/fuse_i.h
> +++ b/fs/fuse/fuse_i.h
> @@ -1308,6 +1308,7 @@ void fuse_backing_put(struct fuse_backing *fb);
>
>  struct fuse_backing *fuse_backing_lookup(struct fuse_conn *fc, u64 backing_id, bool is_64bit);
>  int fuse_backing_add_64(struct fuse_conn *fc, struct fuse_backing *fb);
> +bool fuse_backing_is_dax(struct fuse_backing *fb);
>  #else
>
>  static inline void fuse_backing_put(struct fuse_backing *fb)
> @@ -1367,6 +1368,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_map_out *arg,
>                           struct fuse_extent *ext);
> diff --git a/fs/fuse/passthrough.c b/fs/fuse/passthrough.c
> index adc9d2d8e203..5b0886562408 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,6 +73,9 @@ ssize_t fuse_passthrough_write_iter(struct kiocb *iocb,
>         if (!count)
>                 return 0;
>
> +       if (!backing_file)
> +               return fuse_ext_map_write_iter(iocb, iter);
> +
>         inode_lock(inode);
>         ret = backing_file_write_iter(backing_file, iter, iocb, iocb->ki_flags,
>                                       &ctx);
> @@ -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);
>  }
>
> @@ -167,6 +178,14 @@ struct fuse_backing *fuse_passthrough_open(struct file *file, u64 backing_id, bo
>         if (!fb)
>                 return fuse_ptr_EIO("backing not found");
>
> +       if (fb->type == FUSE_BACKING_EXTMAP) {
> +               if (fuse_backing_is_dax(fb) != !!IS_DAX(file_inode(file))) {
> +                       fuse_backing_put(fb);
> +                       return fuse_ptr_EIO("dax mode mismatch");
> +               }
> +               return fb;
> +       }
> +
>         if (fb->type != FUSE_BACKING_PATH) {
>                 fuse_backing_put(fb);
>                 return fuse_ptr_EIO("invalid backing type");
> --
> 2.54.0
>

Reviewed-by: Amir Goldstein <amir73il@gmail.com>

More for the backing parts, less for the DAX parts.

Thanks,
Amir.

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

* Re: [PATCH 01/11] dax: replace exported dax_dev_get() with non-allocating dax_dev_find()
  2026-09-22  6:10 ` [PATCH 01/11] dax: replace exported dax_dev_get() with non-allocating dax_dev_find() Miklos Szeredi
  2026-09-22  7:36   ` Amir Goldstein
@ 2026-09-22 19:07   ` Alison Schofield
  2026-09-28  9:16     ` Miklos Szeredi
  1 sibling, 1 reply; 33+ messages in thread
From: Alison Schofield @ 2026-09-22 19:07 UTC (permalink / raw)
  To: Miklos Szeredi
  Cc: fuse-devel, John Groves, Amir Goldstein, Darrick J . Wong,
	Dave Jiang

On Tue, Sep 22, 2026 at 08:10:01AM +0200, Miklos Szeredi wrote:
> From: John Groves <John@Groves.net>
> 
> This fix is in response to a Sashiko review, and some subsequent
> analysis.
> 
> dax_dev_get() uses iget5_locked() which creates a new inode if no
> matching one exists. This is correct for the internal caller
> (alloc_dax), but dangerous for external callers that look up devices
> from user-supplied or metadata-supplied dev_t values:
> 
> 1. A new inode is created with DAXDEV_ALIVE set but no backing driver,
>    no ops, and no IDA-allocated minor number.
> 
> 2. On teardown, dax_destroy_inode() warns because kill_dax() was never
>    called, and dax_free_inode() calls ida_free() for a minor that was
>    never ida_alloc'd -- potentially freeing the minor of a real device.
> 
> Add dax_dev_find() which uses ilookup5() for lookup-only semantics:
> it returns an existing dax_device with an elevated inode reference, or
> NULL if no device with the given dev_t exists. It never creates inodes.
> A dax_alive() check under dax_read_lock() guards against returning a
> device that is concurrently being torn down by kill_dax().
> 
> Make dax_dev_get() static again (internal to super.c for alloc_dax),
> export dax_dev_find() instead, and update the two external callers
> (famfs_inode.c, famfs.c). Also add the missing CONFIG_DAX=n stub.
> 
> About the 'fixes' tag: this removes the export of dax_dev_get(),
> which was flawed, and replaces is with dax_dev_find(). It feels like
> the fixes tag makes sense for correcting an ABI error.
> 
> Fixes: 2ae624d5a555d ("dax: export dax_dev_get()")
> Reviewed-by: Dave Jiang <dave.jiang@intel.com>
> Reviewed-by: Alison Schofield <alison.schofield@intel.com>
> Reviewed-by: "Darrick J. Wong" <djwong@kernel.org>
> Signed-off-by: John Groves <john@groves.net>
> Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>

A couple of process things
- use get_maintainers to send this patch to the correct folks, including
  the correct mailing list, nvdimm.
- those folks and the nvdimm list should be included for the entire
  series so reviewers of this one patch can see what this is a part of.

So what is the thinking today?  Will the person who merges this
fuse-devel list work include the DAX patch in the pull request or should
I (as the dax/bus patch wrangler) plan to apply this patch?

FWIW, we looked at this patch last merge window and decided to
wait for fuse to land before making further changes to DAX for FUSE.

-- Alison


  



> ---
>  drivers/dax/super.c | 38 ++++++++++++++++++++++++++++++++++++--
>  include/linux/dax.h |  6 +++++-
>  2 files changed, 41 insertions(+), 3 deletions(-)
> 
> diff --git a/drivers/dax/super.c b/drivers/dax/super.c
> index 45f84b0eb909..824e1f6df378 100644
> --- a/drivers/dax/super.c
> +++ b/drivers/dax/super.c
> @@ -565,7 +565,7 @@ static int dax_set(struct inode *inode, void *data)
>  	return 0;
>  }
>  
> -struct dax_device *dax_dev_get(dev_t devt)
> +static struct dax_device *dax_dev_get(dev_t devt)
>  {
>  	struct dax_device *dax_dev;
>  	struct inode *inode;
> @@ -588,7 +588,41 @@ struct dax_device *dax_dev_get(dev_t devt)
>  
>  	return dax_dev;
>  }
> -EXPORT_SYMBOL_GPL(dax_dev_get);
> +
> +/**
> + * dax_dev_find - look up an existing dax_device by dev_t
> + * @devt: the device number to find
> + *
> + * Returns a dax_device with an elevated inode reference, or NULL if no
> + * device with the given dev_t exists. Unlike dax_dev_get(), this never
> + * allocates a new inode -- it is safe for external callers that are looking
> + * up devices from user-supplied or metadata-supplied dev_t values.
> + *
> + * Caller must put_dax() the returned device when done.
> + */
> +struct dax_device *dax_dev_find(dev_t devt)
> +{
> +	struct dax_device *dax_dev;
> +	struct inode *inode;
> +	int id;
> +
> +	inode = ilookup5(dax_superblock, hash_32(devt + DAXFS_MAGIC, 31),
> +			 dax_test, &devt);
> +	if (!inode)
> +		return NULL;
> +
> +	dax_dev = to_dax_dev(inode);
> +	id = dax_read_lock();
> +	if (!dax_alive(dax_dev)) {
> +		dax_read_unlock(id);
> +		iput(inode);
> +		return NULL;
> +	}
> +	dax_read_unlock(id);
> +
> +	return dax_dev;
> +}
> +EXPORT_SYMBOL_GPL(dax_dev_find);
>  
>  struct dax_device *alloc_dax(void *private, const struct dax_operations *ops)
>  {
> diff --git a/include/linux/dax.h b/include/linux/dax.h
> index fe6c3ded1b50..29113eb95e72 100644
> --- a/include/linux/dax.h
> +++ b/include/linux/dax.h
> @@ -54,7 +54,7 @@ struct dax_device *alloc_dax(void *private, const struct dax_operations *ops);
>  void *dax_holder(struct dax_device *dax_dev);
>  void put_dax(struct dax_device *dax_dev);
>  void kill_dax(struct dax_device *dax_dev);
> -struct dax_device *dax_dev_get(dev_t devt);
> +struct dax_device *dax_dev_find(dev_t devt);
>  void dax_write_cache(struct dax_device *dax_dev, bool wc);
>  bool dax_write_cache_enabled(struct dax_device *dax_dev);
>  bool dax_synchronous(struct dax_device *dax_dev);
> @@ -92,6 +92,10 @@ static inline void put_dax(struct dax_device *dax_dev)
>  static inline void kill_dax(struct dax_device *dax_dev)
>  {
>  }
> +static inline struct dax_device *dax_dev_find(dev_t devt)
> +{
> +	return NULL;
> +}
>  static inline void dax_write_cache(struct dax_device *dax_dev, bool wc)
>  {
>  }
> -- 
> 2.54.0
> 

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

* Re: [PATCH 11/11] fuse: add support for striped backing
  2026-09-22  6:10 ` [PATCH 11/11] fuse: add support for striped backing Miklos Szeredi
@ 2026-09-23  5:41   ` Amir Goldstein
  0 siblings, 0 replies; 33+ messages in thread
From: Amir Goldstein @ 2026-09-23  5:41 UTC (permalink / raw)
  To: Miklos Szeredi; +Cc: fuse-devel, John Groves, Darrick J . Wong

On Tue, Sep 22, 2026 at 8:10 AM Miklos Szeredi <mszeredi@redhat.com> wrote:
>
> Add the FUSE_MAP_CYCLIC flag for FUSE_NOTIFY_MAP notifications to enable
> creating striped maps.
>
> This is just a set of regular extents that repeats after the end of the
> last extent using a striping pattern (i.e. on the target backings the
> stripes have a contiguous footprint).
>
> Two additional limits apply compared to non-cyclic extent map population:
>
>  - the size of the extents (chunk size) must be equal
>
>  - the extents must start from zero offset and follow the previous without
>    one with a gap.

some mixup here -
follow the previous one without a gap
I presume

Reviewed-by: Amir Goldstein <amir73il@gmail.com>

>
> Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>
> ---
>  fs/fuse/ext_map.c         | 27 ++++++++++++++++++++++++---
>  fs/fuse/fuse_i.h          |  1 +
>  fs/fuse/notify.c          |  2 +-
>  include/uapi/linux/fuse.h |  4 +++-
>  4 files changed, 29 insertions(+), 5 deletions(-)
>
> diff --git a/fs/fuse/ext_map.c b/fs/fuse/ext_map.c
> index c752b6ef0686..52f3fb0bb571 100644
> --- a/fs/fuse/ext_map.c
> +++ b/fs/fuse/ext_map.c
> @@ -101,12 +101,18 @@ static int fuse_ext_map_iomap_begin(struct inode *inode, loff_t offset, loff_t l
>  {
>         struct fuse_backing *fb = fuse_inode_backing(get_fuse_inode(inode));
>         struct fuse_iext *fie;
> +       u64 ncycle = 0, seq_off = 0, addr, tmp;
>         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 (fb->cycle_length) {
> +               ncycle = offset / fb->cycle_length;
> +               seq_off = ncycle * fb->cycle_length;
> +       }
> +
> +       fie = fuse_find_extent(&fb->extents, offset - seq_off);
>         if (!fie)
>                 return fuse_EIO("missing mapping");
>
> @@ -119,9 +125,12 @@ static int fuse_ext_map_iomap_begin(struct inode *inode, loff_t offset, loff_t l
>         }
>
>         ext_len = fie->end - fie->start;
> +       addr = fie->backing_offset;
> +       if (check_mul_overflow(ncycle, ext_len, &tmp) || check_add_overflow(addr, tmp, &addr))
> +               return fuse_EIO("extent address overflow");
>
> -       iomap->offset = fie->start;
> -       iomap->addr = fie->backing_offset;
> +       iomap->offset = fie->start + seq_off;
> +       iomap->addr = addr;
>         iomap->length = ext_len;
>         iomap->dax_dev = fie->backing->dax_dev;
>         iomap->type = IOMAP_MAPPED;
> @@ -231,6 +240,7 @@ int fuse_ext_map_populate(struct fuse_conn *fc, struct fuse_notify_map_out *arg,
>         struct fuse_backing *fb;
>         unsigned int i;
>         int err;
> +       u64 chunk_size = 0;
>
>         if (!arg->num_extents)
>                 return fuse_EIO("no extents");
> @@ -248,7 +258,18 @@ int fuse_ext_map_populate(struct fuse_conn *fc, struct fuse_notify_map_out *arg,
>                 return -EINVAL;
>         }
>
> +       if (arg->flags & FUSE_MAP_CYCLIC) {
> +               chunk_size = ext[0].length;
> +               err = -EINVAL;
> +               if (check_mul_overflow(chunk_size, arg->num_extents, &fb->cycle_length))
> +                       goto err_put;
> +       }
> +
>         for (i = 0; i < arg->num_extents; i++) {
> +               err = -EINVAL;
> +               if (chunk_size && (ext[i].offset != chunk_size * i || ext[i].length != chunk_size))
> +                       goto err_put;
> +
>                 err = fuse_add_extent(fc, &fb->extents, &ext[i]);
>                 if (err)
>                         goto err_put;
> diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h
> index 50b61e16a15f..a34db4ad4bc4 100644
> --- a/fs/fuse/fuse_i.h
> +++ b/fs/fuse/fuse_i.h
> @@ -114,6 +114,7 @@ struct fuse_backing {
>                 };
>                 struct {
>                         struct rb_root extents;
> +                       u64 cycle_length;
>                 };
>         };
>         u64 backing_id;
> diff --git a/fs/fuse/notify.c b/fs/fuse/notify.c
> index ce6d85834ea5..cbaa953e9db5 100644
> --- a/fs/fuse/notify.c
> +++ b/fs/fuse/notify.c
> @@ -453,7 +453,7 @@ static int fuse_notify_map(struct fuse_conn *fc, unsigned int size,
>         if (outarg.reserved[0] != 0 || outarg.reserved[1] != 0)
>                 return -EINVAL;
>
> -       if (outarg.flags & ~FUSE_MAP_BACKING_CREATE)
> +       if (outarg.flags & ~(FUSE_MAP_BACKING_CREATE | FUSE_MAP_CYCLIC))
>                 return -EINVAL;
>
>         ext = kvmalloc_objs(*ext, outarg.num_extents);
> diff --git a/include/uapi/linux/fuse.h b/include/uapi/linux/fuse.h
> index 5368889710c7..c100e6d78f7e 100644
> --- a/include/uapi/linux/fuse.h
> +++ b/include/uapi/linux/fuse.h
> @@ -259,7 +259,7 @@
>   *  - add backing_id to fuse_backing_map
>   *  - add FUSE_BACKING_IS_DEV (fuse_backing_map.flags)
>   *  - add FUSE_BACKING_ID_64 (multiple structs)
> - *  - add FUSE_MAP_BACKING_CREATE (fuse_notify_map_out.flags)
> + *  - add FUSE_MAP_CYCLIC, FUSE_MAP_BACKING_CREATE (fuse_map_out.flags)
>   */
>
>  #ifndef _LINUX_FUSE_H
> @@ -1225,8 +1225,10 @@ struct fuse_notify_backing_close_out {
>   * notify_map flags
>   *
>   * FUSE_MAP_BACKING_CREATE:    create backing with the supplied ID
> + * FUSE_MAP_CYCLIC:            map repeats after last extent
>   */
>  #define FUSE_MAP_BACKING_CREATE        (1 << 0)
> +#define FUSE_MAP_CYCLIC                (1 << 1)
>
>  struct fuse_notify_map_out {
>         uint64_t        backing_id;
> --
> 2.54.0
>

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

* Re: [PATCH 06/11] fuse: support 64 bit, server allocated backing ID
  2026-09-22  6:10 ` [PATCH 06/11] fuse: support 64 bit, server allocated backing ID Miklos Szeredi
  2026-09-22 10:29   ` Amir Goldstein
@ 2026-09-23  7:08   ` Amir Goldstein
  2026-09-30 10:15     ` Miklos Szeredi
  1 sibling, 1 reply; 33+ messages in thread
From: Amir Goldstein @ 2026-09-23  7:08 UTC (permalink / raw)
  To: Miklos Szeredi; +Cc: fuse-devel, John Groves, Darrick J . Wong, Joanne Koong

On Tue, Sep 22, 2026 at 8:10 AM Miklos Szeredi <mszeredi@redhat.com> wrote:
>
> Add support for server allocated 64-bit backing IDs alongside the
> existing kernel allocated 32-bit IDs.
>
> Opened with FUSE_DEV_IOC_BACKING_OPEN, but the backing ID is sent via
> fuse_backing_map.backing_id, with FUSE_BACKING_ID_64 being set in .flags.
>
> Closed with FUSE_NOTIFY_BACKING_CLOSE.  Since close only provides the
> backing ID, not the file descriptor, it doesn't have to be done with an
> ioctl.
>
> All 64 bit values are valid.
>
> Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>
> ---
[...]

> diff --git a/include/uapi/linux/fuse.h b/include/uapi/linux/fuse.h
> index 10a7f31c4bdf..f271faa60424 100644
> --- a/include/uapi/linux/fuse.h
> +++ b/include/uapi/linux/fuse.h
> @@ -251,6 +251,10 @@
>   *
>   *  7.47
>   *  - add FUSE_HAS_SYNCFS opt-in flag for privileged userspace servers
> + *  - add FUSE_NOTIFY_BACKING_CLOSE
> + *  - add struct fuse_notify_backing_close_out
> + *  - add backing_id to fuse_backing_map
> + *  - add FUSE_BACKING_ID_64 (multiple structs)
>   */
>
>  #ifndef _LINUX_FUSE_H
> @@ -709,6 +713,7 @@ enum fuse_notify_code {
>         FUSE_NOTIFY_RESEND = 7,
>         FUSE_NOTIFY_INC_EPOCH = 8,
>         FUSE_NOTIFY_PRUNE = 9,
> +       FUSE_NOTIFY_BACKING_CLOSE = 10,
>  };
>
>  /* The read buffer is required to be at least 8k, but may be much larger */
> @@ -1153,10 +1158,17 @@ struct fuse_notify_prune_out {
>         uint64_t        spare;
>  };
>
> +/**
> + * flags for fuse_backing_map
> + *
> + * FUSE_BACKING_ID_64: backing ID is server allocated, stored in @backing_id
> + */
> +#define FUSE_BACKING_ID_64     (1 << 30) /* used in multiple structs */
> +
>  struct fuse_backing_map {
>         int32_t         fd;
>         uint32_t        flags;
> -       uint64_t        padding;
> +       uint64_t        backing_id;
>  };
>
>  /* Device ioctls: */
> @@ -1193,6 +1205,11 @@ struct fuse_copy_file_range_out {
>         uint64_t        bytes_copied;
>  };
>
> +struct fuse_notify_backing_close_out {
> +       uint64_t        backing_id;
> +       uint64_t        reserved;
> +};
> +

On second look at this uAPI and after failing to retrofit ops_mask into
fuse_backing_map, I think it would be better, along with init flag
FUSE_PASSTHROUGH_V2, to decommission both old ioctls
and use new ioctl with consistent verbs for the new notify's:

/**
 * flags for fuse_backing_map
 *
 * FUSE_BACKING_IS_DEV: @fd refers to a device file
 */
#define FUSE_BACKING_IS_DEV     (1 << 0)

struct fuse_backing_map_v1 {
        int32_t         fd;
        uint32_t        flags;
        uint64_t        padding;
};

struct fuse_backing_map {
        int32_t         fd;
        uint32_t        flags;
        uint64_t        backing_id;
        uint64_t        reserved[2]; // for ops_mask etc
};

...

#define FUSE_DEV_IOC_BACKING_OPEN       _IOW(FUSE_DEV_IOC_MAGIC, 1, \
                                             struct fuse_backing_map_v1)
#define FUSE_DEV_IOC_BACKING_CLOSE      _IOW(FUSE_DEV_IOC_MAGIC, 2, uint32_t)
...

#define FUSE_DEV_IOC_BACKING_MAP        _IOW(FUSE_DEV_IOC_MAGIC, 4, \
                                             struct fuse_backing_map)
...
        FUSE_NOTIFY_BACKING_MAP = 10,
        FUSE_NOTIFY_BACKING_UNMAP = 11,

This way the backing unmap notify verb matches the ioctl and notify
backing map verbs and FUSE_NOTIFY_MAP is disambiguated from
the non-backing FUSE_SETUPMAPPING.

And we don't really need FUSE_BACKING_ID_64 flag in this API,
whose meaning is not very descriptive of its API overloading nature.

Thanks,
Amir.

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

* Re: [PATCH 01/11] dax: replace exported dax_dev_get() with non-allocating dax_dev_find()
  2026-09-22 19:07   ` Alison Schofield
@ 2026-09-28  9:16     ` Miklos Szeredi
  0 siblings, 0 replies; 33+ messages in thread
From: Miklos Szeredi @ 2026-09-28  9:16 UTC (permalink / raw)
  To: Alison Schofield
  Cc: Miklos Szeredi, fuse-devel, John Groves, Amir Goldstein,
	Darrick J . Wong, Dave Jiang

On Tue, 22 Sept 2026 at 21:07, Alison Schofield
<alison.schofield@intel.com> wrote:

> A couple of process things
> - use get_maintainers to send this patch to the correct folks, including
>   the correct mailing list, nvdimm.
> - those folks and the nvdimm list should be included for the entire
>   series so reviewers of this one patch can see what this is a part of.

Okay.

Since it has the RvB from two maintainers, I didn't think there was
anything to do on my side.

Side note: RvB and Acked-By from a maintainer seems sort of
equivalent, though Acked-by is I think the more formal way of
approving something that will be merged via a different tree.

> So what is the thinking today?  Will the person who merges this
> fuse-devel list work include the DAX patch in the pull request or should
> I (as the dax/bus patch wrangler) plan to apply this patch?

Simplest is if I push this together with the fuse stuff.

Thanks,
Miklos

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

* Re: [PATCH 02/11] fuse: use "vdax" naming for virtiofs DAX
  2026-09-22  8:43   ` Amir Goldstein
@ 2026-09-28 10:38     ` Miklos Szeredi
  2026-10-01 13:18       ` Amir Goldstein
  0 siblings, 1 reply; 33+ messages in thread
From: Miklos Szeredi @ 2026-09-28 10:38 UTC (permalink / raw)
  To: Amir Goldstein; +Cc: Miklos Szeredi, fuse-devel, John Groves, Darrick J . Wong

On Tue, 22 Sept 2026 at 10:47, Amir Goldstein <amir73il@gmail.com> wrote:
>
> On Tue, Sep 22, 2026 at 8:10 AM Miklos Szeredi <mszeredi@redhat.com> wrote:
> >
> > We are introducing DAX functionality largely unrelated to the virtiofs
> > code.  Rename dax -> vdax for the virtiofs case for clarity.
> >
> > Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>
>
> I am sure it has crossed your mind that leaving CONFIG_FUSE_DAX
> all over the place is ugly and just as confusing.
>
> You may want to create an alias in Kconfig and rename the checks
> in the code to FUSE_VDAX:
>
> config FUSE_VDAX
>         def_bool FUSE_DAX

But then the .config file will contain redundant info and be confusing.

There's a fix for that:

config FUSE_DAX
    bool
    transitional

with the only drawback that now FUSE_VDAX will have "no" as the
default value if old .config had neither FUSE_VDAX nor FUSE_DAX.
AFAICS that's not fixable with current config engine.

Anyway, that's a very minor issue, so I'm going this route.

Thanks,
Miklos

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

* Re: [PATCH 05/11] fuse: add helpers for EIO return value with kernel message
  2026-09-22  9:58   ` Amir Goldstein
@ 2026-09-28 10:57     ` Miklos Szeredi
  0 siblings, 0 replies; 33+ messages in thread
From: Miklos Szeredi @ 2026-09-28 10:57 UTC (permalink / raw)
  To: Amir Goldstein; +Cc: Miklos Szeredi, fuse-devel, John Groves, Darrick J . Wong

On Tue, 22 Sept 2026 at 11:59, Amir Goldstein <amir73il@gmail.com> wrote:

> > -fail:
> > -       pr_debug("failed to open file in requested io mode (open_flags=0x%x, err=%i).\n",
> > -                ff->open_flags, err);
> > -       /*
> > -        * The file open mode determines the inode io mode.
> > -        * Using incorrect open mode is a server mistake, which results in
> > -        * user visible failure of open() with EIO error.
> > -        */
> > -       return -EIO;
>
> The reason for EIO is not immediately apparent so suggest to move this
> comment up and remove moot err = -EINVAL;

Makes sense.

> > -out:
> > -       pr_debug("%s: backing_id=%d, fb=0x%p, backing_file=0x%p, err=%i\n", __func__,
> > -                backing_id, fb, ff->passthrough, err);
>
> Why remove this pr_debug of the success branch?
> it is used as a debugging trace for the lifetime and usage of backing files.

Restored.

I'm generally not a fan of inline tracing code, as it can be
detrimental to readability.   But I agree that such ad-hoc removal is
not the way to go.

Thanks,
Miklos

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

* Re: [PATCH 06/11] fuse: support 64 bit, server allocated backing ID
  2026-09-23  7:08   ` Amir Goldstein
@ 2026-09-30 10:15     ` Miklos Szeredi
  2026-09-30 10:48       ` Amir Goldstein
  0 siblings, 1 reply; 33+ messages in thread
From: Miklos Szeredi @ 2026-09-30 10:15 UTC (permalink / raw)
  To: Amir Goldstein
  Cc: Miklos Szeredi, fuse-devel, John Groves, Darrick J . Wong,
	Joanne Koong

On Wed, 23 Sept 2026 at 09:09, Amir Goldstein <amir73il@gmail.com> wrote:

> On second look at this uAPI and after failing to retrofit ops_mask into
> fuse_backing_map, I think it would be better, along with init flag
> FUSE_PASSTHROUGH_V2, to decommission both old ioctls
> and use new ioctl with consistent verbs for the new notify's:

Yes, unfortunately the "map" verb is overloaded:

 - map from backing_id to realfile
 - extent map

Would be nice to find something better for the backing_id mapping to
avoid confusion.

How about:

  FUSE_DEV_IOC_BACKING_CREATE/struct fuse_backing_create_in
  FUSE_NOTIFY_BACKING_REMOVE/struct fuse_backing_remove_out

?

I'm not opposed to FUSE_NOTIFY_BACKING_MAP, as that tells us it's
operating on a backing.

Thanks,
Miklos

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

* Re: [PATCH 06/11] fuse: support 64 bit, server allocated backing ID
  2026-09-30 10:15     ` Miklos Szeredi
@ 2026-09-30 10:48       ` Amir Goldstein
  0 siblings, 0 replies; 33+ messages in thread
From: Amir Goldstein @ 2026-09-30 10:48 UTC (permalink / raw)
  To: Miklos Szeredi
  Cc: Miklos Szeredi, fuse-devel, John Groves, Darrick J . Wong,
	Joanne Koong

On Wed, Sep 30, 2026 at 12:15 PM Miklos Szeredi <miklos@szeredi.hu> wrote:
>
> On Wed, 23 Sept 2026 at 09:09, Amir Goldstein <amir73il@gmail.com> wrote:
>
> > On second look at this uAPI and after failing to retrofit ops_mask into
> > fuse_backing_map, I think it would be better, along with init flag
> > FUSE_PASSTHROUGH_V2, to decommission both old ioctls
> > and use new ioctl with consistent verbs for the new notify's:
>
> Yes, unfortunately the "map" verb is overloaded:
>
>  - map from backing_id to realfile
>  - extent map
>
> Would be nice to find something better for the backing_id mapping to
> avoid confusion.
>
> How about:
>
>   FUSE_DEV_IOC_BACKING_CREATE/struct fuse_backing_create_in
>   FUSE_NOTIFY_BACKING_REMOVE/struct fuse_backing_remove_out
>
> ?

Sounds good to me.

>
> I'm not opposed to FUSE_NOTIFY_BACKING_MAP, as that tells us it's
> operating on a backing.

OK, flags namespace check:

/**
 * flags for fuse_backing_create_in
 *
 * FUSE_BACKING_CREATE_ID_64: not needed
 * FUSE_BACKING_CREATE_INO_ATTACH: for illustration
 */

struct fuse_backing_create_in {

/**
 * notify_backing_map flags
 *
 * FUSE_BACKING_MAP_CREATE:    create backing with the supplied ID
 * FUSE_BACKING_MAP_CYCLIC:            map repeats after last extent
 */

struct fuse_notify_backing_map_out {

FUSE_BACKING_MAP_CREATE is somewhat awkward, but still works IMO.

Thanks,
Amir.

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

* Re: [PATCH 02/11] fuse: use "vdax" naming for virtiofs DAX
  2026-09-28 10:38     ` Miklos Szeredi
@ 2026-10-01 13:18       ` Amir Goldstein
  0 siblings, 0 replies; 33+ messages in thread
From: Amir Goldstein @ 2026-10-01 13:18 UTC (permalink / raw)
  To: Miklos Szeredi; +Cc: Miklos Szeredi, fuse-devel, John Groves, Darrick J . Wong

On Mon, Sep 28, 2026 at 12:39 PM Miklos Szeredi <miklos@szeredi.hu> wrote:
>
> On Tue, 22 Sept 2026 at 10:47, Amir Goldstein <amir73il@gmail.com> wrote:
> >
> > On Tue, Sep 22, 2026 at 8:10 AM Miklos Szeredi <mszeredi@redhat.com> wrote:
> > >
> > > We are introducing DAX functionality largely unrelated to the virtiofs
> > > code.  Rename dax -> vdax for the virtiofs case for clarity.
> > >
> > > Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>
> >
> > I am sure it has crossed your mind that leaving CONFIG_FUSE_DAX
> > all over the place is ugly and just as confusing.
> >
> > You may want to create an alias in Kconfig and rename the checks
> > in the code to FUSE_VDAX:
> >
> > config FUSE_VDAX
> >         def_bool FUSE_DAX
>
> But then the .config file will contain redundant info and be confusing.
>
> There's a fix for that:
>
> config FUSE_DAX
>     bool
>     transitional
>
> with the only drawback that now FUSE_VDAX will have "no" as the
> default value if old .config had neither FUSE_VDAX nor FUSE_DAX.
> AFAICS that's not fixable with current config engine.
>
> Anyway, that's a very minor issue, so I'm going this route.
>

Nice trick.

Minor nit. I see that commit 77613eff2d02e on for-next has
spaces instead of tab indentation for the FUSE_DAX config entry.

Thanks,
Amir.

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

* Re: [PATCH 08/11] fuse: add support for opening dax device as backing
  2026-09-22 11:00   ` Amir Goldstein
@ 2026-10-01 15:06     ` Amir Goldstein
  2026-10-01 15:34       ` Miklos Szeredi
  0 siblings, 1 reply; 33+ messages in thread
From: Amir Goldstein @ 2026-10-01 15:06 UTC (permalink / raw)
  To: Miklos Szeredi; +Cc: fuse-devel, John Groves, Darrick J . Wong

On Tue, Sep 22, 2026 at 1:00 PM Amir Goldstein <amir73il@gmail.com> wrote:
>
> On Tue, Sep 22, 2026 at 8:10 AM Miklos Szeredi <mszeredi@redhat.com> wrote:
> >
> > Add FUSE_BACKING_IS_DEV flag that allows opening a character device
> > (dax device) as a backing, in addition to regular files.
> >
> > 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 <mszeredi@redhat.com>
> > ---
> >  fs/fuse/backing.c         | 131 ++++++++++++++++++++++++++------------
> >  fs/fuse/file.c            |   2 +-
> >  fs/fuse/fuse_i.h          |  28 +++++++-
> >  fs/fuse/inode.c           |  11 +++-
> >  fs/fuse/passthrough.c     |   8 ++-
> >  include/uapi/linux/fuse.h |   3 +
> >  6 files changed, 136 insertions(+), 47 deletions(-)
> >
> > diff --git a/fs/fuse/backing.c b/fs/fuse/backing.c
> > index 58dbdd17c1ef..aa558a0c2e64 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);
> >  }
> >
> > @@ -79,23 +87,88 @@ static struct fuse_backing *fuse_backing_id_remove(struct fuse_conn *fc, u64 id,
> >         return fb;
> >  }
> >
> > +static int fuse_dax_notify_failure(struct dax_device *daxdev, u64 offset, u64 len, int mf_flags)
> > +{
> > +       struct fuse_backing *fb = dax_holder(daxdev);
> > +
> > +       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,
> > +                                 bool is_dev)
> > +{
> > +       struct inode *inode = file_inode(file);
> > +       struct dax_device *daxdev;
> > +       int err;
> > +
> > +       switch (inode->i_mode & S_IFMT) {
> > +       case S_IFREG:
> > +               if (is_dev)
> > +                       return -EINVAL;
> > +               /* 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 (!is_dev)
> > +                       return -EINVAL;
> > +               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;
> > +

I am looking ahead at passthrough inode ops which need work on all file types
so this is_dev is confusing.

Therefore I prefer to call the flag
FUSE_BACKING_IS_DEV/FUSE_BACKING_IS_DAXDEV
and variable is_daxdev

Because this is what it actually means.

This flag would not be allowed in the PASSTHROUG_INO mode.

Thanks,
Amir.

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

* Re: [PATCH 08/11] fuse: add support for opening dax device as backing
  2026-10-01 15:06     ` Amir Goldstein
@ 2026-10-01 15:34       ` Miklos Szeredi
  0 siblings, 0 replies; 33+ messages in thread
From: Miklos Szeredi @ 2026-10-01 15:34 UTC (permalink / raw)
  To: Amir Goldstein; +Cc: Miklos Szeredi, fuse-devel, John Groves, Darrick J . Wong

On Thu, 1 Oct 2026 at 17:21, Amir Goldstein <amir73il@gmail.com> wrote:
>
> On Tue, Sep 22, 2026 at 1:00 PM Amir Goldstein <amir73il@gmail.com> wrote:
> >
> > On Tue, Sep 22, 2026 at 8:10 AM Miklos Szeredi <mszeredi@redhat.com> wrote:
> > >
> > > Add FUSE_BACKING_IS_DEV flag that allows opening a character device
> > > (dax device) as a backing, in addition to regular files.
> > >
> > > 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 <mszeredi@redhat.com>
> > > ---
> > >  fs/fuse/backing.c         | 131 ++++++++++++++++++++++++++------------
> > >  fs/fuse/file.c            |   2 +-
> > >  fs/fuse/fuse_i.h          |  28 +++++++-
> > >  fs/fuse/inode.c           |  11 +++-
> > >  fs/fuse/passthrough.c     |   8 ++-
> > >  include/uapi/linux/fuse.h |   3 +
> > >  6 files changed, 136 insertions(+), 47 deletions(-)
> > >
> > > diff --git a/fs/fuse/backing.c b/fs/fuse/backing.c
> > > index 58dbdd17c1ef..aa558a0c2e64 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);
> > >  }
> > >
> > > @@ -79,23 +87,88 @@ static struct fuse_backing *fuse_backing_id_remove(struct fuse_conn *fc, u64 id,
> > >         return fb;
> > >  }
> > >
> > > +static int fuse_dax_notify_failure(struct dax_device *daxdev, u64 offset, u64 len, int mf_flags)
> > > +{
> > > +       struct fuse_backing *fb = dax_holder(daxdev);
> > > +
> > > +       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,
> > > +                                 bool is_dev)
> > > +{
> > > +       struct inode *inode = file_inode(file);
> > > +       struct dax_device *daxdev;
> > > +       int err;
> > > +
> > > +       switch (inode->i_mode & S_IFMT) {
> > > +       case S_IFREG:
> > > +               if (is_dev)
> > > +                       return -EINVAL;
> > > +               /* 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 (!is_dev)
> > > +                       return -EINVAL;
> > > +               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;
> > > +
>
> I am looking ahead at passthrough inode ops which need work on all file types
> so this is_dev is confusing.
>
> Therefore I prefer to call the flag
> FUSE_BACKING_IS_DEV/FUSE_BACKING_IS_DAXDEV
> and variable is_daxdev

Dropped this entirely in v2.  My thinking was that for backward compat
IOC_BACKING_OPEN must return an error for device fd.  That no longer
matters with the new IOC_BACKING_CREATE.

Thanks,
Miklos

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

end of thread, other threads:[~2026-10-01 15:34 UTC | newest]

Thread overview: 33+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-22  6:10 [PATCH 00/11] fuse: DAX device based extent maps (famfs) Miklos Szeredi
2026-09-22  6:10 ` [PATCH 01/11] dax: replace exported dax_dev_get() with non-allocating dax_dev_find() Miklos Szeredi
2026-09-22  7:36   ` Amir Goldstein
2026-09-22 19:07   ` Alison Schofield
2026-09-28  9:16     ` Miklos Szeredi
2026-09-22  6:10 ` [PATCH 02/11] fuse: use "vdax" naming for virtiofs DAX Miklos Szeredi
2026-09-22  8:43   ` Amir Goldstein
2026-09-28 10:38     ` Miklos Szeredi
2026-10-01 13:18       ` Amir Goldstein
2026-09-22  6:10 ` [PATCH 03/11] fuse: don't assume ff->passthrough is set for FOPEN_PASSTHROUGH Miklos Szeredi
2026-09-22  8:44   ` Amir Goldstein
2026-09-22  6:10 ` [PATCH 04/11] fuse: make fuse_backing_get() static Miklos Szeredi
2026-09-22  8:44   ` Amir Goldstein
2026-09-22  6:10 ` [PATCH 05/11] fuse: add helpers for EIO return value with kernel message Miklos Szeredi
2026-09-22  9:58   ` Amir Goldstein
2026-09-28 10:57     ` Miklos Szeredi
2026-09-22  6:10 ` [PATCH 06/11] fuse: support 64 bit, server allocated backing ID Miklos Szeredi
2026-09-22 10:29   ` Amir Goldstein
2026-09-23  7:08   ` Amir Goldstein
2026-09-30 10:15     ` Miklos Szeredi
2026-09-30 10:48       ` Amir Goldstein
2026-09-22  6:10 ` [PATCH 07/11] fuse: support opening 64 bit " Miklos Szeredi
2026-09-22 10:33   ` Amir Goldstein
2026-09-22  6:10 ` [PATCH 08/11] fuse: add support for opening dax device as backing Miklos Szeredi
2026-09-22 11:00   ` Amir Goldstein
2026-10-01 15:06     ` Amir Goldstein
2026-10-01 15:34       ` Miklos Szeredi
2026-09-22  6:10 ` [PATCH 09/11] fuse: add extent map data structure Miklos Szeredi
2026-09-22 12:57   ` Amir Goldstein
2026-09-22  6:10 ` [PATCH 10/11] fuse: add extent map I/O support Miklos Szeredi
2026-09-22 13:04   ` Amir Goldstein
2026-09-22  6:10 ` [PATCH 11/11] fuse: add support for striped backing Miklos Szeredi
2026-09-23  5:41   ` Amir Goldstein

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox