ext4: fix ZERO_RANGE bug hidden by flag aliasing
We accidently aliased EXT4_EX_NOCACHE and EXT4_GET_CONVERT_UNWRITTEN falgs, which apparently was hiding a bug that was unmasked when this flag aliasing issue was addressed (see the subsequent commit). The reproduction case was: fsx -N 10000 -l 500000 -r 4096 -t 4096 -w 4096 -Z -R -W /vdb/junk ... which would cause fsx to report corruption in the data file. The fix we have is a bit of an overkill, but I'd much rather be conservative for now, and we can optimize ZERO_RANGE_FL handling later. The fact that we need to zap the extent_status cache for the inode is unfortunate, but correctness is far more important than performance. Signed-off-by: Theodore Ts'o <tytso@mit.edu> Cc: Namjae Jeon <namjae.jeon@samsung.com>
This commit is contained in:
parent
19008f6dfa
commit
713e8dde3e
|
@ -4802,7 +4802,8 @@ static long ext4_zero_range(struct file *file, loff_t offset,
|
||||||
max_blocks -= lblk;
|
max_blocks -= lblk;
|
||||||
|
|
||||||
flags = EXT4_GET_BLOCKS_CREATE_UNWRIT_EXT |
|
flags = EXT4_GET_BLOCKS_CREATE_UNWRIT_EXT |
|
||||||
EXT4_GET_BLOCKS_CONVERT_UNWRITTEN;
|
EXT4_GET_BLOCKS_CONVERT_UNWRITTEN |
|
||||||
|
EXT4_EX_NOCACHE;
|
||||||
if (mode & FALLOC_FL_KEEP_SIZE)
|
if (mode & FALLOC_FL_KEEP_SIZE)
|
||||||
flags |= EXT4_GET_BLOCKS_KEEP_SIZE;
|
flags |= EXT4_GET_BLOCKS_KEEP_SIZE;
|
||||||
|
|
||||||
|
@ -4840,17 +4841,23 @@ static long ext4_zero_range(struct file *file, loff_t offset,
|
||||||
ext4_inode_block_unlocked_dio(inode);
|
ext4_inode_block_unlocked_dio(inode);
|
||||||
inode_dio_wait(inode);
|
inode_dio_wait(inode);
|
||||||
|
|
||||||
/*
|
|
||||||
* Remove entire range from the extent status tree.
|
|
||||||
*/
|
|
||||||
ret = ext4_es_remove_extent(inode, lblk, max_blocks);
|
|
||||||
if (ret)
|
|
||||||
goto out_dio;
|
|
||||||
|
|
||||||
ret = ext4_alloc_file_blocks(file, lblk, max_blocks, new_size,
|
ret = ext4_alloc_file_blocks(file, lblk, max_blocks, new_size,
|
||||||
flags, mode);
|
flags, mode);
|
||||||
if (ret)
|
if (ret)
|
||||||
goto out_dio;
|
goto out_dio;
|
||||||
|
/*
|
||||||
|
* Remove entire range from the extent status tree.
|
||||||
|
*
|
||||||
|
* ext4_es_remove_extent(inode, lblk, max_blocks) is
|
||||||
|
* NOT sufficient. I'm not sure why this is the case,
|
||||||
|
* but let's be conservative and remove the extent
|
||||||
|
* status tree for the entire inode. There should be
|
||||||
|
* no outstanding delalloc extents thanks to the
|
||||||
|
* filemap_write_and_wait_range() call above.
|
||||||
|
*/
|
||||||
|
ret = ext4_es_remove_extent(inode, 0, EXT_MAX_BLOCKS);
|
||||||
|
if (ret)
|
||||||
|
goto out_dio;
|
||||||
}
|
}
|
||||||
if (!partial_begin && !partial_end)
|
if (!partial_begin && !partial_end)
|
||||||
goto out_dio;
|
goto out_dio;
|
||||||
|
|
Loading…
Reference in New Issue