diff options
author | Jan Kara <jack@suse.com> | 2015-12-07 14:34:49 -0500 |
---|---|---|
committer | Theodore Ts'o <tytso@mit.edu> | 2015-12-07 14:34:49 -0500 |
commit | 011278485ecc3cd2a3954b5d4c73101d919bf1fa (patch) | |
tree | 740938e00aa0c972db386b5576656e646c02e36d /fs/ext4/inode.c | |
parent | 32ebffd3bbb4162da5ff88f9a35dd32d0a28ea70 (diff) |
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>
Diffstat (limited to 'fs/ext4/inode.c')
-rw-r--r-- | fs/ext4/inode.c | 35 |
1 files changed, 34 insertions, 1 deletions
diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c index d1207d03c961..472e608da13d 100644 --- a/fs/ext4/inode.c +++ b/fs/ext4/inode.c | |||
@@ -3559,6 +3559,35 @@ int ext4_can_truncate(struct inode *inode) | |||
3559 | } | 3559 | } |
3560 | 3560 | ||
3561 | /* | 3561 | /* |
3562 | * We have to make sure i_disksize gets properly updated before we truncate | ||
3563 | * page cache due to hole punching or zero range. Otherwise i_disksize update | ||
3564 | * can get lost as it may have been postponed to submission of writeback but | ||
3565 | * that will never happen after we truncate page cache. | ||
3566 | */ | ||
3567 | int ext4_update_disksize_before_punch(struct inode *inode, loff_t offset, | ||
3568 | loff_t len) | ||
3569 | { | ||
3570 | handle_t *handle; | ||
3571 | loff_t size = i_size_read(inode); | ||
3572 | |||
3573 | WARN_ON(!mutex_is_locked(&inode->i_mutex)); | ||
3574 | if (offset > size || offset + len < size) | ||
3575 | return 0; | ||
3576 | |||
3577 | if (EXT4_I(inode)->i_disksize >= size) | ||
3578 | return 0; | ||
3579 | |||
3580 | handle = ext4_journal_start(inode, EXT4_HT_MISC, 1); | ||
3581 | if (IS_ERR(handle)) | ||
3582 | return PTR_ERR(handle); | ||
3583 | ext4_update_i_disksize(inode, size); | ||
3584 | ext4_mark_inode_dirty(handle, inode); | ||
3585 | ext4_journal_stop(handle); | ||
3586 | |||
3587 | return 0; | ||
3588 | } | ||
3589 | |||
3590 | /* | ||
3562 | * ext4_punch_hole: punches a hole in a file by releaseing the blocks | 3591 | * ext4_punch_hole: punches a hole in a file by releaseing the blocks |
3563 | * associated with the given offset and length | 3592 | * associated with the given offset and length |
3564 | * | 3593 | * |
@@ -3636,9 +3665,13 @@ int ext4_punch_hole(struct inode *inode, loff_t offset, loff_t length) | |||
3636 | last_block_offset = round_down((offset + length), sb->s_blocksize) - 1; | 3665 | last_block_offset = round_down((offset + length), sb->s_blocksize) - 1; |
3637 | 3666 | ||
3638 | /* Now release the pages and zero block aligned part of pages*/ | 3667 | /* Now release the pages and zero block aligned part of pages*/ |
3639 | if (last_block_offset > first_block_offset) | 3668 | if (last_block_offset > first_block_offset) { |
3669 | ret = ext4_update_disksize_before_punch(inode, offset, length); | ||
3670 | if (ret) | ||
3671 | goto out_dio; | ||
3640 | truncate_pagecache_range(inode, first_block_offset, | 3672 | truncate_pagecache_range(inode, first_block_offset, |
3641 | last_block_offset); | 3673 | last_block_offset); |
3674 | } | ||
3642 | 3675 | ||
3643 | if (ext4_test_inode_flag(inode, EXT4_INODE_EXTENTS)) | 3676 | if (ext4_test_inode_flag(inode, EXT4_INODE_EXTENTS)) |
3644 | credits = ext4_writepage_trans_blocks(inode); | 3677 | credits = ext4_writepage_trans_blocks(inode); |