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

Zhang Yi <[email protected]>
Newsgroups org.kernel.vger.linux-fsdevel,org.kernel.vger.linux-ext4,org.kernel.vger.linux-kernel
Message-ID <[email protected]>
From: Zhang Yi <[email protected]>

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 <[email protected]>
Signed-off-by: Zhang Yi <[email protected]>
---
 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
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.