[RFC PATCH 1/8] fat: support FIEMAP
From: Matheus Alves de Almeida
Date: Sat Oct 31 2026 - 12:45:24 EST
Implement support for ->fiemap() using iomap_fiemap(). Add a read-only
fat_iomap_begin() and a fat_get_cont_clusters() helper for finding
contiguous clusters.
Signed-off-by: Matheus Alves de Almeida <matheus.aalmeida@xxxxxxxxxxxx>
---
fs/fat/Kconfig | 1 +
fs/fat/Makefile | 2 +-
fs/fat/cache.c | 80 ++++++++++++++++++++++++++++++++++++++-----------
fs/fat/fat.h | 5 ++++
fs/fat/file.c | 12 ++++++++
fs/fat/iomap.c | 71 +++++++++++++++++++++++++++++++++++++++++++
6 files changed, 152 insertions(+), 19 deletions(-)
create mode 100644 fs/fat/iomap.c
diff --git a/fs/fat/Kconfig b/fs/fat/Kconfig
index 25fae1c83725..d31d7866ce19 100644
--- a/fs/fat/Kconfig
+++ b/fs/fat/Kconfig
@@ -2,6 +2,7 @@
config FAT_FS
tristate
select BUFFER_HEAD
+ select FS_IOMAP
select NLS
select LEGACY_DIRECT_IO
help
diff --git a/fs/fat/Makefile b/fs/fat/Makefile
index 2b034112690d..238668f03744 100644
--- a/fs/fat/Makefile
+++ b/fs/fat/Makefile
@@ -7,7 +7,7 @@ obj-$(CONFIG_FAT_FS) += fat.o
obj-$(CONFIG_VFAT_FS) += vfat.o
obj-$(CONFIG_MSDOS_FS) += msdos.o
-fat-y := cache.o dir.o fatent.o file.o inode.o misc.o nfs.o
+fat-y := cache.o dir.o fatent.o file.o inode.o misc.o nfs.o iomap.o
vfat-y := namei_vfat.o
msdos-y := namei_msdos.o
diff --git a/fs/fat/cache.c b/fs/fat/cache.c
index 1b87354e24ba..f545e1db1d47 100644
--- a/fs/fat/cache.c
+++ b/fs/fat/cache.c
@@ -76,7 +76,7 @@ static int fat_cache_lookup(struct inode *inode, int fclus,
struct fat_cache_id *cid,
int *cached_fclus, int *cached_dclus)
{
- static struct fat_cache nohit = { .fcluster = 0, };
+ static struct fat_cache nohit = { .fcluster = -1, };
struct fat_cache *hit = &nohit, *p;
int offset = -1;
@@ -217,14 +217,16 @@ static inline void cache_init(struct fat_cache_id *cid, int fclus, int dclus)
cid->nr_contig = 0;
}
-int fat_get_cluster(struct inode *inode, int cluster, int *fclus, int *dclus)
+static int __fat_get_cluster(struct inode *inode, int cluster, int *fclus,
+ int *dclus, int *nr_cont)
{
struct super_block *sb = inode->i_sb;
struct msdos_sb_info *sbi = MSDOS_SB(sb);
const int limit = sb->s_maxbytes >> sbi->cluster_bits;
struct fat_entry fatent;
struct fat_cache_id cid;
- int nr;
+ int last = nr_cont ? cluster + *nr_cont - 1 : cluster;
+ int fcluster, dcluster, nr;
BUG_ON(MSDOS_I(inode)->i_start == 0);
@@ -236,21 +238,27 @@ int fat_get_cluster(struct inode *inode, int cluster, int *fclus, int *dclus)
__func__, MSDOS_I(inode)->i_pos, *dclus);
return -EIO;
}
- if (cluster == 0)
+ if (last == 0)
return 0;
if (fat_cache_lookup(inode, cluster, &cid, fclus, dclus) < 0) {
- /*
- * dummy, always not contiguous
- * This is reinitialized by cache_init(), later.
- */
- cache_init(&cid, -1, -1);
+ /* Cache the contiguous prefix from the known first cluster. */
+ cache_init(&cid, 0, *dclus);
}
+ fcluster = *fclus;
+ dcluster = *dclus;
fatent_init(&fatent);
- while (*fclus < cluster) {
+ while (fcluster < last) {
+ if (nr_cont && fcluster == cluster && cid.fcluster != -1) {
+ fcluster = min(last, cid.fcluster + cid.nr_contig);
+ dcluster = cid.dcluster + fcluster - cid.fcluster;
+ if (fcluster == last)
+ break;
+ }
+
/* prevent the infinite loop of cluster chain */
- if (*fclus > limit) {
+ if (fcluster > limit) {
fat_fs_error_ratelimit(sb,
"%s: detected the cluster chain loop (i_pos %lld)",
__func__, MSDOS_I(inode)->i_pos);
@@ -258,31 +266,67 @@ int fat_get_cluster(struct inode *inode, int cluster, int *fclus, int *dclus)
goto out;
}
- nr = fat_ent_read(inode, &fatent, *dclus);
+ nr = fat_ent_read(inode, &fatent, dcluster);
if (nr < 0)
goto out;
+ else if (nr == FAT_ENT_EOF)
+ goto cache_add;
else if (nr == FAT_ENT_FREE) {
fat_fs_error_ratelimit(sb,
"%s: invalid cluster chain (i_pos %lld)",
__func__, MSDOS_I(inode)->i_pos);
nr = -EIO;
goto out;
- } else if (nr == FAT_ENT_EOF) {
+ }
+ if (nr_cont && fcluster >= cluster && nr != dcluster + 1) {
fat_cache_add(inode, &cid);
- goto out;
+ cache_init(&cid, fcluster + 1, nr);
+ break;
+ }
+ fcluster++;
+ dcluster = nr;
+ if (!cache_contiguous(&cid, dcluster))
+ cache_init(&cid, fcluster, dcluster);
+ if (fcluster <= cluster) {
+ *fclus = fcluster;
+ *dclus = dcluster;
}
- (*fclus)++;
- *dclus = nr;
- if (!cache_contiguous(&cid, *dclus))
- cache_init(&cid, *fclus, *dclus);
}
nr = 0;
+ if (nr_cont)
+ *nr_cont = fcluster - cluster + 1;
+cache_add:
fat_cache_add(inode, &cid);
out:
fatent_brelse(&fatent);
return nr;
}
+int fat_get_cluster(struct inode *inode, int cluster, int *fclus, int *dclus)
+{
+ return __fat_get_cluster(inode, cluster, fclus, dclus, NULL);
+}
+
+int fat_get_cont_clusters(struct inode *inode, int cluster, int *nr)
+{
+ int ret, fclus, dclus;
+
+ if (!MSDOS_I(inode)->i_start)
+ return -EIO;
+
+ ret = __fat_get_cluster(inode, cluster, &fclus, &dclus, nr);
+ if (ret < 0)
+ return ret;
+ if (ret == FAT_ENT_EOF) {
+ fat_fs_error(inode->i_sb,
+ "%s: request beyond EOF (i_pos %lld)",
+ __func__, MSDOS_I(inode)->i_pos);
+ return -EIO;
+ }
+ return dclus;
+}
+
+
static int fat_bmap_cluster(struct inode *inode, int cluster)
{
struct super_block *sb = inode->i_sb;
diff --git a/fs/fat/fat.h b/fs/fat/fat.h
index 204c7b87108f..3d0f91fa11df 100644
--- a/fs/fat/fat.h
+++ b/fs/fat/fat.h
@@ -3,6 +3,7 @@
#define _FAT_H
#include <linux/buffer_head.h>
+#include <linux/iomap.h>
#include <linux/nls.h>
#include <linux/hash.h>
#include <linux/ratelimit.h>
@@ -321,6 +322,7 @@ static inline void fatwchar_to16(__u8 *dst, const wchar_t *src, size_t len)
extern void fat_cache_inval_inode(struct inode *inode);
extern int fat_get_cluster(struct inode *inode, int cluster,
int *fclus, int *dclus);
+int fat_get_cont_clusters(struct inode *inode, int cluster, int *nr);
extern int fat_get_mapped_cluster(struct inode *inode, sector_t sector,
sector_t last_block,
unsigned long *mapped_blocks, sector_t *bmap);
@@ -488,6 +490,9 @@ void fat_cache_destroy(void);
extern const struct export_operations fat_export_ops;
extern const struct export_operations fat_export_ops_nostale;
+/* fat/iomap.c */
+extern const struct iomap_ops fat_iomap_ops;
+
/* helper for printk */
typedef unsigned long long llu;
diff --git a/fs/fat/file.c b/fs/fat/file.c
index adc5554eeab6..531abf9e533c 100644
--- a/fs/fat/file.c
+++ b/fs/fat/file.c
@@ -636,9 +636,21 @@ int fat_setattr(const struct mnt_idmap *idmap, struct dentry *dentry,
}
EXPORT_SYMBOL_GPL(fat_setattr);
+static int fat_fiemap(struct inode *inode, struct fiemap_extent_info *fieinfo,
+ u64 start, u64 len)
+{
+ int err;
+
+ inode_lock_shared(inode);
+ err = iomap_fiemap(inode, fieinfo, start, len, &fat_iomap_ops);
+ inode_unlock_shared(inode);
+ return err;
+}
+
const struct inode_operations fat_file_inode_operations = {
.setattr = fat_setattr,
.getattr = fat_getattr,
.fileattr_get = fat_fileattr_get,
.update_time = fat_update_time,
+ .fiemap = fat_fiemap,
};
diff --git a/fs/fat/iomap.c b/fs/fat/iomap.c
new file mode 100644
index 000000000000..7fb24d0296f7
--- /dev/null
+++ b/fs/fat/iomap.c
@@ -0,0 +1,71 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * FAT iomap support
+ *
+ * Copyright (C) 2026 Matheus Alves de Almeida
+ */
+
+#include "fat.h"
+
+static int __fat_iomap_begin(struct inode *inode, loff_t offset, loff_t length,
+ unsigned int flags, struct iomap *iomap)
+{
+ struct super_block *sb = inode->i_sb;
+ struct msdos_sb_info *sbi = MSDOS_SB(sb);
+ int start_cluster, end_cluster, nr_cluster, ret;
+ loff_t curr_size = i_size_read(inode);
+ loff_t aligned_offset;
+
+ iomap->flags = 0;
+ /* Return IOMAP_HOLE if we are beyond any kind of disk allocation */
+ if (inode_get_bytes(inode) <= offset) {
+ iomap->addr = IOMAP_NULL_ADDR;
+ iomap->offset = offset;
+ iomap->length = length;
+ iomap->type = IOMAP_HOLE;
+ return 0;
+ }
+
+ /*
+ * We need to separate normal data clusters and
+ * uninitialized FALLOC_FL_KEEP_SIZE clusters.
+ */
+ start_cluster = offset >> sbi->cluster_bits;
+ aligned_offset = (u64)start_cluster << sbi->cluster_bits;
+ if (curr_size <= aligned_offset) {
+ iomap->type = IOMAP_UNWRITTEN;
+ length = min(offset - aligned_offset + length,
+ inode_get_bytes(inode) - aligned_offset);
+ } else {
+ iomap->type = IOMAP_MAPPED;
+ length = min(offset - aligned_offset + length,
+ curr_size - aligned_offset);
+ }
+
+ end_cluster = (aligned_offset + length - 1) >> sbi->cluster_bits;
+ nr_cluster = end_cluster - start_cluster + 1;
+ ret = fat_get_cont_clusters(inode, start_cluster, &nr_cluster);
+ if (ret < 0)
+ return ret;
+
+ iomap->addr = (u64)fat_clus_to_blknr(sbi, ret) << sb->s_blocksize_bits;
+ iomap->offset = aligned_offset;
+ iomap->length = (u64)nr_cluster << sbi->cluster_bits;
+ iomap->flags |= IOMAP_F_MERGED;
+ iomap->bdev = sb->s_bdev;
+
+ return 0;
+}
+
+static int fat_iomap_begin(struct inode *inode, loff_t offset,
+ loff_t length, unsigned int flags,
+ struct iomap *iomap, struct iomap *srcmap)
+{
+ return __fat_iomap_begin(inode, offset, length, flags, iomap);
+}
+
+static DEFINE_IOMAP_ITER_NEXT(fat_iomap_next, fat_iomap_begin);
+
+const struct iomap_ops fat_iomap_ops = {
+ .iomap_next = fat_iomap_next
+};
--
2.43.0