| // 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_obj(*transaction); | |
| 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 merge tests on metadata refs"); | |
| ret = merge_tests(&trans, BTRFS_REF_METADATA); | |
| } | |
| if (!ret) { | |
| test_msg("running delayed refs merge tests on data refs"); | |
| ret = merge_tests(&trans, BTRFS_REF_DATA); | |
| } | |
| if (!ret) | |
| ret = select_delayed_refs_test(&trans); | |
| kfree(transaction); | |
| out_free_fs_info: | |
| btrfs_free_dummy_fs_info(fs_info); | |
| return ret; | |
| } |