Re: [PATCH v4 02/23] ext4: factor out ext4_truncate_[up|down]()
From: Ojaswin Mujoo
Date: Tue May 19 2026 - 02:07:27 EST
On Mon, May 11, 2026 at 03:23:22PM +0800, Zhang Yi wrote:
> 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>
Looks good to me Zhang:
Reviewed-by: Ojaswin Mujoo <ojaswin@xxxxxxxxxxxxx>
> ---
> 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
>