aboutsummaryrefslogtreecommitdiffstats
path: root/fs/ext4
diff options
context:
space:
mode:
authorDmitry Monakhov <dmonakhov@openvz.org>2013-03-04 00:34:34 -0500
committerTheodore Ts'o <tytso@mit.edu>2013-03-04 00:34:34 -0500
commit357b66fdc8ad4cea6e6336956a70742f961f0a4d (patch)
treebbb15a0d60de8c3ab70595484e72b3ef38515026 /fs/ext4
parent9b2ff35753c0512bc8c6adae9e9c87cbeee86f82 (diff)
ext4: ext4_split_extent should take care of extent zeroout
When ext4_split_extent_at() ends up doing zeroout & conversion to initialized instead of split & conversion, ext4_split_extent() gets confused and can wrongly mark the extent back as uninitialized resulting in end IO code getting confused from large unwritten extents and may result in data loss. The example of problematic behavior is: lblk len lblk len ext4_split_extent() (ex=[1000,30,uninit], map=[1010,10]) ext4_split_extent_at() (split [1000,30,uninit] at 1020) ext4_ext_insert_extent() -> ENOSPC ext4_ext_zeroout() -> extent [1000,30] is now initialized ext4_split_extent_at() (split [1000,30,init] at 1010, MARK_UNINIT1 | MARK_UNINIT2) -> extent is split and parts marked as uninitialized Fix the problem by rechecking extent type after the first ext4_split_extent_at() returns. None of split_flags can not be applied to initialized extent so this patch also add BUG_ON to prevent similar issues in future. TESTCASE: https://github.com/dmonakhov/xfstests/commit/b8a55eb5ce28c6ff29e620ab090902fcd5833597 Signed-off-by: Dmitry Monakhov <dmonakhov@openvz.org> Signed-off-by: "Theodore Ts'o" <tytso@mit.edu> Reviewed-by: Jan Kara <jack@suse.cz>
Diffstat (limited to 'fs/ext4')
-rw-r--r--fs/ext4/extents.c23
1 files changed, 17 insertions, 6 deletions
diff --git a/fs/ext4/extents.c b/fs/ext4/extents.c
index 372b2cbee07e..bef194a14437 100644
--- a/fs/ext4/extents.c
+++ b/fs/ext4/extents.c
@@ -2943,6 +2943,10 @@ static int ext4_split_extent_at(handle_t *handle,
2943 newblock = split - ee_block + ext4_ext_pblock(ex); 2943 newblock = split - ee_block + ext4_ext_pblock(ex);
2944 2944
2945 BUG_ON(split < ee_block || split >= (ee_block + ee_len)); 2945 BUG_ON(split < ee_block || split >= (ee_block + ee_len));
2946 BUG_ON(!ext4_ext_is_uninitialized(ex) &&
2947 split_flag & (EXT4_EXT_MAY_ZEROOUT |
2948 EXT4_EXT_MARK_UNINIT1 |
2949 EXT4_EXT_MARK_UNINIT2));
2946 2950
2947 err = ext4_ext_get_access(handle, inode, path + depth); 2951 err = ext4_ext_get_access(handle, inode, path + depth);
2948 if (err) 2952 if (err)
@@ -3061,19 +3065,26 @@ static int ext4_split_extent(handle_t *handle,
3061 if (err) 3065 if (err)
3062 goto out; 3066 goto out;
3063 } 3067 }
3064 3068 /*
3069 * Update path is required because previous ext4_split_extent_at() may
3070 * result in split of original leaf or extent zeroout.
3071 */
3065 ext4_ext_drop_refs(path); 3072 ext4_ext_drop_refs(path);
3066 path = ext4_ext_find_extent(inode, map->m_lblk, path); 3073 path = ext4_ext_find_extent(inode, map->m_lblk, path);
3067 if (IS_ERR(path)) 3074 if (IS_ERR(path))
3068 return PTR_ERR(path); 3075 return PTR_ERR(path);
3076 depth = ext_depth(inode);
3077 ex = path[depth].p_ext;
3078 uninitialized = ext4_ext_is_uninitialized(ex);
3079 split_flag1 = 0;
3069 3080
3070 if (map->m_lblk >= ee_block) { 3081 if (map->m_lblk >= ee_block) {
3071 split_flag1 = split_flag & (EXT4_EXT_MAY_ZEROOUT | 3082 split_flag1 = split_flag & EXT4_EXT_DATA_VALID2;
3072 EXT4_EXT_DATA_VALID2); 3083 if (uninitialized) {
3073 if (uninitialized)
3074 split_flag1 |= EXT4_EXT_MARK_UNINIT1; 3084 split_flag1 |= EXT4_EXT_MARK_UNINIT1;
3075 if (split_flag & EXT4_EXT_MARK_UNINIT2) 3085 split_flag1 |= split_flag & (EXT4_EXT_MAY_ZEROOUT |
3076 split_flag1 |= EXT4_EXT_MARK_UNINIT2; 3086 EXT4_EXT_MARK_UNINIT2);
3087 }
3077 err = ext4_split_extent_at(handle, inode, path, 3088 err = ext4_split_extent_at(handle, inode, path,
3078 map->m_lblk, split_flag1, flags); 3089 map->m_lblk, split_flag1, flags);
3079 if (err) 3090 if (err)