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