diff options
author | Eric Sandeen <sandeen@redhat.com> | 2011-10-29 10:15:35 -0400 |
---|---|---|
committer | Theodore Ts'o <tytso@mit.edu> | 2011-10-29 10:15:35 -0400 |
commit | 6d6a435190bdf2e04c9465cde5bdc3ac68cf11a4 (patch) | |
tree | d20f0dc543e58f424f782ba70648c88b7fb0f3e5 /fs/ext4/xattr.c | |
parent | e7b319e39776bd0e9c0c7855b023dafed2c93d27 (diff) |
ext4: fix race in xattr block allocation path
Ceph users reported that when using Ceph on ext4, the filesystem
would often become corrupted, containing inodes with incorrect
i_blocks counters.
I managed to reproduce this with a very hacked-up "streamtest"
binary from the Ceph tree.
Ceph is doing a lot of xattr writes, to out-of-inode blocks.
There is also another thread which does sync_file_range and close,
of the same files. The problem appears to happen due to this race:
sync/flush thread xattr-set thread
----------------- ----------------
do_writepages ext4_xattr_set
ext4_da_writepages ext4_xattr_set_handle
mpage_da_map_blocks ext4_xattr_block_set
set DELALLOC_RESERVE
ext4_new_meta_blocks
ext4_mb_new_blocks
if (!i_delalloc_reserved_flag)
vfs_dq_alloc_block
ext4_get_blocks
down_write(i_data_sem)
set i_delalloc_reserved_flag
...
up_write(i_data_sem)
if (i_delalloc_reserved_flag)
vfs_dq_alloc_block_nofail
In other words, the sync/flush thread pops in and sets
i_delalloc_reserved_flag on the inode, which makes the xattr thread
think that it's in a delalloc path in ext4_new_meta_blocks(),
and add the block for a second time, after already having added
it once in the !i_delalloc_reserved_flag case in ext4_mb_new_blocks
The real problem is that we shouldn't be using the DELALLOC_RESERVED
state flag, and instead we should be passing
EXT4_GET_BLOCKS_DELALLOC_RESERVE down to ext4_map_blocks() instead of
using an inode state flag. We'll fix this for now with using
i_data_sem to prevent this race, but this is really not the right way
to fix things.
Signed-off-by: Eric Sandeen <sandeen@redhat.com>
Signed-off-by: "Theodore Ts'o" <tytso@mit.edu>
Cc: stable@kernel.org
Diffstat (limited to 'fs/ext4/xattr.c')
-rw-r--r-- | fs/ext4/xattr.c | 6 |
1 files changed, 6 insertions, 0 deletions
diff --git a/fs/ext4/xattr.c b/fs/ext4/xattr.c index 0ae3668520f8..93a00d89a220 100644 --- a/fs/ext4/xattr.c +++ b/fs/ext4/xattr.c | |||
@@ -820,8 +820,14 @@ inserted: | |||
820 | if (!(ext4_test_inode_flag(inode, EXT4_INODE_EXTENTS))) | 820 | if (!(ext4_test_inode_flag(inode, EXT4_INODE_EXTENTS))) |
821 | goal = goal & EXT4_MAX_BLOCK_FILE_PHYS; | 821 | goal = goal & EXT4_MAX_BLOCK_FILE_PHYS; |
822 | 822 | ||
823 | /* | ||
824 | * take i_data_sem because we will test | ||
825 | * i_delalloc_reserved_flag in ext4_mb_new_blocks | ||
826 | */ | ||
827 | down_read((&EXT4_I(inode)->i_data_sem)); | ||
823 | block = ext4_new_meta_blocks(handle, inode, goal, 0, | 828 | block = ext4_new_meta_blocks(handle, inode, goal, 0, |
824 | NULL, &error); | 829 | NULL, &error); |
830 | up_read((&EXT4_I(inode)->i_data_sem)); | ||
825 | if (error) | 831 | if (error) |
826 | goto cleanup; | 832 | goto cleanup; |
827 | 833 | ||