Skip to content

Commit f2b1325

Browse files
Jan Karasashalevin
authored andcommitted
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 <[email protected]> Signed-off-by: Theodore Ts'o <[email protected]> Reviewed-by: Mingming Cao <[email protected]> Signed-off-by: Sasha Levin <[email protected]>
1 parent 181aaeb commit f2b1325

File tree

3 files changed

+42
-1
lines changed

3 files changed

+42
-1
lines changed

fs/ext4/ext4.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2642,6 +2642,9 @@ static inline int ext4_update_inode_size(struct inode *inode, loff_t newsize)
26422642
return changed;
26432643
}
26442644

2645+
int ext4_update_disksize_before_punch(struct inode *inode, loff_t offset,
2646+
loff_t len);
2647+
26452648
struct ext4_group_info {
26462649
unsigned long bb_state;
26472650
struct rb_root bb_free_root;

fs/ext4/extents.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4824,6 +4824,11 @@ static long ext4_zero_range(struct file *file, loff_t offset,
48244824
* released from page cache.
48254825
*/
48264826
down_write(&EXT4_I(inode)->i_mmap_sem);
4827+
ret = ext4_update_disksize_before_punch(inode, offset, len);
4828+
if (ret) {
4829+
up_write(&EXT4_I(inode)->i_mmap_sem);
4830+
goto out_dio;
4831+
}
48274832
/* Now release the pages and zero block aligned part of pages */
48284833
truncate_pagecache_range(inode, start, end - 1);
48294834
inode->i_mtime = inode->i_ctime = ext4_current_time(inode);

fs/ext4/inode.c

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3523,6 +3523,35 @@ int ext4_can_truncate(struct inode *inode)
35233523
return 0;
35243524
}
35253525

3526+
/*
3527+
* We have to make sure i_disksize gets properly updated before we truncate
3528+
* page cache due to hole punching or zero range. Otherwise i_disksize update
3529+
* can get lost as it may have been postponed to submission of writeback but
3530+
* that will never happen after we truncate page cache.
3531+
*/
3532+
int ext4_update_disksize_before_punch(struct inode *inode, loff_t offset,
3533+
loff_t len)
3534+
{
3535+
handle_t *handle;
3536+
loff_t size = i_size_read(inode);
3537+
3538+
WARN_ON(!mutex_is_locked(&inode->i_mutex));
3539+
if (offset > size || offset + len < size)
3540+
return 0;
3541+
3542+
if (EXT4_I(inode)->i_disksize >= size)
3543+
return 0;
3544+
3545+
handle = ext4_journal_start(inode, EXT4_HT_MISC, 1);
3546+
if (IS_ERR(handle))
3547+
return PTR_ERR(handle);
3548+
ext4_update_i_disksize(inode, size);
3549+
ext4_mark_inode_dirty(handle, inode);
3550+
ext4_journal_stop(handle);
3551+
3552+
return 0;
3553+
}
3554+
35263555
/*
35273556
* ext4_punch_hole: punches a hole in a file by releaseing the blocks
35283557
* associated with the given offset and length
@@ -3601,9 +3630,13 @@ int ext4_punch_hole(struct inode *inode, loff_t offset, loff_t length)
36013630
last_block_offset = round_down((offset + length), sb->s_blocksize) - 1;
36023631

36033632
/* Now release the pages and zero block aligned part of pages*/
3604-
if (last_block_offset > first_block_offset)
3633+
if (last_block_offset > first_block_offset) {
3634+
ret = ext4_update_disksize_before_punch(inode, offset, length);
3635+
if (ret)
3636+
goto out_dio;
36053637
truncate_pagecache_range(inode, first_block_offset,
36063638
last_block_offset);
3639+
}
36073640

36083641
if (ext4_test_inode_flag(inode, EXT4_INODE_EXTENTS))
36093642
credits = ext4_writepage_trans_blocks(inode);

0 commit comments

Comments
 (0)