1
0
Fork 0
mirror of synced 2025-03-06 20:59:54 +01:00
linux/fs/btrfs/tests/delayed-refs-tests.c
David Disseldorp 290237fde9 btrfs: selftests: fix btrfs_test_delayed_refs() leak of transaction
The btrfs_transaction struct leaks, which can cause sporadic fstests
failures when kmemleak checking is enabled:

kmemleak: 5 new suspected memory leaks (see /sys/kernel/debug/kmemleak)
> cat /sys/kernel/debug/kmemleak
unreferenced object 0xffff88810fdc6c00 (size 512):
  comm "modprobe", pid 203, jiffies 4294892552
  hex dump (first 32 bytes):
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
  backtrace (crc 6736050f):
    __kmalloc_cache_noprof+0x133/0x2c0
    btrfs_test_delayed_refs+0x6f/0xbb0 [btrfs]
    btrfs_run_sanity_tests.cold+0x91/0xf9 [btrfs]
    0xffffffffa02fd055
    do_one_initcall+0x49/0x1c0
    do_init_module+0x5b/0x1f0
    init_module_from_file+0x70/0x90
    idempotent_init_module+0xe8/0x2c0
    __x64_sys_finit_module+0x6b/0xd0
    do_syscall_64+0x54/0x110
    entry_SYSCALL_64_after_hwframe+0x76/0x7e

The transaction struct was initially stack-allocated but switched to
heap following frame size compiler warnings.

Fixes: 2b34879d97 ("btrfs: selftests: add delayed ref self test cases")
Signed-off-by: David Disseldorp <ddiss@suse.de>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
2025-02-17 17:24:14 +01:00

1016 lines
24 KiB
C

// 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);
kfree(transaction);
out_free_fs_info:
btrfs_free_dummy_fs_info(fs_info);
return ret;
}