ext4: fix races of writeback with punch hole and zero range
When doing delayed allocation, update of on-disk inode size is postponed until IO submission time. However hole punch or zero range fallocate calls can end up discarding the tail page cache page and thus on-disk inode size would never be properly updated. Make sure the on-disk inode size is updated before truncating page cache. Signed-off-by: Jan Kara <jack@suse.com> Signed-off-by: Theodore Ts'o <tytso@mit.edu>
This commit is contained in:
parent
32ebffd3bb
commit
011278485e
|
@ -2858,6 +2858,9 @@ static inline int ext4_update_inode_size(struct inode *inode, loff_t newsize)
|
|||
return changed;
|
||||
}
|
||||
|
||||
int ext4_update_disksize_before_punch(struct inode *inode, loff_t offset,
|
||||
loff_t len);
|
||||
|
||||
struct ext4_group_info {
|
||||
unsigned long bb_state;
|
||||
struct rb_root bb_free_root;
|
||||
|
|
|
@ -4847,6 +4847,11 @@ static long ext4_zero_range(struct file *file, loff_t offset,
|
|||
* released from page cache.
|
||||
*/
|
||||
down_write(&EXT4_I(inode)->i_mmap_sem);
|
||||
ret = ext4_update_disksize_before_punch(inode, offset, len);
|
||||
if (ret) {
|
||||
up_write(&EXT4_I(inode)->i_mmap_sem);
|
||||
goto out_dio;
|
||||
}
|
||||
/* Now release the pages and zero block aligned part of pages */
|
||||
truncate_pagecache_range(inode, start, end - 1);
|
||||
inode->i_mtime = inode->i_ctime = ext4_current_time(inode);
|
||||
|
|
|
@ -3558,6 +3558,35 @@ int ext4_can_truncate(struct inode *inode)
|
|||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* We have to make sure i_disksize gets properly updated before we truncate
|
||||
* page cache due to hole punching or zero range. Otherwise i_disksize update
|
||||
* can get lost as it may have been postponed to submission of writeback but
|
||||
* that will never happen after we truncate page cache.
|
||||
*/
|
||||
int ext4_update_disksize_before_punch(struct inode *inode, loff_t offset,
|
||||
loff_t len)
|
||||
{
|
||||
handle_t *handle;
|
||||
loff_t size = i_size_read(inode);
|
||||
|
||||
WARN_ON(!mutex_is_locked(&inode->i_mutex));
|
||||
if (offset > size || offset + len < size)
|
||||
return 0;
|
||||
|
||||
if (EXT4_I(inode)->i_disksize >= size)
|
||||
return 0;
|
||||
|
||||
handle = ext4_journal_start(inode, EXT4_HT_MISC, 1);
|
||||
if (IS_ERR(handle))
|
||||
return PTR_ERR(handle);
|
||||
ext4_update_i_disksize(inode, size);
|
||||
ext4_mark_inode_dirty(handle, inode);
|
||||
ext4_journal_stop(handle);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* ext4_punch_hole: punches a hole in a file by releaseing the blocks
|
||||
* associated with the given offset and length
|
||||
|
@ -3636,9 +3665,13 @@ int ext4_punch_hole(struct inode *inode, loff_t offset, loff_t length)
|
|||
last_block_offset = round_down((offset + length), sb->s_blocksize) - 1;
|
||||
|
||||
/* Now release the pages and zero block aligned part of pages*/
|
||||
if (last_block_offset > first_block_offset)
|
||||
if (last_block_offset > first_block_offset) {
|
||||
ret = ext4_update_disksize_before_punch(inode, offset, length);
|
||||
if (ret)
|
||||
goto out_dio;
|
||||
truncate_pagecache_range(inode, first_block_offset,
|
||||
last_block_offset);
|
||||
}
|
||||
|
||||
if (ext4_test_inode_flag(inode, EXT4_INODE_EXTENTS))
|
||||
credits = ext4_writepage_trans_blocks(inode);
|
||||
|
|
Loading…
Reference in New Issue