aboutsummaryrefslogtreecommitdiffstats
path: root/fs/ext4/extents.c
diff options
context:
space:
mode:
authorAllison Henderson <achender@linux.vnet.ibm.com>2011-09-03 11:56:52 -0400
committerTheodore Ts'o <tytso@mit.edu>2011-09-03 11:56:52 -0400
commit2be4751b21ae1cacb002da48cfc5bf6743fee8c1 (patch)
tree4a7ce445a963d54c572f1216b90bdef2df91cdeb /fs/ext4/extents.c
parentba06208a1315ab2d2217e09c79582b886c9f629e (diff)
ext4: fix 2nd xfstests 127 punch hole failure
This patch fixes a second punch hole bug found by xfstests 127. This bug happens because punch hole needs to flush the pages of the hole to avoid race conditions. But if the end of the hole is in the same page as i_size, the buffer heads beyond i_size need to be unmapped and the page needs to be zeroed after it is flushed. To correct this, the new ext4_discard_partial_page_buffers routine is used to zero and unmap the partial page beyond i_size if the end of the hole appears in the same page as i_size. The code has also been optimized to set the end of the hole to the page after i_size if the specified hole exceeds i_size, and the code that flushes the pages has been simplified. Signed-off-by: Allison Henderson <achender@linux.vnet.ibm.com>
Diffstat (limited to 'fs/ext4/extents.c')
-rw-r--r--fs/ext4/extents.c41
1 files changed, 37 insertions, 4 deletions
diff --git a/fs/ext4/extents.c b/fs/ext4/extents.c
index 18f7e04a4fa3..9124cd24e093 100644
--- a/fs/ext4/extents.c
+++ b/fs/ext4/extents.c
@@ -4166,6 +4166,20 @@ int ext4_ext_punch_hole(struct file *file, loff_t offset, loff_t length)
4166 loff_t first_page_offset, last_page_offset; 4166 loff_t first_page_offset, last_page_offset;
4167 int ret, credits, blocks_released, err = 0; 4167 int ret, credits, blocks_released, err = 0;
4168 4168
4169 /* No need to punch hole beyond i_size */
4170 if (offset >= inode->i_size)
4171 return 0;
4172
4173 /*
4174 * If the hole extends beyond i_size, set the hole
4175 * to end after the page that contains i_size
4176 */
4177 if (offset + length > inode->i_size) {
4178 length = inode->i_size +
4179 PAGE_CACHE_SIZE - (inode->i_size & (PAGE_CACHE_SIZE - 1)) -
4180 offset;
4181 }
4182
4169 first_block = (offset + sb->s_blocksize - 1) >> 4183 first_block = (offset + sb->s_blocksize - 1) >>
4170 EXT4_BLOCK_SIZE_BITS(sb); 4184 EXT4_BLOCK_SIZE_BITS(sb);
4171 last_block = (offset + length) >> EXT4_BLOCK_SIZE_BITS(sb); 4185 last_block = (offset + length) >> EXT4_BLOCK_SIZE_BITS(sb);
@@ -4182,11 +4196,10 @@ int ext4_ext_punch_hole(struct file *file, loff_t offset, loff_t length)
4182 */ 4196 */
4183 if (mapping->nrpages && mapping_tagged(mapping, PAGECACHE_TAG_DIRTY)) { 4197 if (mapping->nrpages && mapping_tagged(mapping, PAGECACHE_TAG_DIRTY)) {
4184 err = filemap_write_and_wait_range(mapping, 4198 err = filemap_write_and_wait_range(mapping,
4185 first_page_offset == 0 ? 0 : first_page_offset-1, 4199 offset, offset + length - 1);
4186 last_page_offset);
4187 4200
4188 if (err) 4201 if (err)
4189 return err; 4202 return err;
4190 } 4203 }
4191 4204
4192 /* Now release the pages */ 4205 /* Now release the pages */
@@ -4249,6 +4262,26 @@ int ext4_ext_punch_hole(struct file *file, loff_t offset, loff_t length)
4249 } 4262 }
4250 } 4263 }
4251 4264
4265
4266 /*
4267 * If i_size is contained in the last page, we need to
4268 * unmap and zero the partial page after i_size
4269 */
4270 if (inode->i_size >> PAGE_CACHE_SHIFT == last_page &&
4271 inode->i_size % PAGE_CACHE_SIZE != 0) {
4272
4273 page_len = PAGE_CACHE_SIZE -
4274 (inode->i_size & (PAGE_CACHE_SIZE - 1));
4275
4276 if (page_len > 0) {
4277 err = ext4_discard_partial_page_buffers(handle,
4278 mapping, inode->i_size, page_len, 0);
4279
4280 if (err)
4281 goto out;
4282 }
4283 }
4284
4252 /* If there are no blocks to remove, return now */ 4285 /* If there are no blocks to remove, return now */
4253 if (first_block >= last_block) 4286 if (first_block >= last_block)
4254 goto out; 4287 goto out;