mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [RFC PATCH 0/8] fat: convert to iomap
@ 2026-10-31 16:43 Matheus Alves de Almeida
  2026-10-31 16:43 ` [RFC PATCH 1/8] fat: support FIEMAP Matheus Alves de Almeida
                   ` (7 more replies)
  0 siblings, 8 replies; 14+ messages in thread
From: Matheus Alves de Almeida @ 2026-10-31 16:43 UTC (permalink / raw)
  To: OGAWA Hirofumi
  Cc: linux-fsdevel, brauner, djwong, hch, linux-kernel,
	Matheus Alves de Almeida

Hi,

This series converts fat to use iomap. It follows roughly the direction
suggested by https://docs.kernel.org/filesystems/iomap/porting.html,
with the exfat conversion series and related fixes as a reference.

Overall, the largest performance improvements were seen for extending
writes (~30%) and fallocate() (~18%).

Thanks,
Matheus

Matheus Alves de Almeida (8):
  fat: support FIEMAP
  fat: use iomap for ->bmap()
  fat: add iomap direct I/O support
  fat: clean stale bdev buffers of newly allocated clusters
  fat: drop ->write_begin() and ->write_end() usage from
    fat_cont_expand()
  fat: add iomap buffered I/O support
  fat: remove ->mmu_private
  fat: add multi-cluster allocation for write

 fs/fat/Kconfig  |   2 +-
 fs/fat/Makefile |   2 +-
 fs/fat/cache.c  |  95 ++++++++++-----
 fs/fat/dir.c    |   1 -
 fs/fat/fat.h    |  20 +--
 fs/fat/fatent.c |  67 ++++++++--
 fs/fat/file.c   | 316 ++++++++++++++++++++++++++++++++++++++----------
 fs/fat/inode.c  | 248 +++++--------------------------------
 fs/fat/iomap.c  | 180 +++++++++++++++++++++++++++
 9 files changed, 599 insertions(+), 332 deletions(-)
 create mode 100644 fs/fat/iomap.c

-- 
2.43.0


^ permalink raw reply	[flat|nested] 14+ messages in thread

* [RFC PATCH 1/8] fat: support FIEMAP
  2026-10-31 16:43 [RFC PATCH 0/8] fat: convert to iomap Matheus Alves de Almeida
@ 2026-10-31 16:43 ` Matheus Alves de Almeida
  2026-11-08 14:51   ` OGAWA Hirofumi
  2026-10-31 16:43 ` [RFC PATCH 2/8] fat: use iomap for ->bmap() Matheus Alves de Almeida
                   ` (6 subsequent siblings)
  7 siblings, 1 reply; 14+ messages in thread
From: Matheus Alves de Almeida @ 2026-10-31 16:43 UTC (permalink / raw)
  To: OGAWA Hirofumi
  Cc: linux-fsdevel, brauner, djwong, hch, linux-kernel,
	Matheus Alves de Almeida

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@inf.ufrgs.br>
---
 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


^ permalink raw reply	[flat|nested] 14+ messages in thread

* [RFC PATCH 2/8] fat: use iomap for ->bmap()
  2026-10-31 16:43 [RFC PATCH 0/8] fat: convert to iomap Matheus Alves de Almeida
  2026-10-31 16:43 ` [RFC PATCH 1/8] fat: support FIEMAP Matheus Alves de Almeida
@ 2026-10-31 16:43 ` Matheus Alves de Almeida
  2026-10-31 16:43 ` [RFC PATCH 3/8] fat: add iomap direct I/O support Matheus Alves de Almeida
                   ` (5 subsequent siblings)
  7 siblings, 0 replies; 14+ messages in thread
From: Matheus Alves de Almeida @ 2026-10-31 16:43 UTC (permalink / raw)
  To: OGAWA Hirofumi
  Cc: linux-fsdevel, brauner, djwong, hch, linux-kernel,
	Matheus Alves de Almeida

Make FAT ->bmap() use iomap_bmap(). Add an iomap-backed
fat_swap_activate() to prevent a swapon() deadlock. Remove
truncate_lock as ->bmap() now uses i_rwsem.

Signed-off-by: Matheus Alves de Almeida <matheus.aalmeida@inf.ufrgs.br>
---
 fs/fat/fat.h   |  2 +-
 fs/fat/file.c  |  2 --
 fs/fat/inode.c | 47 ++++++++++++++---------------------------------
 fs/fat/iomap.c | 24 ++++++++++++++++++++++++
 4 files changed, 39 insertions(+), 36 deletions(-)

diff --git a/fs/fat/fat.h b/fs/fat/fat.h
index 3d0f91fa11df..81c454f8a78a 100644
--- a/fs/fat/fat.h
+++ b/fs/fat/fat.h
@@ -131,7 +131,6 @@ struct msdos_inode_info {
 	loff_t i_pos;		/* on-disk position of directory entry or 0 */
 	struct hlist_node i_fat_hash;	/* hash by i_location */
 	struct hlist_node i_dir_hash;	/* hash by i_logstart */
-	struct rw_semaphore truncate_lock; /* protect bmap against truncate */
 	struct timespec64 i_crtime;	/* File creation (birth) time */
 	struct mapping_metadata_bhs i_metadata_bhs;
 	struct inode vfs_inode;
@@ -492,6 +491,7 @@ extern const struct export_operations fat_export_ops_nostale;
 
 /* fat/iomap.c */
 extern const struct iomap_ops fat_iomap_ops;
+extern const struct iomap_ops fat_iomap_bmap_ops;
 
 /* helper for printk */
 typedef unsigned long long	llu;
diff --git a/fs/fat/file.c b/fs/fat/file.c
index 531abf9e533c..c27d4e7f698c 100644
--- a/fs/fat/file.c
+++ b/fs/fat/file.c
@@ -613,10 +613,8 @@ int fat_setattr(const struct mnt_idmap *idmap, struct dentry *dentry,
 		error = fat_block_truncate_page(inode, attr->ia_size);
 		if (error)
 			goto out;
-		down_write(&MSDOS_I(inode)->truncate_lock);
 		truncate_setsize(inode, attr->ia_size);
 		fat_truncate_blocks(inode, attr->ia_size);
-		up_write(&MSDOS_I(inode)->truncate_lock);
 	}
 
 	/*
diff --git a/fs/fat/inode.c b/fs/fat/inode.c
index 2a16ed415631..3885ddb916bd 100644
--- a/fs/fat/inode.c
+++ b/fs/fat/inode.c
@@ -216,9 +216,7 @@ static void fat_write_failed(struct address_space *mapping, loff_t to)
 
 	if (to > inode->i_size) {
 		truncate_pagecache(inode, inode->i_size);
-		down_write(&MSDOS_I(inode)->truncate_lock);
 		fat_truncate_blocks(inode, inode->i_size);
-		up_write(&MSDOS_I(inode)->truncate_lock);
 	}
 }
 
@@ -290,43 +288,26 @@ static ssize_t fat_direct_IO(struct kiocb *iocb, struct iov_iter *iter)
 	return ret;
 }
 
-static int fat_get_block_bmap(struct inode *inode, sector_t iblock,
-		struct buffer_head *bh_result, int create)
-{
-	struct super_block *sb = inode->i_sb;
-	unsigned long max_blocks = bh_result->b_size >> inode->i_blkbits;
-	int err;
-	sector_t bmap;
-	unsigned long mapped_blocks;
-
-	BUG_ON(create != 0);
-
-	err = fat_bmap(inode, iblock, &bmap, &mapped_blocks, create, true);
-	if (err)
-		return err;
-
-	if (bmap) {
-		map_bh(bh_result, sb, bmap);
-		max_blocks = min(mapped_blocks, max_blocks);
-	}
-
-	bh_result->b_size = max_blocks << sb->s_blocksize_bits;
-
-	return 0;
-}
-
 static sector_t _fat_bmap(struct address_space *mapping, sector_t block)
 {
 	sector_t blocknr;
 
-	/* fat_get_cluster() assumes the requested blocknr isn't truncated. */
-	down_read(&MSDOS_I(mapping->host)->truncate_lock);
-	blocknr = generic_block_bmap(mapping, block, fat_get_block_bmap);
-	up_read(&MSDOS_I(mapping->host)->truncate_lock);
-
+	inode_lock_shared(mapping->host);
+	blocknr = iomap_bmap(mapping, block, &fat_iomap_bmap_ops);
+	inode_unlock_shared(mapping->host);
 	return blocknr;
 }
 
+static int fat_swap_activate(struct swap_info_struct *sis, struct file *file,
+			     sector_t *span)
+{
+#ifdef CONFIG_SWAP
+	return iomap_swapfile_activate(sis, file, span, &fat_iomap_ops);
+#else
+	return -EIO;
+#endif
+}
+
 /*
  * fat_block_truncate_page() zeroes out a mapping from file offset `from'
  * up to the end of the block which corresponds to `from'.
@@ -350,6 +331,7 @@ static const struct address_space_operations fat_aops = {
 	.direct_IO	= fat_direct_IO,
 	.bmap		= _fat_bmap,
 	.migrate_folio	= buffer_migrate_folio,
+	.swap_activate  = fat_swap_activate,
 };
 
 /*
@@ -790,7 +772,6 @@ static struct inode *fat_alloc_inode(struct super_block *sb)
 	if (!ei)
 		return NULL;
 
-	init_rwsem(&ei->truncate_lock);
 	/* Zeroing to allow iput() even if partial initialized inode. */
 	ei->mmu_private = 0;
 	ei->i_start = 0;
diff --git a/fs/fat/iomap.c b/fs/fat/iomap.c
index 7fb24d0296f7..7977147889bb 100644
--- a/fs/fat/iomap.c
+++ b/fs/fat/iomap.c
@@ -69,3 +69,27 @@ static DEFINE_IOMAP_ITER_NEXT(fat_iomap_next, fat_iomap_begin);
 const struct iomap_ops fat_iomap_ops = {
 	.iomap_next = fat_iomap_next
 };
+
+static int fat_iomap_begin_bmap(struct inode *inode, loff_t offset,
+				loff_t length, unsigned int flags,
+				struct iomap *iomap, struct iomap *srcmap)
+{
+	int err;
+
+	err = __fat_iomap_begin(inode, offset, length, flags, iomap);
+	if (err)
+		return err;
+	/*
+	 * iomap_bmap() only returns blocks in an IOMAP_MAPPED range.
+	 * As the old ->bmap() returned KEEP_SIZE blocks, keep that behavior.
+	 */
+	if (iomap->type == IOMAP_UNWRITTEN)
+		iomap->type = IOMAP_MAPPED;
+	return 0;
+}
+
+static DEFINE_IOMAP_ITER_NEXT(fat_iomap_next_bmap, fat_iomap_begin_bmap);
+
+const struct iomap_ops fat_iomap_bmap_ops = {
+	.iomap_next = fat_iomap_next_bmap
+};
-- 
2.43.0


^ permalink raw reply	[flat|nested] 14+ messages in thread

* [RFC PATCH 3/8] fat: add iomap direct I/O support
  2026-10-31 16:43 [RFC PATCH 0/8] fat: convert to iomap Matheus Alves de Almeida
  2026-10-31 16:43 ` [RFC PATCH 1/8] fat: support FIEMAP Matheus Alves de Almeida
  2026-10-31 16:43 ` [RFC PATCH 2/8] fat: use iomap for ->bmap() Matheus Alves de Almeida
@ 2026-10-31 16:43 ` Matheus Alves de Almeida
  2026-11-08 14:56   ` OGAWA Hirofumi
  2026-10-31 16:43 ` [RFC PATCH 4/8] fat: clean stale bdev buffers of newly allocated clusters Matheus Alves de Almeida
                   ` (4 subsequent siblings)
  7 siblings, 1 reply; 14+ messages in thread
From: Matheus Alves de Almeida @ 2026-10-31 16:43 UTC (permalink / raw)
  To: OGAWA Hirofumi
  Cc: linux-fsdevel, brauner, djwong, hch, linux-kernel,
	Matheus Alves de Almeida

Replace the deprecated ->direct_IO() path with ->{read,write}_iter() and
iomap_dio_rw(). Add fat_file_open() to advertise direct I/O support.

Signed-off-by: Matheus Alves de Almeida <matheus.aalmeida@inf.ufrgs.br>
---
 fs/fat/Kconfig |   1 -
 fs/fat/fat.h   |   1 +
 fs/fat/file.c  | 119 ++++++++++++++++++++++++++++++++++++++++++++++++-
 fs/fat/inode.c |  36 ---------------
 fs/fat/iomap.c |  24 ++++++++++
 5 files changed, 142 insertions(+), 39 deletions(-)

diff --git a/fs/fat/Kconfig b/fs/fat/Kconfig
index d31d7866ce19..7989c6bf0fb6 100644
--- a/fs/fat/Kconfig
+++ b/fs/fat/Kconfig
@@ -4,7 +4,6 @@ config FAT_FS
 	select BUFFER_HEAD
 	select FS_IOMAP
 	select NLS
-	select LEGACY_DIRECT_IO
 	help
 	  If you want to use one of the FAT-based file systems (the MS-DOS and
 	  VFAT (Windows 95) file systems), then you must say Y or M here
diff --git a/fs/fat/fat.h b/fs/fat/fat.h
index 81c454f8a78a..e14d6d9a6ac1 100644
--- a/fs/fat/fat.h
+++ b/fs/fat/fat.h
@@ -492,6 +492,7 @@ extern const struct export_operations fat_export_ops_nostale;
 /* fat/iomap.c */
 extern const struct iomap_ops fat_iomap_ops;
 extern const struct iomap_ops fat_iomap_bmap_ops;
+extern const struct iomap_dio_ops fat_iomap_write_dio_ops;
 
 /* helper for printk */
 typedef unsigned long long	llu;
diff --git a/fs/fat/file.c b/fs/fat/file.c
index c27d4e7f698c..177a26186ddc 100644
--- a/fs/fat/file.c
+++ b/fs/fat/file.c
@@ -201,11 +201,126 @@ int fat_file_fsync(struct file *filp, loff_t start, loff_t end, int datasync)
 	return blkdev_issue_flush(inode->i_sb->s_bdev);
 }
 
+static int fat_file_open(struct inode *inode, struct file *file)
+{
+	int err;
+
+	err = generic_file_open(inode, file);
+	if (err)
+		return err;
+
+	file->f_mode |= FMODE_CAN_ODIRECT;
+	return 0;
+}
+
+static ssize_t fat_file_read_iter(struct kiocb *iocb, struct iov_iter *iter)
+{
+	struct inode *inode = file_inode(iocb->ki_filp);
+	ssize_t ret;
+
+	if ((iocb->ki_flags & IOCB_DIRECT) == 0)
+		return generic_file_read_iter(iocb, iter);
+
+	inode_lock_shared(inode);
+	file_accessed(iocb->ki_filp);
+	ret = iomap_dio_rw(iocb, iter, &fat_iomap_ops, NULL, 0, NULL, 0);
+	inode_unlock_shared(inode);
+	return ret;
+}
+
+static ssize_t fat_file_dio_write_iter(struct kiocb *iocb,
+				       struct iov_iter *iter)
+{
+	struct inode *inode = file_inode(iocb->ki_filp);
+	struct msdos_inode_info *mi = MSDOS_I(inode);
+	unsigned int dio_flags = 0;
+	ssize_t ret;
+
+	inode_lock(inode);
+	ret = generic_write_checks(iocb, iter);
+	if (ret <= 0)
+		goto out;
+
+	/* We do not support DIO writes which would need zeroing */
+	if (iocb->ki_pos + iov_iter_count(iter) > mi->mmu_private) {
+		ret = -ENOTBLK;
+		goto out;
+	}
+
+	ret = file_modified(iocb->ki_filp);
+	if (ret)
+		goto out;
+
+	/* Wait under the inode lock for size updates and partial-block zeroing. */
+	if (iocb->ki_pos + iov_iter_count(iter) > i_size_read(inode) ||
+	    !IS_ALIGNED(iocb->ki_pos | iov_iter_alignment(iter),
+			i_blocksize(inode)))
+		dio_flags |= IOMAP_DIO_FORCE_WAIT;
+
+	ret = iomap_dio_rw(iocb, iter, &fat_iomap_ops,
+			   &fat_iomap_write_dio_ops, dio_flags, NULL, 0);
+out:
+	inode_unlock(inode);
+	return ret;
+}
+
+static ssize_t fat_file_write_iter_buffered_fallback(struct kiocb *iocb,
+						     struct iov_iter *iter)
+{
+	ssize_t ret;
+	loff_t end, offset;
+	int err;
+
+	iocb->ki_flags &= ~IOCB_DIRECT;
+	ret = generic_file_write_iter(iocb, iter);
+	if (ret <= 0)
+		return ret;
+
+	offset = iocb->ki_pos - ret;
+	end = iocb->ki_pos - 1;
+	err = filemap_write_and_wait_range(iocb->ki_filp->f_mapping, offset,
+					   end);
+	if (err)
+		return err;
+
+	invalidate_mapping_pages(iocb->ki_filp->f_mapping,
+				 offset >> PAGE_SHIFT,
+				 end >> PAGE_SHIFT);
+
+	return ret;
+}
+
+static ssize_t fat_file_write_iter(struct kiocb *iocb, struct iov_iter *iter)
+{
+	ssize_t ret;
+
+	if ((iocb->ki_flags & IOCB_DIRECT) == 0)
+		return generic_file_write_iter(iocb, iter);
+
+	ret = fat_file_dio_write_iter(iocb, iter);
+	if (ret == -ENOTBLK)
+		ret = 0;
+	else if (ret < 0)
+		return ret;
+
+	/* Fall back to buffered I/O if we could not do DIO */
+	if (iov_iter_count(iter)) {
+		ssize_t done;
+
+		done = fat_file_write_iter_buffered_fallback(iocb, iter);
+		if (done < 0)
+			return done;
+		ret += done;
+	}
+
+	return ret;
+}
 
 const struct file_operations fat_file_operations = {
+	.open		= fat_file_open,
 	.llseek		= generic_file_llseek,
-	.read_iter	= generic_file_read_iter,
-	.write_iter	= generic_file_write_iter,
+	.read_iter	= fat_file_read_iter,
+	.write_iter	= fat_file_write_iter,
 	.mmap_prepare	= generic_file_mmap_prepare,
 	.release	= fat_file_release,
 	.unlocked_ioctl	= fat_generic_ioctl,
diff --git a/fs/fat/inode.c b/fs/fat/inode.c
index 3885ddb916bd..953706aab68b 100644
--- a/fs/fat/inode.c
+++ b/fs/fat/inode.c
@@ -253,41 +253,6 @@ static int fat_write_end(const struct kiocb *iocb,
 	return err;
 }
 
-static ssize_t fat_direct_IO(struct kiocb *iocb, struct iov_iter *iter)
-{
-	struct file *file = iocb->ki_filp;
-	struct address_space *mapping = file->f_mapping;
-	struct inode *inode = mapping->host;
-	size_t count = iov_iter_count(iter);
-	loff_t offset = iocb->ki_pos;
-	ssize_t ret;
-
-	if (iov_iter_rw(iter) == WRITE) {
-		/*
-		 * FIXME: blockdev_direct_IO() doesn't use ->write_begin(),
-		 * so we need to update the ->mmu_private to block boundary.
-		 *
-		 * But we must fill the remaining area or hole by nul for
-		 * updating ->mmu_private.
-		 *
-		 * Return 0, and fallback to normal buffered write.
-		 */
-		loff_t size = offset + count;
-		if (MSDOS_I(inode)->mmu_private < size)
-			return 0;
-	}
-
-	/*
-	 * FAT need to use the DIO_LOCKING for avoiding the race
-	 * condition of fat_get_block() and ->truncate().
-	 */
-	ret = blockdev_direct_IO(iocb, inode, iter, fat_get_block);
-	if (ret < 0 && iov_iter_rw(iter) == WRITE)
-		fat_write_failed(mapping, offset + count);
-
-	return ret;
-}
-
 static sector_t _fat_bmap(struct address_space *mapping, sector_t block)
 {
 	sector_t blocknr;
@@ -328,7 +293,6 @@ static const struct address_space_operations fat_aops = {
 	.writepages	= fat_writepages,
 	.write_begin	= fat_write_begin,
 	.write_end	= fat_write_end,
-	.direct_IO	= fat_direct_IO,
 	.bmap		= _fat_bmap,
 	.migrate_folio	= buffer_migrate_folio,
 	.swap_activate  = fat_swap_activate,
diff --git a/fs/fat/iomap.c b/fs/fat/iomap.c
index 7977147889bb..8a697deba9d4 100644
--- a/fs/fat/iomap.c
+++ b/fs/fat/iomap.c
@@ -26,6 +26,10 @@ static int __fat_iomap_begin(struct inode *inode, loff_t offset, loff_t length,
 		return 0;
 	}
 
+	/* Keep the same behavior of syncing metadata on direct writes */
+	if (flags & IOMAP_WRITE)
+		iomap->flags |= IOMAP_F_DIRTY;
+
 	/*
 	 * We need to separate normal data clusters and
 	 * uninitialized FALLOC_FL_KEEP_SIZE clusters.
@@ -93,3 +97,23 @@ static DEFINE_IOMAP_ITER_NEXT(fat_iomap_next_bmap, fat_iomap_begin_bmap);
 const struct iomap_ops fat_iomap_bmap_ops = {
 	.iomap_next = fat_iomap_next_bmap
 };
+
+static int fat_dio_write_end_io(struct kiocb *iocb, ssize_t size, int error,
+				unsigned int flags)
+{
+	struct inode *inode = file_inode(iocb->ki_filp);
+
+	if (error)
+		return error;
+
+	if (size && i_size_read(inode) < iocb->ki_pos + size) {
+		i_size_write(inode, iocb->ki_pos + size);
+		mark_inode_dirty(inode);
+	}
+
+	return 0;
+}
+
+const struct iomap_dio_ops fat_iomap_write_dio_ops = {
+	.end_io = fat_dio_write_end_io
+};
-- 
2.43.0


^ permalink raw reply	[flat|nested] 14+ messages in thread

* [RFC PATCH 4/8] fat: clean stale bdev buffers of newly allocated clusters
  2026-10-31 16:43 [RFC PATCH 0/8] fat: convert to iomap Matheus Alves de Almeida
                   ` (2 preceding siblings ...)
  2026-10-31 16:43 ` [RFC PATCH 3/8] fat: add iomap direct I/O support Matheus Alves de Almeida
@ 2026-10-31 16:43 ` Matheus Alves de Almeida
  2026-11-08 14:57   ` OGAWA Hirofumi
  2026-10-31 16:43 ` [RFC PATCH 5/8] fat: drop ->write_begin() and ->write_end() usage from fat_cont_expand() Matheus Alves de Almeida
                   ` (3 subsequent siblings)
  7 siblings, 1 reply; 14+ messages in thread
From: Matheus Alves de Almeida @ 2026-10-31 16:43 UTC (permalink / raw)
  To: OGAWA Hirofumi
  Cc: linux-fsdevel, brauner, djwong, hch, linux-kernel,
	Matheus Alves de Almeida

Directory I/O uses buffer_heads on the block device, which may leave
stale buffers behind when clusters are freed and reused. The buffer_head
I/O paths currently call clean_bdev_aliases() for newly allocated blocks,
preventing stale buffers from overwriting new data.

Buffered iomap I/O does not provide this protection. In preparation for
the buffered iomap conversion, clean bdev aliases on newly allocated
clusters, like exfat patch "exfat: clean stale bdev buffers of newly
allocated clusters".

Signed-off-by: Matheus Alves de Almeida <matheus.aalmeida@inf.ufrgs.br>
---
 fs/fat/fatent.c | 34 ++++++++++++++++++++++++++++++++--
 1 file changed, 32 insertions(+), 2 deletions(-)

diff --git a/fs/fat/fatent.c b/fs/fat/fatent.c
index 4fa1fae5abf9..46899efa3cf8 100644
--- a/fs/fat/fatent.c
+++ b/fs/fat/fatent.c
@@ -523,6 +523,21 @@ static int fat_flush_bhs(struct super_block *sb, struct buffer_head **bhs,
 	return 0;
 }
 
+/*
+ * Newly allocated clusters may still have dirty buffer_heads in the block
+ * device mapping, e.g. the dentries of a directory that was just removed.
+ * File data does not go through those buffers, so drop them; otherwise a
+ * later flush of the block device writes them over the new data.
+ */
+static void fat_clean_bdev_aliases(struct super_block *sb,
+				   unsigned int clu, unsigned int count)
+{
+	struct msdos_sb_info *sbi = MSDOS_SB(sb);
+
+	clean_bdev_aliases(sb->s_bdev, fat_clus_to_blknr(sbi, clu),
+			   (sector_t)count * sbi->sec_per_clus);
+}
+
 static int __fat_alloc_clusters(struct inode *inode, int *cluster, int nr_cluster,
 				bool return_all)
 {
@@ -531,7 +546,7 @@ static int __fat_alloc_clusters(struct inode *inode, int *cluster, int nr_cluste
 	const struct fatent_operations *ops = sbi->fatent_ops;
 	struct fat_entry fatent, prev_ent;
 	struct buffer_head *bhs[MAX_BUF_PER_PAGE];
-	int i, count, err, nr_bhs, idx_clus;
+	int i, count, err, nr_bhs, idx_clus, cont_start, cont_len;
 
 	lock_fat(sbi);
 	if (sbi->free_clusters != -1 && sbi->free_clus_valid &&
@@ -540,14 +555,20 @@ static int __fat_alloc_clusters(struct inode *inode, int *cluster, int nr_cluste
 		return -ENOSPC;
 	}
 
+	cont_len = 0;
 	err = nr_bhs = idx_clus = 0;
 	count = FAT_START_ENT;
 	fatent_init(&prev_ent);
 	fatent_init(&fatent);
 	fatent_set_entry(&fatent, sbi->prev_free + 1);
 	while (count < sbi->max_cluster) {
-		if (fatent.entry >= sbi->max_cluster)
+		if (fatent.entry >= sbi->max_cluster) {
+			if (cont_len) {
+				fat_clean_bdev_aliases(sb, cont_start, cont_len);
+				cont_len = 0;
+			}
 			fatent.entry = FAT_START_ENT;
+		}
 		fatent_set_entry(&fatent, fatent.entry);
 		err = fat_ent_read_block(sb, &fatent);
 		if (err)
@@ -573,6 +594,10 @@ static int __fat_alloc_clusters(struct inode *inode, int *cluster, int nr_cluste
 					*cluster = entry;
 				idx_clus++;
 
+				if (cont_len == 0)
+					cont_start = entry;
+				cont_len++;
+
 				if (nr_bhs + fatent.nr_bhs > MAX_BUF_PER_PAGE) {
 					err = fat_flush_bhs(sb, bhs, &nr_bhs,
 							    inode_needs_sync(inode));
@@ -589,6 +614,9 @@ static int __fat_alloc_clusters(struct inode *inode, int *cluster, int nr_cluste
 				 * so we can still use the prev_ent.
 				 */
 				prev_ent = fatent;
+			} else if (cont_len) {
+				fat_clean_bdev_aliases(sb, cont_start, cont_len);
+				cont_len = 0;
 			}
 			count++;
 			if (count == sbi->max_cluster)
@@ -602,6 +630,8 @@ static int __fat_alloc_clusters(struct inode *inode, int *cluster, int nr_cluste
 	err = -ENOSPC;
 
 out:
+	if (cont_len)
+		fat_clean_bdev_aliases(sb, cont_start, cont_len);
 	unlock_fat(sbi);
 	mark_fsinfo_dirty(sb);
 	fatent_brelse(&fatent);
-- 
2.43.0


^ permalink raw reply	[flat|nested] 14+ messages in thread

* [RFC PATCH 5/8] fat: drop ->write_begin() and ->write_end() usage from fat_cont_expand()
  2026-10-31 16:43 [RFC PATCH 0/8] fat: convert to iomap Matheus Alves de Almeida
                   ` (3 preceding siblings ...)
  2026-10-31 16:43 ` [RFC PATCH 4/8] fat: clean stale bdev buffers of newly allocated clusters Matheus Alves de Almeida
@ 2026-10-31 16:43 ` Matheus Alves de Almeida
  2026-11-08 14:59   ` OGAWA Hirofumi
  2026-10-31 16:43 ` [RFC PATCH 6/8] fat: add iomap buffered I/O support Matheus Alves de Almeida
                   ` (2 subsequent siblings)
  7 siblings, 1 reply; 14+ messages in thread
From: Matheus Alves de Almeida @ 2026-10-31 16:43 UTC (permalink / raw)
  To: OGAWA Hirofumi
  Cc: linux-fsdevel, brauner, djwong, hch, linux-kernel,
	Matheus Alves de Almeida

fat_cont_expand() currently relies on ->write_begin() and ->write_end()
to zero the new file range. These callbacks will no longer be available
after the iomap conversion, so a new method for zeroing is needed.

Zero new ranges folio-by-folio instead.

Signed-off-by: Matheus Alves de Almeida <matheus.aalmeida@inf.ufrgs.br>
---
 fs/fat/file.c | 122 +++++++++++++++++++++++++++++++-------------------
 1 file changed, 76 insertions(+), 46 deletions(-)

diff --git a/fs/fat/file.c b/fs/fat/file.c
index 177a26186ddc..8689cbbdad13 100644
--- a/fs/fat/file.c
+++ b/fs/fat/file.c
@@ -18,6 +18,8 @@
 #include <linux/security.h>
 #include <linux/falloc.h>
 #include <linux/fileattr.h>
+#include <linux/rmap.h>
+#include <linux/writeback.h>
 #include "fat.h"
 
 static long fat_fallocate(struct file *file, int mode,
@@ -353,68 +355,96 @@ static int fat_preallocate(struct inode *inode, loff_t new_size)
 	return fat_add_clusters(inode, nr_cluster);
 }
 
-static int fat_cont_expand_simple(struct inode *inode, loff_t size)
+static int fat_zero_and_extend(struct inode *inode, loff_t new_size)
 {
+	struct msdos_inode_info *mi = MSDOS_I(inode);
 	struct address_space *mapping = inode->i_mapping;
-	const struct address_space_operations *aops = mapping->a_ops;
+	loff_t pos = i_size_read(inode), fpos, next;
+	loff_t old_size = pos;
 	struct folio *folio;
-	void *fsdata = NULL;
-	int err;
+	size_t from, to;
+	pgoff_t index;
 
-	err = aops->write_begin(NULL, mapping, size, 0, &folio, &fsdata);
-	if (err)
-		goto out;
+	while (pos < new_size) {
+		index = pos >> PAGE_SHIFT;
 
-	err = aops->write_end(NULL, mapping, size, 0, 0, folio, fsdata);
+		folio = read_mapping_folio(mapping, index, NULL);
+		if (IS_ERR(folio))
+			return PTR_ERR(folio);
 
-out:
-	return err;
+		folio_lock(folio);
+		if (folio->mapping != mapping) {
+			folio_unlock(folio);
+			folio_put(folio);
+			continue;
+		}
+		folio_wait_stable(folio);
+
+		fpos = folio_pos(folio);
+		next = min_t(loff_t, new_size, fpos + folio_size(folio));
+		from = pos - fpos;
+		to = next - fpos;
+
+		/*
+		 * An existing shared writeable mmap of the file could allow
+		 * writing into the newly zeroed range, so make it read-only.
+		 */
+		if (pos == old_size && from)
+			folio_mkclean(folio);
+
+		folio_zero_segment(folio, from, to);
+		folio_mark_dirty(folio);
+		mi->mmu_private = max(mi->mmu_private, next);
+		i_size_write(inode, next);
+
+		folio_unlock(folio);
+		folio_put(folio);
+
+		balance_dirty_pages_ratelimited(mapping);
+		if (fatal_signal_pending(current))
+			return -EINTR;
+
+		pos = next;
+	}
+
+	return 0;
 }
 
-static int fat_cont_expand(struct inode *inode, loff_t size)
+static int fat_cont_expand(struct inode *inode, loff_t new_size)
 {
-	struct address_space *mapping = inode->i_mapping;
-	loff_t start = inode->i_size, count = size - inode->i_size;
+	struct msdos_inode_info *mi = MSDOS_I(inode);
+	loff_t old_alloc = inode_get_bytes(inode);
+	loff_t old_size = i_size_read(inode);
+	loff_t old_mmu_private = mi->mmu_private;
 	int err;
 
-	/*
-	 * Preallocating space here is more efficient than during
-	 * fat_cont_expand_simple() because of batching.
-	 */
-	err = inode_newsize_ok(inode, size);
-	if (err)
-		goto out;
-	err = fat_preallocate(inode, size);
-	if (err)
-		goto out;
-	err = fat_cont_expand_simple(inode, size);
+	err = inode_newsize_ok(inode, new_size);
 	if (err)
-		goto out;
+		return err;
+	inode_dio_wait(inode);
+	err = fat_preallocate(inode, new_size);
+	if (err) {
+		/* fat_chain_add() does not rollback on failure */
+		fat_truncate_blocks(inode, old_alloc);
+		return err;
+	}
+	err = fat_zero_and_extend(inode, new_size);
+	if (err) {
+		filemap_invalidate_lock(inode->i_mapping);
+		truncate_setsize(inode, old_size);
+		fat_truncate_blocks(inode, old_alloc);
+		mi->mmu_private = old_mmu_private;
+		filemap_invalidate_unlock(inode->i_mapping);
+		return err;
+	}
 
 	fat_truncate_time(inode, NULL, FAT_UPDATE_CMTIME);
+	mi->i_attrs |= ATTR_ARCH;
 	mark_inode_dirty(inode);
-	if (IS_SYNC(inode)) {
-		int err2;
+	if (IS_SYNC(inode))
+		return write_inode_now(inode, 1);
 
-		/*
-		 * Opencode syncing since we don't have a file open to use
-		 * standard fsync path.
-		 */
-		err = filemap_fdatawrite_range(mapping, start,
-					       start + count - 1);
-		err2 = mmb_sync(&MSDOS_I(inode)->i_metadata_bhs);
-		if (!err)
-			err = err2;
-		err2 = write_inode_now(inode, 1);
-		if (!err)
-			err = err2;
-		if (!err) {
-			err =  filemap_fdatawait_range(mapping, start,
-						       start + count - 1);
-		}
-	}
-out:
-	return err;
+	return 0;
 }
 
 /*
-- 
2.43.0


^ permalink raw reply	[flat|nested] 14+ messages in thread

* [RFC PATCH 6/8] fat: add iomap buffered I/O support
  2026-10-31 16:43 [RFC PATCH 0/8] fat: convert to iomap Matheus Alves de Almeida
                   ` (4 preceding siblings ...)
  2026-10-31 16:43 ` [RFC PATCH 5/8] fat: drop ->write_begin() and ->write_end() usage from fat_cont_expand() Matheus Alves de Almeida
@ 2026-10-31 16:43 ` Matheus Alves de Almeida
  2026-11-08 15:03   ` OGAWA Hirofumi
  2026-10-31 16:43 ` [RFC PATCH 7/8] fat: remove ->mmu_private Matheus Alves de Almeida
  2026-10-31 16:43 ` [RFC PATCH 8/8] fat: add multi-cluster allocation for write Matheus Alves de Almeida
  7 siblings, 1 reply; 14+ messages in thread
From: Matheus Alves de Almeida @ 2026-10-31 16:43 UTC (permalink / raw)
  To: OGAWA Hirofumi
  Cc: linux-fsdevel, brauner, djwong, hch, linux-kernel,
	Matheus Alves de Almeida

Use iomap for buffered I/O, replacing buffer_heads. Remove
fat_get_block(), fat_write_begin(), fat_write_failed() as the iomap path
makes them unnecessary.

Signed-off-by: Matheus Alves de Almeida <matheus.aalmeida@inf.ufrgs.br>
---
 fs/fat/fat.h   |   4 +-
 fs/fat/file.c  |  72 +++++++++++++++++++----
 fs/fat/inode.c | 157 +++++--------------------------------------------
 fs/fat/iomap.c |  84 ++++++++++++++++++++++++++
 4 files changed, 164 insertions(+), 153 deletions(-)

diff --git a/fs/fat/fat.h b/fs/fat/fat.h
index e14d6d9a6ac1..78c6fb5cdc46 100644
--- a/fs/fat/fat.h
+++ b/fs/fat/fat.h
@@ -415,9 +415,9 @@ extern int fat_getattr(const struct mnt_idmap *idmap,
 int fat_fileattr_get(struct dentry *dentry, struct file_kattr *fa);
 extern int fat_file_fsync(struct file *file, loff_t start, loff_t end,
 			  int datasync);
+int fat_preallocate(struct inode *inode, loff_t new_size);
 
 /* fat/inode.c */
-extern int fat_block_truncate_page(struct inode *inode, loff_t from);
 extern void fat_attach(struct inode *inode, loff_t i_pos);
 extern void fat_detach(struct inode *inode);
 extern struct inode *fat_iget(struct super_block *sb, loff_t i_pos);
@@ -492,7 +492,9 @@ extern const struct export_operations fat_export_ops_nostale;
 /* fat/iomap.c */
 extern const struct iomap_ops fat_iomap_ops;
 extern const struct iomap_ops fat_iomap_bmap_ops;
+extern const struct iomap_ops fat_iomap_write_ops;
 extern const struct iomap_dio_ops fat_iomap_write_dio_ops;
+extern const struct iomap_writeback_ops fat_writeback_ops;
 
 /* helper for printk */
 typedef unsigned long long	llu;
diff --git a/fs/fat/file.c b/fs/fat/file.c
index 8689cbbdad13..723b671d5b4b 100644
--- a/fs/fat/file.c
+++ b/fs/fat/file.c
@@ -220,12 +220,13 @@ static ssize_t fat_file_read_iter(struct kiocb *iocb, struct iov_iter *iter)
 	struct inode *inode = file_inode(iocb->ki_filp);
 	ssize_t ret;
 
-	if ((iocb->ki_flags & IOCB_DIRECT) == 0)
-		return generic_file_read_iter(iocb, iter);
-
 	inode_lock_shared(inode);
-	file_accessed(iocb->ki_filp);
-	ret = iomap_dio_rw(iocb, iter, &fat_iomap_ops, NULL, 0, NULL, 0);
+	if (iocb->ki_flags & IOCB_DIRECT) {
+		file_accessed(iocb->ki_filp);
+		ret = iomap_dio_rw(iocb, iter, &fat_iomap_ops, NULL, 0, NULL, 0);
+	} else {
+		ret = generic_file_read_iter(iocb, iter);
+	}
 	inode_unlock_shared(inode);
 	return ret;
 }
@@ -266,6 +267,50 @@ static ssize_t fat_file_dio_write_iter(struct kiocb *iocb,
 	return ret;
 }
 
+static int fat_cont_expand(struct inode *inode, loff_t new_size);
+
+static ssize_t fat_file_write_iter_buffered(struct kiocb *iocb,
+					    struct iov_iter *iter)
+{
+	struct inode *inode = file_inode(iocb->ki_filp);
+	struct msdos_sb_info *sbi = MSDOS_SB(inode->i_sb);
+	loff_t offset, old_size, old_alloc, keep_alloc;
+	ssize_t ret;
+
+	inode_lock(inode);
+	ret = generic_write_checks(iocb, iter);
+	if (ret <= 0)
+		goto out;
+
+	ret = file_modified(iocb->ki_filp);
+	if (ret)
+		goto out;
+
+	offset = iocb->ki_pos;
+	old_size = i_size_read(inode);
+	if (offset > old_size) {
+		/* We need to fill this hole */
+		ret = fat_cont_expand(inode, offset);
+		if (ret)
+			goto out;
+	}
+
+	inode_dio_wait(inode);
+	old_alloc = inode_get_bytes(inode);
+	ret = iomap_file_buffered_write(iocb, iter, &fat_iomap_write_ops,
+					NULL, NULL);
+	keep_alloc = max(old_alloc, round_up(i_size_read(inode),
+					     sbi->cluster_size));
+	if (inode_get_bytes(inode) > keep_alloc)
+		fat_truncate_blocks(inode, keep_alloc);
+	if (ret > 0)
+		ret = generic_write_sync(iocb, ret);
+
+out:
+	inode_unlock(inode);
+	return ret;
+}
+
 static ssize_t fat_file_write_iter_buffered_fallback(struct kiocb *iocb,
 						     struct iov_iter *iter)
 {
@@ -274,7 +319,7 @@ static ssize_t fat_file_write_iter_buffered_fallback(struct kiocb *iocb,
 	int err;
 
 	iocb->ki_flags &= ~IOCB_DIRECT;
-	ret = generic_file_write_iter(iocb, iter);
+	ret = fat_file_write_iter_buffered(iocb, iter);
 	if (ret <= 0)
 		return ret;
 
@@ -297,7 +342,7 @@ static ssize_t fat_file_write_iter(struct kiocb *iocb, struct iov_iter *iter)
 	ssize_t ret;
 
 	if ((iocb->ki_flags & IOCB_DIRECT) == 0)
-		return generic_file_write_iter(iocb, iter);
+		return fat_file_write_iter_buffered(iocb, iter);
 
 	ret = fat_file_dio_write_iter(iocb, iter);
 	if (ret == -ENOTBLK)
@@ -334,7 +379,7 @@ const struct file_operations fat_file_operations = {
 	.setlease	= generic_setlease,
 };
 
-static int fat_preallocate(struct inode *inode, loff_t new_size)
+int fat_preallocate(struct inode *inode, loff_t new_size)
 {
 	int nr_cluster; /* Number of clusters to be allocated */
 	loff_t mm_bytes; /* Number of bytes to be allocated for file */
@@ -755,11 +800,16 @@ int fat_setattr(const struct mnt_idmap *idmap, struct dentry *dentry,
 	}
 
 	if (attr->ia_valid & ATTR_SIZE) {
-		error = fat_block_truncate_page(inode, attr->ia_size);
+		filemap_invalidate_lock(inode->i_mapping);
+		error = iomap_truncate_page(inode, attr->ia_size, NULL,
+					    &fat_iomap_ops, NULL, NULL);
+		if (!error) {
+			truncate_setsize(inode, attr->ia_size);
+			fat_truncate_blocks(inode, attr->ia_size);
+		}
+		filemap_invalidate_unlock(inode->i_mapping);
 		if (error)
 			goto out;
-		truncate_setsize(inode, attr->ia_size);
-		fat_truncate_blocks(inode, attr->ia_size);
 	}
 
 	/*
diff --git a/fs/fat/inode.c b/fs/fat/inode.c
index 953706aab68b..9df10e5a1ede 100644
--- a/fs/fat/inode.c
+++ b/fs/fat/inode.c
@@ -116,141 +116,27 @@ int fat_add_clusters(struct inode *inode, int nr_cluster)
 	return err;
 }
 
-static inline int __fat_get_block(struct inode *inode, sector_t iblock,
-				  unsigned long *max_blocks,
-				  struct buffer_head *bh_result, int create)
-{
-	struct super_block *sb = inode->i_sb;
-	struct msdos_sb_info *sbi = MSDOS_SB(sb);
-	unsigned long mapped_blocks;
-	sector_t phys, last_block;
-	int err, offset;
-
-	err = fat_bmap(inode, iblock, &phys, &mapped_blocks, create, false);
-	if (err)
-		return err;
-	if (phys) {
-		map_bh(bh_result, sb, phys);
-		*max_blocks = min(mapped_blocks, *max_blocks);
-		return 0;
-	}
-	if (!create)
-		return 0;
-
-	if (iblock != MSDOS_I(inode)->mmu_private >> sb->s_blocksize_bits) {
-		fat_fs_error(sb, "corrupted file size (i_pos %lld, %lld)",
-			MSDOS_I(inode)->i_pos, MSDOS_I(inode)->mmu_private);
-		return -EIO;
-	}
-
-	last_block = inode->i_blocks >> (sb->s_blocksize_bits - 9);
-	offset = (unsigned long)iblock & (sbi->sec_per_clus - 1);
-	/*
-	 * allocate a cluster according to the following.
-	 * 1) no more available blocks
-	 * 2) not part of fallocate region
-	 */
-	if (!offset && !(iblock < last_block)) {
-		/* TODO: multiple cluster allocation would be desirable. */
-		err = fat_add_clusters(inode, 1);
-		if (err)
-			return err;
-	}
-	/* available blocks on this cluster */
-	mapped_blocks = sbi->sec_per_clus - offset;
-
-	*max_blocks = min(mapped_blocks, *max_blocks);
-	MSDOS_I(inode)->mmu_private += *max_blocks << sb->s_blocksize_bits;
-
-	err = fat_bmap(inode, iblock, &phys, &mapped_blocks, create, false);
-	if (err)
-		return err;
-	if (!phys) {
-		fat_fs_error(sb,
-			     "invalid FAT chain (i_pos %lld, last_block %llu)",
-			     MSDOS_I(inode)->i_pos,
-			     (unsigned long long)last_block);
-		return -EIO;
-	}
-
-	BUG_ON(*max_blocks != mapped_blocks);
-	set_buffer_new(bh_result);
-	map_bh(bh_result, sb, phys);
-
-	return 0;
-}
-
-static int fat_get_block(struct inode *inode, sector_t iblock,
-			 struct buffer_head *bh_result, int create)
-{
-	struct super_block *sb = inode->i_sb;
-	unsigned long max_blocks = bh_result->b_size >> inode->i_blkbits;
-	int err;
-
-	err = __fat_get_block(inode, iblock, &max_blocks, bh_result, create);
-	if (err)
-		return err;
-	bh_result->b_size = max_blocks << sb->s_blocksize_bits;
-	return 0;
-}
-
 static int fat_writepages(struct address_space *mapping,
 			  struct writeback_control *wbc)
 {
-	return mpage_writepages(mapping, wbc, fat_get_block);
+	struct iomap_writepage_ctx wpc = {
+		.inode = mapping->host,
+		.wbc = wbc,
+		.ops = &fat_writeback_ops,
+	};
+
+	return iomap_writepages(&wpc);
 }
 
 static int fat_read_folio(struct file *file, struct folio *folio)
 {
-	return mpage_read_folio(folio, fat_get_block);
+	iomap_bio_read_folio(folio, &fat_iomap_ops);
+	return 0;
 }
 
 static void fat_readahead(struct readahead_control *rac)
 {
-	mpage_readahead(rac, fat_get_block);
-}
-
-static void fat_write_failed(struct address_space *mapping, loff_t to)
-{
-	struct inode *inode = mapping->host;
-
-	if (to > inode->i_size) {
-		truncate_pagecache(inode, inode->i_size);
-		fat_truncate_blocks(inode, inode->i_size);
-	}
-}
-
-static int fat_write_begin(const struct kiocb *iocb,
-			   struct address_space *mapping,
-			   loff_t pos, unsigned len,
-			   struct folio **foliop, void **fsdata)
-{
-	int err;
-
-	err = cont_write_begin(iocb, mapping, pos, len,
-				foliop, fsdata, fat_get_block,
-				&MSDOS_I(mapping->host)->mmu_private);
-	if (err < 0)
-		fat_write_failed(mapping, pos + len);
-	return err;
-}
-
-static int fat_write_end(const struct kiocb *iocb,
-			 struct address_space *mapping,
-			 loff_t pos, unsigned len, unsigned copied,
-			 struct folio *folio, void *fsdata)
-{
-	struct inode *inode = mapping->host;
-	int err;
-	err = generic_write_end(iocb, mapping, pos, len, copied, folio, fsdata);
-	if (err < len)
-		fat_write_failed(mapping, pos + len);
-	if (!(err < 0) && !(MSDOS_I(inode)->i_attrs & ATTR_ARCH)) {
-		fat_truncate_time(inode, NULL, FAT_UPDATE_CMTIME);
-		MSDOS_I(inode)->i_attrs |= ATTR_ARCH;
-		mark_inode_dirty(inode);
-	}
-	return err;
+	iomap_bio_readahead(rac, &fat_iomap_ops);
 }
 
 static sector_t _fat_bmap(struct address_space *mapping, sector_t block)
@@ -273,29 +159,18 @@ static int fat_swap_activate(struct swap_info_struct *sis, struct file *file,
 #endif
 }
 
-/*
- * fat_block_truncate_page() zeroes out a mapping from file offset `from'
- * up to the end of the block which corresponds to `from'.
- * This is required during truncate to physically zeroout the tail end
- * of that block so it doesn't yield old data if the file is later grown.
- * Also, avoid causing failure from fsx for cases of "data past EOF"
- */
-int fat_block_truncate_page(struct inode *inode, loff_t from)
-{
-	return block_truncate_page(inode->i_mapping, from, fat_get_block);
-}
-
 static const struct address_space_operations fat_aops = {
-	.dirty_folio	= block_dirty_folio,
-	.invalidate_folio = block_invalidate_folio,
+	.dirty_folio	= iomap_dirty_folio,
+	.invalidate_folio = iomap_invalidate_folio,
 	.read_folio	= fat_read_folio,
 	.readahead	= fat_readahead,
 	.writepages	= fat_writepages,
-	.write_begin	= fat_write_begin,
-	.write_end	= fat_write_end,
 	.bmap		= _fat_bmap,
-	.migrate_folio	= buffer_migrate_folio,
+	.migrate_folio	= filemap_migrate_folio,
 	.swap_activate  = fat_swap_activate,
+	.is_partially_uptodate = iomap_is_partially_uptodate,
+	.error_remove_folio = generic_error_remove_folio,
+	.release_folio = iomap_release_folio,
 };
 
 /*
diff --git a/fs/fat/iomap.c b/fs/fat/iomap.c
index 8a697deba9d4..cfeaea5d352e 100644
--- a/fs/fat/iomap.c
+++ b/fs/fat/iomap.c
@@ -5,6 +5,9 @@
  * Copyright (C) 2026 Matheus Alves de Almeida
  */
 
+#include <linux/highmem.h>
+#include <linux/pagemap.h>
+
 #include "fat.h"
 
 static int __fat_iomap_begin(struct inode *inode, loff_t offset, loff_t length,
@@ -74,6 +77,65 @@ const struct iomap_ops fat_iomap_ops = {
 	.iomap_next = fat_iomap_next
 };
 
+static int fat_iomap_end_write(struct inode *inode, loff_t offset,
+			       loff_t length, ssize_t written,
+			       unsigned int flags, struct iomap *iomap)
+{
+	struct msdos_inode_info *mi = MSDOS_I(inode);
+
+	if (written > 0) {
+		mi->mmu_private = max(mi->mmu_private, offset + written);
+		if (!(mi->i_attrs & ATTR_ARCH)) {
+			mi->i_attrs |= ATTR_ARCH;
+			mark_inode_dirty(inode);
+		}
+	}
+
+	if (iomap->flags & IOMAP_F_SIZE_CHANGED)
+		mark_inode_dirty(inode);
+
+	return 0;
+}
+
+static int fat_iomap_begin_write(struct inode *inode, loff_t offset,
+				 loff_t length, unsigned int flags,
+				 struct iomap *iomap, struct iomap *srcmap)
+{
+	loff_t curr_size = i_size_read(inode);
+	loff_t old_alloc = inode_get_bytes(inode);
+	int err;
+
+	/* Only support extending writes that start at EOF */
+	if (WARN_ONCE(offset > curr_size, "Write starting past EOF"))
+		return -EOPNOTSUPP;
+
+	if (offset == curr_size) {
+		/*
+		 * TODO: allocate multiple blocks while allowing
+		 * short writes
+		 */
+		err = fat_preallocate(inode, offset + 1);
+		if (err)
+			goto err_truncate;
+	}
+
+	err = __fat_iomap_begin(inode, offset, length, flags, iomap);
+	if (err)
+		goto err_truncate;
+
+	return 0;
+err_truncate:
+	fat_truncate_blocks(inode, old_alloc);
+	return err;
+}
+
+static DEFINE_IOMAP_ITER_NEXT_END(fat_iomap_next_write, fat_iomap_begin_write,
+				  fat_iomap_end_write);
+
+const struct iomap_ops fat_iomap_write_ops = {
+	.iomap_next = fat_iomap_next_write
+};
+
 static int fat_iomap_begin_bmap(struct inode *inode, loff_t offset,
 				loff_t length, unsigned int flags,
 				struct iomap *iomap, struct iomap *srcmap)
@@ -117,3 +179,25 @@ static int fat_dio_write_end_io(struct kiocb *iocb, ssize_t size, int error,
 const struct iomap_dio_ops fat_iomap_write_dio_ops = {
 	.end_io = fat_dio_write_end_io
 };
+
+static ssize_t fat_writeback_range(struct iomap_writepage_ctx *wpc,
+				   struct folio *folio, u64 offset,
+				   unsigned int len, u64 end_pos)
+{
+	if (offset < wpc->iomap.offset ||
+	    offset >= wpc->iomap.offset + wpc->iomap.length) {
+		int err;
+
+		err = __fat_iomap_begin(wpc->inode, offset, len, 0,
+					&wpc->iomap);
+		if (err)
+			return err;
+	}
+
+	return iomap_add_to_ioend(wpc, folio, offset, end_pos, len);
+}
+
+const struct iomap_writeback_ops fat_writeback_ops = {
+	.writeback_range = fat_writeback_range,
+	.writeback_submit = iomap_ioend_writeback_submit,
+};
-- 
2.43.0


^ permalink raw reply	[flat|nested] 14+ messages in thread

* [RFC PATCH 7/8] fat: remove ->mmu_private
  2026-10-31 16:43 [RFC PATCH 0/8] fat: convert to iomap Matheus Alves de Almeida
                   ` (5 preceding siblings ...)
  2026-10-31 16:43 ` [RFC PATCH 6/8] fat: add iomap buffered I/O support Matheus Alves de Almeida
@ 2026-10-31 16:43 ` Matheus Alves de Almeida
  2026-10-31 16:43 ` [RFC PATCH 8/8] fat: add multi-cluster allocation for write Matheus Alves de Almeida
  7 siblings, 0 replies; 14+ messages in thread
From: Matheus Alves de Almeida @ 2026-10-31 16:43 UTC (permalink / raw)
  To: OGAWA Hirofumi
  Cc: linux-fsdevel, brauner, djwong, hch, linux-kernel,
	Matheus Alves de Almeida

With the iomap conversion of buffered I/O complete, the ->mmu_private
inode field is no longer necessary.

Replace remaining users with ->i_size checks. Remove the DIO end callback
as DIO now checks ->i_size directly to reject extending writes.

Signed-off-by: Matheus Alves de Almeida <matheus.aalmeida@inf.ufrgs.br>
---
 fs/fat/cache.c | 15 ++-------------
 fs/fat/dir.c   |  1 -
 fs/fat/fat.h   |  4 ----
 fs/fat/file.c  | 23 +++++------------------
 fs/fat/inode.c | 10 +++-------
 fs/fat/iomap.c | 29 +++--------------------------
 6 files changed, 13 insertions(+), 69 deletions(-)

diff --git a/fs/fat/cache.c b/fs/fat/cache.c
index f545e1db1d47..89c950f87493 100644
--- a/fs/fat/cache.c
+++ b/fs/fat/cache.c
@@ -377,19 +377,8 @@ static int is_exceed_eof(struct inode *inode, sector_t sector,
 	const unsigned char blocksize_bits = sb->s_blocksize_bits;
 
 	*last_block = (i_size_read(inode) + (blocksize - 1)) >> blocksize_bits;
-	if (sector >= *last_block) {
-		if (!create)
-			return 1;
-
-		/*
-		 * ->mmu_private can access on only allocation path.
-		 * (caller must hold ->i_mutex)
-		 */
-		*last_block = (MSDOS_I(inode)->mmu_private + (blocksize - 1))
-			>> blocksize_bits;
-		if (sector >= *last_block)
-			return 1;
-	}
+	if (sector >= *last_block && !create)
+		return 1;
 
 	return 0;
 }
diff --git a/fs/fat/dir.c b/fs/fat/dir.c
index 35bdb62944a2..9996bd121e07 100644
--- a/fs/fat/dir.c
+++ b/fs/fat/dir.c
@@ -1443,7 +1443,6 @@ int fat_add_entries(struct inode *dir, void *slots, int nr_slots,
 				& ~((loff_t)sbi->cluster_size - 1);
 		}
 		dir->i_size += nr_cluster << sbi->cluster_bits;
-		MSDOS_I(dir)->mmu_private += nr_cluster << sbi->cluster_bits;
 	}
 	sinfo->slot_off = pos;
 	sinfo->de = de;
diff --git a/fs/fat/fat.h b/fs/fat/fat.h
index 78c6fb5cdc46..962f6f6b7717 100644
--- a/fs/fat/fat.h
+++ b/fs/fat/fat.h
@@ -122,9 +122,6 @@ struct msdos_inode_info {
 	/* for avoiding the race between fat_free() and fat_get_cluster() */
 	unsigned int cache_valid_id;
 
-	/* NOTE: mmu_private is 64bits, so must hold ->i_mutex to access */
-	loff_t mmu_private;	/* physically allocated size */
-
 	int i_start;		/* first cluster or 0 */
 	int i_logstart;		/* logical first cluster */
 	int i_attrs;		/* unused attribute bits */
@@ -493,7 +490,6 @@ extern const struct export_operations fat_export_ops_nostale;
 extern const struct iomap_ops fat_iomap_ops;
 extern const struct iomap_ops fat_iomap_bmap_ops;
 extern const struct iomap_ops fat_iomap_write_ops;
-extern const struct iomap_dio_ops fat_iomap_write_dio_ops;
 extern const struct iomap_writeback_ops fat_writeback_ops;
 
 /* helper for printk */
diff --git a/fs/fat/file.c b/fs/fat/file.c
index 723b671d5b4b..ac6329d36062 100644
--- a/fs/fat/file.c
+++ b/fs/fat/file.c
@@ -235,7 +235,6 @@ static ssize_t fat_file_dio_write_iter(struct kiocb *iocb,
 				       struct iov_iter *iter)
 {
 	struct inode *inode = file_inode(iocb->ki_filp);
-	struct msdos_inode_info *mi = MSDOS_I(inode);
 	unsigned int dio_flags = 0;
 	ssize_t ret;
 
@@ -245,7 +244,7 @@ static ssize_t fat_file_dio_write_iter(struct kiocb *iocb,
 		goto out;
 
 	/* We do not support DIO writes which would need zeroing */
-	if (iocb->ki_pos + iov_iter_count(iter) > mi->mmu_private) {
+	if (iocb->ki_pos + iov_iter_count(iter) > i_size_read(inode)) {
 		ret = -ENOTBLK;
 		goto out;
 	}
@@ -254,14 +253,13 @@ static ssize_t fat_file_dio_write_iter(struct kiocb *iocb,
 	if (ret)
 		goto out;
 
-	/* Wait under the inode lock for size updates and partial-block zeroing. */
-	if (iocb->ki_pos + iov_iter_count(iter) > i_size_read(inode) ||
-	    !IS_ALIGNED(iocb->ki_pos | iov_iter_alignment(iter),
+	/* Wait under the inode lock for unaligned writes */
+	if (!IS_ALIGNED(iocb->ki_pos | iov_iter_alignment(iter),
 			i_blocksize(inode)))
 		dio_flags |= IOMAP_DIO_FORCE_WAIT;
 
-	ret = iomap_dio_rw(iocb, iter, &fat_iomap_ops,
-			   &fat_iomap_write_dio_ops, dio_flags, NULL, 0);
+	ret = iomap_dio_rw(iocb, iter, &fat_iomap_ops, NULL, dio_flags,
+			   NULL, 0);
 out:
 	inode_unlock(inode);
 	return ret;
@@ -402,7 +400,6 @@ int fat_preallocate(struct inode *inode, loff_t new_size)
 
 static int fat_zero_and_extend(struct inode *inode, loff_t new_size)
 {
-	struct msdos_inode_info *mi = MSDOS_I(inode);
 	struct address_space *mapping = inode->i_mapping;
 	loff_t pos = i_size_read(inode), fpos, next;
 	loff_t old_size = pos;
@@ -439,7 +436,6 @@ static int fat_zero_and_extend(struct inode *inode, loff_t new_size)
 
 		folio_zero_segment(folio, from, to);
 		folio_mark_dirty(folio);
-		mi->mmu_private = max(mi->mmu_private, next);
 		i_size_write(inode, next);
 
 		folio_unlock(folio);
@@ -460,7 +456,6 @@ static int fat_cont_expand(struct inode *inode, loff_t new_size)
 	struct msdos_inode_info *mi = MSDOS_I(inode);
 	loff_t old_alloc = inode_get_bytes(inode);
 	loff_t old_size = i_size_read(inode);
-	loff_t old_mmu_private = mi->mmu_private;
 	int err;
 
 	err = inode_newsize_ok(inode, new_size);
@@ -478,7 +473,6 @@ static int fat_cont_expand(struct inode *inode, loff_t new_size)
 		filemap_invalidate_lock(inode->i_mapping);
 		truncate_setsize(inode, old_size);
 		fat_truncate_blocks(inode, old_alloc);
-		mi->mmu_private = old_mmu_private;
 		filemap_invalidate_unlock(inode->i_mapping);
 		return err;
 	}
@@ -605,13 +599,6 @@ void fat_truncate_blocks(struct inode *inode, loff_t offset)
 	const unsigned int cluster_size = sbi->cluster_size;
 	int nr_clusters;
 
-	/*
-	 * This protects against truncating a file bigger than it was then
-	 * trying to write into the hole.
-	 */
-	if (MSDOS_I(inode)->mmu_private > offset)
-		MSDOS_I(inode)->mmu_private = offset;
-
 	nr_clusters = (offset + (cluster_size - 1)) >> sbi->cluster_bits;
 
 	fat_free(inode, nr_clusters);
diff --git a/fs/fat/inode.c b/fs/fat/inode.c
index 9df10e5a1ede..cb2316062bb3 100644
--- a/fs/fat/inode.c
+++ b/fs/fat/inode.c
@@ -356,7 +356,6 @@ int fat_fill_inode(struct inode *inode, struct msdos_dir_entry *de)
 		error = fat_calc_dir_size(inode);
 		if (error < 0)
 			return error;
-		MSDOS_I(inode)->mmu_private = inode->i_size;
 
 		set_nlink(inode, fat_subdirs(inode));
 
@@ -375,7 +374,6 @@ int fat_fill_inode(struct inode *inode, struct msdos_dir_entry *de)
 		inode->i_op = &fat_file_inode_operations;
 		inode->i_fop = &fat_file_operations;
 		inode->i_mapping->a_ops = &fat_aops;
-		MSDOS_I(inode)->mmu_private = inode->i_size;
 	}
 	if (de->attr & ATTR_SYS) {
 		if (sbi->options.sys_immutable)
@@ -486,11 +484,11 @@ static void fat_free_eofblocks(struct inode *inode)
 {
 	/* Release unwritten fallocated blocks on inode eviction. */
 	if ((inode->i_blocks << 9) >
-			round_up(MSDOS_I(inode)->mmu_private,
-				MSDOS_SB(inode->i_sb)->cluster_size)) {
+			round_up(i_size_read(inode),
+				 MSDOS_SB(inode->i_sb)->cluster_size)) {
 		int err;
 
-		fat_truncate_blocks(inode, MSDOS_I(inode)->mmu_private);
+		fat_truncate_blocks(inode, i_size_read(inode));
 		/* Fallocate results in updating the i_start/iogstart
 		 * for the zero byte file. So, make it return to
 		 * original state during evict and commit it to avoid
@@ -612,7 +610,6 @@ static struct inode *fat_alloc_inode(struct super_block *sb)
 		return NULL;
 
 	/* Zeroing to allow iput() even if partial initialized inode. */
-	ei->mmu_private = 0;
 	ei->i_start = 0;
 	ei->i_logstart = 0;
 	ei->i_attrs = 0;
@@ -1194,7 +1191,6 @@ static int fat_read_root(struct inode *inode)
 	inode->i_blocks = ((inode->i_size + (sbi->cluster_size - 1))
 			   & ~((loff_t)sbi->cluster_size - 1)) >> 9;
 	MSDOS_I(inode)->i_logstart = 0;
-	MSDOS_I(inode)->mmu_private = inode->i_size;
 
 	fat_save_attrs(inode, ATTR_DIR);
 	inode_set_mtime_to_ts(inode,
diff --git a/fs/fat/iomap.c b/fs/fat/iomap.c
index cfeaea5d352e..893d94f6b70c 100644
--- a/fs/fat/iomap.c
+++ b/fs/fat/iomap.c
@@ -83,12 +83,9 @@ static int fat_iomap_end_write(struct inode *inode, loff_t offset,
 {
 	struct msdos_inode_info *mi = MSDOS_I(inode);
 
-	if (written > 0) {
-		mi->mmu_private = max(mi->mmu_private, offset + written);
-		if (!(mi->i_attrs & ATTR_ARCH)) {
-			mi->i_attrs |= ATTR_ARCH;
-			mark_inode_dirty(inode);
-		}
+	if (written > 0 && !(mi->i_attrs & ATTR_ARCH)) {
+		mi->i_attrs |= ATTR_ARCH;
+		mark_inode_dirty(inode);
 	}
 
 	if (iomap->flags & IOMAP_F_SIZE_CHANGED)
@@ -160,26 +157,6 @@ const struct iomap_ops fat_iomap_bmap_ops = {
 	.iomap_next = fat_iomap_next_bmap
 };
 
-static int fat_dio_write_end_io(struct kiocb *iocb, ssize_t size, int error,
-				unsigned int flags)
-{
-	struct inode *inode = file_inode(iocb->ki_filp);
-
-	if (error)
-		return error;
-
-	if (size && i_size_read(inode) < iocb->ki_pos + size) {
-		i_size_write(inode, iocb->ki_pos + size);
-		mark_inode_dirty(inode);
-	}
-
-	return 0;
-}
-
-const struct iomap_dio_ops fat_iomap_write_dio_ops = {
-	.end_io = fat_dio_write_end_io
-};
-
 static ssize_t fat_writeback_range(struct iomap_writepage_ctx *wpc,
 				   struct folio *folio, u64 offset,
 				   unsigned int len, u64 end_pos)
-- 
2.43.0


^ permalink raw reply	[flat|nested] 14+ messages in thread

* [RFC PATCH 8/8] fat: add multi-cluster allocation for write
  2026-10-31 16:43 [RFC PATCH 0/8] fat: convert to iomap Matheus Alves de Almeida
                   ` (6 preceding siblings ...)
  2026-10-31 16:43 ` [RFC PATCH 7/8] fat: remove ->mmu_private Matheus Alves de Almeida
@ 2026-10-31 16:43 ` Matheus Alves de Almeida
  7 siblings, 0 replies; 14+ messages in thread
From: Matheus Alves de Almeida @ 2026-10-31 16:43 UTC (permalink / raw)
  To: OGAWA Hirofumi
  Cc: linux-fsdevel, brauner, djwong, hch, linux-kernel,
	Matheus Alves de Almeida

The iomap code expands the file cluster-by-cluster, requiring many calls
into the allocator.

Implement multi-cluster allocation on write. To preserve short write
semantics, allow allocations of chains smaller than the requested size
in fat_alloc_chain().

Signed-off-by: Matheus Alves de Almeida <matheus.aalmeida@inf.ufrgs.br>
---
 fs/fat/fat.h    |  6 ++++--
 fs/fat/fatent.c | 33 ++++++++++++++++++++++-----------
 fs/fat/file.c   |  2 +-
 fs/fat/inode.c  |  4 ++--
 fs/fat/iomap.c  | 12 ++++++------
 5 files changed, 35 insertions(+), 22 deletions(-)

diff --git a/fs/fat/fat.h b/fs/fat/fat.h
index 962f6f6b7717..c953b2fd9c69 100644
--- a/fs/fat/fat.h
+++ b/fs/fat/fat.h
@@ -393,7 +393,8 @@ extern int fat_ent_write(struct inode *inode, struct fat_entry *fatent,
 			 int new, int wait);
 extern int fat_alloc_clusters(struct inode *inode, int *cluster,
 			      int nr_cluster);
-int fat_alloc_chain(struct inode *inode, int *cluster, int nr_cluster);
+int fat_alloc_chain(struct inode *inode, int *cluster, int *nr_cluster,
+		    bool allow_partial);
 extern int fat_free_clusters(struct inode *inode, int cluster);
 extern int fat_count_free_clusters(struct super_block *sb);
 extern int fat_trim_fs(struct inode *inode, struct fstrim_range *range);
@@ -439,7 +440,8 @@ static inline unsigned long fat_dir_hash(int logstart)
 {
 	return hash_32(logstart, FAT_HASH_BITS);
 }
-extern int fat_add_clusters(struct inode *inode, int nr_cluster);
+
+int fat_add_clusters(struct inode *inode, int nr_cluster, bool allow_partial);
 
 /* fat/misc.c */
 extern __printf(3, 4) __cold
diff --git a/fs/fat/fatent.c b/fs/fat/fatent.c
index 46899efa3cf8..6fb99a8759b8 100644
--- a/fs/fat/fatent.c
+++ b/fs/fat/fatent.c
@@ -538,8 +538,8 @@ static void fat_clean_bdev_aliases(struct super_block *sb,
 			   (sector_t)count * sbi->sec_per_clus);
 }
 
-static int __fat_alloc_clusters(struct inode *inode, int *cluster, int nr_cluster,
-				bool return_all)
+static int __fat_alloc_clusters(struct inode *inode, int *cluster, int *nr_cluster,
+				bool return_all, bool allow_partial)
 {
 	struct super_block *sb = inode->i_sb;
 	struct msdos_sb_info *sbi = MSDOS_SB(sb);
@@ -547,12 +547,18 @@ static int __fat_alloc_clusters(struct inode *inode, int *cluster, int nr_cluste
 	struct fat_entry fatent, prev_ent;
 	struct buffer_head *bhs[MAX_BUF_PER_PAGE];
 	int i, count, err, nr_bhs, idx_clus, cont_start, cont_len;
+	int nr_alloc = *nr_cluster;
 
 	lock_fat(sbi);
-	if (sbi->free_clusters != -1 && sbi->free_clus_valid &&
-	    sbi->free_clusters < nr_cluster) {
-		unlock_fat(sbi);
-		return -ENOSPC;
+	if (sbi->free_clusters != -1 && sbi->free_clus_valid) {
+		if (sbi->free_clusters == 0 ||
+		    (!allow_partial && sbi->free_clusters < nr_alloc)) {
+			unlock_fat(sbi);
+			return -ENOSPC;
+		}
+		if (allow_partial)
+			nr_alloc = min_t(unsigned int, nr_alloc,
+					 sbi->free_clusters);
 	}
 
 	cont_len = 0;
@@ -606,7 +612,7 @@ static int __fat_alloc_clusters(struct inode *inode, int *cluster, int nr_cluste
 				}
 				fat_collect_bhs(bhs, &nr_bhs, &fatent);
 
-				if (idx_clus == nr_cluster)
+				if (idx_clus == nr_alloc)
 					goto out;
 
 				/*
@@ -627,7 +633,8 @@ static int __fat_alloc_clusters(struct inode *inode, int *cluster, int nr_cluste
 	/* Couldn't allocate the free entries */
 	sbi->free_clusters = 0;
 	sbi->free_clus_valid = 1;
-	err = -ENOSPC;
+	if (!allow_partial || !idx_clus)
+		err = -ENOSPC;
 
 out:
 	if (cont_len)
@@ -647,17 +654,21 @@ static int __fat_alloc_clusters(struct inode *inode, int *cluster, int nr_cluste
 	if (err && idx_clus)
 		fat_free_clusters(inode, cluster[0]);
 
+	if (!err)
+		*nr_cluster = idx_clus;
 	return err;
 }
 
 int fat_alloc_clusters(struct inode *inode, int *cluster, int nr_cluster)
 {
-	return __fat_alloc_clusters(inode, cluster, nr_cluster, true);
+	return __fat_alloc_clusters(inode, cluster, &nr_cluster, true, false);
 }
 
-int fat_alloc_chain(struct inode *inode, int *cluster, int nr_cluster)
+int fat_alloc_chain(struct inode *inode, int *cluster, int *nr_cluster,
+		    bool allow_partial)
 {
-	return __fat_alloc_clusters(inode, cluster, nr_cluster, false);
+	return __fat_alloc_clusters(inode, cluster, nr_cluster, false,
+				    allow_partial);
 }
 
 int fat_free_clusters(struct inode *inode, int cluster)
diff --git a/fs/fat/file.c b/fs/fat/file.c
index ac6329d36062..a7349b2b94ee 100644
--- a/fs/fat/file.c
+++ b/fs/fat/file.c
@@ -395,7 +395,7 @@ int fat_preallocate(struct inode *inode, loff_t new_size)
 		     sbi->cluster_bits;
 
 	/* Allocate the clusters. We are not zeroing them out */
-	return fat_add_clusters(inode, nr_cluster);
+	return fat_add_clusters(inode, nr_cluster, false);
 }
 
 static int fat_zero_and_extend(struct inode *inode, loff_t new_size)
diff --git a/fs/fat/inode.c b/fs/fat/inode.c
index cb2316062bb3..6896c0c0dec9 100644
--- a/fs/fat/inode.c
+++ b/fs/fat/inode.c
@@ -101,11 +101,11 @@ static struct fat_floppy_defaults {
 },
 };
 
-int fat_add_clusters(struct inode *inode, int nr_cluster)
+int fat_add_clusters(struct inode *inode, int nr_cluster, bool allow_partial)
 {
 	int err, cluster;
 
-	err = fat_alloc_chain(inode, &cluster, nr_cluster);
+	err = fat_alloc_chain(inode, &cluster, &nr_cluster, allow_partial);
 	if (err)
 		return err;
 	/* FIXME: this cluster should be added after data of this
diff --git a/fs/fat/iomap.c b/fs/fat/iomap.c
index 893d94f6b70c..581fa609ae18 100644
--- a/fs/fat/iomap.c
+++ b/fs/fat/iomap.c
@@ -98,6 +98,7 @@ static int fat_iomap_begin_write(struct inode *inode, loff_t offset,
 				 loff_t length, unsigned int flags,
 				 struct iomap *iomap, struct iomap *srcmap)
 {
+	struct msdos_sb_info *sbi = MSDOS_SB(inode->i_sb);
 	loff_t curr_size = i_size_read(inode);
 	loff_t old_alloc = inode_get_bytes(inode);
 	int err;
@@ -106,12 +107,11 @@ static int fat_iomap_begin_write(struct inode *inode, loff_t offset,
 	if (WARN_ONCE(offset > curr_size, "Write starting past EOF"))
 		return -EOPNOTSUPP;
 
-	if (offset == curr_size) {
-		/*
-		 * TODO: allocate multiple blocks while allowing
-		 * short writes
-		 */
-		err = fat_preallocate(inode, offset + 1);
+	if (offset == curr_size && offset >= old_alloc) {
+		int nr_cluster = (offset + length - old_alloc +
+				  sbi->cluster_size - 1) >> sbi->cluster_bits;
+
+		err = fat_add_clusters(inode, nr_cluster, true);
 		if (err)
 			goto err_truncate;
 	}
-- 
2.43.0


^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [RFC PATCH 1/8] fat: support FIEMAP
  2026-10-31 16:43 ` [RFC PATCH 1/8] fat: support FIEMAP Matheus Alves de Almeida
@ 2026-11-08 14:51   ` OGAWA Hirofumi
  0 siblings, 0 replies; 14+ messages in thread
From: OGAWA Hirofumi @ 2026-11-08 14:51 UTC (permalink / raw)
  To: Matheus Alves de Almeida
  Cc: linux-fsdevel, brauner, djwong, hch, linux-kernel

Matheus Alves de Almeida <matheus.aalmeida@inf.ufrgs.br> writes:

> 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.

Not fully reviewed and I'm not understanding iomap fully though.

> Signed-off-by: Matheus Alves de Almeida <matheus.aalmeida@inf.ufrgs.br>
> ---
>  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/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, };

Maybe this change is pointless?

>  	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;

[...]

>  
>  	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);

Doesn't this add a needless cache? 0 is cached in the inode itself,
isn't it? (and can be waste small fatent cache slots?)

>  	}
>  
> +	fcluster = *fclus;
> +	dcluster = *dclus;
>  	fatent_init(&fatent);
> -	while (*fclus < cluster) {
> +	while (fcluster < last) {
> +		if (nr_cont && fcluster == cluster && cid.fcluster != -1) {

cid.fcluster == -1 is possible with this patch?

> +			fcluster = min(last, cid.fcluster + cid.nr_contig);
> +			dcluster = cid.dcluster + fcluster - cid.fcluster;
> +			if (fcluster == last)
> +				break;
> +		}
> +

Thanks.
-- 
OGAWA Hirofumi <hirofumi@mail.parknet.co.jp>

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [RFC PATCH 3/8] fat: add iomap direct I/O support
  2026-10-31 16:43 ` [RFC PATCH 3/8] fat: add iomap direct I/O support Matheus Alves de Almeida
@ 2026-11-08 14:56   ` OGAWA Hirofumi
  0 siblings, 0 replies; 14+ messages in thread
From: OGAWA Hirofumi @ 2026-11-08 14:56 UTC (permalink / raw)
  To: Matheus Alves de Almeida
  Cc: linux-fsdevel, brauner, djwong, hch, linux-kernel

Matheus Alves de Almeida <matheus.aalmeida@inf.ufrgs.br> writes:

> Replace the deprecated ->direct_IO() path with ->{read,write}_iter() and
> iomap_dio_rw(). Add fat_file_open() to advertise direct I/O support.
>
> Signed-off-by: Matheus Alves de Almeida <matheus.aalmeida@inf.ufrgs.br>
> ---
>  fs/fat/Kconfig |   1 -
>  fs/fat/fat.h   |   1 +
>  fs/fat/file.c  | 119 ++++++++++++++++++++++++++++++++++++++++++++++++-
>  fs/fat/inode.c |  36 ---------------
>  fs/fat/iomap.c |  24 ++++++++++
>  5 files changed, 142 insertions(+), 39 deletions(-)
>

[...]

> diff --git a/fs/fat/file.c b/fs/fat/file.c
> index c27d4e7f698c..177a26186ddc 100644
> --- a/fs/fat/file.c
> +++ b/fs/fat/file.c
> @@ -201,11 +201,126 @@ int fat_file_fsync(struct file *filp, loff_t start, loff_t end, int datasync)
>  	return blkdev_issue_flush(inode->i_sb->s_bdev);
>  }
>  

[...]

> +static ssize_t fat_file_write_iter(struct kiocb *iocb, struct iov_iter *iter)
> +{
> +	ssize_t ret;
> +
> +	if ((iocb->ki_flags & IOCB_DIRECT) == 0)
> +		return generic_file_write_iter(iocb, iter);
> +
> +	ret = fat_file_dio_write_iter(iocb, iter);
> +	if (ret == -ENOTBLK)
> +		ret = 0;
> +	else if (ret < 0)
> +		return ret;
> +
> +	/* Fall back to buffered I/O if we could not do DIO */
> +	if (iov_iter_count(iter)) {
> +		ssize_t done;
> +
> +		done = fat_file_write_iter_buffered_fallback(iocb, iter);
> +		if (done < 0)
> +			return done;

If fat_file_dio_write_iter() wrote some, this should return ret (how
many written), instead of the error? Or it never happen?

> +		ret += done;
> +	}
> +
> +	return ret;
> +}

Thanks.
-- 
OGAWA Hirofumi <hirofumi@mail.parknet.co.jp>

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [RFC PATCH 4/8] fat: clean stale bdev buffers of newly allocated clusters
  2026-10-31 16:43 ` [RFC PATCH 4/8] fat: clean stale bdev buffers of newly allocated clusters Matheus Alves de Almeida
@ 2026-11-08 14:57   ` OGAWA Hirofumi
  0 siblings, 0 replies; 14+ messages in thread
From: OGAWA Hirofumi @ 2026-11-08 14:57 UTC (permalink / raw)
  To: Matheus Alves de Almeida
  Cc: linux-fsdevel, brauner, djwong, hch, linux-kernel

Matheus Alves de Almeida <matheus.aalmeida@inf.ufrgs.br> writes:

> Directory I/O uses buffer_heads on the block device, which may leave
> stale buffers behind when clusters are freed and reused. The buffer_head
> I/O paths currently call clean_bdev_aliases() for newly allocated blocks,
> preventing stale buffers from overwriting new data.
>
> Buffered iomap I/O does not provide this protection. In preparation for
> the buffered iomap conversion, clean bdev aliases on newly allocated
> clusters, like exfat patch "exfat: clean stale bdev buffers of newly
> allocated clusters".
>
> Signed-off-by: Matheus Alves de Almeida <matheus.aalmeida@inf.ufrgs.br>
> ---
>  fs/fat/fatent.c | 34 ++++++++++++++++++++++++++++++++--
>  1 file changed, 32 insertions(+), 2 deletions(-)
>
> diff --git a/fs/fat/fatent.c b/fs/fat/fatent.c
> index 4fa1fae5abf9..46899efa3cf8 100644
> --- a/fs/fat/fatent.c
> +++ b/fs/fat/fatent.c
> @@ -523,6 +523,21 @@ static int fat_flush_bhs(struct super_block *sb, struct buffer_head **bhs,
>  	return 0;
>  }
>  
> +/*
> + * Newly allocated clusters may still have dirty buffer_heads in the block
> + * device mapping, e.g. the dentries of a directory that was just removed.
> + * File data does not go through those buffers, so drop them; otherwise a
> + * later flush of the block device writes them over the new data.
> + */
> +static void fat_clean_bdev_aliases(struct super_block *sb,
> +				   unsigned int clu, unsigned int count)
> +{
> +	struct msdos_sb_info *sbi = MSDOS_SB(sb);
> +
> +	clean_bdev_aliases(sb->s_bdev, fat_clus_to_blknr(sbi, clu),
> +			   (sector_t)count * sbi->sec_per_clus);
> +}
> +
>  static int __fat_alloc_clusters(struct inode *inode, int *cluster, int nr_cluster,
>  				bool return_all)
>  {
> @@ -531,7 +546,7 @@ static int __fat_alloc_clusters(struct inode *inode, int *cluster, int nr_cluste
>  	const struct fatent_operations *ops = sbi->fatent_ops;
>  	struct fat_entry fatent, prev_ent;
>  	struct buffer_head *bhs[MAX_BUF_PER_PAGE];
> -	int i, count, err, nr_bhs, idx_clus;
> +	int i, count, err, nr_bhs, idx_clus, cont_start, cont_len;
>  
>  	lock_fat(sbi);
>  	if (sbi->free_clusters != -1 && sbi->free_clus_valid &&
> @@ -540,14 +555,20 @@ static int __fat_alloc_clusters(struct inode *inode, int *cluster, int nr_cluste
>  		return -ENOSPC;
>  	}
>  
> +	cont_len = 0;
>  	err = nr_bhs = idx_clus = 0;
>  	count = FAT_START_ENT;
>  	fatent_init(&prev_ent);
>  	fatent_init(&fatent);
>  	fatent_set_entry(&fatent, sbi->prev_free + 1);
>  	while (count < sbi->max_cluster) {
> -		if (fatent.entry >= sbi->max_cluster)
> +		if (fatent.entry >= sbi->max_cluster) {
> +			if (cont_len) {
> +				fat_clean_bdev_aliases(sb, cont_start, cont_len);
> +				cont_len = 0;
> +			}
>  			fatent.entry = FAT_START_ENT;
> +		}
>  		fatent_set_entry(&fatent, fatent.entry);
>  		err = fat_ent_read_block(sb, &fatent);
>  		if (err)
> @@ -573,6 +594,10 @@ static int __fat_alloc_clusters(struct inode *inode, int *cluster, int nr_cluste
>  					*cluster = entry;
>  				idx_clus++;
>  
> +				if (cont_len == 0)
> +					cont_start = entry;
> +				cont_len++;
> +
>  				if (nr_bhs + fatent.nr_bhs > MAX_BUF_PER_PAGE) {
>  					err = fat_flush_bhs(sb, bhs, &nr_bhs,
>  							    inode_needs_sync(inode));
> @@ -589,6 +614,9 @@ static int __fat_alloc_clusters(struct inode *inode, int *cluster, int nr_cluste
>  				 * so we can still use the prev_ent.
>  				 */
>  				prev_ent = fatent;
> +			} else if (cont_len) {
> +				fat_clean_bdev_aliases(sb, cont_start, cont_len);
> +				cont_len = 0;
>  			}
>  			count++;
>  			if (count == sbi->max_cluster)
> @@ -602,6 +630,8 @@ static int __fat_alloc_clusters(struct inode *inode, int *cluster, int nr_cluste
>  	err = -ENOSPC;
>  
>  out:
> +	if (cont_len)
> +		fat_clean_bdev_aliases(sb, cont_start, cont_len);

We would want to make as possible as shorter lock_fat. At least, this
part can move outside of unlock_fat(sbi)?

>  	unlock_fat(sbi);
>  	mark_fsinfo_dirty(sb);
>  	fatent_brelse(&fatent);

Thanks.
-- 
OGAWA Hirofumi <hirofumi@mail.parknet.co.jp>

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [RFC PATCH 5/8] fat: drop ->write_begin() and ->write_end() usage from fat_cont_expand()
  2026-10-31 16:43 ` [RFC PATCH 5/8] fat: drop ->write_begin() and ->write_end() usage from fat_cont_expand() Matheus Alves de Almeida
@ 2026-11-08 14:59   ` OGAWA Hirofumi
  0 siblings, 0 replies; 14+ messages in thread
From: OGAWA Hirofumi @ 2026-11-08 14:59 UTC (permalink / raw)
  To: Matheus Alves de Almeida
  Cc: linux-fsdevel, brauner, djwong, hch, linux-kernel

Matheus Alves de Almeida <matheus.aalmeida@inf.ufrgs.br> writes:

> fat_cont_expand() currently relies on ->write_begin() and ->write_end()
> to zero the new file range. These callbacks will no longer be available
> after the iomap conversion, so a new method for zeroing is needed.
>
> Zero new ranges folio-by-folio instead.
>
> Signed-off-by: Matheus Alves de Almeida <matheus.aalmeida@inf.ufrgs.br>
> ---
>  fs/fat/file.c | 122 +++++++++++++++++++++++++++++++-------------------
>  1 file changed, 76 insertions(+), 46 deletions(-)
>
> diff --git a/fs/fat/file.c b/fs/fat/file.c
> index 177a26186ddc..8689cbbdad13 100644
> --- a/fs/fat/file.c
> +++ b/fs/fat/file.c
> @@ -18,6 +18,8 @@
>  #include <linux/security.h>
>  #include <linux/falloc.h>
>  #include <linux/fileattr.h>
> +#include <linux/rmap.h>
> +#include <linux/writeback.h>
>  #include "fat.h"
>  
>  static long fat_fallocate(struct file *file, int mode,
> @@ -353,68 +355,96 @@ static int fat_preallocate(struct inode *inode, loff_t new_size)
>  	return fat_add_clusters(inode, nr_cluster);
>  }
>  
> -static int fat_cont_expand_simple(struct inode *inode, loff_t size)
> +static int fat_zero_and_extend(struct inode *inode, loff_t new_size)
>  {
> +	struct msdos_inode_info *mi = MSDOS_I(inode);
>  	struct address_space *mapping = inode->i_mapping;
> -	const struct address_space_operations *aops = mapping->a_ops;
> +	loff_t pos = i_size_read(inode), fpos, next;
> +	loff_t old_size = pos;
>  	struct folio *folio;
> -	void *fsdata = NULL;
> -	int err;
> +	size_t from, to;
> +	pgoff_t index;
>  
> -	err = aops->write_begin(NULL, mapping, size, 0, &folio, &fsdata);
> -	if (err)
> -		goto out;
> +	while (pos < new_size) {
> +		index = pos >> PAGE_SHIFT;
>  
> -	err = aops->write_end(NULL, mapping, size, 0, 0, folio, fsdata);
> +		folio = read_mapping_folio(mapping, index, NULL);

Hm, is this reading blocks from the device for zeroing blocks even if
page is completely unwritten block? If so, we would not want to read
from the device.

> +		if (IS_ERR(folio))
> +			return PTR_ERR(folio);
>  
> -out:
> -	return err;
> +		folio_lock(folio);
> +		if (folio->mapping != mapping) {
> +			folio_unlock(folio);
> +			folio_put(folio);
> +			continue;
> +		}
> +		folio_wait_stable(folio);

Thanks.
-- 
OGAWA Hirofumi <hirofumi@mail.parknet.co.jp>

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [RFC PATCH 6/8] fat: add iomap buffered I/O support
  2026-10-31 16:43 ` [RFC PATCH 6/8] fat: add iomap buffered I/O support Matheus Alves de Almeida
@ 2026-11-08 15:03   ` OGAWA Hirofumi
  0 siblings, 0 replies; 14+ messages in thread
From: OGAWA Hirofumi @ 2026-11-08 15:03 UTC (permalink / raw)
  To: Matheus Alves de Almeida
  Cc: linux-fsdevel, brauner, djwong, hch, linux-kernel

Matheus Alves de Almeida <matheus.aalmeida@inf.ufrgs.br> writes:

> Use iomap for buffered I/O, replacing buffer_heads. Remove
> fat_get_block(), fat_write_begin(), fat_write_failed() as the iomap path
> makes them unnecessary.
>
> Signed-off-by: Matheus Alves de Almeida <matheus.aalmeida@inf.ufrgs.br>
> ---
>  fs/fat/fat.h   |   4 +-
>  fs/fat/file.c  |  72 +++++++++++++++++++----
>  fs/fat/inode.c | 157 +++++--------------------------------------------
>  fs/fat/iomap.c |  84 ++++++++++++++++++++++++++
>  4 files changed, 164 insertions(+), 153 deletions(-)
>

[...]

> diff --git a/fs/fat/file.c b/fs/fat/file.c
> index 8689cbbdad13..723b671d5b4b 100644
> --- a/fs/fat/file.c
> +++ b/fs/fat/file.c

[...]

> +static int fat_cont_expand(struct inode *inode, loff_t new_size);
> +
> +static ssize_t fat_file_write_iter_buffered(struct kiocb *iocb,
> +					    struct iov_iter *iter)
> +{
> +	struct inode *inode = file_inode(iocb->ki_filp);
> +	struct msdos_sb_info *sbi = MSDOS_SB(inode->i_sb);
> +	loff_t offset, old_size, old_alloc, keep_alloc;
> +	ssize_t ret;
> +
> +	inode_lock(inode);
> +	ret = generic_write_checks(iocb, iter);
> +	if (ret <= 0)
> +		goto out;
> +
> +	ret = file_modified(iocb->ki_filp);
> +	if (ret)
> +		goto out;
> +
> +	offset = iocb->ki_pos;
> +	old_size = i_size_read(inode);
> +	if (offset > old_size) {
> +		/* We need to fill this hole */
> +		ret = fat_cont_expand(inode, offset);
> +		if (ret)
> +			goto out;
> +	}
> +
> +	inode_dio_wait(inode);
> +	old_alloc = inode_get_bytes(inode);
> +	ret = iomap_file_buffered_write(iocb, iter, &fat_iomap_write_ops,
> +					NULL, NULL);
> +	keep_alloc = max(old_alloc, round_up(i_size_read(inode),
> +					     sbi->cluster_size));
> +	if (inode_get_bytes(inode) > keep_alloc)
> +		fat_truncate_blocks(inode, keep_alloc);

What is doing by this fat_truncate_blocks?

> +	if (ret > 0)
> +		ret = generic_write_sync(iocb, ret);

Maybe we want to do this outside inode_unlock()?

> +out:
> +	inode_unlock(inode);
> +	return ret;
> +}
> +

Thanks.
-- 
OGAWA Hirofumi <hirofumi@mail.parknet.co.jp>

^ permalink raw reply	[flat|nested] 14+ messages in thread

end of thread, other threads:[~2026-11-08 15:03 UTC | newest]

Thread overview: 14+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-31 16:43 [RFC PATCH 0/8] fat: convert to iomap Matheus Alves de Almeida
2026-10-31 16:43 ` [RFC PATCH 1/8] fat: support FIEMAP Matheus Alves de Almeida
2026-11-08 14:51   ` OGAWA Hirofumi
2026-10-31 16:43 ` [RFC PATCH 2/8] fat: use iomap for ->bmap() Matheus Alves de Almeida
2026-10-31 16:43 ` [RFC PATCH 3/8] fat: add iomap direct I/O support Matheus Alves de Almeida
2026-11-08 14:56   ` OGAWA Hirofumi
2026-10-31 16:43 ` [RFC PATCH 4/8] fat: clean stale bdev buffers of newly allocated clusters Matheus Alves de Almeida
2026-11-08 14:57   ` OGAWA Hirofumi
2026-10-31 16:43 ` [RFC PATCH 5/8] fat: drop ->write_begin() and ->write_end() usage from fat_cont_expand() Matheus Alves de Almeida
2026-11-08 14:59   ` OGAWA Hirofumi
2026-10-31 16:43 ` [RFC PATCH 6/8] fat: add iomap buffered I/O support Matheus Alves de Almeida
2026-11-08 15:03   ` OGAWA Hirofumi
2026-10-31 16:43 ` [RFC PATCH 7/8] fat: remove ->mmu_private Matheus Alves de Almeida
2026-10-31 16:43 ` [RFC PATCH 8/8] fat: add multi-cluster allocation for write Matheus Alves de Almeida

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®