[PATCH v6 03/31] ext4: skip ordered I/O wait when zeroing beyond i_disksize block
From: Zhang Yi
Date: Thu Sep 03 2026 - 09:03:30 EST
From: Zhang Yi <yi.zhang@xxxxxxxxxx>
ext4_block_zero_eof() zeros the tail of a partial block beyond EOF.
After zeroing, it waits for ordered I/O completion to prevent stale
data exposure from concurrent post-EOF mmap writes during folio
writeback.
However, if the zeroed range lies entirely beyond the block containing
i_disksize, no stale data can be exposed because the zeroed region is
beyond existing on-disk data. The zeroed pages will be written out
before i_disksize is later extended past i_size, so the ordered I/O
wait is unnecessary. Add a condition to skip it.
Suggested-by: Ojaswin Mujoo <ojaswin@xxxxxxxxxxxxx>
Signed-off-by: Zhang Yi <yi.zhang@xxxxxxxxxx>
---
fs/ext4/inode.c | 15 ++++++++++++++-
1 file changed, 14 insertions(+), 1 deletion(-)
diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c
index 8654006a57ef..4fc0d331c49d 100644
--- a/fs/ext4/inode.c
+++ b/fs/ext4/inode.c
@@ -4241,9 +4241,22 @@ int ext4_block_zero_eof(struct inode *inode, loff_t from, loff_t end)
* truncating up or performing an append write, because there might be
* exposing stale on-disk data which may caused by concurrent post-EOF
* mmap write during folio writeback.
+ *
+ * Ordered I/O is required only when zeroing the tail of a block that
+ * overlaps with i_disksize. If the zeroed range falls outside that
+ * block, the zeroed data lies beyond the existing on-disk data. It
+ * will be written out before i_disksize is later extended past
+ * i_size, so no stale data can be exposed.
+ *
+ * Note that it's safe to read i_disksize without holding i_data_sem
+ * here. Since we already hold i_rwsem, the only possible race is with
+ * concurrent writeback that updates i_disksize. And if such a race
+ * occurs, it means the previous unaligned EOF block has already been
+ * zeroed (if needed) and persisted to disk.
*/
if (ext4_should_order_data(inode) &&
- did_zero && zero_written && !IS_DAX(inode)) {
+ did_zero && zero_written && !IS_DAX(inode) &&
+ from < round_up(READ_ONCE(EXT4_I(inode)->i_disksize), blocksize)) {
handle_t *handle;
handle = ext4_journal_start(inode, EXT4_HT_MISC, 1);
--
2.52.0