// SPDX-License-Identifier: GPL-2.0 #include #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; }