In case ext4_ext_swap_inode_data() fails it will clean up a failed migration by freeing the extent tree in the tmp_inode. However, in case we already copied the temporary inode's extent tree root into the original inode, the process has been functionally finished. The error case clean up calls free_ext_block() which will free those extent tree blocks while the original inode already points at them and the filesystem ends up with multiply claimed blocks. Fix it by resetting the temporary inode's root to an empty tree right after copying i_data to the original inode, so free_ext_block() won't free the original inode extent tree blocks. Move temporary inode's i_blocks as well so eviction won't try to truncate metadata blocks the tmp_inode no longer owns. Signed-off-by: Lukas Czerner --- fs/ext4/ext4.h | 1 + fs/ext4/extents.c | 11 ++++++++++- fs/ext4/migrate.c | 13 ++++++++++++- 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h index 1ee5733e2b31..915476d4d089 100644 --- a/fs/ext4/ext4.h +++ b/fs/ext4/ext4.h @@ -3903,6 +3903,7 @@ struct ext4_extent; */ #define EXT_MAX_BLOCKS 0xffffffff +extern void ext4_ext_init_root(struct inode *inode); extern void ext4_ext_tree_init(handle_t *handle, struct inode *inode); extern int ext4_ext_index_trans_blocks(struct inode *inode, int extents); extern int ext4_ext_map_blocks(handle_t *handle, struct inode *inode, diff --git a/fs/ext4/extents.c b/fs/ext4/extents.c index 3bf0a6cdbec2..ada059f83295 100644 --- a/fs/ext4/extents.c +++ b/fs/ext4/extents.c @@ -869,7 +869,11 @@ ext4_ext_binsearch(struct inode *inode, } -void ext4_ext_tree_init(handle_t *handle, struct inode *inode) +/* + * Initialize the extent tree of inode. This only changes the in-memory + * inode, the caller has to mark the inode dirty. + */ +void ext4_ext_init_root(struct inode *inode) { struct ext4_extent_header *eh; @@ -879,6 +883,11 @@ void ext4_ext_tree_init(handle_t *handle, struct inode *inode) eh->eh_magic = EXT4_EXT_MAGIC; eh->eh_max = cpu_to_le16(ext4_ext_space_root(inode, 0)); eh->eh_generation = 0; +} + +void ext4_ext_tree_init(handle_t *handle, struct inode *inode) +{ + ext4_ext_init_root(inode); ext4_mark_inode_dirty(handle, inode); } diff --git a/fs/ext4/migrate.c b/fs/ext4/migrate.c index 5d60ef10fe11..0d32cfa55785 100644 --- a/fs/ext4/migrate.c +++ b/fs/ext4/migrate.c @@ -322,6 +322,12 @@ static int ext4_ext_swap_inode_data(handle_t *handle, struct inode *inode, */ ext4_set_inode_flag(inode, EXT4_INODE_EXTENTS); memcpy(ei->i_data, tmp_ei->i_data, sizeof(ei->i_data)); + /* + * The extent tree belongs to the inode now. Make tmp_inode's extent + * tree empty, so that cleaning up tmp_inode can't free the tree + * blocks, if we fail to finish the migration. + */ + ext4_ext_init_root(tmp_inode); /* * Update i_blocks with the new blocks that got @@ -331,10 +337,13 @@ static int ext4_ext_swap_inode_data(handle_t *handle, struct inode *inode, * While converting to extents we need not * update the original inode i_blocks for extent blocks * via quota APIs. The quota update happened via tmp_inode already. + * Clear tmp_inode i_blocks to avoid truncating original data blocks + * in case we fail to finish the migration. */ spin_lock(&inode->i_lock); inode->i_blocks += tmp_inode->i_blocks; spin_unlock(&inode->i_lock); + tmp_inode->i_blocks = 0; up_write(&EXT4_I(inode)->i_data_sem); /* @@ -561,7 +570,9 @@ int ext4_ext_migrate(struct inode *inode) if (retval) /* * if we fail to swap inode data free the extent - * details of the tmp inode + * details of the tmp inode. If the tree was already + * moved to the original inode, the tmp_inode has an + * empty tree and there is nothing to free. */ free_ext_block(handle, tmp_inode); } -- 2.56.0