Skip to content

Commit 758eb51

Browse files
fdmananamasoncl
authored andcommitted
Btrfs: fix freeing used extent after removing empty block group
Due to ignoring errors returned by clear_extent_bits (at the moment only -ENOMEM is possible), we can end up freeing an extent that is actually in use (i.e. return the extent to the free space cache). The sequence of steps that lead to this: 1) Cleaner thread starts execution and calls btrfs_delete_unused_bgs(), with the goal of freeing empty block groups; 2) btrfs_delete_unused_bgs() finds an empty block group, joins the current transaction (or starts a new one if none is running) and attempts to clear the EXTENT_DIRTY bit for the block group's range from freed_extents[0] and freed_extents[1] (of which one corresponds to fs_info->pinned_extents); 3) Clearing the EXTENT_DIRTY bit (via clear_extent_bits()) fails with -ENOMEM, but such error is ignored and btrfs_delete_unused_bgs() proceeds to delete the block group and the respective chunk, while pinned_extents remains with that bit set for the whole (or a part of the) range covered by the block group; 4) Later while the transaction is still running, the chunk ends up being reused for a new block group (maybe for different purpose, data or metadata), and extents belonging to the new block group are allocated for file data or btree nodes/leafs; 5) The current transaction is committed, meaning that we unpinned one or more extents from the new block group (through btrfs_finish_extent_commit() and unpin_extent_range()) which are now being used for new file data or new metadata (through btrfs_finish_extent_commit() and unpin_extent_range()). And unpinning means we returned the extents to the free space cache of the new block group, which implies those extents can be used for future allocations while they're still in use. Alternatively, we can hit a BUG_ON() when doing a lookup for a block group's cache object in unpin_extent_range() if a new block group didn't end up being allocated for the same chunk (step 4 above). Fix this by not freeing the block group and chunk if we fail to clear the dirty bit. Signed-off-by: Filipe Manana <[email protected]> Signed-off-by: Chris Mason <[email protected]>
1 parent 8f608de commit 758eb51

File tree

1 file changed

+11
-2
lines changed

1 file changed

+11
-2
lines changed

fs/btrfs/extent-tree.c

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9513,10 +9513,18 @@ void btrfs_delete_unused_bgs(struct btrfs_fs_info *fs_info)
95139513
*/
95149514
start = block_group->key.objectid;
95159515
end = start + block_group->key.offset - 1;
9516-
clear_extent_bits(&fs_info->freed_extents[0], start, end,
9516+
ret = clear_extent_bits(&fs_info->freed_extents[0], start, end,
95179517
EXTENT_DIRTY, GFP_NOFS);
9518-
clear_extent_bits(&fs_info->freed_extents[1], start, end,
9518+
if (ret) {
9519+
btrfs_set_block_group_rw(root, block_group);
9520+
goto end_trans;
9521+
}
9522+
ret = clear_extent_bits(&fs_info->freed_extents[1], start, end,
95199523
EXTENT_DIRTY, GFP_NOFS);
9524+
if (ret) {
9525+
btrfs_set_block_group_rw(root, block_group);
9526+
goto end_trans;
9527+
}
95209528

95219529
/* Reset pinned so btrfs_put_block_group doesn't complain */
95229530
block_group->pinned = 0;
@@ -9527,6 +9535,7 @@ void btrfs_delete_unused_bgs(struct btrfs_fs_info *fs_info)
95279535
*/
95289536
ret = btrfs_remove_chunk(trans, root,
95299537
block_group->key.objectid);
9538+
end_trans:
95309539
btrfs_end_transaction(trans, root);
95319540
next:
95329541
btrfs_put_block_group(block_group);

0 commit comments

Comments
 (0)