1 ext4: fix ZERO_RANGE bug hidden by flag aliasing
3 We accidently aliased EXT4_EX_NOCACHE and EXT4_GET_CONVERT_UNWRITTEN
4 falgs, which apparently was hiding a bug that was unmasked when this
5 flag aliasing issue was addressed (see the subsequent commit). The
8 fsx -N 10000 -l 500000 -r 4096 -t 4096 -w 4096 -Z -R -W /vdb/junk
10 ... which would cause fsx to report corruption in the data file.
12 The fix we have is a bit of an overkill, but I'd much rather be
13 conservative for now, and we can optimize ZERO_RANGE_FL handling
14 later. The fact that we need to zap the extent_status cache for the
15 inode is unfortunate, but correctness is far more important than
18 Signed-off-by: Theodore Ts'o <tytso@mit.edu>
19 Cc: Namjae Jeon <namjae.jeon@samsung.com>
21 fs/ext4/extents.c | 21 ++++++++++++++-------
22 1 file changed, 14 insertions(+), 7 deletions(-)
24 diff --git a/fs/ext4/extents.c b/fs/ext4/extents.c
25 index bc3b49f..4571b5d 100644
26 --- a/fs/ext4/extents.c
27 +++ b/fs/ext4/extents.c
28 @@ -4808,7 +4808,8 @@ static long ext4_zero_range(struct file *file, loff_t offset,
31 flags = EXT4_GET_BLOCKS_CREATE_UNWRIT_EXT |
32 - EXT4_GET_BLOCKS_CONVERT_UNWRITTEN;
33 + EXT4_GET_BLOCKS_CONVERT_UNWRITTEN |
35 if (mode & FALLOC_FL_KEEP_SIZE)
36 flags |= EXT4_GET_BLOCKS_KEEP_SIZE;
38 @@ -4846,15 +4847,21 @@ static long ext4_zero_range(struct file *file, loff_t offset,
39 ext4_inode_block_unlocked_dio(inode);
40 inode_dio_wait(inode);
42 + ret = ext4_alloc_file_blocks(file, lblk, max_blocks, new_size,
47 * Remove entire range from the extent status tree.
49 + * ext4_es_remove_extent(inode, lblk, max_blocks) is
50 + * NOT sufficient. I'm not sure why this is the case,
51 + * but let's be conservative and remove the extent
52 + * status tree for the entire inode. There should be
53 + * no outstanding delalloc extents thanks to the
54 + * filemap_write_and_wait_range() call above.
56 - ret = ext4_es_remove_extent(inode, lblk, max_blocks);
60 - ret = ext4_alloc_file_blocks(file, lblk, max_blocks, new_size,
62 + ret = ext4_es_remove_extent(inode, 0, EXT_MAX_BLOCKS);