[RFC PATCH 02/17] vfs: Implement a FIEMAP callback

David Howells
2026-03-04T14:04:08Z

Implement a callback in the internal kernel FIEMAP API so that kernel users
can make use of it as the filler function expects to write to userspace.
This allows the FIEMAP data to be captured and parsed. This is useful for
cachefiles and also potentially for knfsd and ksmbd to implement their
equivalents of FIEMAP remotely rather than using SEEK_DATA/SEEK_HOLE.

Signed-off-by: David Howells <dhowells@redhat.com>
cc: Paulo Alcantara <pc@manguebit.org>
cc: Matthew Wilcox <willy@infradead.org>
cc: Christoph Hellwig <hch@infradead.org>
cc: Steve French <sfrench@samba.org>
cc: Namjae Jeon <linkinjeon@kernel.org>
cc: Tom Talpey <tom@talpey.com>
cc: Chuck Lever <chuck.lever@oracle.com>
cc: linux-cifs@vger.kernel.org
cc: linux-nfs@vger.kernel.org
cc: netfs@lists.linux.dev
cc: linux-fsdevel@vger.kernel.org
---
fs/ioctl.c | 29 ++++++++++++++++++++---------
include/linux/fiemap.h | 3 +++
2 files changed, 23 insertions(+), 9 deletions(-)

diff --git a/fs/ioctl.c b/fs/ioctl.c
index 1c152c2b1b67..f0513e282eb7 100644
--- a/fs/ioctl.c
+++ b/fs/ioctl.c
@@ -93,6 +93,21 @@ static int ioctl_fibmap(struct file *filp, int __user *p)
return error;
}

+static int fiemap_fill(struct fiemap_extent_info *fieinfo,
+ const struct fiemap_extent *extent)
+{
+ struct fiemap_extent __user *dest = fieinfo->fi_extents_start;
+
+ dest += fieinfo->fi_extents_mapped;
+ if (copy_to_user(dest, extent, sizeof(*extent)))
+ return -EFAULT;
+
+ fieinfo->fi_extents_mapped++;
+ if (fieinfo->fi_extents_mapped >= fieinfo->fi_extents_max)
+ return 1;
+ return 0;
+}
+
/**
* fiemap_fill_next_extent - Fiemap helper function
* @fieinfo: Fiemap context passed into ->fiemap
@@ -112,7 +127,7 @@ int fiemap_fill_next_extent(struct fiemap_extent_info *fieinfo, u64 logical,
u64 phys, u64 len, u32 flags)
{
struct fiemap_extent extent;
- struct fiemap_extent __user *dest = fieinfo->fi_extents_start;
+ int ret;

/* only count the extents */
if (fieinfo->fi_extents_max == 0) {
@@ -140,13 +155,9 @@ int fiemap_fill_next_extent(struct fiemap_extent_info *fieinfo, u64 logical,
extent.fe_length = len;
extent.fe_flags = flags;

- dest += fieinfo->fi_extents_mapped;
- if (copy_to_user(dest, &extent, sizeof(extent)))
- return -EFAULT;
-
- fieinfo->fi_extents_mapped++;
- if (fieinfo->fi_extents_mapped == fieinfo->fi_extents_max)
- return 1;
+ ret = fieinfo->fi_fill(fieinfo, &extent);
+ if (ret != 0)
+ return ret; /* 1 to stop. */
return (flags & FIEMAP_EXTENT_LAST) ? 1 : 0;
}
EXPORT_SYMBOL(fiemap_fill_next_extent);
@@ -199,7 +210,7 @@ EXPORT_SYMBOL(fiemap_prep);
static int ioctl_fiemap(struct file *filp, struct fiemap __user *ufiemap)
{
struct fiemap fiemap;
- struct fiemap_extent_info fieinfo = { 0, };
+ struct fiemap_extent_info fieinfo = { .fi_fill = fiemap_fill, };
struct inode *inode = file_inode(filp);
int error;

diff --git a/include/linux/fiemap.h b/include/linux/fiemap.h
index 966092ffa89a..01929ca4b834 100644
--- a/include/linux/fiemap.h
+++ b/include/linux/fiemap.h
@@ -11,12 +11,15 @@
* @fi_extents_mapped: Number of mapped extents
* @fi_extents_max: Size of fiemap_extent array
* @fi_extents_start: Start of fiemap_extent array
+ * @fi_fill: Function to fill the extents array
*/
struct fiemap_extent_info {
unsigned int fi_flags;
unsigned int fi_extents_mapped;
unsigned int fi_extents_max;
struct fiemap_extent __user *fi_extents_start;
+ int (*fi_fill)(struct fiemap_extent_info *fiefinfo,
+ const struct fiemap_extent *extent);
};

int fiemap_prep(struct inode *inode, struct fiemap_extent_info *fieinfo,