[PATCH v4 02/23] ext4: factor out ext4_truncate_[up|down]()
From: Zhang Yi
Date: Mon May 11 2026 - 03:29:04 EST
From: Zhang Yi <yi.zhang@xxxxxxxxxx>
Refactor ext4_setattr() by introducing two helper functions,
ext4_truncate_up() and ext4_truncate_down(), to handle size changes. The
current ATTR_SIZE processing consolidates checks for both shrinking and
non-shrinking cases, leading to cluttered code. Separating the
truncation paths improves readability.
Signed-off-by: Zhang Yi <yi.zhang@xxxxxxxxxx>
---
fs/ext4/inode.c | 199 +++++++++++++++++++++++++++---------------------
1 file changed, 112 insertions(+), 87 deletions(-)
diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c
index 0751dc55e94f..35e958f89bd5 100644
--- a/fs/ext4/inode.c
+++ b/fs/ext4/inode.c
@@ -5855,6 +5855,112 @@ static void ext4_wait_for_tail_page_commit(struct inode *inode)
}
}
+/*
+ * Set i_size and i_disksize to 'newsize'.
+ *
+ * Both i_rwsem and i_data_sem are required here to avoid races between
+ * generic append writeback and concurrent truncate that also modify
+ * i_size and i_disksize.
+ */
+static inline void ext4_set_inode_size(struct inode *inode, loff_t newsize)
+{
+ WARN_ON_ONCE(S_ISREG(inode->i_mode) && !inode_is_locked(inode));
+
+ down_write(&EXT4_I(inode)->i_data_sem);
+ i_size_write(inode, newsize);
+ EXT4_I(inode)->i_disksize = newsize;
+ up_write(&EXT4_I(inode)->i_data_sem);
+}
+
+static int ext4_truncate_up(struct inode *inode, loff_t oldsize, loff_t newsize)
+{
+ ext4_lblk_t old_lblk, new_lblk;
+ handle_t *handle;
+ int ret;
+
+ if (!IS_ALIGNED(oldsize | newsize, i_blocksize(inode))) {
+ ret = ext4_inode_attach_jinode(inode);
+ if (ret)
+ return ret;
+ }
+
+ inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode));
+ if (!IS_ALIGNED(oldsize, i_blocksize(inode))) {
+ ret = ext4_block_zero_eof(inode, oldsize, LLONG_MAX);
+ if (ret)
+ return ret;
+ }
+
+ handle = ext4_journal_start(inode, EXT4_HT_INODE, 3);
+ if (IS_ERR(handle))
+ return PTR_ERR(handle);
+
+ old_lblk = oldsize > 0 ? (oldsize - 1) >> inode->i_blkbits : 0;
+ new_lblk = newsize > 0 ? (newsize - 1) >> inode->i_blkbits : 0;
+ ext4_fc_track_range(handle, inode, old_lblk, new_lblk);
+
+ ext4_set_inode_size(inode, newsize);
+
+ ret = ext4_mark_inode_dirty(handle, inode);
+ ext4_journal_stop(handle);
+ if (ret)
+ return ret;
+ /*
+ * isize extend must be called outside an active handle due to
+ * the lock ordering of transaction start and folio lock in the
+ * iomap buffered I/O path (folio lock -> transaction start).
+ */
+ pagecache_isize_extended(inode, oldsize, newsize);
+ return 0;
+}
+
+static int ext4_truncate_down(struct inode *inode, loff_t oldsize,
+ loff_t newsize, int *orphan)
+{
+ ext4_lblk_t start_lblk;
+ handle_t *handle;
+ int ret;
+
+ /* Do not change i_size. */
+ if (newsize == oldsize)
+ goto truncate;
+
+ /* Shrink. */
+ handle = ext4_journal_start(inode, EXT4_HT_INODE, 3);
+ if (IS_ERR(handle))
+ return PTR_ERR(handle);
+
+ if (ext4_handle_valid(handle)) {
+ ret = ext4_orphan_add(handle, inode);
+ *orphan = 1;
+ if (ret) {
+ ext4_journal_stop(handle);
+ return ret;
+ }
+ }
+
+ start_lblk = newsize > 0 ? (newsize - 1) >> inode->i_blkbits : 0;
+ ext4_fc_track_range(handle, inode, start_lblk, EXT_MAX_BLOCKS - 1);
+
+ ext4_set_inode_size(inode, newsize);
+
+ ret = ext4_mark_inode_dirty(handle, inode);
+ ext4_journal_stop(handle);
+ if (ret)
+ return ret;
+
+ if (ext4_should_journal_data(inode))
+ ext4_wait_for_tail_page_commit(inode);
+truncate:
+ /*
+ * Truncate pagecache after we've waited for commit in data=journal
+ * mode to make pages freeable. Call ext4_truncate() even if
+ * i_size didn't change to truncatea possible preallocated blocks.
+ */
+ truncate_pagecache(inode, newsize);
+ return ext4_truncate(inode);
+}
+
/*
* ext4_setattr()
*
@@ -5951,7 +6057,6 @@ int ext4_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
}
if (attr->ia_valid & ATTR_SIZE) {
- handle_t *handle;
loff_t oldsize = inode->i_size;
int shrink = (attr->ia_size < inode->i_size);
@@ -6003,94 +6108,14 @@ int ext4_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
goto err_out;
}
- if (attr->ia_size != inode->i_size) {
- /* attach jbd2 jinode for EOF folio tail zeroing */
- if (attr->ia_size & (inode->i_sb->s_blocksize - 1) ||
- oldsize & (inode->i_sb->s_blocksize - 1)) {
- error = ext4_inode_attach_jinode(inode);
- if (error)
- goto out_mmap_sem;
- }
-
- /*
- * Update c/mtime and tail zero the EOF folio on
- * truncate up. ext4_truncate() handles the shrink case
- * below.
- */
- if (!shrink) {
- inode_set_mtime_to_ts(inode,
- inode_set_ctime_current(inode));
- if (oldsize & (inode->i_sb->s_blocksize - 1)) {
- error = ext4_block_zero_eof(inode,
- oldsize, LLONG_MAX);
- if (error)
- goto out_mmap_sem;
- }
- }
-
- handle = ext4_journal_start(inode, EXT4_HT_INODE, 3);
- if (IS_ERR(handle)) {
- error = PTR_ERR(handle);
- goto out_mmap_sem;
- }
- if (ext4_handle_valid(handle) && shrink) {
- error = ext4_orphan_add(handle, inode);
- orphan = 1;
- if (error)
- goto out_handle;
- }
-
- if (shrink)
- ext4_fc_track_range(handle, inode,
- (attr->ia_size > 0 ? attr->ia_size - 1 : 0) >>
- inode->i_sb->s_blocksize_bits,
- EXT_MAX_BLOCKS - 1);
- else
- ext4_fc_track_range(
- handle, inode,
- (oldsize > 0 ? oldsize - 1 : oldsize) >>
- inode->i_sb->s_blocksize_bits,
- (attr->ia_size > 0 ? attr->ia_size - 1 : 0) >>
- inode->i_sb->s_blocksize_bits);
-
- /*
- * We have to update i_size under i_data_sem together
- * with i_disksize to avoid races with writeback code
- * updating disksize in mpage_map_and_submit_extent().
- */
- down_write(&EXT4_I(inode)->i_data_sem);
- i_size_write(inode, attr->ia_size);
- EXT4_I(inode)->i_disksize = attr->ia_size;
- up_write(&EXT4_I(inode)->i_data_sem);
-
- error = ext4_mark_inode_dirty(handle, inode);
-out_handle:
- ext4_journal_stop(handle);
- if (error)
- goto out_mmap_sem;
- if (!shrink) {
- pagecache_isize_extended(inode, oldsize,
- inode->i_size);
- } else if (ext4_should_journal_data(inode)) {
- ext4_wait_for_tail_page_commit(inode);
- }
+ if (attr->ia_size > oldsize)
+ error = ext4_truncate_up(inode, oldsize, attr->ia_size);
+ else {
+ /* Shrink or do not change i_size. */
+ error = ext4_truncate_down(inode, oldsize,
+ attr->ia_size, &orphan);
}
- /*
- * Truncate pagecache after we've waited for commit
- * in data=journal mode to make pages freeable.
- */
- truncate_pagecache(inode, inode->i_size);
- /*
- * Call ext4_truncate() even if i_size didn't change to
- * truncate possible preallocated blocks.
- */
- if (attr->ia_size <= oldsize) {
- rc = ext4_truncate(inode);
- if (rc)
- error = rc;
- }
-out_mmap_sem:
filemap_invalidate_unlock(inode->i_mapping);
}
--
2.52.0