[PATCH 24/28] fuse: implement inline data file IO via iomap

Darrick J. Wong
2025-09-16T00:34:14Z

Implement inline data file IO by issuing FUSE_READ/FUSE_WRITE commands
in response to an inline data mapping.

Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
fs/fuse/file_iomap.c | 184 ++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 184 insertions(+)

diff --git a/fs/fuse/file_iomap.c b/fs/fuse/file_iomap.c
index d2945f8071a296..8faf16f58df035 100644
--- a/fs/fuse/file_iomap.c
+++ b/fs/fuse/file_iomap.c
@@ -417,6 +417,150 @@ fuse_iomap_find_dev(struct fuse_conn *fc, const struct fuse_iomap_io *map)
return ret;
}

+static inline int fuse_iomap_inline_alloc(struct iomap *iomap)
+{
+ ASSERT(iomap->inline_data == NULL);
+ ASSERT(iomap->length > 0);
+
+ iomap->inline_data = kvzalloc(iomap->length, GFP_KERNEL);
+ return iomap->inline_data ? 0 : -ENOMEM;
+}
+
+static inline void fuse_iomap_inline_free(struct iomap *iomap)
+{
+ kvfree(iomap->inline_data);
+ iomap->inline_data = NULL;
+}
+
+/*
+ * Use the FUSE_READ command to read inline file data from the fuse server.
+ * Note that there's no file handle attached, so the fuse server must be able
+ * to reconnect to the inode via the nodeid.
+ */
+static int fuse_iomap_inline_read(struct inode *inode, loff_t pos,
+ loff_t count, struct iomap *iomap)
+{
+ struct fuse_read_in in = {
+ .offset = pos,
+ .size = count,
+ };
+ struct fuse_inode *fi = get_fuse_inode(inode);
+ struct fuse_mount *fm = get_fuse_mount(inode);
+ FUSE_ARGS(args);
+ ssize_t ret;
+
+ if (BAD_DATA(!iomap_inline_data_valid(iomap)))
+ return -EFSCORRUPTED;
+
+ args.opcode = FUSE_READ;
+ args.nodeid = fi->nodeid;
+ args.in_numargs = 1;
+ args.in_args[0].size = sizeof(in);
+ args.in_args[0].value = &in;
+ args.out_argvar = true;
+ args.out_numargs = 1;
+ args.out_args[0].size = count;
+ args.out_args[0].value = iomap_inline_data(iomap, pos);
+
+ ret = fuse_simple_request(fm, &args);
+ if (ret < 0) {
+ fuse_iomap_inline_free(iomap);
+ return ret;
+ }
+ /* no readahead means something bad happened */
+ if (ret == 0) {
+ fuse_iomap_inline_free(iomap);
+ return -EIO;
+ }
+
+ return 0;
+}
+
+/*
+ * Use the FUSE_WRITE command to write inline file data from the fuse server.
+ * Note that there's no file handle attached, so the fuse server must be able
+ * to reconnect to the inode via the nodeid.
+ */
+static int fuse_iomap_inline_write(struct inode *inode, loff_t pos,
+ loff_t count, struct iomap *iomap)
+{
+ struct fuse_write_in in = {
+ .offset = pos,
+ .size = count,
+ };
+ struct fuse_write_out out = { };
+ struct fuse_inode *fi = get_fuse_inode(inode);
+ struct fuse_mount *fm = get_fuse_mount(inode);
+ FUSE_ARGS(args);
+ ssize_t ret;
+
+ if (BAD_DATA(!iomap_inline_data_valid(iomap)))
+ return -EFSCORRUPTED;
+
+ args.opcode = FUSE_WRITE;
+ args.nodeid = fi->nodeid;
+ args.in_numargs = 2;
+ args.in_args[0].size = sizeof(in);
+ args.in_args[0].value = &in;
+ args.in_args[1].size = count;
+ args.in_args[1].value = iomap_inline_data(iomap, pos);
+ args.out_numargs = 1;
+ args.out_args[0].size = sizeof(out);
+ args.out_args[0].value = &out;
+
+ ret = fuse_simple_request(fm, &args);
+ if (ret < 0) {
+ fuse_iomap_inline_free(iomap);
+ return ret;
+ }
+ /* short write means something bad happened */
+ if (out.size < count) {
+ fuse_iomap_inline_free(iomap);
+ return -EIO;
+ }
+
+ return 0;
+}
+
+/* Set up inline data buffers for iomap_begin */
+static int fuse_iomap_set_inline(struct inode *inode, unsigned opflags,
+ loff_t pos, loff_t count,
+ struct iomap *iomap, struct iomap *srcmap)
+{
+ int err;
+
+ if (opflags & IOMAP_REPORT)
+ return 0;
+
+ if (fuse_is_iomap_file_write(opflags)) {
+ if (iomap->type == IOMAP_INLINE) {
+ err = fuse_iomap_inline_alloc(iomap);
+ if (err)
+ return err;
+ }
+
+ if (srcmap->type == IOMAP_INLINE) {
+ err = fuse_iomap_inline_alloc(srcmap);
+ if (!err)
+ err = fuse_iomap_inline_read(inode, pos, count,
+ srcmap);
+ if (err) {
+ fuse_iomap_inline_free(iomap);
+ return err;
+ }
+ }
+ } else if (iomap->type == IOMAP_INLINE) {
+ /* inline data read */
+ err = fuse_iomap_inline_alloc(iomap);
+ if (!err)
+ err = fuse_iomap_inline_read(inode, pos, count, iomap);
+ if (err)
+ return err;
+ }
+
+ return 0;
+}
+
static int fuse_iomap_begin(struct inode *inode, loff_t pos, loff_t count,
unsigned opflags, struct iomap *iomap,
struct iomap *srcmap)
@@ -486,12 +630,20 @@ static int fuse_iomap_begin(struct inode *inode, loff_t pos, loff_t count,
fuse_iomap_from_server(inode, iomap, read_dev, &outarg.read);
}

+ if (iomap->type == IOMAP_INLINE || srcmap->type == IOMAP_INLINE) {
+ err = fuse_iomap_set_inline(inode, opflags, pos, count, iomap,
+ srcmap);
+ if (err)
+ goto out_write_dev;
+ }
+
/*
* XXX: if we ever want to support closing devices, we need a way to
* track the fuse_backing refcount all the way through bio endios.
* For now we put the refcount here because you can't remove an iomap
* device until unmount time.
*/
+out_write_dev:
fuse_backing_put(write_dev);
out_read_dev:
fuse_backing_put(read_dev);
@@ -530,8 +682,28 @@ static int fuse_iomap_end(struct inode *inode, loff_t pos, loff_t count,
{
struct fuse_inode *fi = get_fuse_inode(inode);
struct fuse_mount *fm = get_fuse_mount(inode);
+ struct iomap_iter *iter = container_of(iomap, struct iomap_iter, iomap);
+ struct iomap *srcmap = &iter->srcmap;
int err = 0;

+ if (srcmap->inline_data)
+ fuse_iomap_inline_free(srcmap);
+
+ if (iomap->inline_data) {
+ if (fuse_is_iomap_file_write(opflags) && written > 0) {
+ err = fuse_iomap_inline_write(inode, pos, written,
+ iomap);
+ fuse_iomap_inline_free(iomap);
+ if (err)
+ return err;
+ } else {
+ fuse_iomap_inline_free(iomap);
+ }
+
+ /* fuse server should already be aware of what happened */
+ return 0;
+ }
+
if (fuse_should_send_iomap_end(fm, iomap, opflags, count, written)) {
struct fuse_iomap_end_in inarg = {
.opflags = fuse_iomap_op_to_server(opflags),
@@ -1431,6 +1603,18 @@ static ssize_t fuse_iomap_writeback_range(struct iomap_writepage_ctx *wpc,
if (ret)
goto discard_folio;

+ if (BAD_DATA(write_iomap.type == IOMAP_INLINE)) {
+ /*
+ * iomap assumes that inline data writes are completed
+ * by the time ->iomap_end completes, so it should
+ * never mark a pagecache folio dirty.
+ */
+ fuse_iomap_end(inode, offset, len, 0,
+ FUSE_IOMAP_OP_WRITEBACK, &write_iomap);
+ ret = -EIO;
+ goto discard_folio;
+ }
+
/*
* Landed in a hole or beyond EOF? Send that to iomap, it'll
* skip writing back the file range.