btrfs-progs: Handle error properly in btrfs_commit_transaction()

[BUG]
When running fuzz-tests/003 and fuzz-tests/009, btrfs-progs will crash
due to BUG_ON().

[CAUSE]
We abused BUG_ON() in btrfs_commit_transaction(), which is one of the
most error prone function for fuzzed images.

Currently to cleanup the aborted transaction, we only need to clean up
the only per-transaction data: delayed refs.

This patch will introduce a new function, btrfs_destroy_delayed_refs()
to cleanup delayed refs when we failed to commit transaction.

With that function, we will gently destroy per-trans delayed ref, and
remove the BUG_ON()s in btrfs_commit_transaction().

Reviewed-by: Nikolay Borisov <nborisov@suse.com>
Signed-off-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
This commit is contained in:
Qu Wenruo 2019-04-16 15:15:26 +08:00 committed by David Sterba
parent 45e58a1acf
commit 5672a69639
4 changed files with 47 additions and 9 deletions

View File

@ -605,3 +605,27 @@ free_ref:
return -ENOMEM;
}
void btrfs_destroy_delayed_refs(struct btrfs_trans_handle *trans)
{
struct btrfs_fs_info *fs_info = trans->fs_info;
struct rb_node *node;
struct btrfs_delayed_ref_root *delayed_refs;
delayed_refs = &trans->delayed_refs;
if (RB_EMPTY_ROOT(&delayed_refs->href_root))
return;
while ((node = rb_first(&delayed_refs->href_root)) != NULL) {
struct btrfs_delayed_ref_head *head;
struct btrfs_delayed_ref_node *ref;
struct rb_node *n;
head = rb_entry(node, struct btrfs_delayed_ref_head, href_node);
while ((n = rb_first(&head->ref_tree)) != NULL) {
ref = rb_entry(n, struct btrfs_delayed_ref_node,
ref_node);
drop_delayed_ref(trans, delayed_refs, head, ref);
}
ASSERT(cleanup_ref_head(trans, fs_info, head) == 0);
}
}

View File

@ -205,4 +205,10 @@ btrfs_delayed_node_to_tree_ref(struct btrfs_delayed_ref_node *node)
{
return container_of(node, struct btrfs_delayed_tree_ref, node);
}
int cleanup_ref_head(struct btrfs_trans_handle *trans,
struct btrfs_fs_info *fs_info,
struct btrfs_delayed_ref_head *head);
void btrfs_destroy_delayed_refs(struct btrfs_trans_handle *trans);
#endif

View File

@ -4085,9 +4085,9 @@ static void unselect_delayed_ref_head(struct btrfs_delayed_ref_root *delayed_ref
delayed_refs->num_heads_ready++;
}
static int cleanup_ref_head(struct btrfs_trans_handle *trans,
struct btrfs_fs_info *fs_info,
struct btrfs_delayed_ref_head *head)
int cleanup_ref_head(struct btrfs_trans_handle *trans,
struct btrfs_fs_info *fs_info,
struct btrfs_delayed_ref_head *head)
{
struct btrfs_delayed_ref_root *delayed_refs;

View File

@ -17,6 +17,7 @@
#include "kerncompat.h"
#include "disk-io.h"
#include "transaction.h"
#include "delayed-ref.h"
#include "messages.h"
@ -165,7 +166,8 @@ int btrfs_commit_transaction(struct btrfs_trans_handle *trans,
* consistent
*/
ret = btrfs_run_delayed_refs(trans, -1);
BUG_ON(ret);
if (ret < 0)
goto error;
if (root->commit_root == root->node)
goto commit_tree;
@ -182,21 +184,24 @@ int btrfs_commit_transaction(struct btrfs_trans_handle *trans,
root->root_item.level = btrfs_header_level(root->node);
ret = btrfs_update_root(trans, root->fs_info->tree_root,
&root->root_key, &root->root_item);
BUG_ON(ret);
if (ret < 0)
goto error;
commit_tree:
ret = commit_tree_roots(trans, fs_info);
BUG_ON(ret);
if (ret < 0)
goto error;
/*
* Ensure that all committed roots are properly accounted in the
* extent tree
*/
ret = btrfs_run_delayed_refs(trans, -1);
BUG_ON(ret);
if (ret < 0)
goto error;
btrfs_write_dirty_block_groups(trans);
__commit_transaction(trans, root);
if (ret < 0)
goto out;
goto error;
ret = write_ctree_super(trans);
btrfs_finish_extent_commit(trans);
kfree(trans);
@ -204,7 +209,10 @@ commit_tree:
root->commit_root = NULL;
fs_info->running_transaction = NULL;
fs_info->last_trans_committed = transid;
out:
return ret;
error:
btrfs_destroy_delayed_refs(trans);
free(trans);
return ret;
}