mirror of
https://github.com/torvalds/linux.git
synced 2026-11-07 06:17:02 +01:00
ext4: submit and wait for disksize-grow I/O in fallocate paths
Collapse range and insert range update i_disksize directly under i_data_sem. If the operation runs while the zeroed EOF block is still awaiting writeback, i_disksize could advance past the zeroed boundary before the zeroed data is persisted, exposing stale data on crash. Deferring i_disksize updates like fallocate and zero_range is not an option here because the shift would move written extents beyond the current i_disksize. So flush and wait for the pending zeroed EOF block before these operations advance i_disksize. Since these operations already perform writeback, the extra flush does not add significant overhead. In addition, for ext4_update_disksize_before_punch(), if the punch discards the pending block, the zeroed data will never be written back before advancing i_disksize, so it is also necessary to sync the pending EOF range there. Finally, for the SYNC variants of zero_range and fallocate, this also guarantees the i_disksize update is persisted on the synchronous return. Note that EXT4_STATE_DISKSIZE_GROW_PENDING is not set for now, and it will be set after everything is done. Signed-off-by: Zhang Yi <yi.zhang@huawei.com> Tested-by: syzbot@syzkaller.appspotmail.com Link: https://patch.msgid.link/20261009103033.2920530-26-yi.zhang@huaweicloud.com Signed-off-by: Theodore Ts'o <tytso@mit.edu>
This commit is contained in:
parent
ee6539fc9e
commit
09e4d87016
|
|
@ -3226,6 +3226,8 @@ void ext4_iomap_clear_disksize_pending(struct inode *inode);
|
|||
void ext4_iomap_wait_disksize_pending(struct inode *inode);
|
||||
unsigned int ext4_iomap_get_disksize_pending_range(struct inode *inode,
|
||||
loff_t *start);
|
||||
extern int ext4_iomap_sync_zeroed_eof(struct inode *inode,
|
||||
loff_t offset, loff_t end);
|
||||
extern int ext4_block_zero_eof(struct inode *inode, loff_t from, loff_t end);
|
||||
|
||||
#define EXT4_PARTIAL_ZERO_START 0x1
|
||||
|
|
|
|||
|
|
@ -4857,6 +4857,17 @@ static long ext4_zero_range(struct file *file, loff_t offset,
|
|||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* In SYNC mode, sync the pending zeroed EOF block to ensure the
|
||||
* i_disksize update is persisted.
|
||||
*/
|
||||
if (((file->f_flags & O_SYNC) || IS_SYNC(inode)) && new_size) {
|
||||
ret = ext4_iomap_sync_zeroed_eof(inode, 0, LLONG_MAX);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Finish zeroing out if it doesn't contain partial block */
|
||||
if (IS_ALIGNED(offset | end, blocksize))
|
||||
return ret;
|
||||
|
|
@ -4928,10 +4939,20 @@ static long ext4_do_fallocate(struct file *file, loff_t offset,
|
|||
if (ret)
|
||||
goto out;
|
||||
|
||||
if (((file->f_flags & O_SYNC) || IS_SYNC(inode)) &&
|
||||
EXT4_SB(inode->i_sb)->s_journal) {
|
||||
ret = ext4_fc_commit(EXT4_SB(inode->i_sb)->s_journal,
|
||||
EXT4_I(inode)->i_sync_tid);
|
||||
if ((file->f_flags & O_SYNC) || IS_SYNC(inode)) {
|
||||
/*
|
||||
* Sync the pending zeroed EOF block to ensure the
|
||||
* i_disksize update is persisted.
|
||||
*/
|
||||
if (new_size) {
|
||||
ret = ext4_iomap_sync_zeroed_eof(inode, 0, LLONG_MAX);
|
||||
if (ret)
|
||||
goto out;
|
||||
}
|
||||
if (EXT4_SB(inode->i_sb)->s_journal) {
|
||||
ret = ext4_fc_commit(EXT4_SB(inode->i_sb)->s_journal,
|
||||
EXT4_I(inode)->i_sync_tid);
|
||||
}
|
||||
}
|
||||
out:
|
||||
trace_ext4_fallocate_exit(inode, offset,
|
||||
|
|
@ -5673,6 +5694,14 @@ static int ext4_collapse_range(struct file *file, loff_t offset, loff_t len)
|
|||
if (end >= inode->i_size)
|
||||
return -EINVAL;
|
||||
|
||||
/*
|
||||
* Persist the pending zeroed EOF block to ensure i_disksize
|
||||
* can be safely updated thereafter.
|
||||
*/
|
||||
ret = ext4_iomap_sync_zeroed_eof(inode, 0, LLONG_MAX);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/*
|
||||
* Write tail of the last page before removed range and data that
|
||||
* will be shifted since they will get removed from the page cache
|
||||
|
|
@ -5722,7 +5751,7 @@ static int ext4_collapse_range(struct file *file, loff_t offset, loff_t len)
|
|||
|
||||
new_size = inode->i_size - len;
|
||||
i_size_write(inode, new_size);
|
||||
EXT4_I(inode)->i_disksize = new_size;
|
||||
__ext4_set_i_disksize(inode, new_size);
|
||||
|
||||
up_write(&EXT4_I(inode)->i_data_sem);
|
||||
ret = ext4_mark_inode_dirty(handle, inode);
|
||||
|
|
@ -5775,6 +5804,14 @@ static int ext4_insert_range(struct file *file, loff_t offset, loff_t len)
|
|||
if (len > inode->i_sb->s_maxbytes - inode->i_size)
|
||||
return -EFBIG;
|
||||
|
||||
/*
|
||||
* Persist the pending zeroed EOF block to ensure i_disksize
|
||||
* can be safely updated thereafter.
|
||||
*/
|
||||
ret = ext4_iomap_sync_zeroed_eof(inode, 0, LLONG_MAX);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/*
|
||||
* Write out all dirty pages. Need to round down to align start offset
|
||||
* to page size boundary for page size > block size.
|
||||
|
|
@ -5794,8 +5831,7 @@ static int ext4_insert_range(struct file *file, loff_t offset, loff_t len)
|
|||
ext4_fc_mark_ineligible(sb, EXT4_FC_REASON_FALLOC_RANGE, handle);
|
||||
|
||||
/* Expand file to avoid data loss if there is error while shifting */
|
||||
inode->i_size += len;
|
||||
EXT4_I(inode)->i_disksize += len;
|
||||
ext4_update_inode_size(inode, inode->i_size + len);
|
||||
ret = ext4_mark_inode_dirty(handle, inode);
|
||||
if (ret)
|
||||
goto out_handle;
|
||||
|
|
|
|||
|
|
@ -4836,6 +4836,39 @@ static int ext4_block_zero_range(struct inode *inode,
|
|||
zero_written);
|
||||
}
|
||||
|
||||
/*
|
||||
* Submit and wait for the pending zeroed EOF block range to complete
|
||||
* if the given range [@offset, @end) fully covers it. Must be called
|
||||
* outside the context of an active journal handle and hold the i_rwsem.
|
||||
*/
|
||||
int ext4_iomap_sync_zeroed_eof(struct inode *inode, loff_t offset, loff_t end)
|
||||
{
|
||||
loff_t pstart, plen;
|
||||
int ret;
|
||||
|
||||
if (!ext4_inode_buffered_iomap(inode))
|
||||
return 0;
|
||||
|
||||
plen = ext4_iomap_get_disksize_pending_range(inode, &pstart);
|
||||
if (!plen || offset > pstart || end < pstart + plen)
|
||||
return 0;
|
||||
|
||||
ret = filemap_write_and_wait_range(inode->i_mapping, pstart,
|
||||
pstart + plen - 1);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/*
|
||||
* The pending bit should have been cleared by the ioend worker,
|
||||
* which runs before the folio writeback flag is cleared. The
|
||||
* caller holds i_rwsem, so no concurrent ext4_block_zero_eof()
|
||||
* can re-set it.
|
||||
*/
|
||||
WARN_ON_ONCE(ext4_test_inode_state(inode,
|
||||
EXT4_STATE_DISKSIZE_GROW_PENDING));
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Zero out a mapping from file offset 'from' up to the end of the block
|
||||
* which corresponds to 'from' or to the given 'end' inside this block.
|
||||
|
|
@ -4994,6 +5027,14 @@ int ext4_update_disksize_before_punch(struct inode *inode, loff_t offset,
|
|||
if (offset > size)
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* We are going to punch the pending zeroed EOF block, persist
|
||||
* it to ensure i_disksize can be safely updated thereafter.
|
||||
*/
|
||||
ret = ext4_iomap_sync_zeroed_eof(inode, offset, offset + len);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (offset + len < size)
|
||||
size = offset + len;
|
||||
if (EXT4_I(inode)->i_disksize >= size)
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user