[PATCH -next v5 22/32] ext4: add EXT4_STATE_DISKSIZE_GROW_PENDING state bit and helpers

From: Zhang Yi

Date: Fri Aug 14 2026 - 05:54:06 EST


From: Zhang Yi <yi.zhang@xxxxxxxxxx>

Inodes using the iomap buffered I/O path do not use data=ordered
mode, so the zeroed EOF block that straddles i_disksize needs explicit
tracking to ensure it is written back before i_disksize is advanced.

Add the EXT4_STATE_DISKSIZE_GROW_PENDING inode state bit and three
helpers: ext4_iomap_clear_disksize_pending() to atomically clear the
bit and wake waiters, ext4_iomap_wait_disksize_pending() to block until
the bit is cleared, and ext4_iomap_get_disksize_pending_range() to
compute the pending range from i_disksize.

These will be used by subsequent patches to serialize i_disksize
updates with the writeback of the zeroed EOF block.

Suggested-by: Jan Kara <jack@xxxxxxx>
Signed-off-by: Zhang Yi <yi.zhang@xxxxxxxxxx>
---
fs/ext4/ext4.h | 7 ++++++
fs/ext4/inode.c | 64 +++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 71 insertions(+)

diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h
index 03fa90d2986f..1c3d736fb700 100644
--- a/fs/ext4/ext4.h
+++ b/fs/ext4/ext4.h
@@ -2052,6 +2052,9 @@ enum {
EXT4_STATE_ORPHAN_FILE, /* Inode orphaned in orphan file */
EXT4_STATE_FC_REQUEUE, /* Inode modified during fast commit */
EXT4_STATE_BUFFERED_IOMAP, /* Inode use iomap for buffered IO */
+ EXT4_STATE_DISKSIZE_GROW_PENDING,
+ /* Has zeroed EOF block straddles
+ * i_disksize awaiting writeback */
};

#define EXT4_INODE_BIT_FNS(name, field, offset) \
@@ -3219,6 +3222,10 @@ extern int ext4_chunk_trans_blocks(struct inode *, int nrblocks);
extern int ext4_chunk_trans_extent(struct inode *inode, int nrblocks);
extern int ext4_meta_trans_blocks(struct inode *inode, int lblocks,
int pextents, int alloc_extents);
+void ext4_iomap_clear_disksize_pending(struct inode *inode);
+void ext4_iomap_wait_disksize_pending(struct inode *inode);
+unsigned int ext4_iomap_get_disksize_pending_range(struct inode *inode,
+ loff_t *start);
extern int ext4_block_zero_eof(struct inode *inode, loff_t from, loff_t end);

#define EXT4_PARTIAL_ZERO_START 0x1
diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c
index a2f060ae6cbc..e4a4396eaf87 100644
--- a/fs/ext4/inode.c
+++ b/fs/ext4/inode.c
@@ -126,6 +126,70 @@ void ext4_inode_csum_set(struct inode *inode, struct ext4_inode *raw,
raw->i_checksum_hi = cpu_to_le16(csum >> 16);
}

+/*
+ * Clear the disksize-grow-pending state and wake up all waiters.
+ * Called when the pending zeroed EOF block which straddles i_disksize
+ * has completed writeback or its folio is discarded.
+ */
+void ext4_iomap_clear_disksize_pending(struct inode *inode)
+{
+ ext4_clear_inode_state(inode, EXT4_STATE_DISKSIZE_GROW_PENDING);
+ /*
+ * Make sure clearing of EXT4_STATE_DISKSIZE_GROW_PENDING is
+ * visible before we send the wakeup. Pairs with the implicit
+ * barrier in prepare_to_wait() inside wait_on_bit() in
+ * ext4_iomap_wait_disksize_pending().
+ */
+ smp_mb();
+ wake_up_bit(ext4_inode_state_wait_word(inode),
+ ext4_inode_state_wait_bit(EXT4_STATE_DISKSIZE_GROW_PENDING));
+}
+
+/*
+ * Wait for the disksize-grow-pending zeroed EOF block which straddles
+ * i_disksize to be written back or cleared.
+ */
+void ext4_iomap_wait_disksize_pending(struct inode *inode)
+{
+ wait_on_bit(ext4_inode_state_wait_word(inode),
+ ext4_inode_state_wait_bit(EXT4_STATE_DISKSIZE_GROW_PENDING),
+ TASK_UNINTERRUPTIBLE);
+}
+
+/*
+ * Get the range of the disksize-grow-pending zeroed EOF block range
+ * which straddles i_disksize if the EXT4_STATE_DISKSIZE_GROW_PENDING
+ * bit is set.
+ *
+ * Return the pending range, or zero if the BIT has already been cleared.
+ */
+unsigned int ext4_iomap_get_disksize_pending_range(struct inode *inode,
+ loff_t *start)
+{
+ unsigned int blocksize = i_blocksize(inode);
+ loff_t disksize;
+
+ if (!ext4_test_inode_state(inode, EXT4_STATE_DISKSIZE_GROW_PENDING))
+ return 0;
+
+ /*
+ * The pending bit should be set only when i_disksize is not
+ * block-size aligned. While set, i_disksize must not be advanced,
+ * and the bit must be cleared when i_disksize is shrunk.
+ */
+ down_read(&EXT4_I(inode)->i_data_sem);
+ disksize = READ_ONCE(EXT4_I(inode)->i_disksize);
+ if (!ext4_test_inode_state(inode, EXT4_STATE_DISKSIZE_GROW_PENDING) ||
+ WARN_ON_ONCE(IS_ALIGNED(disksize, blocksize))) {
+ up_read(&EXT4_I(inode)->i_data_sem);
+ return 0;
+ }
+
+ up_read(&EXT4_I(inode)->i_data_sem);
+ *start = disksize;
+ return blocksize - (disksize & (blocksize - 1));
+}
+
static inline int ext4_begin_ordered_truncate(struct inode *inode,
loff_t new_size)
{
--
2.52.0