[PATCH 04/18] libfuse: add upper level iomap commands

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

Teach the upper level fuse library about the iomap begin and end
operations, and connect it to the lower level. This is needed for
fuse2fs to start using iomap.

Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
include/fuse.h | 17 ++++++++++
lib/fuse.c | 98 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 115 insertions(+)

diff --git a/include/fuse.h b/include/fuse.h
index 209102651e9454..958034a539abe6 100644
--- a/include/fuse.h
+++ b/include/fuse.h
@@ -864,6 +864,23 @@ struct fuse_operations {
*/
int (*statx)(const char *path, int flags, int mask, struct statx *stxbuf,
struct fuse_file_info *fi);
+
+ /**
+ * Send a mapping to the kernel so that a file IO operation can run.
+ */
+ int (*iomap_begin) (const char *path, uint64_t nodeid,
+ uint64_t attr_ino, off_t pos_in,
+ uint64_t length_in, uint32_t opflags_in,
+ struct fuse_file_iomap *read_out,
+ struct fuse_file_iomap *write_out);
+
+ /**
+ * Respond to the outcome of a previous file mapping operation.
+ */
+ int (*iomap_end) (const char *path, uint64_t nodeid, uint64_t attr_ino,
+ off_t pos_in, uint64_t length_in,
+ uint32_t opflags_in, ssize_t written_in,
+ const struct fuse_file_iomap *iomap);
};

/** Extra context that may be needed by some filesystems
diff --git a/lib/fuse.c b/lib/fuse.c
index e997531f4433bb..eef0967f796ed6 100644
--- a/lib/fuse.c
+++ b/lib/fuse.c
@@ -2793,6 +2793,45 @@ int fuse_fs_chmod(struct fuse_fs *fs, const char *path, mode_t mode,
return fs->op.chmod(path, mode, fi);
}

+static int fuse_fs_iomap_begin(struct fuse_fs *fs, const char *path,
+ fuse_ino_t nodeid, uint64_t attr_ino, off_t pos,
+ uint64_t count, uint32_t opflags,
+ struct fuse_file_iomap *read,
+ struct fuse_file_iomap *write)
+{
+ fuse_get_context()->private_data = fs->user_data;
+ if (!fs->op.iomap_begin)
+ return -ENOSYS;
+
+ if (fs->debug) {
+ fuse_log(FUSE_LOG_DEBUG,
+ "iomap_begin[%s] nodeid %llu attr_ino %llu pos %llu count %llu opflags 0x%x\n",
+ path, nodeid, attr_ino, pos, count, opflags);
+ }
+
+ return fs->op.iomap_begin(path, nodeid, attr_ino, pos, count, opflags,
+ read, write);
+}
+
+static int fuse_fs_iomap_end(struct fuse_fs *fs, const char *path,
+ fuse_ino_t nodeid, uint64_t attr_ino, off_t pos,
+ uint64_t count, uint32_t opflags, ssize_t written,
+ const struct fuse_file_iomap *iomap)
+{
+ fuse_get_context()->private_data = fs->user_data;
+ if (!fs->op.iomap_end)
+ return -ENOSYS;
+
+ if (fs->debug) {
+ fuse_log(FUSE_LOG_DEBUG,
+ "iomap_end[%s] nodeid %llu attr_ino %llu pos %llu count %llu opflags 0x%x written %zd\n",
+ path, nodeid, attr_ino, pos, count, opflags, written);
+ }
+
+ return fs->op.iomap_end(path, nodeid, attr_ino, pos, count, opflags,
+ written, iomap);
+}
+
static void fuse_lib_setattr(fuse_req_t req, fuse_ino_t ino, struct stat *attr,
int valid, struct fuse_file_info *fi)
{
@@ -4466,6 +4505,63 @@ static void fuse_lib_statx(fuse_req_t req, fuse_ino_t ino, int flags, int mask,
}
#endif

+static void fuse_lib_iomap_begin(fuse_req_t req, fuse_ino_t nodeid,
+ uint64_t attr_ino, off_t pos, uint64_t count,
+ uint32_t opflags)
+{
+ struct fuse *f = req_fuse_prepare(req);
+ struct fuse_file_iomap read = { };
+ struct fuse_file_iomap write = { };
+ struct fuse_intr_data d;
+ char *path;
+ int err;
+
+ err = get_path_nullok(f, nodeid, &path);
+ if (err) {
+ reply_err(req, err);
+ return;
+ }
+
+ fuse_prepare_interrupt(f, req, &d);
+ err = fuse_fs_iomap_begin(f->fs, path, nodeid, attr_ino, pos, count,
+ opflags, &read, &write);
+ fuse_finish_interrupt(f, req, &d);
+ free_path(f, nodeid, path);
+ if (err) {
+ reply_err(req, err);
+ return;
+ }
+
+ if (write.length == 0)
+ fuse_iomap_pure_overwrite(&write, &read);
+
+ fuse_reply_iomap_begin(req, &read, &write);
+}
+
+static void fuse_lib_iomap_end(fuse_req_t req, fuse_ino_t nodeid,
+ uint64_t attr_ino, off_t pos, uint64_t count,
+ uint32_t opflags, ssize_t written,
+ const struct fuse_file_iomap *iomap)
+{
+ struct fuse *f = req_fuse_prepare(req);
+ struct fuse_intr_data d;
+ char *path;
+ int err;
+
+ err = get_path_nullok(f, nodeid, &path);
+ if (err) {
+ reply_err(req, err);
+ return;
+ }
+
+ fuse_prepare_interrupt(f, req, &d);
+ err = fuse_fs_iomap_end(f->fs, path, nodeid, attr_ino, pos, count,
+ opflags, written, iomap);
+ fuse_finish_interrupt(f, req, &d);
+ free_path(f, nodeid, path);
+ reply_err(req, err);
+}
+
static int clean_delay(struct fuse *f)
{
/*
@@ -4567,6 +4663,8 @@ static struct fuse_lowlevel_ops fuse_path_ops = {
#ifdef HAVE_STATX
.statx = fuse_lib_statx,
#endif
+ .iomap_begin = fuse_lib_iomap_begin,
+ .iomap_end = fuse_lib_iomap_end,
};

int fuse_notify_poll(struct fuse_pollhandle *ph)