summaryrefslogtreecommitdiffstats
path: root/ext4-fix-races-of-writeback-with-punch-hole-and-zero.patch
diff options
context:
space:
mode:
Diffstat (limited to 'ext4-fix-races-of-writeback-with-punch-hole-and-zero.patch')
-rw-r--r--ext4-fix-races-of-writeback-with-punch-hole-and-zero.patch110
1 files changed, 110 insertions, 0 deletions
diff --git a/ext4-fix-races-of-writeback-with-punch-hole-and-zero.patch b/ext4-fix-races-of-writeback-with-punch-hole-and-zero.patch
new file mode 100644
index 000000000..9ff9e2761
--- /dev/null
+++ b/ext4-fix-races-of-writeback-with-punch-hole-and-zero.patch
@@ -0,0 +1,110 @@
+From 011278485ecc3cd2a3954b5d4c73101d919bf1fa Mon Sep 17 00:00:00 2001
+From: Jan Kara <jack@suse.com>
+Date: Mon, 7 Dec 2015 14:34:49 -0500
+Subject: [PATCH 4/4] 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>
+---
+ fs/ext4/ext4.h | 3 +++
+ fs/ext4/extents.c | 5 +++++
+ fs/ext4/inode.c | 35 ++++++++++++++++++++++++++++++++++-
+ 3 files changed, 42 insertions(+), 1 deletion(-)
+
+diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h
+index 348a5ff4a0e2..80f76f092079 100644
+--- a/fs/ext4/ext4.h
++++ b/fs/ext4/ext4.h
+@@ -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;
+diff --git a/fs/ext4/extents.c b/fs/ext4/extents.c
+index 4b105c96df08..3578b25fccfd 100644
+--- a/fs/ext4/extents.c
++++ b/fs/ext4/extents.c
+@@ -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);
+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)
+ }
+
+ /*
++ * 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);
+--
+2.5.5
+