Final changes before pull request to Linus
[ext4-patch-queue.git] / fix-races-of-writeback-with-punch-hole-and-zero-range
blobc7eae0fecd7df8c05a0ec6764ef60d50ee4c15aa
1 ext4: fix races of writeback with punch hole and zero range
3 From: Jan Kara <jack@suse.com>
5 When doing delayed allocation, update of on-disk inode size is postponed
6 until IO submission time. However hole punch or zero range fallocate
7 calls can end up discarding the tail page cache page and thus on-disk
8 inode size would never be properly updated.
10 Make sure the on-disk inode size is updated before truncating page
11 cache.
13 Signed-off-by: Jan Kara <jack@suse.com>
14 Signed-off-by: Theodore Ts'o <tytso@mit.edu>
15 ---
16  fs/ext4/ext4.h    |  3 +++
17  fs/ext4/extents.c |  5 +++++
18  fs/ext4/inode.c   | 35 ++++++++++++++++++++++++++++++++++-
19  3 files changed, 42 insertions(+), 1 deletion(-)
21 diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h
22 index c19ff61ccbdf..c86546efec30 100644
23 --- a/fs/ext4/ext4.h
24 +++ b/fs/ext4/ext4.h
25 @@ -2691,6 +2691,9 @@ static inline int ext4_update_inode_size(struct inode *inode, loff_t newsize)
26         return changed;
27  }
29 +int ext4_update_disksize_before_punch(struct inode *inode, loff_t offset,
30 +                                     loff_t len);
32  struct ext4_group_info {
33         unsigned long   bb_state;
34         struct rb_root  bb_free_root;
35 diff --git a/fs/ext4/extents.c b/fs/ext4/extents.c
36 index 892245a55c53..9977b557d50d 100644
37 --- a/fs/ext4/extents.c
38 +++ b/fs/ext4/extents.c
39 @@ -4843,6 +4843,11 @@ static long ext4_zero_range(struct file *file, loff_t offset,
40                  * released from page cache.
41                  */
42                 down_write(&EXT4_I(inode)->i_mmap_sem);
43 +               ret = ext4_update_disksize_before_punch(inode, offset, len);
44 +               if (ret) {
45 +                       up_write(&EXT4_I(inode)->i_mmap_sem);
46 +                       goto out_dio;
47 +               }
48                 /* Now release the pages and zero block aligned part of pages */
49                 truncate_pagecache_range(inode, start, end - 1);
50                 inode->i_mtime = inode->i_ctime = ext4_current_time(inode);
51 diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c
52 index 36ad45906d26..f72212ef1fee 100644
53 --- a/fs/ext4/inode.c
54 +++ b/fs/ext4/inode.c
55 @@ -3517,6 +3517,35 @@ int ext4_can_truncate(struct inode *inode)
56  }
58  /*
59 + * We have to make sure i_disksize gets properly updated before we truncate
60 + * page cache due to hole punching or zero range. Otherwise i_disksize update
61 + * can get lost as it may have been postponed to submission of writeback but
62 + * that will never happen after we truncate page cache.
63 + */
64 +int ext4_update_disksize_before_punch(struct inode *inode, loff_t offset,
65 +                                     loff_t len)
67 +       handle_t *handle;
68 +       loff_t size = i_size_read(inode);
70 +       WARN_ON(!mutex_is_locked(&inode->i_mutex));
71 +       if (offset > size || offset + len < size)
72 +               return 0;
74 +       if (EXT4_I(inode)->i_disksize >= size)
75 +               return 0;
77 +       handle = ext4_journal_start(inode, EXT4_HT_MISC, 1);
78 +       if (IS_ERR(handle))
79 +               return PTR_ERR(handle);
80 +       ext4_update_i_disksize(inode, size);
81 +       ext4_mark_inode_dirty(handle, inode);
82 +       ext4_journal_stop(handle);
84 +       return 0;
87 +/*
88   * ext4_punch_hole: punches a hole in a file by releaseing the blocks
89   * associated with the given offset and length
90   *
91 @@ -3594,9 +3623,13 @@ int ext4_punch_hole(struct inode *inode, loff_t offset, loff_t length)
92         last_block_offset = round_down((offset + length), sb->s_blocksize) - 1;
94         /* Now release the pages and zero block aligned part of pages*/
95 -       if (last_block_offset > first_block_offset)
96 +       if (last_block_offset > first_block_offset) {
97 +               ret = ext4_update_disksize_before_punch(inode, offset, length);
98 +               if (ret)
99 +                       goto out_dio;
100                 truncate_pagecache_range(inode, first_block_offset,
101                                          last_block_offset);
102 +       }
104         if (ext4_test_inode_flag(inode, EXT4_INODE_EXTENTS))
105                 credits = ext4_writepage_trans_blocks(inode);
106 -- 
107 2.1.4