[PATCH 02/18] libfuse: add kernel gates for FUSE_IOMAP

Darrick J. Wong
2025-09-16T00:42:19Z

Add some flags to query and request kernel support for filesystem iomap
for regular files.

Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
include/fuse_common.h | 5 +++++
include/fuse_kernel.h | 3 +++
lib/fuse_lowlevel.c | 12 +++++++++++-
3 files changed, 19 insertions(+), 1 deletion(-)

diff --git a/include/fuse_common.h b/include/fuse_common.h
index 041188ec7fa732..9d53354de78868 100644
--- a/include/fuse_common.h
+++ b/include/fuse_common.h
@@ -512,6 +512,11 @@ struct fuse_loop_config_v1 {
*/
#define FUSE_CAP_OVER_IO_URING (1UL << 31)

+/**
+ * Client supports using iomap for regular file operations
+ */
+#define FUSE_CAP_IOMAP (1ULL << 32)
+
/**
* Ioctl flags
*
diff --git a/include/fuse_kernel.h b/include/fuse_kernel.h
index 4d68c4e8a71d5f..cfa71dab28fdde 100644
--- a/include/fuse_kernel.h
+++ b/include/fuse_kernel.h
@@ -237,6 +237,7 @@
* - add FUSE_NOTIFY_INC_EPOCH
*
* 7.99
+ * - add FUSE_IOMAP and iomap_{begin,end,ioend} for regular file operations
*/

#ifndef _LINUX_FUSE_H
@@ -445,6 +446,7 @@ struct fuse_file_lock {
* FUSE_OVER_IO_URING: Indicate that client supports io-uring
* FUSE_REQUEST_TIMEOUT: kernel supports timing out requests.
* init_out.request_timeout contains the timeout (in secs)
+ * FUSE_IOMAP: Client supports iomap for regular file operations
*/
#define FUSE_ASYNC_READ (1 << 0)
#define FUSE_POSIX_LOCKS (1 << 1)
@@ -492,6 +494,7 @@ struct fuse_file_lock {
#define FUSE_ALLOW_IDMAP (1ULL << 40)
#define FUSE_OVER_IO_URING (1ULL << 41)
#define FUSE_REQUEST_TIMEOUT (1ULL << 42)
+#define FUSE_IOMAP (1ULL << 43)

/**
* CUSE INIT request/reply flags
diff --git a/lib/fuse_lowlevel.c b/lib/fuse_lowlevel.c
index e4544dfc30d5c6..649a500614b80e 100644
--- a/lib/fuse_lowlevel.c
+++ b/lib/fuse_lowlevel.c
@@ -2686,7 +2686,10 @@ _do_init(fuse_req_t req, const fuse_ino_t nodeid, const void *op_in,
se->conn.capable_ext |= FUSE_CAP_NO_EXPORT_SUPPORT;
if (inargflags & FUSE_OVER_IO_URING)
se->conn.capable_ext |= FUSE_CAP_OVER_IO_URING;
-
+ if (inargflags & FUSE_IOMAP)
+ se->conn.capable_ext |= FUSE_CAP_IOMAP;
+ /* Don't let anyone touch iomap until the end of the patchset. */
+ se->conn.capable_ext &= ~FUSE_CAP_IOMAP;
} else {
se->conn.max_readahead = 0;
}
@@ -2732,6 +2735,9 @@ _do_init(fuse_req_t req, const fuse_ino_t nodeid, const void *op_in,
FUSE_CAP_READDIRPLUS_AUTO);
LL_SET_DEFAULT(1, FUSE_CAP_OVER_IO_URING);

+ /* servers need to opt-in to iomap explicitly */
+ LL_SET_DEFAULT(0, FUSE_CAP_IOMAP);
+
/* This could safely become default, but libfuse needs an API extension
* to support it
* LL_SET_DEFAULT(1, FUSE_CAP_SETXATTR_EXT);
@@ -2850,6 +2856,8 @@ _do_init(fuse_req_t req, const fuse_ino_t nodeid, const void *op_in,
outargflags |= FUSE_REQUEST_TIMEOUT;
outarg.request_timeout = se->conn.request_timeout;
}
+ if (se->conn.want_ext & FUSE_CAP_IOMAP)
+ outargflags |= FUSE_IOMAP;

if (inargflags & FUSE_INIT_EXT) {
outargflags |= FUSE_INIT_EXT;
@@ -2891,6 +2899,8 @@ _do_init(fuse_req_t req, const fuse_ino_t nodeid, const void *op_in,
if (se->conn.want_ext & FUSE_CAP_PASSTHROUGH)
fuse_log(FUSE_LOG_DEBUG, " max_stack_depth=%u\n",
outarg.max_stack_depth);
+ if (se->conn.want_ext & FUSE_CAP_IOMAP)
+ fuse_log(FUSE_LOG_DEBUG, " iomap=1\n");
}
if (arg->minor < 5)
outargsize = FUSE_COMPAT_INIT_OUT_SIZE;