[PATCH 10/28] fuse: implement basic iomap reporting such as FIEMAP and SEEK_{DATA,HOLE}

Darrick J. Wong
2025-09-16T00:30:35Z

Implement the basic file mapping reporting functions like FIEMAP, BMAP,
and SEEK_DATA/HOLE.

Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
fs/fuse/fuse_i.h | 8 ++++++
fs/fuse/dir.c | 1 +
fs/fuse/file.c | 13 ++++++++++
fs/fuse/file_iomap.c | 68 +++++++++++++++++++++++++++++++++++++++++++++++++-
4 files changed, 89 insertions(+), 1 deletion(-)

diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h
index 791e868e568cc5..ea879b45e904c5 100644
--- a/fs/fuse/fuse_i.h
+++ b/fs/fuse/fuse_i.h
@@ -1727,6 +1727,11 @@ static inline bool fuse_inode_has_iomap(const struct inode *inode)

return test_bit(FUSE_I_IOMAP, &fi->state);
}
+
+int fuse_iomap_fiemap(struct inode *inode, struct fiemap_extent_info *fieinfo,
+ u64 start, u64 length);
+loff_t fuse_iomap_lseek(struct file *file, loff_t offset, int whence);
+sector_t fuse_iomap_bmap(struct address_space *mapping, sector_t block);
#else
# define fuse_iomap_enabled(...) (false)
# define fuse_has_iomap(...) (false)
@@ -1735,6 +1740,9 @@ static inline bool fuse_inode_has_iomap(const struct inode *inode)
# define fuse_iomap_init_inode(...) ((void)0)
# define fuse_iomap_evict_inode(...) ((void)0)
# define fuse_inode_has_iomap(...) (false)
+# define fuse_iomap_fiemap NULL
+# define fuse_iomap_lseek(...) (-ENOSYS)
+# define fuse_iomap_bmap(...) (-ENOSYS)
#endif

#endif /* _FS_FUSE_I_H */
diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c
index 6dbce975dc96b7..467ea2f46798ba 100644
--- a/fs/fuse/dir.c
+++ b/fs/fuse/dir.c
@@ -2303,6 +2303,7 @@ static const struct inode_operations fuse_common_inode_operations = {
.set_acl = fuse_set_acl,
.fileattr_get = fuse_fileattr_get,
.fileattr_set = fuse_fileattr_set,
+ .fiemap = fuse_iomap_fiemap,
};

static const struct inode_operations fuse_symlink_inode_operations = {
diff --git a/fs/fuse/file.c b/fs/fuse/file.c
index 0f253837b57fdc..1941dd7846f12a 100644
--- a/fs/fuse/file.c
+++ b/fs/fuse/file.c
@@ -2522,6 +2522,12 @@ static sector_t fuse_bmap(struct address_space *mapping, sector_t block)
struct fuse_bmap_out outarg;
int err;

+ if (fuse_inode_has_iomap(inode)) {
+ sector_t alt_sec = fuse_iomap_bmap(mapping, block);
+ if (alt_sec > 0)
+ return alt_sec;
+ }
+
if (!inode->i_sb->s_bdev || fm->fc->no_bmap)
return 0;

@@ -2557,6 +2563,13 @@ static loff_t fuse_lseek(struct file *file, loff_t offset, int whence)
struct fuse_lseek_out outarg;
int err;

+ if (fuse_inode_has_iomap(inode)) {
+ loff_t alt_pos = fuse_iomap_lseek(file, offset, whence);
+
+ if (alt_pos >= 0 || (alt_pos < 0 && alt_pos != -ENOSYS))
+ return alt_pos;
+ }
+
if (fm->fc->no_lseek)
goto fallback;

diff --git a/fs/fuse/file_iomap.c b/fs/fuse/file_iomap.c
index 0759704847598b..29603eec939589 100644
--- a/fs/fuse/file_iomap.c
+++ b/fs/fuse/file_iomap.c
@@ -4,6 +4,7 @@
* Author: Darrick J. Wong <djwong@kernel.org>
*/
#include <linux/iomap.h>
+#include <linux/fiemap.h>
#include "fuse_i.h"
#include "fuse_trace.h"
#include "iomap_priv.h"
@@ -561,7 +562,7 @@ static int fuse_iomap_end(struct inode *inode, loff_t pos, loff_t count,
return err;
}

-const struct iomap_ops fuse_iomap_ops = {
+static const struct iomap_ops fuse_iomap_ops = {
.iomap_begin = fuse_iomap_begin,
.iomap_end = fuse_iomap_end,
};
@@ -669,3 +670,68 @@ void fuse_iomap_evict_inode(struct inode *inode)
if (fuse_inode_has_iomap(inode))
fuse_inode_clear_iomap(inode);
}
+
+int fuse_iomap_fiemap(struct inode *inode, struct fiemap_extent_info *fieinfo,
+ u64 start, u64 count)
+{
+ struct fuse_conn *fc = get_fuse_conn(inode);
+ int error;
+
+ /*
+ * We are called directly from the vfs so we need to check per-inode
+ * support here explicitly.
+ */
+ if (!fuse_inode_has_iomap(inode))
+ return -EOPNOTSUPP;
+
+ if (fieinfo->fi_flags & FIEMAP_FLAG_XATTR)
+ return -EOPNOTSUPP;
+
+ if (fuse_is_bad(inode))
+ return -EIO;
+
+ if (!fuse_allow_current_process(fc))
+ return -EACCES;
+
+ inode_lock_shared(inode);
+ error = iomap_fiemap(inode, fieinfo, start, count, &fuse_iomap_ops);
+ inode_unlock_shared(inode);
+
+ return error;
+}
+
+sector_t fuse_iomap_bmap(struct address_space *mapping, sector_t block)
+{
+ ASSERT(fuse_inode_has_iomap(mapping->host));
+
+ return iomap_bmap(mapping, block, &fuse_iomap_ops);
+}
+
+loff_t fuse_iomap_lseek(struct file *file, loff_t offset, int whence)
+{
+ struct inode *inode = file->f_mapping->host;
+ struct fuse_conn *fc = get_fuse_conn(inode);
+
+ ASSERT(fuse_inode_has_iomap(inode));
+
+ if (fuse_is_bad(inode))
+ return -EIO;
+
+ if (!fuse_allow_current_process(fc))
+ return -EACCES;
+
+ switch (whence) {
+ case SEEK_HOLE:
+ offset = iomap_seek_hole(inode, offset, &fuse_iomap_ops);
+ break;
+ case SEEK_DATA:
+ offset = iomap_seek_data(inode, offset, &fuse_iomap_ops);
+ break;
+ default:
+ return -ENOSYS;
+ }
+
+ if (offset < 0)
+ return offset;
+ return vfs_setpos(file, offset, inode->i_sb->s_maxbytes);
+}