[PATCH 22/28] fuse: invalidate ranges of block devices being used for iomap

Darrick J. Wong
2025-09-16T00:33:43Z

Make it easier to invalidate the page cache for a block device that is
being used in conjunction with iomap. This allows a fuse server to kill
all cached data for a block that is being freed, so that block reuse
doesn't result in file corruption. Right now, the only way to do this
is with fadvise, which ignores and doesn't wait for pages undergoing
writeback.

Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
fs/fuse/fuse_i.h | 3 +++
include/uapi/linux/fuse.h | 10 ++++++++++
fs/fuse/dev.c | 27 +++++++++++++++++++++++++++
fs/fuse/file_iomap.c | 40 ++++++++++++++++++++++++++++++++++++++++
4 files changed, 80 insertions(+)

diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h
index d59c19f61d5337..4aa7199dd0cd9f 100644
--- a/fs/fuse/fuse_i.h
+++ b/fs/fuse/fuse_i.h
@@ -1789,6 +1789,8 @@ int fuse_iomap_flush_unmap_range(struct inode *inode, loff_t pos,

int fuse_dev_ioctl_iomap_support(struct file *file,
struct fuse_iomap_support __user *argp);
+int fuse_iomap_dev_inval(struct fuse_conn *fc,
+ const struct fuse_iomap_dev_inval_out *arg);

int fuse_iomap_fadvise(struct file *file, loff_t start, loff_t end, int advice);
#else
@@ -1815,6 +1817,7 @@ int fuse_iomap_fadvise(struct file *file, loff_t start, loff_t end, int advice);
# define fuse_iomap_fallocate(...) (-ENOSYS)
# define fuse_iomap_flush_unmap_range(...) (-ENOSYS)
# define fuse_dev_ioctl_iomap_support(...) (-EOPNOTSUPP)
+# define fuse_iomap_dev_inval(...) (-ENOSYS)
# define fuse_iomap_fadvise NULL
#endif

diff --git a/include/uapi/linux/fuse.h b/include/uapi/linux/fuse.h
index 19c1ac5006faa9..b63fba0a2c52c9 100644
--- a/include/uapi/linux/fuse.h
+++ b/include/uapi/linux/fuse.h
@@ -240,6 +240,7 @@
* - add FUSE_IOMAP and iomap_{begin,end,ioend} for regular file operations
* - add FUSE_ATTR_IOMAP to enable iomap for specific inodes
* - add FUSE_IOMAP_CONFIG so the fuse server can configure more fs geometry
+ * - add FUSE_NOTIFY_IOMAP_DEV_INVAL to invalidate iomap bdev ranges
*/

#ifndef _LINUX_FUSE_H
@@ -689,6 +690,7 @@ enum fuse_notify_code {
FUSE_NOTIFY_DELETE = 6,
FUSE_NOTIFY_RESEND = 7,
FUSE_NOTIFY_INC_EPOCH = 8,
+ FUSE_NOTIFY_IOMAP_DEV_INVAL = 9,
FUSE_NOTIFY_CODE_MAX,
};

@@ -1464,4 +1466,12 @@ struct fuse_iomap_config_out {
int64_t s_maxbytes; /* max file size */
};

+struct fuse_iomap_dev_inval_out {
+ uint32_t dev; /* device cookie */
+ uint32_t reserved; /* zero */
+
+ uint64_t offset; /* range to invalidate pagecache, bytes */
+ uint64_t length;
+};
+
#endif /* _LINUX_FUSE_H */
diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c
index bb0ec19a368bea..adbe2a65e6fe87 100644
--- a/fs/fuse/dev.c
+++ b/fs/fuse/dev.c
@@ -1868,6 +1868,30 @@ static int fuse_notify_store(struct fuse_conn *fc, unsigned int size,
return err;
}

+static int fuse_notify_iomap_dev_inval(struct fuse_conn *fc, unsigned int size,
+ struct fuse_copy_state *cs)
+{
+ struct fuse_iomap_dev_inval_out outarg;
+ int err = -EINVAL;
+
+ if (size != sizeof(outarg))
+ goto err;
+
+ err = fuse_copy_one(cs, &outarg, sizeof(outarg));
+ if (err)
+ goto err;
+ if (outarg.reserved) {
+ err = -EINVAL;
+ goto err;
+ }
+ fuse_copy_finish(cs);
+
+ return fuse_iomap_dev_inval(fc, &outarg);
+err:
+ fuse_copy_finish(cs);
+ return err;
+}
+
struct fuse_retrieve_args {
struct fuse_args_pages ap;
struct fuse_notify_retrieve_in inarg;
@@ -2114,6 +2138,9 @@ static int fuse_notify(struct fuse_conn *fc, enum fuse_notify_code code,
case FUSE_NOTIFY_INC_EPOCH:
return fuse_notify_inc_epoch(fc);

+ case FUSE_NOTIFY_IOMAP_DEV_INVAL:
+ return fuse_notify_iomap_dev_inval(fc, size, cs);
+
default:
fuse_copy_finish(cs);
return -EINVAL;
diff --git a/fs/fuse/file_iomap.c b/fs/fuse/file_iomap.c
index a484cd235d9da2..9c798435e45633 100644
--- a/fs/fuse/file_iomap.c
+++ b/fs/fuse/file_iomap.c
@@ -1876,3 +1876,43 @@ int fuse_iomap_fadvise(struct file *file, loff_t start, loff_t end, int advice)
inode_unlock_shared(inode);
return ret;
}
+
+int fuse_iomap_dev_inval(struct fuse_conn *fc,
+ const struct fuse_iomap_dev_inval_out *arg)
+{
+ struct fuse_backing *fb;
+ struct block_device *bdev;
+ loff_t end;
+ int ret = 0;
+
+ if (!fc->iomap || arg->dev == FUSE_IOMAP_DEV_NULL)
+ return -EINVAL;
+
+ down_read(&fc->killsb);
+ fb = fuse_backing_lookup(fc, &fuse_iomap_backing_ops, arg->dev);
+ if (!fb) {
+ ret = -ENODEV;
+ goto out_killsb;
+ }
+ bdev = fb->bdev;
+
+ inode_lock(bdev->bd_mapping->host);
+ filemap_invalidate_lock(bdev->bd_mapping);
+
+ if (check_add_overflow(arg->offset, arg->length, &end) ||
+ arg->offset >= bdev_nr_bytes(bdev)) {
+ ret = -EINVAL;
+ goto out_unlock;
+ }
+
+ end = min(end, bdev_nr_bytes(bdev));
+ truncate_inode_pages_range(bdev->bd_mapping, arg->offset, end - 1);
+
+out_unlock:
+ filemap_invalidate_unlock(bdev->bd_mapping);
+ inode_unlock(bdev->bd_mapping->host);
+ fuse_backing_put(fb);
+out_killsb:
+ up_read(&fc->killsb);
+ return ret;
+}