[PATCH v2 7/7] exfat: publish i_size and valid_size on direct I/O completion
From: Chi Zhiling
Date: Mon Oct 12 2026 - 23:44:01 EST
From: Chi Zhiling <chizhiling@xxxxxxxxxx>
exfat_write_iomap_end() advances valid_size from the submission path.
A direct write that later fails or completes short has therefore already
marked its range valid, and __exfat_write_inode() records
max(i_size, valid_size) as DataLength, growing the file on disk even
though no data was written. Advancing valid_size early also lets a
racing mmap fault map the range and pull the not-yet-overwritten contents
of the recycled cluster into the page cache.
Publish both fields from ->end_io() instead, once the transfer has
completed successfully, and stop advancing valid_size from ->iomap_end();
buffered writes still publish valid_size under the folio lock via
->put_folio().
Signed-off-by: Chi Zhiling <chizhiling@xxxxxxxxxx>
---
fs/exfat/iomap.c | 34 +++++++++++++++++++---------------
1 file changed, 19 insertions(+), 15 deletions(-)
diff --git a/fs/exfat/iomap.c b/fs/exfat/iomap.c
index 26d216ef9c44..9e25c18c148a 100644
--- a/fs/exfat/iomap.c
+++ b/fs/exfat/iomap.c
@@ -15,22 +15,31 @@
/*
* exfat_file_write_dio_end_io - Direct I/O write completion handler
*
- * Updates i_size if the write extended the file. Called from the dio layer
- * after I/O completion.
+ * Updates i_size and valid_size after the transfer has completed
+ * successfully. The updating of i_size and valid_size runs in process
+ * context and under i_rwsem.
*/
static int exfat_file_write_dio_end_io(struct kiocb *iocb, ssize_t size,
int error, unsigned int flags)
{
struct inode *inode = file_inode(iocb->ki_filp);
+ loff_t end = iocb->ki_pos + size;
+ bool dirtied = false;
- if (error)
+ if (error || !size)
return error;
- if (size && i_size_read(inode) < iocb->ki_pos + size) {
- i_size_write(inode, iocb->ki_pos + size);
- mark_inode_dirty(inode);
+ if (i_size_read(inode) < end) {
+ i_size_write(inode, end);
+ dirtied = true;
}
+ if (exfat_advance_valid_size(EXFAT_I(inode), end))
+ dirtied = true;
+
+ if (dirtied)
+ mark_inode_dirty(inode);
+
return 0;
}
@@ -213,15 +222,11 @@ static const struct iomap_ops exfat_swap_iomap_ops = {
/*
* exfat_write_iomap_end - Update the state after write
- *
- * Extends ->valid_size to cover the newly written range.
- * Marks the inode dirty if metadata was changed.
*/
static int exfat_write_iomap_end(struct inode *inode, loff_t pos, loff_t length,
ssize_t written, unsigned int flags, struct iomap *iomap)
{
struct exfat_inode_info *ei = EXFAT_I(inode);
- bool dirtied = false;
loff_t end;
if (!written)
@@ -229,18 +234,17 @@ static int exfat_write_iomap_end(struct inode *inode, loff_t pos, loff_t length,
end = pos + written;
- if (exfat_advance_valid_size(ei, end))
- dirtied = true;
-
/*
* IOMAP_F_ZERO_TAIL zeroes the remainder of the last block. Track that
* block as zeroed so later valid_size extensions do not zero it again.
*/
if (iomap->flags & IOMAP_F_ZERO_TAIL)
end = round_up(end, i_blocksize(inode));
- exfat_advance_zeroed_size(ei, end);
- if (dirtied || iomap->flags & IOMAP_F_SIZE_CHANGED)
+ if (!(flags & IOMAP_DIRECT))
+ exfat_advance_zeroed_size(ei, end);
+
+ if (iomap->flags & IOMAP_F_SIZE_CHANGED)
mark_inode_dirty(inode);
return written;
--
2.53.0