Commit f2b13259 authored by Jan Kara's avatar Jan Kara Committed by Sasha Levin

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: default avatarJan Kara <jack@suse.com>
Signed-off-by: default avatarTheodore Ts'o <tytso@mit.edu>
Reviewed-by: default avatarMingming Cao <mingming.cao@oracle.com>
Signed-off-by: default avatarSasha Levin <sasha.levin@oracle.com>
parent 181aaebd
...@@ -2642,6 +2642,9 @@ static inline int ext4_update_inode_size(struct inode *inode, loff_t newsize) ...@@ -2642,6 +2642,9 @@ static inline int ext4_update_inode_size(struct inode *inode, loff_t newsize)
return changed; return changed;
} }
int ext4_update_disksize_before_punch(struct inode *inode, loff_t offset,
loff_t len);
struct ext4_group_info { struct ext4_group_info {
unsigned long bb_state; unsigned long bb_state;
struct rb_root bb_free_root; struct rb_root bb_free_root;
......
...@@ -4824,6 +4824,11 @@ static long ext4_zero_range(struct file *file, loff_t offset, ...@@ -4824,6 +4824,11 @@ static long ext4_zero_range(struct file *file, loff_t offset,
* released from page cache. * released from page cache.
*/ */
down_write(&EXT4_I(inode)->i_mmap_sem); 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 */ /* Now release the pages and zero block aligned part of pages */
truncate_pagecache_range(inode, start, end - 1); truncate_pagecache_range(inode, start, end - 1);
inode->i_mtime = inode->i_ctime = ext4_current_time(inode); inode->i_mtime = inode->i_ctime = ext4_current_time(inode);
......
...@@ -3523,6 +3523,35 @@ int ext4_can_truncate(struct inode *inode) ...@@ -3523,6 +3523,35 @@ int ext4_can_truncate(struct inode *inode)
return 0; 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 * ext4_punch_hole: punches a hole in a file by releaseing the blocks
* associated with the given offset and length * associated with the given offset and length
...@@ -3601,9 +3630,13 @@ int ext4_punch_hole(struct inode *inode, loff_t offset, loff_t length) ...@@ -3601,9 +3630,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; last_block_offset = round_down((offset + length), sb->s_blocksize) - 1;
/* Now release the pages and zero block aligned part of pages*/ /* 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, truncate_pagecache_range(inode, first_block_offset,
last_block_offset); last_block_offset);
}
if (ext4_test_inode_flag(inode, EXT4_INODE_EXTENTS)) if (ext4_test_inode_flag(inode, EXT4_INODE_EXTENTS))
credits = ext4_writepage_trans_blocks(inode); credits = ext4_writepage_trans_blocks(inode);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment