mirror of
https://github.com/torvalds/linux.git
synced 2026-11-07 06:17:02 +01:00
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:
parent
e9941268ba
commit
bbe1fc8a29
199
fs/ext4/inode.c
199
fs/ext4/inode.c
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user