20c2f18
From: Lukas Czerner <lczerner@redhat.com>
20c2f18
Date: Wed, 18 Feb 2015 17:49:28 +0100
20c2f18
Subject: [PATCH] ext4: Allocate entire range in zero range
20c2f18
20c2f18
Currently there is a bug in zero range code which causes zero range
20c2f18
calls to only allocate block aligned portion of the range, while
20c2f18
ignoring the rest in some cases.
20c2f18
20c2f18
In some cases, namely if the end of the range is past isize, we do
20c2f18
attempt to preallocate the last nonaligned block. However this might
20c2f18
cause kernel to BUG() in some carefully designed zero range requests on
20c2f18
setups where page size > block size.
20c2f18
20c2f18
Fix this problem by first preallocating the entire range, including the
20c2f18
nonaligned edges and converting the written extents to unwritten in the
20c2f18
next step. This approach will also give us the advantage of having the
20c2f18
range to be as linearly contiguous as possible.
20c2f18
20c2f18
Signed-off-by: Lukas Czerner <lczerner@redhat.com>
20c2f18
---
20c2f18
 fs/ext4/extents.c | 31 +++++++++++++++++++------------
20c2f18
 1 file changed, 19 insertions(+), 12 deletions(-)
20c2f18
20c2f18
diff --git a/fs/ext4/extents.c b/fs/ext4/extents.c
20c2f18
index 0b16fb4c06d3..e3bf236a36ac 100644
20c2f18
--- a/fs/ext4/extents.c
20c2f18
+++ b/fs/ext4/extents.c
20c2f18
@@ -4792,12 +4792,6 @@ static long ext4_zero_range(struct file *file, loff_t offset,
20c2f18
 	else
20c2f18
 		max_blocks -= lblk;
20c2f18
 
20c2f18
-	flags = EXT4_GET_BLOCKS_CREATE_UNWRIT_EXT |
20c2f18
-		EXT4_GET_BLOCKS_CONVERT_UNWRITTEN |
20c2f18
-		EXT4_EX_NOCACHE;
20c2f18
-	if (mode & FALLOC_FL_KEEP_SIZE)
20c2f18
-		flags |= EXT4_GET_BLOCKS_KEEP_SIZE;
20c2f18
-
20c2f18
 	mutex_lock(&inode->i_mutex);
20c2f18
 
20c2f18
 	/*
20c2f18
@@ -4814,15 +4808,28 @@ static long ext4_zero_range(struct file *file, loff_t offset,
20c2f18
 		ret = inode_newsize_ok(inode, new_size);
20c2f18
 		if (ret)
20c2f18
 			goto out_mutex;
20c2f18
-		/*
20c2f18
-		 * If we have a partial block after EOF we have to allocate
20c2f18
-		 * the entire block.
20c2f18
-		 */
20c2f18
-		if (partial_end)
20c2f18
-			max_blocks += 1;
20c2f18
 	}
20c2f18
 
20c2f18
+	flags = EXT4_GET_BLOCKS_CREATE_UNWRIT_EXT;
20c2f18
+	if (mode & FALLOC_FL_KEEP_SIZE)
20c2f18
+		flags |= EXT4_GET_BLOCKS_KEEP_SIZE;
20c2f18
+
20c2f18
+	/* Preallocate the range including the unaligned edges */
20c2f18
+	if (partial_begin || partial_end) {
20c2f18
+		ret = ext4_alloc_file_blocks(file,
20c2f18
+				round_down(offset, 1 << blkbits) >> blkbits,
20c2f18
+				(round_up((offset + len), 1 << blkbits) -
20c2f18
+				 round_down(offset, 1 << blkbits)) >> blkbits,
20c2f18
+				new_size, flags, mode);
20c2f18
+		if (ret)
20c2f18
+			goto out_mutex;
20c2f18
+
20c2f18
+	}
20c2f18
+
20c2f18
+	/* Zero range excluding the unaligned edges */
20c2f18
 	if (max_blocks > 0) {
20c2f18
+		flags |= (EXT4_GET_BLOCKS_CONVERT_UNWRITTEN |
20c2f18
+			  EXT4_EX_NOCACHE);
20c2f18
 
20c2f18
 		/* Now release the pages and zero block aligned part of pages*/
20c2f18
 		truncate_pagecache_range(inode, start, end - 1);
20c2f18
-- 
20c2f18
2.1.0
20c2f18