forked from Mirrors/btrfs-progs
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>master
parent
45e58a1acf
commit
5672a69639
|
@ -605,3 +605,27 @@ free_ref:
|
||||||
|
|
||||||
return -ENOMEM;
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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);
|
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
|
#endif
|
||||||
|
|
|
@ -4085,7 +4085,7 @@ static void unselect_delayed_ref_head(struct btrfs_delayed_ref_root *delayed_ref
|
||||||
delayed_refs->num_heads_ready++;
|
delayed_refs->num_heads_ready++;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int cleanup_ref_head(struct btrfs_trans_handle *trans,
|
int cleanup_ref_head(struct btrfs_trans_handle *trans,
|
||||||
struct btrfs_fs_info *fs_info,
|
struct btrfs_fs_info *fs_info,
|
||||||
struct btrfs_delayed_ref_head *head)
|
struct btrfs_delayed_ref_head *head)
|
||||||
{
|
{
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
#include "kerncompat.h"
|
#include "kerncompat.h"
|
||||||
#include "disk-io.h"
|
#include "disk-io.h"
|
||||||
#include "transaction.h"
|
#include "transaction.h"
|
||||||
|
#include "delayed-ref.h"
|
||||||
|
|
||||||
#include "messages.h"
|
#include "messages.h"
|
||||||
|
|
||||||
|
@ -165,7 +166,8 @@ int btrfs_commit_transaction(struct btrfs_trans_handle *trans,
|
||||||
* consistent
|
* consistent
|
||||||
*/
|
*/
|
||||||
ret = btrfs_run_delayed_refs(trans, -1);
|
ret = btrfs_run_delayed_refs(trans, -1);
|
||||||
BUG_ON(ret);
|
if (ret < 0)
|
||||||
|
goto error;
|
||||||
|
|
||||||
if (root->commit_root == root->node)
|
if (root->commit_root == root->node)
|
||||||
goto commit_tree;
|
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);
|
root->root_item.level = btrfs_header_level(root->node);
|
||||||
ret = btrfs_update_root(trans, root->fs_info->tree_root,
|
ret = btrfs_update_root(trans, root->fs_info->tree_root,
|
||||||
&root->root_key, &root->root_item);
|
&root->root_key, &root->root_item);
|
||||||
BUG_ON(ret);
|
if (ret < 0)
|
||||||
|
goto error;
|
||||||
|
|
||||||
commit_tree:
|
commit_tree:
|
||||||
ret = commit_tree_roots(trans, fs_info);
|
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
|
* Ensure that all committed roots are properly accounted in the
|
||||||
* extent tree
|
* extent tree
|
||||||
*/
|
*/
|
||||||
ret = btrfs_run_delayed_refs(trans, -1);
|
ret = btrfs_run_delayed_refs(trans, -1);
|
||||||
BUG_ON(ret);
|
if (ret < 0)
|
||||||
|
goto error;
|
||||||
btrfs_write_dirty_block_groups(trans);
|
btrfs_write_dirty_block_groups(trans);
|
||||||
__commit_transaction(trans, root);
|
__commit_transaction(trans, root);
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
goto out;
|
goto error;
|
||||||
ret = write_ctree_super(trans);
|
ret = write_ctree_super(trans);
|
||||||
btrfs_finish_extent_commit(trans);
|
btrfs_finish_extent_commit(trans);
|
||||||
kfree(trans);
|
kfree(trans);
|
||||||
|
@ -204,7 +209,10 @@ commit_tree:
|
||||||
root->commit_root = NULL;
|
root->commit_root = NULL;
|
||||||
fs_info->running_transaction = NULL;
|
fs_info->running_transaction = NULL;
|
||||||
fs_info->last_trans_committed = transid;
|
fs_info->last_trans_committed = transid;
|
||||||
out:
|
return ret;
|
||||||
|
error:
|
||||||
|
btrfs_destroy_delayed_refs(trans);
|
||||||
|
free(trans);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue