From: Zhang Yi 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 Signed-off-by: Zhang Yi --- 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