ext4: factor out ext4_truncate_[up|down]()

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@huawei.com>
Reviewed-by: Ojaswin Mujoo <ojaswin@linux.ibm.com>
Reviewed-by: Jan Kara <jack@suse.cz>
Tested-by: syzbot@syzkaller.appspotmail.com
Link: https://patch.msgid.link/20261009103033.2920530-3-yi.zhang@huaweicloud.com
Signed-off-by: Theodore Ts'o <tytso@mit.edu>
This commit is contained in:
Zhang Yi 2026-10-09 18:30:03 +08:00 committed by Theodore Ts'o
parent e9941268ba
commit bbe1fc8a29

View File

@ -5982,6 +5982,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 truncate possible preallocated blocks.
*/
truncate_pagecache(inode, newsize);
return ext4_truncate(inode);
}
/*
* ext4_setattr()
*
@ -6078,7 +6184,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);
@ -6130,94 +6235,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);
}