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