diff options
author | Theodore Ts'o <tytso@mit.edu> | 2014-09-01 14:32:09 -0400 |
---|---|---|
committer | Theodore Ts'o <tytso@mit.edu> | 2014-09-01 14:32:09 -0400 |
commit | 713e8dde3e71e92db2d8cc8459d236ce1fb576ce (patch) | |
tree | 775c6493db0b4a15cd9e33d86f8981b3e27332df /fs/ext4 | |
parent | 19008f6dfa16d23afcd09dceaa598bb6da8de4b1 (diff) |
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>
Diffstat (limited to 'fs/ext4')
-rw-r--r-- | fs/ext4/extents.c | 21 |
1 files changed, 14 insertions, 7 deletions
diff --git a/fs/ext4/extents.c b/fs/ext4/extents.c index d00937336f19..bf205f72be35 100644 --- a/fs/ext4/extents.c +++ b/fs/ext4/extents.c | |||
@@ -4802,7 +4802,8 @@ static long ext4_zero_range(struct file *file, loff_t offset, | |||
4802 | max_blocks -= lblk; | 4802 | max_blocks -= lblk; |
4803 | 4803 | ||
4804 | flags = EXT4_GET_BLOCKS_CREATE_UNWRIT_EXT | | 4804 | flags = EXT4_GET_BLOCKS_CREATE_UNWRIT_EXT | |
4805 | EXT4_GET_BLOCKS_CONVERT_UNWRITTEN; | 4805 | EXT4_GET_BLOCKS_CONVERT_UNWRITTEN | |
4806 | EXT4_EX_NOCACHE; | ||
4806 | if (mode & FALLOC_FL_KEEP_SIZE) | 4807 | if (mode & FALLOC_FL_KEEP_SIZE) |
4807 | flags |= EXT4_GET_BLOCKS_KEEP_SIZE; | 4808 | flags |= EXT4_GET_BLOCKS_KEEP_SIZE; |
4808 | 4809 | ||
@@ -4840,15 +4841,21 @@ static long ext4_zero_range(struct file *file, loff_t offset, | |||
4840 | ext4_inode_block_unlocked_dio(inode); | 4841 | ext4_inode_block_unlocked_dio(inode); |
4841 | inode_dio_wait(inode); | 4842 | inode_dio_wait(inode); |
4842 | 4843 | ||
4844 | ret = ext4_alloc_file_blocks(file, lblk, max_blocks, new_size, | ||
4845 | flags, mode); | ||
4846 | if (ret) | ||
4847 | goto out_dio; | ||
4843 | /* | 4848 | /* |
4844 | * Remove entire range from the extent status tree. | 4849 | * Remove entire range from the extent status tree. |
4850 | * | ||
4851 | * ext4_es_remove_extent(inode, lblk, max_blocks) is | ||
4852 | * NOT sufficient. I'm not sure why this is the case, | ||
4853 | * but let's be conservative and remove the extent | ||
4854 | * status tree for the entire inode. There should be | ||
4855 | * no outstanding delalloc extents thanks to the | ||
4856 | * filemap_write_and_wait_range() call above. | ||
4845 | */ | 4857 | */ |
4846 | ret = ext4_es_remove_extent(inode, lblk, max_blocks); | 4858 | ret = ext4_es_remove_extent(inode, 0, EXT_MAX_BLOCKS); |
4847 | if (ret) | ||
4848 | goto out_dio; | ||
4849 | |||
4850 | ret = ext4_alloc_file_blocks(file, lblk, max_blocks, new_size, | ||
4851 | flags, mode); | ||
4852 | if (ret) | 4859 | if (ret) |
4853 | goto out_dio; | 4860 | goto out_dio; |
4854 | } | 4861 | } |