diff options
Diffstat (limited to 'fs/btrfs/tests/delayed-refs-tests.c')
| -rw-r--r-- | fs/btrfs/tests/delayed-refs-tests.c | 1015 | 
1 files changed, 1015 insertions, 0 deletions
| diff --git a/fs/btrfs/tests/delayed-refs-tests.c b/fs/btrfs/tests/delayed-refs-tests.c new file mode 100644 index 000000000000..6558508c2ddf --- /dev/null +++ b/fs/btrfs/tests/delayed-refs-tests.c @@ -0,0 +1,1015 @@ +// SPDX-License-Identifier: GPL-2.0 +#include <linux/sizes.h> +#include "btrfs-tests.h" +#include "../transaction.h" +#include "../delayed-ref.h" +#include "../extent-tree.h" + +#define FAKE_ROOT_OBJECTID 256 +#define FAKE_BYTENR 0 +#define FAKE_LEVEL 1 +#define FAKE_INO 256 +#define FAKE_FILE_OFFSET 0 +#define FAKE_PARENT SZ_1M + +struct ref_head_check { +	u64 bytenr; +	u64 num_bytes; +	int ref_mod; +	int total_ref_mod; +	int must_insert; +}; + +struct ref_node_check { +	u64 bytenr; +	u64 num_bytes; +	int ref_mod; +	enum btrfs_delayed_ref_action action; +	u8 type; +	u64 parent; +	u64 root; +	u64 owner; +	u64 offset; +}; + +static enum btrfs_ref_type ref_type_from_disk_ref_type(u8 type) +{ +	if ((type == BTRFS_TREE_BLOCK_REF_KEY) || +	    (type == BTRFS_SHARED_BLOCK_REF_KEY)) +		return BTRFS_REF_METADATA; +	return BTRFS_REF_DATA; +} + +static void delete_delayed_ref_head(struct btrfs_trans_handle *trans, +				    struct btrfs_delayed_ref_head *head) +{ +	struct btrfs_fs_info *fs_info = trans->fs_info; +	struct btrfs_delayed_ref_root *delayed_refs = +		&trans->transaction->delayed_refs; + +	spin_lock(&delayed_refs->lock); +	spin_lock(&head->lock); +	btrfs_delete_ref_head(fs_info, delayed_refs, head); +	spin_unlock(&head->lock); +	spin_unlock(&delayed_refs->lock); + +	btrfs_delayed_ref_unlock(head); +	btrfs_put_delayed_ref_head(head); +} + +static void delete_delayed_ref_node(struct btrfs_delayed_ref_head *head, +				    struct btrfs_delayed_ref_node *node) +{ +	rb_erase_cached(&node->ref_node, &head->ref_tree); +	RB_CLEAR_NODE(&node->ref_node); +	if (!list_empty(&node->add_list)) +		list_del_init(&node->add_list); +	btrfs_put_delayed_ref(node); +} + +static int validate_ref_head(struct btrfs_delayed_ref_head *head, +			     struct ref_head_check *check) +{ +	if (head->bytenr != check->bytenr) { +		test_err("invalid bytenr have: %llu want: %llu", head->bytenr, +			 check->bytenr); +		return -EINVAL; +	} + +	if (head->num_bytes != check->num_bytes) { +		test_err("invalid num_bytes have: %llu want: %llu", +			 head->num_bytes, check->num_bytes); +		return -EINVAL; +	} + +	if (head->ref_mod != check->ref_mod) { +		test_err("invalid ref_mod have: %d want: %d", head->ref_mod, +			 check->ref_mod); +		return -EINVAL; +	} + +	if (head->total_ref_mod != check->total_ref_mod) { +		test_err("invalid total_ref_mod have: %d want: %d", +			 head->total_ref_mod, check->total_ref_mod); +		return -EINVAL; +	} + +	if (head->must_insert_reserved != check->must_insert) { +		test_err("invalid must_insert have: %d want: %d", +			 head->must_insert_reserved, check->must_insert); +		return -EINVAL; +	} + +	return 0; +} + +static int validate_ref_node(struct btrfs_delayed_ref_node *node, +			     struct ref_node_check *check) +{ +	if (node->bytenr != check->bytenr) { +		test_err("invalid bytenr have: %llu want: %llu", node->bytenr, +			 check->bytenr); +		return -EINVAL; +	} + +	if (node->num_bytes != check->num_bytes) { +		test_err("invalid num_bytes have: %llu want: %llu", +			 node->num_bytes, check->num_bytes); +		return -EINVAL; +	} + +	if (node->ref_mod != check->ref_mod) { +		test_err("invalid ref_mod have: %d want: %d", node->ref_mod, +			 check->ref_mod); +		return -EINVAL; +	} + +	if (node->action != check->action) { +		test_err("invalid action have: %d want: %d", node->action, +			 check->action); +		return -EINVAL; +	} + +	if (node->parent != check->parent) { +		test_err("invalid parent have: %llu want: %llu", node->parent, +			 check->parent); +		return -EINVAL; +	} + +	if (node->ref_root != check->root) { +		test_err("invalid root have: %llu want: %llu", node->ref_root, +			 check->root); +		return -EINVAL; +	} + +	if (node->type != check->type) { +		test_err("invalid type have: %d want: %d", node->type, +			 check->type); +		return -EINVAL; +	} + +	if (btrfs_delayed_ref_owner(node) != check->owner) { +		test_err("invalid owner have: %llu want: %llu", +			 btrfs_delayed_ref_owner(node), check->owner); +		return -EINVAL; +	} + +	if (btrfs_delayed_ref_offset(node) != check->offset) { +		test_err("invalid offset have: %llu want: %llu", +			 btrfs_delayed_ref_offset(node), check->offset); +		return -EINVAL; +	} + +	return 0; +} + +static int simple_test(struct btrfs_trans_handle *trans, +		       struct ref_head_check *head_check, +		       struct ref_node_check *node_check) +{ +	struct btrfs_delayed_ref_root *delayed_refs = +		&trans->transaction->delayed_refs; +	struct btrfs_fs_info *fs_info = trans->fs_info; +	struct btrfs_delayed_ref_head *head; +	struct btrfs_delayed_ref_node *node; +	struct btrfs_ref ref = { +		.type = ref_type_from_disk_ref_type(node_check->type), +		.action = node_check->action, +		.parent = node_check->parent, +		.ref_root = node_check->root, +		.bytenr = node_check->bytenr, +		.num_bytes = fs_info->nodesize, +	}; +	int ret; + +	if (ref.type == BTRFS_REF_METADATA) +		btrfs_init_tree_ref(&ref, node_check->owner, node_check->root, +				    false); +	else +		btrfs_init_data_ref(&ref, node_check->owner, node_check->offset, +				    node_check->root, true); + +	if (ref.type == BTRFS_REF_METADATA) +		ret = btrfs_add_delayed_tree_ref(trans, &ref, NULL); +	else +		ret = btrfs_add_delayed_data_ref(trans, &ref, 0); +	if (ret) { +		test_err("failed ref action %d", ret); +		return ret; +	} + +	head = btrfs_select_ref_head(fs_info, delayed_refs); +	if (IS_ERR_OR_NULL(head)) { +		if (IS_ERR(head)) +			test_err("failed to select delayed ref head: %ld", +				 PTR_ERR(head)); +		else +			test_err("failed to find delayed ref head"); +		return -EINVAL; +	} + +	ret = -EINVAL; +	if (validate_ref_head(head, head_check)) +		goto out; + +	spin_lock(&head->lock); +	node = btrfs_select_delayed_ref(head); +	spin_unlock(&head->lock); +	if (!node) { +		test_err("failed to select delayed ref"); +		goto out; +	} + +	if (validate_ref_node(node, node_check)) +		goto out; +	ret = 0; +out: +	btrfs_unselect_ref_head(delayed_refs, head); +	btrfs_destroy_delayed_refs(trans->transaction); +	return ret; +} + +/* + * These are simple tests, make sure that our btrfs_ref's get turned into the + * appropriate btrfs_delayed_ref_node based on their settings and action. + */ +static int simple_tests(struct btrfs_trans_handle *trans) +{ +	struct btrfs_fs_info *fs_info = trans->fs_info; +	struct ref_head_check head_check = { +		.bytenr = FAKE_BYTENR, +		.num_bytes = fs_info->nodesize, +		.ref_mod = 1, +		.total_ref_mod = 1, +	}; +	struct ref_node_check node_check = { +		.bytenr = FAKE_BYTENR, +		.num_bytes = fs_info->nodesize, +		.ref_mod = 1, +		.action = BTRFS_ADD_DELAYED_REF, +		.type = BTRFS_TREE_BLOCK_REF_KEY, +		.parent = 0, +		.root = FAKE_ROOT_OBJECTID, +		.owner = FAKE_LEVEL, +		.offset = 0, +	}; + +	if (simple_test(trans, &head_check, &node_check)) { +		test_err("single add tree block failed"); +		return -EINVAL; +	} + +	node_check.type = BTRFS_EXTENT_DATA_REF_KEY; +	node_check.owner = FAKE_INO; +	node_check.offset = FAKE_FILE_OFFSET; + +	if (simple_test(trans, &head_check, &node_check)) { +		test_err("single add extent data failed"); +		return -EINVAL; +	} + +	node_check.parent = FAKE_PARENT; +	node_check.type = BTRFS_SHARED_BLOCK_REF_KEY; +	node_check.owner = FAKE_LEVEL; +	node_check.offset = 0; + +	if (simple_test(trans, &head_check, &node_check)) { +		test_err("single add shared block failed"); +		return -EINVAL; +	} + +	node_check.type = BTRFS_SHARED_DATA_REF_KEY; +	node_check.owner = FAKE_INO; +	node_check.offset = FAKE_FILE_OFFSET; + +	if (simple_test(trans, &head_check, &node_check)) { +		test_err("single add shared data failed"); +		return -EINVAL; +	} + +	head_check.ref_mod = -1; +	head_check.total_ref_mod = -1; +	node_check.action = BTRFS_DROP_DELAYED_REF; +	node_check.type = BTRFS_TREE_BLOCK_REF_KEY; +	node_check.owner = FAKE_LEVEL; +	node_check.offset = 0; +	node_check.parent = 0; + +	if (simple_test(trans, &head_check, &node_check)) { +		test_err("single drop tree block failed"); +		return -EINVAL; +	} + +	node_check.type = BTRFS_EXTENT_DATA_REF_KEY; +	node_check.owner = FAKE_INO; +	node_check.offset = FAKE_FILE_OFFSET; + +	if (simple_test(trans, &head_check, &node_check)) { +		test_err("single drop extent data failed"); +		return -EINVAL; +	} + +	node_check.parent = FAKE_PARENT; +	node_check.type = BTRFS_SHARED_BLOCK_REF_KEY; +	node_check.owner = FAKE_LEVEL; +	node_check.offset = 0; +	if (simple_test(trans, &head_check, &node_check)) { +		test_err("single drop shared block failed"); +		return -EINVAL; +	} + +	node_check.type = BTRFS_SHARED_DATA_REF_KEY; +	node_check.owner = FAKE_INO; +	node_check.offset = FAKE_FILE_OFFSET; +	if (simple_test(trans, &head_check, &node_check)) { +		test_err("single drop shared data failed"); +		return -EINVAL; +	} + +	return 0; +} + +/* + * Merge tests, validate that we do delayed ref merging properly, the ref counts + * all end up properly, and delayed refs are deleted once they're no longer + * needed. + */ +static int merge_tests(struct btrfs_trans_handle *trans, +		       enum btrfs_ref_type type) +{ +	struct btrfs_fs_info *fs_info = trans->fs_info; +	struct btrfs_delayed_ref_head *head = NULL; +	struct btrfs_delayed_ref_node *node; +	struct btrfs_ref ref = { +		.type = type, +		.action = BTRFS_ADD_DELAYED_REF, +		.parent = 0, +		.ref_root = FAKE_ROOT_OBJECTID, +		.bytenr = FAKE_BYTENR, +		.num_bytes = fs_info->nodesize, +	}; +	struct ref_head_check head_check = { +		.bytenr = FAKE_BYTENR, +		.num_bytes = fs_info->nodesize, +		.ref_mod = 0, +		.total_ref_mod = 0, +	}; +	struct ref_node_check node_check = { +		.bytenr = FAKE_BYTENR, +		.num_bytes = fs_info->nodesize, +		.ref_mod = 2, +		.action = BTRFS_ADD_DELAYED_REF, +		.parent = 0, +		.root = FAKE_ROOT_OBJECTID, +	}; +	int ret; + +	/* +	 * First add a ref and then drop it, make sure we get a head ref with a +	 * 0 total ref mod and no nodes. +	 */ +	if (type == BTRFS_REF_METADATA) { +		node_check.type = BTRFS_TREE_BLOCK_REF_KEY; +		node_check.owner = FAKE_LEVEL; +		btrfs_init_tree_ref(&ref, FAKE_LEVEL, FAKE_ROOT_OBJECTID, false); +	} else { +		node_check.type = BTRFS_EXTENT_DATA_REF_KEY; +		node_check.owner = FAKE_INO; +		node_check.offset = FAKE_FILE_OFFSET; +		btrfs_init_data_ref(&ref, FAKE_INO, FAKE_FILE_OFFSET, +				    FAKE_ROOT_OBJECTID, true); +	} + +	if (type == BTRFS_REF_METADATA) +		ret = btrfs_add_delayed_tree_ref(trans, &ref, NULL); +	else +		ret = btrfs_add_delayed_data_ref(trans, &ref, 0); +	if (ret) { +		test_err("failed ref action %d", ret); +		return ret; +	} + +	ref.action = BTRFS_DROP_DELAYED_REF; +	if (type == BTRFS_REF_METADATA) +		ret = btrfs_add_delayed_tree_ref(trans, &ref, NULL); +	else +		ret = btrfs_add_delayed_data_ref(trans, &ref, 0); +	if (ret) { +		test_err("failed ref action %d", ret); +		goto out; +	} + +	head = btrfs_select_ref_head(fs_info, &trans->transaction->delayed_refs); +	if (IS_ERR_OR_NULL(head)) { +		if (IS_ERR(head)) +			test_err("failed to select delayed ref head: %ld", +				 PTR_ERR(head)); +		else +			test_err("failed to find delayed ref head"); +		goto out; +	} + +	ret = -EINVAL; +	if (validate_ref_head(head, &head_check)) { +		test_err("single add and drop failed"); +		goto out; +	} + +	spin_lock(&head->lock); +	node = btrfs_select_delayed_ref(head); +	spin_unlock(&head->lock); +	if (node) { +		test_err("found node when none should exist"); +		goto out; +	} + +	delete_delayed_ref_head(trans, head); +	head = NULL; + +	/* +	 * Add a ref, then add another ref, make sure we get a head ref with a +	 * 2 total ref mod and 1 node. +	 */ +	ref.action = BTRFS_ADD_DELAYED_REF; +	if (type == BTRFS_REF_METADATA) +		ret = btrfs_add_delayed_tree_ref(trans, &ref, NULL); +	else +		ret = btrfs_add_delayed_data_ref(trans, &ref, 0); +	if (ret) { +		test_err("failed ref action %d", ret); +		goto out; +	} + +	if (type == BTRFS_REF_METADATA) +		ret = btrfs_add_delayed_tree_ref(trans, &ref, NULL); +	else +		ret = btrfs_add_delayed_data_ref(trans, &ref, 0); +	if (ret) { +		test_err("failed ref action %d", ret); +		goto out; +	} + +	head = btrfs_select_ref_head(fs_info, &trans->transaction->delayed_refs); +	if (IS_ERR_OR_NULL(head)) { +		if (IS_ERR(head)) +			test_err("failed to select delayed ref head: %ld", +				 PTR_ERR(head)); +		else +			test_err("failed to find delayed ref head"); +		goto out; +	} + +	head_check.ref_mod = 2; +	head_check.total_ref_mod = 2; +	ret = -EINVAL; +	if (validate_ref_head(head, &head_check)) { +		test_err("double add failed"); +		goto out; +	} + +	spin_lock(&head->lock); +	node = btrfs_select_delayed_ref(head); +	spin_unlock(&head->lock); +	if (!node) { +		test_err("failed to select delayed ref"); +		goto out; +	} + +	if (validate_ref_node(node, &node_check)) { +		test_err("node check failed"); +		goto out; +	} + +	delete_delayed_ref_node(head, node); + +	spin_lock(&head->lock); +	node = btrfs_select_delayed_ref(head); +	spin_unlock(&head->lock); +	if (node) { +		test_err("found node when none should exist"); +		goto out; +	} +	delete_delayed_ref_head(trans, head); +	head = NULL; + +	/* Add two drop refs, make sure they are merged properly. */ +	ref.action = BTRFS_DROP_DELAYED_REF; +	if (type == BTRFS_REF_METADATA) +		ret = btrfs_add_delayed_tree_ref(trans, &ref, NULL); +	else +		ret = btrfs_add_delayed_data_ref(trans, &ref, 0); +	if (ret) { +		test_err("failed ref action %d", ret); +		goto out; +	} + +	if (type == BTRFS_REF_METADATA) +		ret = btrfs_add_delayed_tree_ref(trans, &ref, NULL); +	else +		ret = btrfs_add_delayed_data_ref(trans, &ref, 0); +	if (ret) { +		test_err("failed ref action %d", ret); +		goto out; +	} + +	head = btrfs_select_ref_head(fs_info, &trans->transaction->delayed_refs); +	if (IS_ERR_OR_NULL(head)) { +		if (IS_ERR(head)) +			test_err("failed to select delayed ref head: %ld", +				 PTR_ERR(head)); +		else +			test_err("failed to find delayed ref head"); +		goto out; +	} + +	head_check.ref_mod = -2; +	head_check.total_ref_mod = -2; +	ret = -EINVAL; +	if (validate_ref_head(head, &head_check)) { +		test_err("double drop failed"); +		goto out; +	} + +	node_check.action = BTRFS_DROP_DELAYED_REF; +	spin_lock(&head->lock); +	node = btrfs_select_delayed_ref(head); +	spin_unlock(&head->lock); +	if (!node) { +		test_err("failed to select delayed ref"); +		goto out; +	} + +	if (validate_ref_node(node, &node_check)) { +		test_err("node check failed"); +		goto out; +	} + +	delete_delayed_ref_node(head, node); + +	spin_lock(&head->lock); +	node = btrfs_select_delayed_ref(head); +	spin_unlock(&head->lock); +	if (node) { +		test_err("found node when none should exist"); +		goto out; +	} +	delete_delayed_ref_head(trans, head); +	head = NULL; + +	/* Add multiple refs, then drop until we go negative again. */ +	ref.action = BTRFS_ADD_DELAYED_REF; +	for (int i = 0; i < 10; i++) { +		if (type == BTRFS_REF_METADATA) +			ret = btrfs_add_delayed_tree_ref(trans, &ref, NULL); +		else +			ret = btrfs_add_delayed_data_ref(trans, &ref, 0); +		if (ret) { +			test_err("failed ref action %d", ret); +			goto out; +		} +	} + +	ref.action = BTRFS_DROP_DELAYED_REF; +	for (int i = 0; i < 12; i++) { +		if (type == BTRFS_REF_METADATA) +			ret = btrfs_add_delayed_tree_ref(trans, &ref, NULL); +		else +			ret = btrfs_add_delayed_data_ref(trans, &ref, 0); +		if (ret) { +			test_err("failed ref action %d", ret); +			goto out; +		} +	} + +	head = btrfs_select_ref_head(fs_info, &trans->transaction->delayed_refs); +	if (IS_ERR_OR_NULL(head)) { +		if (IS_ERR(head)) +			test_err("failed to select delayed ref head: %ld", +				 PTR_ERR(head)); +		else +			test_err("failed to find delayed ref head"); +		ret = -EINVAL; +		goto out; +	} + +	head_check.ref_mod = -2; +	head_check.total_ref_mod = -2; +	ret = -EINVAL; +	if (validate_ref_head(head, &head_check)) { +		test_err("double drop failed"); +		goto out; +	} + +	spin_lock(&head->lock); +	node = btrfs_select_delayed_ref(head); +	spin_unlock(&head->lock); +	if (!node) { +		test_err("failed to select delayed ref"); +		goto out; +	} + +	if (validate_ref_node(node, &node_check)) { +		test_err("node check failed"); +		goto out; +	} + +	delete_delayed_ref_node(head, node); + +	spin_lock(&head->lock); +	node = btrfs_select_delayed_ref(head); +	spin_unlock(&head->lock); +	if (node) { +		test_err("found node when none should exist"); +		goto out; +	} + +	delete_delayed_ref_head(trans, head); +	head = NULL; + +	/* Drop multiple refs, then add until we go positive again. */ +	ref.action = BTRFS_DROP_DELAYED_REF; +	for (int i = 0; i < 10; i++) { +		if (type == BTRFS_REF_METADATA) +			ret = btrfs_add_delayed_tree_ref(trans, &ref, NULL); +		else +			ret = btrfs_add_delayed_data_ref(trans, &ref, 0); +		if (ret) { +			test_err("failed ref action %d", ret); +			goto out; +		} +	} + +	ref.action = BTRFS_ADD_DELAYED_REF; +	for (int i = 0; i < 12; i++) { +		if (type == BTRFS_REF_METADATA) +			ret = btrfs_add_delayed_tree_ref(trans, &ref, NULL); +		else +			ret = btrfs_add_delayed_data_ref(trans, &ref, 0); +		if (ret) { +			test_err("failed ref action %d", ret); +			goto out; +		} +	} + +	head = btrfs_select_ref_head(fs_info, &trans->transaction->delayed_refs); +	if (IS_ERR_OR_NULL(head)) { +		if (IS_ERR(head)) +			test_err("failed to select delayed ref head: %ld", +				 PTR_ERR(head)); +		else +			test_err("failed to find delayed ref head"); +		ret = -EINVAL; +		goto out; +	} + +	head_check.ref_mod = 2; +	head_check.total_ref_mod = 2; +	ret = -EINVAL; +	if (validate_ref_head(head, &head_check)) { +		test_err("add and drop to positive failed"); +		goto out; +	} + +	node_check.action = BTRFS_ADD_DELAYED_REF; +	spin_lock(&head->lock); +	node = btrfs_select_delayed_ref(head); +	spin_unlock(&head->lock); +	if (!node) { +		test_err("failed to select delayed ref"); +		goto out; +	} + +	if (validate_ref_node(node, &node_check)) { +		test_err("node check failed"); +		goto out; +	} + +	delete_delayed_ref_node(head, node); + +	spin_lock(&head->lock); +	node = btrfs_select_delayed_ref(head); +	spin_unlock(&head->lock); +	if (node) { +		test_err("found node when none should exist"); +		goto out; +	} +	delete_delayed_ref_head(trans, head); +	head = NULL; + +	/* +	 * Add a bunch of refs with different roots and parents, then drop them +	 * all, make sure everything is properly merged. +	 */ +	ref.action = BTRFS_ADD_DELAYED_REF; +	for (int i = 0; i < 50; i++) { +		if (!(i % 2)) { +			ref.parent = 0; +			ref.ref_root = FAKE_ROOT_OBJECTID + i; +		} else { +			ref.parent = FAKE_PARENT + (i * fs_info->nodesize); +		} +		if (type == BTRFS_REF_METADATA) +			ret = btrfs_add_delayed_tree_ref(trans, &ref, NULL); +		else +			ret = btrfs_add_delayed_data_ref(trans, &ref, 0); +		if (ret) { +			test_err("failed ref action %d", ret); +			goto out; +		} +	} + +	ref.action = BTRFS_DROP_DELAYED_REF; +	for (int i = 0; i < 50; i++) { +		if (!(i % 2)) { +			ref.parent = 0; +			ref.ref_root = FAKE_ROOT_OBJECTID + i; +		} else { +			ref.parent = FAKE_PARENT + (i * fs_info->nodesize); +		} +		if (type == BTRFS_REF_METADATA) +			ret = btrfs_add_delayed_tree_ref(trans, &ref, NULL); +		else +			ret = btrfs_add_delayed_data_ref(trans, &ref, 0); +		if (ret) { +			test_err("failed ref action %d", ret); +			goto out; +		} +	} + +	head = btrfs_select_ref_head(fs_info, &trans->transaction->delayed_refs); +	if (IS_ERR_OR_NULL(head)) { +		if (IS_ERR(head)) +			test_err("failed to select delayed ref head: %ld", +				 PTR_ERR(head)); +		else +			test_err("failed to find delayed ref head"); +		ret = -EINVAL; +		goto out; +	} + +	head_check.ref_mod = 0; +	head_check.total_ref_mod = 0; +	ret = -EINVAL; +	if (validate_ref_head(head, &head_check)) { +		test_err("add and drop multiple failed"); +		goto out; +	} + +	spin_lock(&head->lock); +	node = btrfs_select_delayed_ref(head); +	spin_unlock(&head->lock); +	if (node) { +		test_err("found node when none should exist"); +		goto out; +	} +	ret = 0; +out: +	if (!IS_ERR_OR_NULL(head)) +		btrfs_unselect_ref_head(&trans->transaction->delayed_refs, head); +	btrfs_destroy_delayed_refs(trans->transaction); +	return ret; +} + +/* + * Basic test to validate we always get the add operations first followed by any + * delete operations. + */ +static int select_delayed_refs_test(struct btrfs_trans_handle *trans) +{ +	struct btrfs_delayed_ref_root *delayed_refs = +		&trans->transaction->delayed_refs; +	struct btrfs_fs_info *fs_info = trans->fs_info; +	struct btrfs_delayed_ref_head *head = NULL; +	struct btrfs_delayed_ref_node *node; +	struct btrfs_ref ref = { +		.type = BTRFS_REF_METADATA, +		.action = BTRFS_DROP_DELAYED_REF, +		.parent = 0, +		.ref_root = FAKE_ROOT_OBJECTID, +		.bytenr = FAKE_BYTENR, +		.num_bytes = fs_info->nodesize, +	}; +	struct ref_head_check head_check = { +		.bytenr = FAKE_BYTENR, +		.num_bytes = fs_info->nodesize, +		.ref_mod = 0, +		.total_ref_mod = 0, +	}; +	struct ref_node_check node_check = { +		.bytenr = FAKE_BYTENR, +		.num_bytes = fs_info->nodesize, +		.ref_mod = 1, +		.action = BTRFS_ADD_DELAYED_REF, +		.type = BTRFS_TREE_BLOCK_REF_KEY, +		.parent = 0, +		.owner = FAKE_LEVEL, +		.offset = 0, +	}; +	int ret; + +	/* Add the drop first. */ +	btrfs_init_tree_ref(&ref, FAKE_LEVEL, FAKE_ROOT_OBJECTID, false); +	ret = btrfs_add_delayed_tree_ref(trans, &ref, NULL); +	if (ret) { +		test_err("failed ref action %d", ret); +		return ret; +	} + +	/* +	 * Now add the add, and make it a different root so it's logically later +	 * in the rb tree. +	 */ +	ref.action = BTRFS_ADD_DELAYED_REF; +	ref.ref_root = FAKE_ROOT_OBJECTID + 1; +	ret = btrfs_add_delayed_tree_ref(trans, &ref, NULL); +	if (ret) { +		test_err("failed ref action %d", ret); +		goto out; +	} + +	head = btrfs_select_ref_head(fs_info, delayed_refs); +	if (IS_ERR_OR_NULL(head)) { +		if (IS_ERR(head)) +			test_err("failed to select delayed ref head: %ld", +				 PTR_ERR(head)); +		else +			test_err("failed to find delayed ref head"); +		ret = -EINVAL; +		head = NULL; +		goto out; +	} + +	ret = -EINVAL; +	if (validate_ref_head(head, &head_check)) { +		test_err("head check failed"); +		goto out; +	} + +	spin_lock(&head->lock); +	node = btrfs_select_delayed_ref(head); +	spin_unlock(&head->lock); +	if (!node) { +		test_err("failed to select delayed ref"); +		goto out; +	} + +	node_check.root = FAKE_ROOT_OBJECTID + 1; +	if (validate_ref_node(node, &node_check)) { +		test_err("node check failed"); +		goto out; +	} +	delete_delayed_ref_node(head, node); + +	spin_lock(&head->lock); +	node = btrfs_select_delayed_ref(head); +	spin_unlock(&head->lock); +	if (!node) { +		test_err("failed to select delayed ref"); +		goto out; +	} + +	node_check.action = BTRFS_DROP_DELAYED_REF; +	node_check.root = FAKE_ROOT_OBJECTID; +	if (validate_ref_node(node, &node_check)) { +		test_err("node check failed"); +		goto out; +	} +	delete_delayed_ref_node(head, node); +	delete_delayed_ref_head(trans, head); +	head = NULL; + +	/* +	 * Now we're going to do the same thing, but we're going to have an add +	 * that gets deleted because of a merge, and make sure we still have +	 * another add in place. +	 */ +	ref.action = BTRFS_DROP_DELAYED_REF; +	ref.ref_root = FAKE_ROOT_OBJECTID; +	ret = btrfs_add_delayed_tree_ref(trans, &ref, NULL); +	if (ret) { +		test_err("failed ref action %d", ret); +		goto out; +	} + +	ref.action = BTRFS_ADD_DELAYED_REF; +	ref.ref_root = FAKE_ROOT_OBJECTID + 1; +	ret = btrfs_add_delayed_tree_ref(trans, &ref, NULL); +	if (ret) { +		test_err("failed ref action %d", ret); +		goto out; +	} + +	ref.action = BTRFS_DROP_DELAYED_REF; +	ret = btrfs_add_delayed_tree_ref(trans, &ref, NULL); +	if (ret) { +		test_err("failed ref action %d", ret); +		goto out; +	} + +	ref.action = BTRFS_ADD_DELAYED_REF; +	ref.ref_root = FAKE_ROOT_OBJECTID + 2; +	ret = btrfs_add_delayed_tree_ref(trans, &ref, NULL); +	if (ret) { +		test_err("failed ref action %d", ret); +		goto out; +	} + +	head = btrfs_select_ref_head(fs_info, delayed_refs); +	if (IS_ERR_OR_NULL(head)) { +		if (IS_ERR(head)) +			test_err("failed to select delayed ref head: %ld", +				 PTR_ERR(head)); +		else +			test_err("failed to find delayed ref head"); +		ret = -EINVAL; +		head = NULL; +		goto out; +	} + +	ret = -EINVAL; +	if (validate_ref_head(head, &head_check)) { +		test_err("head check failed"); +		goto out; +	} + +	spin_lock(&head->lock); +	node = btrfs_select_delayed_ref(head); +	spin_unlock(&head->lock); +	if (!node) { +		test_err("failed to select delayed ref"); +		goto out; +	} + +	node_check.action = BTRFS_ADD_DELAYED_REF; +	node_check.root = FAKE_ROOT_OBJECTID + 2; +	if (validate_ref_node(node, &node_check)) { +		test_err("node check failed"); +		goto out; +	} +	delete_delayed_ref_node(head, node); + +	spin_lock(&head->lock); +	node = btrfs_select_delayed_ref(head); +	spin_unlock(&head->lock); +	if (!node) { +		test_err("failed to select delayed ref"); +		goto out; +	} + +	node_check.action = BTRFS_DROP_DELAYED_REF; +	node_check.root = FAKE_ROOT_OBJECTID; +	if (validate_ref_node(node, &node_check)) { +		test_err("node check failed"); +		goto out; +	} +	delete_delayed_ref_node(head, node); +	ret = 0; +out: +	if (head) +		btrfs_unselect_ref_head(delayed_refs, head); +	btrfs_destroy_delayed_refs(trans->transaction); +	return ret; +} + +int btrfs_test_delayed_refs(u32 sectorsize, u32 nodesize) +{ +	struct btrfs_transaction *transaction; +	struct btrfs_trans_handle trans; +	struct btrfs_fs_info *fs_info; +	int ret; + +	test_msg("running delayed refs tests"); + +	fs_info = btrfs_alloc_dummy_fs_info(nodesize, sectorsize); +	if (!fs_info) { +		test_std_err(TEST_ALLOC_FS_INFO); +		return -ENOMEM; +	} +	transaction = kmalloc(sizeof(*transaction), GFP_KERNEL); +	if (!transaction) { +		test_std_err(TEST_ALLOC_TRANSACTION); +		ret = -ENOMEM; +		goto out_free_fs_info; +	} +	btrfs_init_dummy_trans(&trans, fs_info); +	btrfs_init_dummy_transaction(transaction, fs_info); +	trans.transaction = transaction; + +	ret = simple_tests(&trans); +	if (!ret) { +		test_msg("running delayed refs merg tests on metadata refs"); +		ret = merge_tests(&trans, BTRFS_REF_METADATA); +	} + +	if (!ret) { +		test_msg("running delayed refs merg tests on data refs"); +		ret = merge_tests(&trans, BTRFS_REF_DATA); +	} + +	if (!ret) +		ret = select_delayed_refs_test(&trans); + +out_free_fs_info: +	btrfs_free_dummy_fs_info(fs_info); +	return ret; +} | 
