Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+314
View File
@@ -0,0 +1,314 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2013 Fusion IO. All rights reserved.
*/
#include <linux/fs.h>
#include <linux/mount.h>
#include <linux/pseudo_fs.h>
#include <linux/magic.h>
#include "btrfs-tests.h"
#include "../ctree.h"
#include "../free-space-cache.h"
#include "../free-space-tree.h"
#include "../transaction.h"
#include "../volumes.h"
#include "../disk-io.h"
#include "../qgroup.h"
#include "../block-group.h"
#include "../fs.h"
static struct vfsmount *test_mnt = NULL;
const char *test_error[] = {
[TEST_ALLOC_FS_INFO] = "cannot allocate fs_info",
[TEST_ALLOC_ROOT] = "cannot allocate root",
[TEST_ALLOC_EXTENT_BUFFER] = "cannot extent buffer",
[TEST_ALLOC_PATH] = "cannot allocate path",
[TEST_ALLOC_INODE] = "cannot allocate inode",
[TEST_ALLOC_BLOCK_GROUP] = "cannot allocate block group",
[TEST_ALLOC_EXTENT_MAP] = "cannot allocate extent map",
[TEST_ALLOC_CHUNK_MAP] = "cannot allocate chunk map",
[TEST_ALLOC_IO_CONTEXT] = "cannot allocate io context",
[TEST_ALLOC_TRANSACTION] = "cannot allocate transaction",
};
static const struct super_operations btrfs_test_super_ops = {
.alloc_inode = btrfs_alloc_inode,
.destroy_inode = btrfs_test_destroy_inode,
};
static int btrfs_test_init_fs_context(struct fs_context *fc)
{
struct pseudo_fs_context *ctx = init_pseudo(fc, BTRFS_TEST_MAGIC);
if (!ctx)
return -ENOMEM;
ctx->ops = &btrfs_test_super_ops;
return 0;
}
static struct file_system_type test_type = {
.name = "btrfs_test_fs",
.init_fs_context = btrfs_test_init_fs_context,
.kill_sb = kill_anon_super,
};
struct inode *btrfs_new_test_inode(void)
{
struct inode *inode;
inode = new_inode(test_mnt->mnt_sb);
if (!inode)
return NULL;
inode->i_mode = S_IFREG;
btrfs_set_inode_number(BTRFS_I(inode), BTRFS_FIRST_FREE_OBJECTID);
inode_init_owner(&nop_mnt_idmap, inode, NULL, S_IFREG);
return inode;
}
static int btrfs_init_test_fs(void)
{
int ret;
ret = register_filesystem(&test_type);
if (ret) {
printk(KERN_ERR "btrfs: cannot register test file system\n");
return ret;
}
test_mnt = kern_mount(&test_type);
if (IS_ERR(test_mnt)) {
printk(KERN_ERR "btrfs: cannot mount test file system\n");
unregister_filesystem(&test_type);
return PTR_ERR(test_mnt);
}
return 0;
}
static void btrfs_destroy_test_fs(void)
{
kern_unmount(test_mnt);
unregister_filesystem(&test_type);
}
struct btrfs_device *btrfs_alloc_dummy_device(struct btrfs_fs_info *fs_info)
{
struct btrfs_device *dev;
dev = kzalloc_obj(*dev);
if (!dev)
return ERR_PTR(-ENOMEM);
btrfs_extent_io_tree_init(fs_info, &dev->alloc_state, 0);
INIT_LIST_HEAD(&dev->dev_list);
list_add(&dev->dev_list, &fs_info->fs_devices->devices);
return dev;
}
static void btrfs_free_dummy_device(struct btrfs_device *dev)
{
btrfs_extent_io_tree_release(&dev->alloc_state);
kfree(dev);
}
struct btrfs_fs_info *btrfs_alloc_dummy_fs_info(u32 nodesize, u32 sectorsize)
{
struct btrfs_fs_info *fs_info = kzalloc_obj(struct btrfs_fs_info);
if (!fs_info)
return fs_info;
fs_info->fs_devices = kzalloc_obj(struct btrfs_fs_devices);
if (!fs_info->fs_devices) {
kfree(fs_info);
return NULL;
}
INIT_LIST_HEAD(&fs_info->fs_devices->devices);
fs_info->super_copy = kzalloc_obj(struct btrfs_super_block);
if (!fs_info->super_copy) {
kfree(fs_info->fs_devices);
kfree(fs_info);
return NULL;
}
btrfs_init_fs_info(fs_info);
fs_info->nodesize = nodesize;
fs_info->sectorsize = sectorsize;
fs_info->sectorsize_bits = ilog2(sectorsize);
/* CRC32C csum size. */
fs_info->csum_size = 4;
fs_info->csums_per_leaf = BTRFS_MAX_ITEM_SIZE(fs_info) /
fs_info->csum_size;
fs_info->use_bitmap = btrfs_use_bitmap;
set_bit(BTRFS_FS_STATE_DUMMY_FS_INFO, &fs_info->fs_state);
test_mnt->mnt_sb->s_fs_info = fs_info;
return fs_info;
}
void btrfs_free_dummy_fs_info(struct btrfs_fs_info *fs_info)
{
struct btrfs_device *dev, *tmp;
struct extent_buffer *eb;
unsigned long index;
if (!fs_info)
return;
if (WARN_ON(!btrfs_is_testing(fs_info)))
return;
test_mnt->mnt_sb->s_fs_info = NULL;
xa_lock_irq(&fs_info->buffer_tree);
xa_for_each(&fs_info->buffer_tree, index, eb) {
xa_unlock_irq(&fs_info->buffer_tree);
free_extent_buffer(eb);
xa_lock_irq(&fs_info->buffer_tree);
}
xa_unlock_irq(&fs_info->buffer_tree);
btrfs_mapping_tree_free(fs_info);
list_for_each_entry_safe(dev, tmp, &fs_info->fs_devices->devices,
dev_list) {
btrfs_free_dummy_device(dev);
}
btrfs_free_qgroup_config(fs_info);
btrfs_free_fs_roots(fs_info);
kfree(fs_info->super_copy);
btrfs_check_leaked_roots(fs_info);
btrfs_extent_buffer_leak_debug_check(fs_info);
kfree(fs_info->fs_devices);
kfree(fs_info);
}
void btrfs_free_dummy_root(struct btrfs_root *root)
{
if (IS_ERR_OR_NULL(root))
return;
/* Will be freed by btrfs_free_fs_roots */
if (WARN_ON(test_bit(BTRFS_ROOT_IN_RADIX, &root->state)))
return;
btrfs_global_root_delete(root);
btrfs_put_root(root);
}
struct btrfs_block_group *
btrfs_alloc_dummy_block_group(struct btrfs_fs_info *fs_info,
unsigned long length)
{
struct btrfs_block_group *cache;
cache = kzalloc_obj(*cache);
if (!cache)
return NULL;
cache->free_space_ctl = kzalloc_obj(*cache->free_space_ctl);
if (!cache->free_space_ctl) {
kfree(cache);
return NULL;
}
cache->start = 0;
cache->length = length;
cache->full_stripe_len = fs_info->sectorsize;
cache->fs_info = fs_info;
INIT_LIST_HEAD(&cache->list);
INIT_LIST_HEAD(&cache->cluster_list);
INIT_LIST_HEAD(&cache->bg_list);
btrfs_init_free_space_ctl(cache, cache->free_space_ctl);
mutex_init(&cache->free_space_lock);
return cache;
}
void btrfs_free_dummy_block_group(struct btrfs_block_group *cache)
{
if (!cache)
return;
btrfs_remove_free_space_cache(cache);
kfree(cache->free_space_ctl);
kfree(cache);
}
void btrfs_init_dummy_transaction(struct btrfs_transaction *trans, struct btrfs_fs_info *fs_info)
{
memset(trans, 0, sizeof(*trans));
trans->fs_info = fs_info;
xa_init(&trans->delayed_refs.head_refs);
xa_init(&trans->delayed_refs.dirty_extents);
spin_lock_init(&trans->delayed_refs.lock);
}
void btrfs_init_dummy_trans(struct btrfs_trans_handle *trans,
struct btrfs_fs_info *fs_info)
{
memset(trans, 0, sizeof(*trans));
trans->transid = 1;
trans->type = __TRANS_DUMMY;
trans->fs_info = fs_info;
}
int btrfs_run_sanity_tests(void)
{
int ret, i;
u32 sectorsize, nodesize;
u32 test_sectorsize[] = {
PAGE_SIZE,
};
ret = btrfs_init_test_fs();
if (ret)
return ret;
for (i = 0; i < ARRAY_SIZE(test_sectorsize); i++) {
sectorsize = test_sectorsize[i];
for (nodesize = sectorsize;
nodesize <= BTRFS_MAX_METADATA_BLOCKSIZE;
nodesize <<= 1) {
pr_info("BTRFS: selftest: sectorsize: %u nodesize: %u\n",
sectorsize, nodesize);
ret = btrfs_test_free_space_cache(sectorsize, nodesize);
if (ret)
goto out;
ret = btrfs_test_extent_buffer_operations(sectorsize,
nodesize);
if (ret)
goto out;
ret = btrfs_test_extent_io(sectorsize, nodesize);
if (ret)
goto out;
ret = btrfs_test_inodes(sectorsize, nodesize);
if (ret)
goto out;
ret = btrfs_test_qgroups(sectorsize, nodesize);
if (ret)
goto out;
ret = btrfs_test_free_space_tree(sectorsize, nodesize);
if (ret)
goto out;
ret = btrfs_test_raid_stripe_tree(sectorsize, nodesize);
if (ret)
goto out;
ret = btrfs_test_delayed_refs(sectorsize, nodesize);
if (ret)
goto out;
ret = btrfs_test_chunk_allocation(sectorsize, nodesize);
if (ret)
goto out;
}
}
ret = btrfs_test_extent_map();
if (ret)
goto out;
ret = btrfs_test_zoned();
out:
btrfs_destroy_test_fs();
return ret;
}
+83
View File
@@ -0,0 +1,83 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (C) 2013 Fusion IO. All rights reserved.
*/
#ifndef BTRFS_TESTS_H
#define BTRFS_TESTS_H
#include <linux/types.h>
#include <linux/cleanup.h>
#ifdef CONFIG_BTRFS_FS_RUN_SANITY_TESTS
int btrfs_run_sanity_tests(void);
#define test_msg(fmt, ...) pr_info("BTRFS: selftest: " fmt "\n", ##__VA_ARGS__)
#define test_err(fmt, ...) pr_err("BTRFS: selftest: %s:%d " fmt "\n", \
__FILE__, __LINE__, ##__VA_ARGS__)
#define test_std_err(index) test_err("%s", test_error[index])
enum {
TEST_ALLOC_FS_INFO,
TEST_ALLOC_ROOT,
TEST_ALLOC_EXTENT_BUFFER,
TEST_ALLOC_PATH,
TEST_ALLOC_INODE,
TEST_ALLOC_BLOCK_GROUP,
TEST_ALLOC_EXTENT_MAP,
TEST_ALLOC_CHUNK_MAP,
TEST_ALLOC_IO_CONTEXT,
TEST_ALLOC_TRANSACTION,
};
extern const char *test_error[];
struct btrfs_root;
struct btrfs_trans_handle;
struct btrfs_transaction;
int btrfs_test_extent_buffer_operations(u32 sectorsize, u32 nodesize);
int btrfs_test_free_space_cache(u32 sectorsize, u32 nodesize);
int btrfs_test_extent_io(u32 sectorsize, u32 nodesize);
int btrfs_test_inodes(u32 sectorsize, u32 nodesize);
int btrfs_test_qgroups(u32 sectorsize, u32 nodesize);
int btrfs_test_free_space_tree(u32 sectorsize, u32 nodesize);
int btrfs_test_raid_stripe_tree(u32 sectorsize, u32 nodesize);
int btrfs_test_extent_map(void);
int btrfs_test_delayed_refs(u32 sectorsize, u32 nodesize);
int btrfs_test_chunk_allocation(u32 sectorsize, u32 nodesize);
struct inode *btrfs_new_test_inode(void);
struct btrfs_fs_info *btrfs_alloc_dummy_fs_info(u32 nodesize, u32 sectorsize);
void btrfs_free_dummy_fs_info(struct btrfs_fs_info *fs_info);
DEFINE_FREE(btrfs_free_dummy_fs_info, struct btrfs_fs_info *,
btrfs_free_dummy_fs_info(_T))
void btrfs_free_dummy_root(struct btrfs_root *root);
struct btrfs_block_group *
btrfs_alloc_dummy_block_group(struct btrfs_fs_info *fs_info, unsigned long length);
void btrfs_free_dummy_block_group(struct btrfs_block_group *cache);
DEFINE_FREE(btrfs_free_dummy_block_group, struct btrfs_block_group *,
btrfs_free_dummy_block_group(_T));
void btrfs_init_dummy_trans(struct btrfs_trans_handle *trans,
struct btrfs_fs_info *fs_info);
void btrfs_init_dummy_transaction(struct btrfs_transaction *trans, struct btrfs_fs_info *fs_info);
struct btrfs_device *btrfs_alloc_dummy_device(struct btrfs_fs_info *fs_info);
#ifdef CONFIG_BLK_DEV_ZONED
int btrfs_test_zoned(void);
#else
static inline int btrfs_test_zoned(void)
{
return 0;
}
#endif
#else
static inline int btrfs_run_sanity_tests(void)
{
return 0;
}
#endif
#endif
@@ -0,0 +1,476 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2026 Meta. All rights reserved.
*/
#include <linux/sizes.h>
#include "btrfs-tests.h"
#include "../volumes.h"
#include "../disk-io.h"
#include "../extent-io-tree.h"
/*
* Tests for chunk allocator pending extent internals.
* These two functions form the core of searching the chunk allocation pending
* extent bitmap and have relatively easily definable semantics, so unit
* testing them can help ensure the correctness of chunk allocation.
*/
/*
* Describes the inputs to the system and expected results
* when testing btrfs_find_hole_in_pending_extents().
*/
struct pending_extent_test_case {
const char *name;
/* Input range to search. */
u64 hole_start;
u64 hole_len;
/* The size of hole we are searching for. */
u64 min_hole_size;
/*
* Pending extents to set up (up to 2 for up to 3 holes)
* If len == 0, then it is skipped.
*/
struct {
u64 start;
u64 len;
} pending_extents[2];
/* Expected outputs. */
bool expected_found;
u64 expected_start;
u64 expected_len;
};
static const struct pending_extent_test_case find_hole_tests[] = {
{
.name = "no pending extents",
.hole_start = 0,
.hole_len = 10ULL * SZ_1G,
.min_hole_size = SZ_1G,
.pending_extents = { },
.expected_found = true,
.expected_start = 0,
.expected_len = 10ULL * SZ_1G,
},
{
.name = "pending extent at start of range",
.hole_start = 0,
.hole_len = 10ULL * SZ_1G,
.min_hole_size = SZ_1G,
.pending_extents = {
{ .start = 0, .len = SZ_1G },
},
.expected_found = true,
.expected_start = SZ_1G,
.expected_len = 9ULL * SZ_1G,
},
{
.name = "pending extent overlapping start of range",
.hole_start = SZ_1G,
.hole_len = 9ULL * SZ_1G,
.min_hole_size = SZ_1G,
.pending_extents = {
{ .start = 0, .len = SZ_2G },
},
.expected_found = true,
.expected_start = SZ_2G,
.expected_len = 8ULL * SZ_1G,
},
{
.name = "two holes; first hole is exactly big enough",
.hole_start = 0,
.hole_len = 10ULL * SZ_1G,
.min_hole_size = SZ_1G,
.pending_extents = {
{ .start = SZ_1G, .len = SZ_1G },
},
.expected_found = true,
.expected_start = 0,
.expected_len = SZ_1G,
},
{
.name = "two holes; first hole is big enough",
.hole_start = 0,
.hole_len = 10ULL * SZ_1G,
.min_hole_size = SZ_1G,
.pending_extents = {
{ .start = SZ_2G, .len = SZ_1G },
},
.expected_found = true,
.expected_start = 0,
.expected_len = SZ_2G,
},
{
.name = "two holes; second hole is big enough",
.hole_start = 0,
.hole_len = 10ULL * SZ_1G,
.min_hole_size = SZ_2G,
.pending_extents = {
{ .start = SZ_1G, .len = SZ_1G },
},
.expected_found = true,
.expected_start = SZ_2G,
.expected_len = 8ULL * SZ_1G,
},
{
.name = "three holes; first hole big enough",
.hole_start = 0,
.hole_len = 10ULL * SZ_1G,
.min_hole_size = SZ_2G,
.pending_extents = {
{ .start = SZ_2G, .len = SZ_1G },
{ .start = 4ULL * SZ_1G, .len = SZ_1G },
},
.expected_found = true,
.expected_start = 0,
.expected_len = SZ_2G,
},
{
.name = "three holes; second hole big enough",
.hole_start = 0,
.hole_len = 10ULL * SZ_1G,
.min_hole_size = SZ_2G,
.pending_extents = {
{ .start = SZ_1G, .len = SZ_1G },
{ .start = 5ULL * SZ_1G, .len = SZ_1G },
},
.expected_found = true,
.expected_start = SZ_2G,
.expected_len = 3ULL * SZ_1G,
},
{
.name = "three holes; third hole big enough",
.hole_start = 0,
.hole_len = 10ULL * SZ_1G,
.min_hole_size = SZ_2G,
.pending_extents = {
{ .start = SZ_1G, .len = SZ_1G },
{ .start = 3ULL * SZ_1G, .len = 5ULL * SZ_1G },
},
.expected_found = true,
.expected_start = 8ULL * SZ_1G,
.expected_len = SZ_2G,
},
{
.name = "three holes; all holes too small",
.hole_start = 0,
.hole_len = 10ULL * SZ_1G,
.min_hole_size = SZ_2G,
.pending_extents = {
{ .start = SZ_1G, .len = SZ_1G },
{ .start = 3ULL * SZ_1G, .len = 6ULL * SZ_1G },
},
.expected_found = false,
.expected_start = 0,
.expected_len = SZ_1G,
},
{
.name = "three holes; all holes too small; first biggest",
.hole_start = 0,
.hole_len = 10ULL * SZ_1G,
.min_hole_size = 3ULL * SZ_1G,
.pending_extents = {
{ .start = SZ_2G, .len = SZ_1G },
{ .start = 4ULL * SZ_1G, .len = 5ULL * SZ_1G },
},
.expected_found = false,
.expected_start = 0,
.expected_len = SZ_2G,
},
{
.name = "three holes; all holes too small; second biggest",
.hole_start = 0,
.hole_len = 10ULL * SZ_1G,
.min_hole_size = 3ULL * SZ_1G,
.pending_extents = {
{ .start = SZ_1G, .len = SZ_1G },
{ .start = 4ULL * SZ_1G, .len = 5ULL * SZ_1G },
},
.expected_found = false,
.expected_start = SZ_2G,
.expected_len = SZ_2G,
},
{
.name = "three holes; all holes too small; third biggest",
.hole_start = 0,
.hole_len = 10ULL * SZ_1G,
.min_hole_size = 3ULL * SZ_1G,
.pending_extents = {
{ .start = SZ_1G, .len = SZ_1G },
{ .start = 3ULL * SZ_1G, .len = 5ULL * SZ_1G },
},
.expected_found = false,
.expected_start = 8ULL * SZ_1G,
.expected_len = SZ_2G,
},
{
.name = "hole entirely allocated by pending",
.hole_start = 0,
.hole_len = 10ULL * SZ_1G,
.min_hole_size = SZ_1G,
.pending_extents = {
{ .start = 0, .len = 10ULL * SZ_1G },
},
.expected_found = false,
.expected_start = 10ULL * SZ_1G,
.expected_len = 0,
},
{
.name = "pending extent at end of range",
.hole_start = 0,
.hole_len = 10ULL * SZ_1G,
.min_hole_size = SZ_1G,
.pending_extents = {
{ .start = 9ULL * SZ_1G, .len = SZ_2G },
},
.expected_found = true,
.expected_start = 0,
.expected_len = 9ULL * SZ_1G,
},
{
.name = "zero length input",
.hole_start = SZ_1G,
.hole_len = 0,
.min_hole_size = SZ_1G,
.pending_extents = { },
.expected_found = false,
.expected_start = SZ_1G,
.expected_len = 0,
},
};
static int test_find_hole_in_pending(u32 sectorsize, u32 nodesize)
{
struct btrfs_fs_info *fs_info;
struct btrfs_device *device;
int ret = 0;
test_msg("running find_hole_in_pending_extents tests");
fs_info = btrfs_alloc_dummy_fs_info(nodesize, sectorsize);
if (!fs_info) {
test_std_err(TEST_ALLOC_FS_INFO);
return -ENOMEM;
}
device = btrfs_alloc_dummy_device(fs_info);
if (IS_ERR(device)) {
test_err("failed to allocate dummy device");
ret = PTR_ERR(device);
goto out_free_fs_info;
}
device->fs_info = fs_info;
for (int i = 0; i < ARRAY_SIZE(find_hole_tests); i++) {
const struct pending_extent_test_case *test_case = &find_hole_tests[i];
u64 hole_start = test_case->hole_start;
u64 hole_len = test_case->hole_len;
bool found;
for (int j = 0; j < ARRAY_SIZE(test_case->pending_extents); j++) {
u64 start = test_case->pending_extents[j].start;
u64 len = test_case->pending_extents[j].len;
if (!len)
continue;
btrfs_set_extent_bit(&device->alloc_state,
start, start + len - 1,
CHUNK_ALLOCATED, NULL);
}
mutex_lock(&fs_info->chunk_mutex);
found = btrfs_find_hole_in_pending_extents(device, &hole_start, &hole_len,
test_case->min_hole_size);
mutex_unlock(&fs_info->chunk_mutex);
if (found != test_case->expected_found) {
test_err("%s: expected found=%d, got found=%d",
test_case->name, test_case->expected_found, found);
ret = -EINVAL;
goto out_clear_pending_extents;
}
if (hole_start != test_case->expected_start ||
hole_len != test_case->expected_len) {
test_err("%s: expected [%llu, %llu), got [%llu, %llu)",
test_case->name, test_case->expected_start,
test_case->expected_start +
test_case->expected_len,
hole_start, hole_start + hole_len);
ret = -EINVAL;
goto out_clear_pending_extents;
}
out_clear_pending_extents:
btrfs_clear_extent_bit(&device->alloc_state, 0, (u64)-1,
CHUNK_ALLOCATED, NULL);
if (ret)
break;
}
out_free_fs_info:
btrfs_free_dummy_fs_info(fs_info);
return ret;
}
/*
* Describes the inputs to the system and expected results
* when testing btrfs_first_pending_extent().
*/
struct first_pending_test_case {
const char *name;
/* The range to look for a pending extent in. */
u64 hole_start;
u64 hole_len;
/* The pending extent to look for. */
struct {
u64 start;
u64 len;
} pending_extent;
/* Expected outputs. */
bool expected_found;
u64 expected_pending_start;
u64 expected_pending_end;
};
static const struct first_pending_test_case first_pending_tests[] = {
{
.name = "no pending extent",
.hole_start = 0,
.hole_len = 10ULL * SZ_1G,
.pending_extent = { 0, 0 },
.expected_found = false,
},
{
.name = "pending extent at search start",
.hole_start = SZ_1G,
.hole_len = 9ULL * SZ_1G,
.pending_extent = { SZ_1G, SZ_1G },
.expected_found = true,
.expected_pending_start = SZ_1G,
.expected_pending_end = SZ_2G - 1,
},
{
.name = "pending extent overlapping search start",
.hole_start = SZ_1G,
.hole_len = 9ULL * SZ_1G,
.pending_extent = { 0, SZ_2G },
.expected_found = true,
.expected_pending_start = 0,
.expected_pending_end = SZ_2G - 1,
},
{
.name = "pending extent inside search range",
.hole_start = 0,
.hole_len = 10ULL * SZ_1G,
.pending_extent = { SZ_2G, SZ_1G },
.expected_found = true,
.expected_pending_start = SZ_2G,
.expected_pending_end = 3ULL * SZ_1G - 1,
},
{
.name = "pending extent outside search range",
.hole_start = 0,
.hole_len = SZ_1G,
.pending_extent = { SZ_2G, SZ_1G },
.expected_found = false,
},
{
.name = "pending extent overlapping end of search range",
.hole_start = 0,
.hole_len = SZ_2G,
.pending_extent = { SZ_1G, SZ_2G },
.expected_found = true,
.expected_pending_start = SZ_1G,
.expected_pending_end = 3ULL * SZ_1G - 1,
},
};
static int test_first_pending_extent(u32 sectorsize, u32 nodesize)
{
struct btrfs_fs_info *fs_info;
struct btrfs_device *device;
int ret = 0;
test_msg("running first_pending_extent tests");
fs_info = btrfs_alloc_dummy_fs_info(nodesize, sectorsize);
if (!fs_info) {
test_std_err(TEST_ALLOC_FS_INFO);
return -ENOMEM;
}
device = btrfs_alloc_dummy_device(fs_info);
if (IS_ERR(device)) {
test_err("failed to allocate dummy device");
ret = PTR_ERR(device);
goto out_free_fs_info;
}
device->fs_info = fs_info;
for (int i = 0; i < ARRAY_SIZE(first_pending_tests); i++) {
const struct first_pending_test_case *test_case = &first_pending_tests[i];
u64 start = test_case->pending_extent.start;
u64 len = test_case->pending_extent.len;
u64 pending_start, pending_end;
bool found;
if (len) {
btrfs_set_extent_bit(&device->alloc_state,
start, start + len - 1,
CHUNK_ALLOCATED, NULL);
}
mutex_lock(&fs_info->chunk_mutex);
found = btrfs_first_pending_extent(device, test_case->hole_start,
test_case->hole_len,
&pending_start, &pending_end);
mutex_unlock(&fs_info->chunk_mutex);
if (found != test_case->expected_found) {
test_err("%s: expected found=%d, got found=%d",
test_case->name, test_case->expected_found, found);
ret = -EINVAL;
goto out_clear_pending_extents;
}
if (!found)
goto out_clear_pending_extents;
if (pending_start != test_case->expected_pending_start ||
pending_end != test_case->expected_pending_end) {
test_err("%s: expected pending [%llu, %llu], got [%llu, %llu]",
test_case->name,
test_case->expected_pending_start,
test_case->expected_pending_end,
pending_start, pending_end);
ret = -EINVAL;
goto out_clear_pending_extents;
}
out_clear_pending_extents:
btrfs_clear_extent_bit(&device->alloc_state, 0, (u64)-1,
CHUNK_ALLOCATED, NULL);
if (ret)
break;
}
out_free_fs_info:
btrfs_free_dummy_fs_info(fs_info);
return ret;
}
int btrfs_test_chunk_allocation(u32 sectorsize, u32 nodesize)
{
int ret;
test_msg("running chunk allocation tests");
ret = test_first_pending_extent(sectorsize, nodesize);
if (ret)
return ret;
ret = test_find_hole_in_pending(sectorsize, nodesize);
if (ret)
return ret;
return 0;
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,223 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2013 Fusion IO. All rights reserved.
*/
#include <linux/slab.h>
#include "btrfs-tests.h"
#include "../ctree.h"
#include "../extent_io.h"
#include "../disk-io.h"
#include "../accessors.h"
static int test_btrfs_split_item(u32 sectorsize, u32 nodesize)
{
struct btrfs_fs_info *fs_info;
struct btrfs_path *path = NULL;
struct btrfs_root *root = NULL;
struct extent_buffer *eb;
char *value = "mary had a little lamb";
char *split1 = "mary had a little";
char *split2 = " lamb";
char *split3 = "mary";
char *split4 = " had a little";
char buf[32];
struct btrfs_key key;
u32 value_len = strlen(value);
int ret = 0;
test_msg("running btrfs_split_item tests");
fs_info = btrfs_alloc_dummy_fs_info(nodesize, sectorsize);
if (!fs_info) {
test_std_err(TEST_ALLOC_FS_INFO);
return -ENOMEM;
}
root = btrfs_alloc_dummy_root(fs_info);
if (IS_ERR(root)) {
test_std_err(TEST_ALLOC_ROOT);
ret = PTR_ERR(root);
goto out;
}
path = btrfs_alloc_path();
if (!path) {
test_std_err(TEST_ALLOC_PATH);
ret = -ENOMEM;
goto out;
}
eb = alloc_dummy_extent_buffer(fs_info, nodesize);
path->nodes[0] = eb;
if (!eb) {
test_std_err(TEST_ALLOC_EXTENT_BUFFER);
ret = -ENOMEM;
goto out;
}
path->slots[0] = 0;
key.objectid = 0;
key.type = BTRFS_EXTENT_CSUM_KEY;
key.offset = 0;
/*
* Passing a NULL trans handle is fine here, we have a dummy root eb
* and the tree is a single node (level 0).
*/
btrfs_setup_item_for_insert(NULL, root, path, &key, value_len);
write_extent_buffer(eb, value, btrfs_item_ptr_offset(eb, 0),
value_len);
key.offset = 3;
/*
* Passing NULL trans here should be safe because we have plenty of
* space in this leaf to split the item without having to split the
* leaf.
*/
ret = btrfs_split_item(NULL, root, path, &key, 17);
if (ret) {
test_err("split item failed %d", ret);
goto out;
}
/*
* Read the first slot, it should have the original key and contain only
* 'mary had a little'
*/
btrfs_item_key_to_cpu(eb, &key, 0);
if (key.objectid != 0 || key.type != BTRFS_EXTENT_CSUM_KEY ||
key.offset != 0) {
test_err("invalid key at slot 0");
ret = -EINVAL;
goto out;
}
if (btrfs_item_size(eb, 0) != strlen(split1)) {
test_err("invalid len in the first split");
ret = -EINVAL;
goto out;
}
read_extent_buffer(eb, buf, btrfs_item_ptr_offset(eb, 0),
strlen(split1));
if (memcmp(buf, split1, strlen(split1))) {
test_err(
"data in the buffer doesn't match what it should in the first split have='%.*s' want '%s'",
(int)strlen(split1), buf, split1);
ret = -EINVAL;
goto out;
}
btrfs_item_key_to_cpu(eb, &key, 1);
if (key.objectid != 0 || key.type != BTRFS_EXTENT_CSUM_KEY ||
key.offset != 3) {
test_err("invalid key at slot 1");
ret = -EINVAL;
goto out;
}
if (btrfs_item_size(eb, 1) != strlen(split2)) {
test_err("invalid len in the second split");
ret = -EINVAL;
goto out;
}
read_extent_buffer(eb, buf, btrfs_item_ptr_offset(eb, 1),
strlen(split2));
if (memcmp(buf, split2, strlen(split2))) {
test_err(
"data in the buffer doesn't match what it should in the second split");
ret = -EINVAL;
goto out;
}
key.offset = 1;
/* Do it again so we test memmoving the other items in the leaf */
ret = btrfs_split_item(NULL, root, path, &key, 4);
if (ret) {
test_err("second split item failed %d", ret);
goto out;
}
btrfs_item_key_to_cpu(eb, &key, 0);
if (key.objectid != 0 || key.type != BTRFS_EXTENT_CSUM_KEY ||
key.offset != 0) {
test_err("invalid key at slot 0");
ret = -EINVAL;
goto out;
}
if (btrfs_item_size(eb, 0) != strlen(split3)) {
test_err("invalid len in the first split");
ret = -EINVAL;
goto out;
}
read_extent_buffer(eb, buf, btrfs_item_ptr_offset(eb, 0),
strlen(split3));
if (memcmp(buf, split3, strlen(split3))) {
test_err(
"data in the buffer doesn't match what it should in the third split");
ret = -EINVAL;
goto out;
}
btrfs_item_key_to_cpu(eb, &key, 1);
if (key.objectid != 0 || key.type != BTRFS_EXTENT_CSUM_KEY ||
key.offset != 1) {
test_err("invalid key at slot 1");
ret = -EINVAL;
goto out;
}
if (btrfs_item_size(eb, 1) != strlen(split4)) {
test_err("invalid len in the second split");
ret = -EINVAL;
goto out;
}
read_extent_buffer(eb, buf, btrfs_item_ptr_offset(eb, 1),
strlen(split4));
if (memcmp(buf, split4, strlen(split4))) {
test_err(
"data in the buffer doesn't match what it should in the fourth split");
ret = -EINVAL;
goto out;
}
btrfs_item_key_to_cpu(eb, &key, 2);
if (key.objectid != 0 || key.type != BTRFS_EXTENT_CSUM_KEY ||
key.offset != 3) {
test_err("invalid key at slot 2");
ret = -EINVAL;
goto out;
}
if (btrfs_item_size(eb, 2) != strlen(split2)) {
test_err("invalid len in the second split");
ret = -EINVAL;
goto out;
}
read_extent_buffer(eb, buf, btrfs_item_ptr_offset(eb, 2),
strlen(split2));
if (memcmp(buf, split2, strlen(split2))) {
test_err(
"data in the buffer doesn't match what it should in the last chunk");
ret = -EINVAL;
goto out;
}
out:
btrfs_free_path(path);
btrfs_free_dummy_root(root);
btrfs_free_dummy_fs_info(fs_info);
return ret;
}
int btrfs_test_extent_buffer_operations(u32 sectorsize, u32 nodesize)
{
test_msg("running extent buffer operation tests");
return test_btrfs_split_item(sectorsize, nodesize);
}
@@ -0,0 +1,830 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2013 Fusion IO. All rights reserved.
*/
#include <linux/pagemap.h>
#include <linux/folio_batch.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/sizes.h>
#include "btrfs-tests.h"
#include "../ctree.h"
#include "../extent_io.h"
#include "../disk-io.h"
#include "../btrfs_inode.h"
#define PROCESS_UNLOCK (1U << 0)
#define PROCESS_RELEASE (1U << 1)
#define PROCESS_TEST_LOCKED (1U << 2)
static noinline int process_page_range(struct inode *inode, u64 start, u64 end,
unsigned long flags)
{
int ret;
struct folio_batch fbatch;
pgoff_t index = start >> PAGE_SHIFT;
pgoff_t end_index = end >> PAGE_SHIFT;
int i;
int count = 0;
int loops = 0;
folio_batch_init(&fbatch);
while (index <= end_index) {
ret = filemap_get_folios_contig(inode->i_mapping, &index,
end_index, &fbatch);
for (i = 0; i < ret; i++) {
struct folio *folio = fbatch.folios[i];
if (flags & PROCESS_TEST_LOCKED &&
!folio_test_locked(folio))
count++;
if (flags & PROCESS_UNLOCK && folio_test_locked(folio))
folio_unlock(folio);
if (flags & PROCESS_RELEASE)
folio_put(folio);
}
folio_batch_release(&fbatch);
cond_resched();
loops++;
if (loops > 100000) {
printk(KERN_ERR
"stuck in a loop, start %llu, end %llu, ret %d\n",
start, end, ret);
break;
}
}
return count;
}
#define STATE_FLAG_STR_LEN 256
#define PRINT_ONE_FLAG(state, dest, cur, name) \
({ \
if (state->state & EXTENT_##name) \
cur += scnprintf(dest + cur, STATE_FLAG_STR_LEN - cur, \
"%s" #name, cur == 0 ? "" : "|"); \
})
static void extent_flag_to_str(const struct extent_state *state, char *dest)
{
int cur = 0;
dest[0] = 0;
PRINT_ONE_FLAG(state, dest, cur, DIRTY);
PRINT_ONE_FLAG(state, dest, cur, LOCKED);
PRINT_ONE_FLAG(state, dest, cur, DIRTY_LOG1);
PRINT_ONE_FLAG(state, dest, cur, DIRTY_LOG2);
PRINT_ONE_FLAG(state, dest, cur, DELALLOC);
PRINT_ONE_FLAG(state, dest, cur, DEFRAG);
PRINT_ONE_FLAG(state, dest, cur, BOUNDARY);
PRINT_ONE_FLAG(state, dest, cur, NODATASUM);
PRINT_ONE_FLAG(state, dest, cur, CLEAR_META_RESV);
PRINT_ONE_FLAG(state, dest, cur, NEED_WAIT);
PRINT_ONE_FLAG(state, dest, cur, NORESERVE);
PRINT_ONE_FLAG(state, dest, cur, QGROUP_RESERVED);
PRINT_ONE_FLAG(state, dest, cur, CLEAR_DATA_RESV);
}
static void dump_extent_io_tree(const struct extent_io_tree *tree)
{
struct rb_node *node;
char flags_str[STATE_FLAG_STR_LEN];
node = rb_first(&tree->state);
test_msg("io tree content:");
while (node) {
struct extent_state *state;
state = rb_entry(node, struct extent_state, rb_node);
extent_flag_to_str(state, flags_str);
test_msg(" start=%llu len=%llu flags=%s", state->start,
state->end + 1 - state->start, flags_str);
node = rb_next(node);
}
}
static int test_find_delalloc(u32 sectorsize, u32 nodesize)
{
struct btrfs_fs_info *fs_info;
struct btrfs_root *root = NULL;
struct inode *inode = NULL;
struct extent_io_tree *tmp;
struct page *page;
struct page *locked_page = NULL;
/* In this test we need at least 2 file extents at its maximum size */
u64 max_bytes = BTRFS_MAX_EXTENT_SIZE;
u64 total_dirty = 2 * max_bytes;
u64 start, end, test_start;
bool found;
int ret = -EINVAL;
test_msg("running find delalloc tests");
fs_info = btrfs_alloc_dummy_fs_info(nodesize, sectorsize);
if (!fs_info) {
test_std_err(TEST_ALLOC_FS_INFO);
return -ENOMEM;
}
root = btrfs_alloc_dummy_root(fs_info);
if (IS_ERR(root)) {
test_std_err(TEST_ALLOC_ROOT);
ret = PTR_ERR(root);
goto out;
}
inode = btrfs_new_test_inode();
if (!inode) {
test_std_err(TEST_ALLOC_INODE);
ret = -ENOMEM;
goto out;
}
tmp = &BTRFS_I(inode)->io_tree;
BTRFS_I(inode)->root = root;
/*
* Passing NULL as we don't have fs_info but tracepoints are not used
* at this point
*/
btrfs_extent_io_tree_init(NULL, tmp, IO_TREE_SELFTEST);
/*
* First go through and create and mark all of our pages dirty, we pin
* everything to make sure our pages don't get evicted and screw up our
* test.
*/
for (pgoff_t index = 0; index < (total_dirty >> PAGE_SHIFT); index++) {
page = find_or_create_page(inode->i_mapping, index, GFP_KERNEL);
if (!page) {
test_err("failed to allocate test page");
ret = -ENOMEM;
goto out;
}
SetPageDirty(page);
if (index) {
unlock_page(page);
} else {
get_page(page);
locked_page = page;
}
}
/* Test this scenario
* |--- delalloc ---|
* |--- search ---|
*/
btrfs_set_extent_bit(tmp, 0, sectorsize - 1, EXTENT_DELALLOC, NULL);
start = 0;
end = start + PAGE_SIZE - 1;
found = find_lock_delalloc_range(inode, page_folio(locked_page), &start,
&end);
if (!found) {
test_err("should have found at least one delalloc");
goto out_bits;
}
if (start != 0 || end != (sectorsize - 1)) {
test_err("expected start 0 end %u, got start %llu end %llu",
sectorsize - 1, start, end);
goto out_bits;
}
btrfs_unlock_extent(tmp, start, end, NULL);
unlock_page(locked_page);
put_page(locked_page);
/*
* Test this scenario
*
* |--- delalloc ---|
* |--- search ---|
*/
test_start = SZ_64M;
locked_page = find_lock_page(inode->i_mapping,
test_start >> PAGE_SHIFT);
if (!locked_page) {
test_err("couldn't find the locked page");
goto out_bits;
}
btrfs_set_extent_bit(tmp, sectorsize, max_bytes - 1, EXTENT_DELALLOC, NULL);
start = test_start;
end = start + PAGE_SIZE - 1;
found = find_lock_delalloc_range(inode, page_folio(locked_page), &start,
&end);
if (!found) {
test_err("couldn't find delalloc in our range");
goto out_bits;
}
if (start != test_start || end != max_bytes - 1) {
test_err("expected start %llu end %llu, got start %llu, end %llu",
test_start, max_bytes - 1, start, end);
goto out_bits;
}
if (process_page_range(inode, start, end,
PROCESS_TEST_LOCKED | PROCESS_UNLOCK)) {
test_err("there were unlocked pages in the range");
goto out_bits;
}
btrfs_unlock_extent(tmp, start, end, NULL);
/* locked_page was unlocked above */
put_page(locked_page);
/*
* Test this scenario
* |--- delalloc ---|
* |--- search ---|
*/
test_start = max_bytes + sectorsize;
locked_page = find_lock_page(inode->i_mapping, test_start >>
PAGE_SHIFT);
if (!locked_page) {
test_err("couldn't find the locked page");
goto out_bits;
}
start = test_start;
end = start + PAGE_SIZE - 1;
found = find_lock_delalloc_range(inode, page_folio(locked_page), &start,
&end);
if (found) {
test_err("found range when we shouldn't have");
goto out_bits;
}
if (end != test_start + PAGE_SIZE - 1) {
test_err("did not return the proper end offset");
goto out_bits;
}
/*
* Test this scenario
* [------- delalloc -------|
* [max_bytes]|-- search--|
*
* We are re-using our test_start from above since it works out well.
*/
btrfs_set_extent_bit(tmp, max_bytes, total_dirty - 1, EXTENT_DELALLOC, NULL);
start = test_start;
end = start + PAGE_SIZE - 1;
found = find_lock_delalloc_range(inode, page_folio(locked_page), &start,
&end);
if (!found) {
test_err("didn't find our range");
goto out_bits;
}
if (start != test_start || end != total_dirty - 1) {
test_err("expected start %llu end %llu, got start %llu end %llu",
test_start, total_dirty - 1, start, end);
goto out_bits;
}
if (process_page_range(inode, start, end,
PROCESS_TEST_LOCKED | PROCESS_UNLOCK)) {
test_err("pages in range were not all locked");
goto out_bits;
}
btrfs_unlock_extent(tmp, start, end, NULL);
/*
* Now to test where we run into a page that is no longer dirty in the
* range we want to find.
*/
page = find_get_page(inode->i_mapping,
(max_bytes + SZ_1M) >> PAGE_SHIFT);
if (!page) {
test_err("couldn't find our page");
goto out_bits;
}
ClearPageDirty(page);
put_page(page);
/* We unlocked it in the previous test */
lock_page(locked_page);
start = test_start;
end = start + PAGE_SIZE - 1;
/*
* Currently if we fail to find dirty pages in the delalloc range we
* will adjust max_bytes down to PAGE_SIZE and then re-search. If
* this changes at any point in the future we will need to fix this
* tests expected behavior.
*/
found = find_lock_delalloc_range(inode, page_folio(locked_page), &start,
&end);
if (!found) {
test_err("didn't find our range");
goto out_bits;
}
if (start != test_start && end != test_start + PAGE_SIZE - 1) {
test_err("expected start %llu end %llu, got start %llu end %llu",
test_start, test_start + PAGE_SIZE - 1, start, end);
goto out_bits;
}
if (process_page_range(inode, start, end, PROCESS_TEST_LOCKED |
PROCESS_UNLOCK)) {
test_err("pages in range were not all locked");
goto out_bits;
}
ret = 0;
out_bits:
if (ret)
dump_extent_io_tree(tmp);
btrfs_clear_extent_bit(tmp, 0, total_dirty - 1, (unsigned)-1, NULL);
out:
if (locked_page)
put_page(locked_page);
process_page_range(inode, 0, total_dirty - 1,
PROCESS_UNLOCK | PROCESS_RELEASE);
iput(inode);
btrfs_free_dummy_root(root);
btrfs_free_dummy_fs_info(fs_info);
return ret;
}
static int check_eb_bitmap(unsigned long *bitmap, struct extent_buffer *eb)
{
unsigned long i;
for (i = 0; i < eb->len * BITS_PER_BYTE; i++) {
bool bit_set, bit1_set;
bit_set = test_bit(i, bitmap);
bit1_set = extent_buffer_test_bit(eb, 0, i);
if (bit1_set != bit_set) {
u8 has;
u8 expect;
read_extent_buffer(eb, &has, i / BITS_PER_BYTE, 1);
expect = bitmap_get_value8(bitmap, ALIGN(i, BITS_PER_BYTE));
test_err(
"bits do not match, start byte 0 bit %lu, byte %lu has 0x%02x expect 0x%02x",
i, i / BITS_PER_BYTE, has, expect);
return -EINVAL;
}
bit1_set = extent_buffer_test_bit(eb, i / BITS_PER_BYTE,
i % BITS_PER_BYTE);
if (bit1_set != bit_set) {
u8 has;
u8 expect;
read_extent_buffer(eb, &has, i / BITS_PER_BYTE, 1);
expect = bitmap_get_value8(bitmap, ALIGN(i, BITS_PER_BYTE));
test_err(
"bits do not match, start byte %lu bit %lu, byte %lu has 0x%02x expect 0x%02x",
i / BITS_PER_BYTE, i % BITS_PER_BYTE,
i / BITS_PER_BYTE, has, expect);
return -EINVAL;
}
}
return 0;
}
static int test_bitmap_set(const char *name, unsigned long *bitmap,
struct extent_buffer *eb,
unsigned long byte_start, unsigned long bit_start,
unsigned long bit_len)
{
int ret;
bitmap_set(bitmap, byte_start * BITS_PER_BYTE + bit_start, bit_len);
extent_buffer_bitmap_set(eb, byte_start, bit_start, bit_len);
ret = check_eb_bitmap(bitmap, eb);
if (ret < 0)
test_err("%s test failed", name);
return ret;
}
static int test_bitmap_clear(const char *name, unsigned long *bitmap,
struct extent_buffer *eb,
unsigned long byte_start, unsigned long bit_start,
unsigned long bit_len)
{
int ret;
bitmap_clear(bitmap, byte_start * BITS_PER_BYTE + bit_start, bit_len);
extent_buffer_bitmap_clear(eb, byte_start, bit_start, bit_len);
ret = check_eb_bitmap(bitmap, eb);
if (ret < 0)
test_err("%s test failed", name);
return ret;
}
static int __test_eb_bitmaps(unsigned long *bitmap, struct extent_buffer *eb)
{
unsigned long i, j;
unsigned long byte_len = eb->len;
u32 x;
int ret;
ret = test_bitmap_clear("clear all run 1", bitmap, eb, 0, 0,
byte_len * BITS_PER_BYTE);
if (ret < 0)
return ret;
ret = test_bitmap_set("set all", bitmap, eb, 0, 0, byte_len * BITS_PER_BYTE);
if (ret < 0)
return ret;
ret = test_bitmap_clear("clear all run 2", bitmap, eb, 0, 0,
byte_len * BITS_PER_BYTE);
if (ret < 0)
return ret;
ret = test_bitmap_set("same byte set", bitmap, eb, 0, 2, 4);
if (ret < 0)
return ret;
ret = test_bitmap_clear("same byte partial clear", bitmap, eb, 0, 4, 1);
if (ret < 0)
return ret;
ret = test_bitmap_set("cross byte set", bitmap, eb, 2, 4, 8);
if (ret < 0)
return ret;
ret = test_bitmap_set("cross multi byte set", bitmap, eb, 4, 4, 24);
if (ret < 0)
return ret;
ret = test_bitmap_clear("cross byte clear", bitmap, eb, 2, 6, 4);
if (ret < 0)
return ret;
ret = test_bitmap_clear("cross multi byte clear", bitmap, eb, 4, 6, 20);
if (ret < 0)
return ret;
/* Straddling pages test */
if (byte_len > PAGE_SIZE) {
ret = test_bitmap_set("cross page set", bitmap, eb,
PAGE_SIZE - sizeof(long) / 2, 0,
sizeof(long) * BITS_PER_BYTE);
if (ret < 0)
return ret;
ret = test_bitmap_set("cross page set all", bitmap, eb, 0, 0,
byte_len * BITS_PER_BYTE);
if (ret < 0)
return ret;
ret = test_bitmap_clear("cross page clear", bitmap, eb,
PAGE_SIZE - sizeof(long) / 2, 0,
sizeof(long) * BITS_PER_BYTE);
if (ret < 0)
return ret;
}
/*
* Generate a wonky pseudo-random bit pattern for the sake of not using
* something repetitive that could miss some hypothetical off-by-n bug.
*/
x = 0;
ret = test_bitmap_clear("clear all run 3", bitmap, eb, 0, 0,
byte_len * BITS_PER_BYTE);
if (ret < 0)
return ret;
for (i = 0; i < byte_len * BITS_PER_BYTE / 32; i++) {
x = (0x19660dULL * (u64)x + 0x3c6ef35fULL) & 0xffffffffU;
for (j = 0; j < 32; j++) {
if (x & (1U << j)) {
bitmap_set(bitmap, i * 32 + j, 1);
extent_buffer_bitmap_set(eb, 0, i * 32 + j, 1);
}
}
}
ret = check_eb_bitmap(bitmap, eb);
if (ret) {
test_err("random bit pattern failed");
return ret;
}
return 0;
}
static int test_eb_bitmaps(u32 sectorsize, u32 nodesize)
{
struct btrfs_fs_info *fs_info;
unsigned long AUTO_KFREE(bitmap);
struct extent_buffer *eb = NULL;
int ret;
test_msg("running extent buffer bitmap tests");
fs_info = btrfs_alloc_dummy_fs_info(nodesize, sectorsize);
if (!fs_info) {
test_std_err(TEST_ALLOC_FS_INFO);
return -ENOMEM;
}
bitmap = kmalloc(nodesize, GFP_KERNEL);
if (!bitmap) {
test_err("couldn't allocate test bitmap");
ret = -ENOMEM;
goto out;
}
eb = alloc_dummy_extent_buffer(fs_info, 0);
if (!eb) {
test_std_err(TEST_ALLOC_ROOT);
ret = -ENOMEM;
goto out;
}
ret = __test_eb_bitmaps(bitmap, eb);
if (ret)
goto out;
free_extent_buffer(eb);
/*
* Test again for case where the tree block is sectorsize aligned but
* not nodesize aligned.
*/
eb = alloc_dummy_extent_buffer(fs_info, sectorsize);
if (!eb) {
test_std_err(TEST_ALLOC_ROOT);
ret = -ENOMEM;
goto out;
}
ret = __test_eb_bitmaps(bitmap, eb);
out:
free_extent_buffer(eb);
btrfs_free_dummy_fs_info(fs_info);
return ret;
}
static int test_find_first_clear_extent_bit(void)
{
struct extent_io_tree tree;
u64 start, end;
int ret = -EINVAL;
test_msg("running find_first_clear_extent_bit test");
btrfs_extent_io_tree_init(NULL, &tree, IO_TREE_SELFTEST);
/* Test correct handling of empty tree */
btrfs_find_first_clear_extent_bit(&tree, 0, &start, &end, CHUNK_TRIMMED);
if (start != 0 || end != -1) {
test_err(
"error getting a range from completely empty tree: start %llu end %llu",
start, end);
goto out;
}
/*
* Set 1M-4M alloc/discard and 32M-64M thus leaving a hole between
* 4M-32M
*/
btrfs_set_extent_bit(&tree, SZ_1M, SZ_4M - 1,
CHUNK_TRIMMED | CHUNK_ALLOCATED, NULL);
btrfs_find_first_clear_extent_bit(&tree, SZ_512K, &start, &end,
CHUNK_TRIMMED | CHUNK_ALLOCATED);
if (start != 0 || end != SZ_1M - 1) {
test_err("error finding beginning range: start %llu end %llu",
start, end);
goto out;
}
/* Now add 32M-64M so that we have a hole between 4M-32M */
btrfs_set_extent_bit(&tree, SZ_32M, SZ_64M - 1,
CHUNK_TRIMMED | CHUNK_ALLOCATED, NULL);
/*
* Request first hole starting at 12M, we should get 4M-32M
*/
btrfs_find_first_clear_extent_bit(&tree, 12 * SZ_1M, &start, &end,
CHUNK_TRIMMED | CHUNK_ALLOCATED);
if (start != SZ_4M || end != SZ_32M - 1) {
test_err("error finding trimmed range: start %llu end %llu",
start, end);
goto out;
}
/*
* Search in the middle of allocated range, should get the next one
* available, which happens to be unallocated -> 4M-32M
*/
btrfs_find_first_clear_extent_bit(&tree, SZ_2M, &start, &end,
CHUNK_TRIMMED | CHUNK_ALLOCATED);
if (start != SZ_4M || end != SZ_32M - 1) {
test_err("error finding next unalloc range: start %llu end %llu",
start, end);
goto out;
}
/*
* Set 64M-72M with CHUNK_ALLOC flag, then search for CHUNK_TRIMMED flag
* being unset in this range, we should get the entry in range 64M-72M
*/
btrfs_set_extent_bit(&tree, SZ_64M, SZ_64M + SZ_8M - 1, CHUNK_ALLOCATED, NULL);
btrfs_find_first_clear_extent_bit(&tree, SZ_64M + SZ_1M, &start, &end,
CHUNK_TRIMMED);
if (start != SZ_64M || end != SZ_64M + SZ_8M - 1) {
test_err("error finding exact range: start %llu end %llu",
start, end);
goto out;
}
btrfs_find_first_clear_extent_bit(&tree, SZ_64M - SZ_8M, &start, &end,
CHUNK_TRIMMED);
/*
* Search in the middle of set range whose immediate neighbour doesn't
* have the bits set so it must be returned
*/
if (start != SZ_64M || end != SZ_64M + SZ_8M - 1) {
test_err("error finding next alloc range: start %llu end %llu",
start, end);
goto out;
}
/*
* Search beyond any known range, shall return after last known range
* and end should be -1
*/
btrfs_find_first_clear_extent_bit(&tree, -1, &start, &end, CHUNK_TRIMMED);
if (start != SZ_64M + SZ_8M || end != -1) {
test_err(
"error handling beyond end of range search: start %llu end %llu",
start, end);
goto out;
}
ret = 0;
out:
if (ret)
dump_extent_io_tree(&tree);
btrfs_clear_extent_bit(&tree, 0, (u64)-1, CHUNK_TRIMMED | CHUNK_ALLOCATED, NULL);
return ret;
}
static void dump_eb_and_memory_contents(struct extent_buffer *eb, void *memory,
const char *test_name)
{
for (int i = 0; i < eb->len; i++) {
struct page *page = folio_page(eb->folios[i >> PAGE_SHIFT], 0);
void *addr = page_address(page) + offset_in_page(i);
if (memcmp(addr, memory + i, 1) != 0) {
test_err("%s failed", test_name);
test_err("eb and memory diffs at byte %u, eb has 0x%02x memory has 0x%02x",
i, *(u8 *)addr, *(u8 *)(memory + i));
return;
}
}
}
static int verify_eb_and_memory(struct extent_buffer *eb, void *memory,
const char *test_name)
{
for (int i = 0; i < (eb->len >> PAGE_SHIFT); i++) {
void *eb_addr = folio_address(eb->folios[i]);
if (memcmp(memory + (i << PAGE_SHIFT), eb_addr, PAGE_SIZE) != 0) {
dump_eb_and_memory_contents(eb, memory, test_name);
return -EUCLEAN;
}
}
return 0;
}
/*
* Init both memory and extent buffer contents to the same randomly generated
* contents.
*/
static void init_eb_and_memory(struct extent_buffer *eb, void *memory)
{
get_random_bytes(memory, eb->len);
write_extent_buffer(eb, memory, 0, eb->len);
}
static int test_eb_mem_ops(u32 sectorsize, u32 nodesize)
{
struct btrfs_fs_info *fs_info;
struct extent_buffer *eb = NULL;
void *memory = NULL;
int ret;
test_msg("running extent buffer memory operation tests");
fs_info = btrfs_alloc_dummy_fs_info(nodesize, sectorsize);
if (!fs_info) {
test_std_err(TEST_ALLOC_FS_INFO);
return -ENOMEM;
}
memory = kvzalloc(nodesize, GFP_KERNEL);
if (!memory) {
test_err("failed to allocate memory");
ret = -ENOMEM;
goto out;
}
eb = alloc_dummy_extent_buffer(fs_info, SZ_1M);
if (!eb) {
test_std_err(TEST_ALLOC_EXTENT_BUFFER);
ret = -ENOMEM;
goto out;
}
init_eb_and_memory(eb, memory);
ret = verify_eb_and_memory(eb, memory, "full eb write");
if (ret < 0)
goto out;
memcpy(memory, memory + 16, 16);
memcpy_extent_buffer(eb, 0, 16, 16);
ret = verify_eb_and_memory(eb, memory, "same page non-overlapping memcpy 1");
if (ret < 0)
goto out;
memcpy(memory, memory + 2048, 16);
memcpy_extent_buffer(eb, 0, 2048, 16);
ret = verify_eb_and_memory(eb, memory, "same page non-overlapping memcpy 2");
if (ret < 0)
goto out;
memcpy(memory, memory + 2048, 2048);
memcpy_extent_buffer(eb, 0, 2048, 2048);
ret = verify_eb_and_memory(eb, memory, "same page non-overlapping memcpy 3");
if (ret < 0)
goto out;
memmove(memory + 512, memory + 256, 512);
memmove_extent_buffer(eb, 512, 256, 512);
ret = verify_eb_and_memory(eb, memory, "same page overlapping memcpy 1");
if (ret < 0)
goto out;
memmove(memory + 2048, memory + 512, 2048);
memmove_extent_buffer(eb, 2048, 512, 2048);
ret = verify_eb_and_memory(eb, memory, "same page overlapping memcpy 2");
if (ret < 0)
goto out;
memmove(memory + 512, memory + 2048, 2048);
memmove_extent_buffer(eb, 512, 2048, 2048);
ret = verify_eb_and_memory(eb, memory, "same page overlapping memcpy 3");
if (ret < 0)
goto out;
if (nodesize > PAGE_SIZE) {
memcpy(memory, memory + 4096 - 128, 256);
memcpy_extent_buffer(eb, 0, 4096 - 128, 256);
ret = verify_eb_and_memory(eb, memory, "cross page non-overlapping memcpy 1");
if (ret < 0)
goto out;
memcpy(memory + 4096 - 128, memory + 4096 + 128, 256);
memcpy_extent_buffer(eb, 4096 - 128, 4096 + 128, 256);
ret = verify_eb_and_memory(eb, memory, "cross page non-overlapping memcpy 2");
if (ret < 0)
goto out;
memmove(memory + 4096 - 128, memory + 4096 - 64, 256);
memmove_extent_buffer(eb, 4096 - 128, 4096 - 64, 256);
ret = verify_eb_and_memory(eb, memory, "cross page overlapping memcpy 1");
if (ret < 0)
goto out;
memmove(memory + 4096 - 64, memory + 4096 - 128, 256);
memmove_extent_buffer(eb, 4096 - 64, 4096 - 128, 256);
ret = verify_eb_and_memory(eb, memory, "cross page overlapping memcpy 2");
if (ret < 0)
goto out;
}
out:
free_extent_buffer(eb);
kvfree(memory);
btrfs_free_dummy_fs_info(fs_info);
return ret;
}
int btrfs_test_extent_io(u32 sectorsize, u32 nodesize)
{
int ret;
test_msg("running extent I/O tests");
ret = test_find_delalloc(sectorsize, nodesize);
if (ret)
goto out;
ret = test_find_first_clear_extent_bit();
if (ret)
goto out;
ret = test_eb_bitmaps(sectorsize, nodesize);
if (ret)
goto out;
ret = test_eb_mem_ops(sectorsize, nodesize);
out:
return ret;
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,586 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2015 Facebook. All rights reserved.
*/
#include <linux/types.h>
#include "btrfs-tests.h"
#include "../ctree.h"
#include "../disk-io.h"
#include "../free-space-tree.h"
#include "../transaction.h"
#include "../block-group.h"
#include "../accessors.h"
struct free_space_extent {
u64 start;
u64 length;
};
static int __check_free_space_extents(struct btrfs_trans_handle *trans,
struct btrfs_fs_info *fs_info,
struct btrfs_block_group *cache,
struct btrfs_path *path,
const struct free_space_extent * const extents,
unsigned int num_extents)
{
struct btrfs_free_space_info *info;
struct btrfs_key key;
int prev_bit = 0, bit;
u64 extent_start = 0, offset, end;
u32 flags, extent_count;
unsigned int i;
int ret;
info = btrfs_search_free_space_info(trans, cache, path, 0);
if (IS_ERR(info)) {
test_err("could not find free space info");
ret = PTR_ERR(info);
goto out;
}
flags = btrfs_free_space_flags(path->nodes[0], info);
extent_count = btrfs_free_space_extent_count(path->nodes[0], info);
if (extent_count != num_extents) {
test_err("extent count is wrong");
ret = -EINVAL;
goto out;
}
if (flags & BTRFS_FREE_SPACE_USING_BITMAPS) {
if (path->slots[0] != 0)
goto invalid;
end = btrfs_block_group_end(cache);
i = 0;
while (++path->slots[0] < btrfs_header_nritems(path->nodes[0])) {
btrfs_item_key_to_cpu(path->nodes[0], &key, path->slots[0]);
if (key.type != BTRFS_FREE_SPACE_BITMAP_KEY)
goto invalid;
offset = key.objectid;
while (offset < key.objectid + key.offset) {
bit = btrfs_free_space_test_bit(cache, path, offset);
if (prev_bit == 0 && bit == 1) {
extent_start = offset;
} else if (prev_bit == 1 && bit == 0) {
if (i >= num_extents ||
extent_start != extents[i].start ||
offset - extent_start != extents[i].length)
goto invalid;
i++;
}
prev_bit = bit;
offset += fs_info->sectorsize;
}
}
if (prev_bit == 1) {
if (i >= num_extents ||
extent_start != extents[i].start ||
end - extent_start != extents[i].length)
goto invalid;
i++;
}
if (i != num_extents)
goto invalid;
} else {
if (btrfs_header_nritems(path->nodes[0]) != num_extents + 1 ||
path->slots[0] != 0)
goto invalid;
for (i = 0; i < num_extents; i++) {
path->slots[0]++;
btrfs_item_key_to_cpu(path->nodes[0], &key, path->slots[0]);
if (key.type != BTRFS_FREE_SPACE_EXTENT_KEY ||
key.objectid != extents[i].start ||
key.offset != extents[i].length)
goto invalid;
}
}
ret = 0;
out:
btrfs_release_path(path);
return ret;
invalid:
test_err("free space tree is invalid");
ret = -EINVAL;
goto out;
}
static int check_free_space_extents(struct btrfs_trans_handle *trans,
struct btrfs_fs_info *fs_info,
struct btrfs_block_group *cache,
struct btrfs_path *path,
const struct free_space_extent * const extents,
unsigned int num_extents)
{
struct btrfs_free_space_info *info;
u32 flags;
int ret;
info = btrfs_search_free_space_info(trans, cache, path, 0);
if (IS_ERR(info)) {
test_err("could not find free space info");
btrfs_release_path(path);
return PTR_ERR(info);
}
flags = btrfs_free_space_flags(path->nodes[0], info);
btrfs_release_path(path);
ret = __check_free_space_extents(trans, fs_info, cache, path, extents,
num_extents);
if (ret)
return ret;
/* Flip it to the other format and check that for good measure. */
if (flags & BTRFS_FREE_SPACE_USING_BITMAPS) {
ret = btrfs_convert_free_space_to_extents(trans, cache, path);
if (ret) {
test_err("could not convert to extents");
return ret;
}
} else {
ret = btrfs_convert_free_space_to_bitmaps(trans, cache, path);
if (ret) {
test_err("could not convert to bitmaps");
return ret;
}
}
return __check_free_space_extents(trans, fs_info, cache, path, extents,
num_extents);
}
static int test_empty_block_group(struct btrfs_trans_handle *trans,
struct btrfs_fs_info *fs_info,
struct btrfs_block_group *cache,
struct btrfs_path *path,
u32 alignment)
{
const struct free_space_extent extents[] = {
{cache->start, cache->length},
};
return check_free_space_extents(trans, fs_info, cache, path,
extents, ARRAY_SIZE(extents));
}
static int test_remove_all(struct btrfs_trans_handle *trans,
struct btrfs_fs_info *fs_info,
struct btrfs_block_group *cache,
struct btrfs_path *path,
u32 alignment)
{
const struct free_space_extent extents[] = {};
int ret;
ret = __btrfs_remove_from_free_space_tree(trans, cache, path,
cache->start, cache->length);
if (ret) {
test_err("could not remove free space");
return ret;
}
return check_free_space_extents(trans, fs_info, cache, path,
extents, ARRAY_SIZE(extents));
}
static int test_remove_beginning(struct btrfs_trans_handle *trans,
struct btrfs_fs_info *fs_info,
struct btrfs_block_group *cache,
struct btrfs_path *path,
u32 alignment)
{
const struct free_space_extent extents[] = {
{cache->start + alignment, cache->length - alignment},
};
int ret;
ret = __btrfs_remove_from_free_space_tree(trans, cache, path,
cache->start, alignment);
if (ret) {
test_err("could not remove free space");
return ret;
}
return check_free_space_extents(trans, fs_info, cache, path,
extents, ARRAY_SIZE(extents));
}
static int test_remove_end(struct btrfs_trans_handle *trans,
struct btrfs_fs_info *fs_info,
struct btrfs_block_group *cache,
struct btrfs_path *path,
u32 alignment)
{
const struct free_space_extent extents[] = {
{cache->start, cache->length - alignment},
};
int ret;
ret = __btrfs_remove_from_free_space_tree(trans, cache, path,
btrfs_block_group_end(cache) - alignment,
alignment);
if (ret) {
test_err("could not remove free space");
return ret;
}
return check_free_space_extents(trans, fs_info, cache, path,
extents, ARRAY_SIZE(extents));
}
static int test_remove_middle(struct btrfs_trans_handle *trans,
struct btrfs_fs_info *fs_info,
struct btrfs_block_group *cache,
struct btrfs_path *path,
u32 alignment)
{
const struct free_space_extent extents[] = {
{cache->start, alignment},
{cache->start + 2 * alignment, cache->length - 2 * alignment},
};
int ret;
ret = __btrfs_remove_from_free_space_tree(trans, cache, path,
cache->start + alignment,
alignment);
if (ret) {
test_err("could not remove free space");
return ret;
}
return check_free_space_extents(trans, fs_info, cache, path,
extents, ARRAY_SIZE(extents));
}
static int test_merge_left(struct btrfs_trans_handle *trans,
struct btrfs_fs_info *fs_info,
struct btrfs_block_group *cache,
struct btrfs_path *path,
u32 alignment)
{
const struct free_space_extent extents[] = {
{cache->start, 2 * alignment},
};
int ret;
ret = __btrfs_remove_from_free_space_tree(trans, cache, path,
cache->start, cache->length);
if (ret) {
test_err("could not remove free space");
return ret;
}
ret = __btrfs_add_to_free_space_tree(trans, cache, path, cache->start,
alignment);
if (ret) {
test_err("could not add free space");
return ret;
}
ret = __btrfs_add_to_free_space_tree(trans, cache, path,
cache->start + alignment, alignment);
if (ret) {
test_err("could not add free space");
return ret;
}
return check_free_space_extents(trans, fs_info, cache, path,
extents, ARRAY_SIZE(extents));
}
static int test_merge_right(struct btrfs_trans_handle *trans,
struct btrfs_fs_info *fs_info,
struct btrfs_block_group *cache,
struct btrfs_path *path,
u32 alignment)
{
const struct free_space_extent extents[] = {
{cache->start + alignment, 2 * alignment},
};
int ret;
ret = __btrfs_remove_from_free_space_tree(trans, cache, path,
cache->start, cache->length);
if (ret) {
test_err("could not remove free space");
return ret;
}
ret = __btrfs_add_to_free_space_tree(trans, cache, path,
cache->start + 2 * alignment,
alignment);
if (ret) {
test_err("could not add free space");
return ret;
}
ret = __btrfs_add_to_free_space_tree(trans, cache, path,
cache->start + alignment, alignment);
if (ret) {
test_err("could not add free space");
return ret;
}
return check_free_space_extents(trans, fs_info, cache, path,
extents, ARRAY_SIZE(extents));
}
static int test_merge_both(struct btrfs_trans_handle *trans,
struct btrfs_fs_info *fs_info,
struct btrfs_block_group *cache,
struct btrfs_path *path,
u32 alignment)
{
const struct free_space_extent extents[] = {
{cache->start, 3 * alignment},
};
int ret;
ret = __btrfs_remove_from_free_space_tree(trans, cache, path,
cache->start, cache->length);
if (ret) {
test_err("could not remove free space");
return ret;
}
ret = __btrfs_add_to_free_space_tree(trans, cache, path, cache->start,
alignment);
if (ret) {
test_err("could not add free space");
return ret;
}
ret = __btrfs_add_to_free_space_tree(trans, cache, path,
cache->start + 2 * alignment, alignment);
if (ret) {
test_err("could not add free space");
return ret;
}
ret = __btrfs_add_to_free_space_tree(trans, cache, path,
cache->start + alignment, alignment);
if (ret) {
test_err("could not add free space");
return ret;
}
return check_free_space_extents(trans, fs_info, cache, path,
extents, ARRAY_SIZE(extents));
}
static int test_merge_none(struct btrfs_trans_handle *trans,
struct btrfs_fs_info *fs_info,
struct btrfs_block_group *cache,
struct btrfs_path *path,
u32 alignment)
{
const struct free_space_extent extents[] = {
{cache->start, alignment},
{cache->start + 2 * alignment, alignment},
{cache->start + 4 * alignment, alignment},
};
int ret;
ret = __btrfs_remove_from_free_space_tree(trans, cache, path,
cache->start, cache->length);
if (ret) {
test_err("could not remove free space");
return ret;
}
ret = __btrfs_add_to_free_space_tree(trans, cache, path, cache->start,
alignment);
if (ret) {
test_err("could not add free space");
return ret;
}
ret = __btrfs_add_to_free_space_tree(trans, cache, path,
cache->start + 4 * alignment, alignment);
if (ret) {
test_err("could not add free space");
return ret;
}
ret = __btrfs_add_to_free_space_tree(trans, cache, path,
cache->start + 2 * alignment, alignment);
if (ret) {
test_err("could not add free space");
return ret;
}
return check_free_space_extents(trans, fs_info, cache, path,
extents, ARRAY_SIZE(extents));
}
typedef int (*test_func_t)(struct btrfs_trans_handle *,
struct btrfs_fs_info *,
struct btrfs_block_group *,
struct btrfs_path *,
u32 alignment);
static int run_test(test_func_t test_func, int bitmaps, u32 sectorsize,
u32 nodesize, u32 alignment)
{
struct btrfs_fs_info *fs_info;
struct btrfs_root *root = NULL;
struct btrfs_block_group *cache = NULL;
struct btrfs_trans_handle trans;
struct btrfs_path *path = NULL;
int ret;
fs_info = btrfs_alloc_dummy_fs_info(nodesize, sectorsize);
if (!fs_info) {
test_std_err(TEST_ALLOC_FS_INFO);
ret = -ENOMEM;
goto out;
}
root = btrfs_alloc_dummy_root(fs_info);
if (IS_ERR(root)) {
test_std_err(TEST_ALLOC_ROOT);
ret = PTR_ERR(root);
goto out;
}
btrfs_set_super_compat_ro_flags(root->fs_info->super_copy,
BTRFS_FEATURE_COMPAT_RO_FREE_SPACE_TREE);
root->root_key.objectid = BTRFS_FREE_SPACE_TREE_OBJECTID;
root->root_key.type = BTRFS_ROOT_ITEM_KEY;
root->root_key.offset = 0;
btrfs_global_root_insert(root);
root->fs_info->tree_root = root;
root->node = alloc_test_extent_buffer(root->fs_info, nodesize);
if (IS_ERR(root->node)) {
test_std_err(TEST_ALLOC_EXTENT_BUFFER);
ret = PTR_ERR(root->node);
goto out;
}
btrfs_set_header_level(root->node, 0);
btrfs_set_header_nritems(root->node, 0);
root->alloc_bytenr += 2 * nodesize;
cache = btrfs_alloc_dummy_block_group(fs_info, 8 * alignment);
if (!cache) {
test_std_err(TEST_ALLOC_BLOCK_GROUP);
ret = -ENOMEM;
goto out;
}
cache->bitmap_low_thresh = 0;
cache->bitmap_high_thresh = (u32)-1;
set_bit(BLOCK_GROUP_FLAG_NEEDS_FREE_SPACE, &cache->runtime_flags);
cache->fs_info = root->fs_info;
btrfs_init_dummy_trans(&trans, root->fs_info);
path = btrfs_alloc_path();
if (!path) {
test_std_err(TEST_ALLOC_ROOT);
ret = -ENOMEM;
goto out;
}
ret = btrfs_add_block_group_free_space(&trans, cache);
if (ret) {
test_err("could not add block group free space");
goto out;
}
if (bitmaps) {
ret = btrfs_convert_free_space_to_bitmaps(&trans, cache, path);
if (ret) {
test_err("could not convert block group to bitmaps");
goto out;
}
}
ret = test_func(&trans, root->fs_info, cache, path, alignment);
if (ret)
goto out;
ret = btrfs_remove_block_group_free_space(&trans, cache);
if (ret) {
test_err("could not remove block group free space");
goto out;
}
if (btrfs_header_nritems(root->node) != 0) {
test_err("free space tree has leftover items");
ret = -EINVAL;
goto out;
}
ret = 0;
out:
btrfs_free_path(path);
btrfs_free_dummy_block_group(cache);
btrfs_free_dummy_root(root);
btrfs_free_dummy_fs_info(fs_info);
return ret;
}
static int run_test_both_formats(test_func_t test_func, u32 sectorsize,
u32 nodesize, u32 alignment)
{
int test_ret = 0;
int ret;
ret = run_test(test_func, 0, sectorsize, nodesize, alignment);
if (ret) {
test_err(
"%ps failed with extents, sectorsize=%u, nodesize=%u, alignment=%u",
test_func, sectorsize, nodesize, alignment);
test_ret = ret;
}
ret = run_test(test_func, 1, sectorsize, nodesize, alignment);
if (ret) {
test_err(
"%ps failed with bitmaps, sectorsize=%u, nodesize=%u, alignment=%u",
test_func, sectorsize, nodesize, alignment);
test_ret = ret;
}
return test_ret;
}
int btrfs_test_free_space_tree(u32 sectorsize, u32 nodesize)
{
test_func_t tests[] = {
test_empty_block_group,
test_remove_all,
test_remove_beginning,
test_remove_end,
test_remove_middle,
test_merge_left,
test_merge_right,
test_merge_both,
test_merge_none,
};
u32 bitmap_alignment;
int test_ret = 0;
int i;
/*
* Align some operations to a page to flush out bugs in the extent
* buffer bitmap handling of highmem.
*/
bitmap_alignment = BTRFS_FREE_SPACE_BITMAP_BITS * PAGE_SIZE;
test_msg("running free space tree tests");
for (i = 0; i < ARRAY_SIZE(tests); i++) {
int ret;
ret = run_test_both_formats(tests[i], sectorsize, nodesize,
sectorsize);
if (ret)
test_ret = ret;
ret = run_test_both_formats(tests[i], sectorsize, nodesize,
bitmap_alignment);
if (ret)
test_ret = ret;
}
return test_ret;
}
File diff suppressed because it is too large Load Diff
+550
View File
@@ -0,0 +1,550 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2013 Facebook. All rights reserved.
*/
#include <linux/types.h>
#include "btrfs-tests.h"
#include "../ctree.h"
#include "../transaction.h"
#include "../disk-io.h"
#include "../qgroup.h"
#include "../backref.h"
#include "../fs.h"
#include "../accessors.h"
static int insert_normal_tree_ref(struct btrfs_root *root, u64 bytenr,
u64 num_bytes, u64 parent, u64 root_objectid)
{
struct btrfs_trans_handle trans;
struct btrfs_extent_item *item;
struct btrfs_extent_inline_ref *iref;
struct btrfs_tree_block_info *block_info;
BTRFS_PATH_AUTO_FREE(path);
struct extent_buffer *leaf;
struct btrfs_key ins;
u32 size = sizeof(*item) + sizeof(*iref) + sizeof(*block_info);
int ret;
btrfs_init_dummy_trans(&trans, NULL);
ins.objectid = bytenr;
ins.type = BTRFS_EXTENT_ITEM_KEY;
ins.offset = num_bytes;
path = btrfs_alloc_path();
if (!path) {
test_std_err(TEST_ALLOC_ROOT);
return -ENOMEM;
}
ret = btrfs_insert_empty_item(&trans, root, path, &ins, size);
if (ret) {
test_err("couldn't insert ref %d", ret);
return ret;
}
leaf = path->nodes[0];
item = btrfs_item_ptr(leaf, path->slots[0], struct btrfs_extent_item);
btrfs_set_extent_refs(leaf, item, 1);
btrfs_set_extent_generation(leaf, item, 1);
btrfs_set_extent_flags(leaf, item, BTRFS_EXTENT_FLAG_TREE_BLOCK);
block_info = (struct btrfs_tree_block_info *)(item + 1);
btrfs_set_tree_block_level(leaf, block_info, 0);
iref = (struct btrfs_extent_inline_ref *)(block_info + 1);
if (parent > 0) {
btrfs_set_extent_inline_ref_type(leaf, iref,
BTRFS_SHARED_BLOCK_REF_KEY);
btrfs_set_extent_inline_ref_offset(leaf, iref, parent);
} else {
btrfs_set_extent_inline_ref_type(leaf, iref, BTRFS_TREE_BLOCK_REF_KEY);
btrfs_set_extent_inline_ref_offset(leaf, iref, root_objectid);
}
return 0;
}
static int add_tree_ref(struct btrfs_root *root, u64 bytenr, u64 num_bytes,
u64 parent, u64 root_objectid)
{
struct btrfs_trans_handle trans;
struct btrfs_extent_item *item;
BTRFS_PATH_AUTO_FREE(path);
struct btrfs_key key;
u64 refs;
int ret;
btrfs_init_dummy_trans(&trans, NULL);
key.objectid = bytenr;
key.type = BTRFS_EXTENT_ITEM_KEY;
key.offset = num_bytes;
path = btrfs_alloc_path();
if (!path) {
test_std_err(TEST_ALLOC_ROOT);
return -ENOMEM;
}
ret = btrfs_search_slot(&trans, root, &key, path, 0, 1);
if (ret) {
test_err("couldn't find extent ref");
return ret;
}
item = btrfs_item_ptr(path->nodes[0], path->slots[0],
struct btrfs_extent_item);
refs = btrfs_extent_refs(path->nodes[0], item);
btrfs_set_extent_refs(path->nodes[0], item, refs + 1);
btrfs_release_path(path);
key.objectid = bytenr;
if (parent) {
key.type = BTRFS_SHARED_BLOCK_REF_KEY;
key.offset = parent;
} else {
key.type = BTRFS_TREE_BLOCK_REF_KEY;
key.offset = root_objectid;
}
ret = btrfs_insert_empty_item(&trans, root, path, &key, 0);
if (ret)
test_err("failed to insert backref");
return ret;
}
static int remove_extent_item(struct btrfs_root *root, u64 bytenr,
u64 num_bytes)
{
struct btrfs_trans_handle trans;
struct btrfs_key key;
BTRFS_PATH_AUTO_FREE(path);
int ret;
btrfs_init_dummy_trans(&trans, NULL);
key.objectid = bytenr;
key.type = BTRFS_EXTENT_ITEM_KEY;
key.offset = num_bytes;
path = btrfs_alloc_path();
if (!path) {
test_std_err(TEST_ALLOC_ROOT);
return -ENOMEM;
}
ret = btrfs_search_slot(&trans, root, &key, path, -1, 1);
if (ret) {
test_err("didn't find our key %d", ret);
return ret;
}
btrfs_del_item(&trans, root, path);
return 0;
}
static int remove_extent_ref(struct btrfs_root *root, u64 bytenr,
u64 num_bytes, u64 parent, u64 root_objectid)
{
struct btrfs_trans_handle trans;
struct btrfs_extent_item *item;
BTRFS_PATH_AUTO_FREE(path);
struct btrfs_key key;
u64 refs;
int ret;
btrfs_init_dummy_trans(&trans, NULL);
key.objectid = bytenr;
key.type = BTRFS_EXTENT_ITEM_KEY;
key.offset = num_bytes;
path = btrfs_alloc_path();
if (!path) {
test_std_err(TEST_ALLOC_ROOT);
return -ENOMEM;
}
ret = btrfs_search_slot(&trans, root, &key, path, 0, 1);
if (ret) {
test_err("couldn't find extent ref");
return ret;
}
item = btrfs_item_ptr(path->nodes[0], path->slots[0],
struct btrfs_extent_item);
refs = btrfs_extent_refs(path->nodes[0], item);
btrfs_set_extent_refs(path->nodes[0], item, refs - 1);
btrfs_release_path(path);
key.objectid = bytenr;
if (parent) {
key.type = BTRFS_SHARED_BLOCK_REF_KEY;
key.offset = parent;
} else {
key.type = BTRFS_TREE_BLOCK_REF_KEY;
key.offset = root_objectid;
}
ret = btrfs_search_slot(&trans, root, &key, path, -1, 1);
if (ret) {
test_err("couldn't find backref %d", ret);
return ret;
}
btrfs_del_item(&trans, root, path);
return ret;
}
static int test_no_shared_qgroup(struct btrfs_root *root,
u32 sectorsize, u32 nodesize)
{
struct btrfs_backref_walk_ctx ctx = { 0 };
struct btrfs_trans_handle trans;
struct btrfs_fs_info *fs_info = root->fs_info;
struct ulist *old_roots = NULL;
struct ulist *new_roots = NULL;
int ret;
btrfs_init_dummy_trans(&trans, fs_info);
test_msg("running qgroup add/remove tests");
ret = btrfs_create_qgroup(&trans, BTRFS_FS_TREE_OBJECTID);
if (ret) {
test_err("couldn't create a qgroup %d", ret);
return ret;
}
ctx.bytenr = nodesize;
ctx.trans = &trans;
ctx.fs_info = fs_info;
/*
* Since the test trans doesn't have the complicated delayed refs,
* we can only call btrfs_qgroup_account_extent() directly to test
* quota.
*/
ret = btrfs_find_all_roots(&ctx, false);
if (ret) {
test_err("couldn't find old roots: %d", ret);
return ret;
}
old_roots = ctx.roots;
ctx.roots = NULL;
ret = insert_normal_tree_ref(root, nodesize, nodesize, 0,
BTRFS_FS_TREE_OBJECTID);
if (ret) {
ulist_free(old_roots);
return ret;
}
ret = btrfs_find_all_roots(&ctx, false);
if (ret) {
ulist_free(old_roots);
test_err("couldn't find old roots: %d", ret);
return ret;
}
new_roots = ctx.roots;
ctx.roots = NULL;
ret = btrfs_qgroup_account_extent(&trans, nodesize, nodesize, old_roots,
new_roots);
if (ret) {
test_err("couldn't account space for a qgroup %d", ret);
return ret;
}
/* btrfs_qgroup_account_extent() always frees the ulists passed to it. */
old_roots = NULL;
new_roots = NULL;
if (btrfs_verify_qgroup_counts(fs_info, BTRFS_FS_TREE_OBJECTID,
nodesize, nodesize)) {
test_err("qgroup counts didn't match expected values");
return -EINVAL;
}
ret = btrfs_find_all_roots(&ctx, false);
if (ret) {
test_err("couldn't find old roots: %d", ret);
return ret;
}
old_roots = ctx.roots;
ctx.roots = NULL;
ret = remove_extent_item(root, nodesize, nodesize);
if (ret) {
ulist_free(old_roots);
return -EINVAL;
}
ret = btrfs_find_all_roots(&ctx, false);
if (ret) {
ulist_free(old_roots);
test_err("couldn't find old roots: %d", ret);
return ret;
}
new_roots = ctx.roots;
ctx.roots = NULL;
ret = btrfs_qgroup_account_extent(&trans, nodesize, nodesize, old_roots,
new_roots);
if (ret) {
test_err("couldn't account space for a qgroup %d", ret);
return -EINVAL;
}
if (btrfs_verify_qgroup_counts(fs_info, BTRFS_FS_TREE_OBJECTID, 0, 0)) {
test_err("qgroup counts didn't match expected values");
return -EINVAL;
}
return 0;
}
/*
* Add a ref for two different roots to make sure the shared value comes out
* right, also remove one of the roots and make sure the exclusive count is
* adjusted properly.
*/
static int test_multiple_refs(struct btrfs_root *root,
u32 sectorsize, u32 nodesize)
{
struct btrfs_backref_walk_ctx ctx = { 0 };
struct btrfs_trans_handle trans;
struct btrfs_fs_info *fs_info = root->fs_info;
struct ulist *old_roots = NULL;
struct ulist *new_roots = NULL;
int ret;
btrfs_init_dummy_trans(&trans, fs_info);
test_msg("running qgroup multiple refs test");
/*
* We have BTRFS_FS_TREE_OBJECTID created already from the
* previous test.
*/
ret = btrfs_create_qgroup(&trans, BTRFS_FIRST_FREE_OBJECTID);
if (ret) {
test_err("couldn't create a qgroup %d", ret);
return ret;
}
ctx.bytenr = nodesize;
ctx.trans = &trans;
ctx.fs_info = fs_info;
ret = btrfs_find_all_roots(&ctx, false);
if (ret) {
test_err("couldn't find old roots: %d", ret);
return ret;
}
old_roots = ctx.roots;
ctx.roots = NULL;
ret = insert_normal_tree_ref(root, nodesize, nodesize, 0,
BTRFS_FS_TREE_OBJECTID);
if (ret) {
ulist_free(old_roots);
return ret;
}
ret = btrfs_find_all_roots(&ctx, false);
if (ret) {
ulist_free(old_roots);
test_err("couldn't find old roots: %d", ret);
return ret;
}
new_roots = ctx.roots;
ctx.roots = NULL;
ret = btrfs_qgroup_account_extent(&trans, nodesize, nodesize, old_roots,
new_roots);
if (ret) {
test_err("couldn't account space for a qgroup %d", ret);
return ret;
}
if (btrfs_verify_qgroup_counts(fs_info, BTRFS_FS_TREE_OBJECTID,
nodesize, nodesize)) {
test_err("qgroup counts didn't match expected values");
return -EINVAL;
}
ret = btrfs_find_all_roots(&ctx, false);
if (ret) {
test_err("couldn't find old roots: %d", ret);
return ret;
}
old_roots = ctx.roots;
ctx.roots = NULL;
ret = add_tree_ref(root, nodesize, nodesize, 0,
BTRFS_FIRST_FREE_OBJECTID);
if (ret) {
ulist_free(old_roots);
return ret;
}
ret = btrfs_find_all_roots(&ctx, false);
if (ret) {
ulist_free(old_roots);
test_err("couldn't find old roots: %d", ret);
return ret;
}
new_roots = ctx.roots;
ctx.roots = NULL;
ret = btrfs_qgroup_account_extent(&trans, nodesize, nodesize, old_roots,
new_roots);
if (ret) {
test_err("couldn't account space for a qgroup %d", ret);
return ret;
}
if (btrfs_verify_qgroup_counts(fs_info, BTRFS_FS_TREE_OBJECTID,
nodesize, 0)) {
test_err("qgroup counts didn't match expected values");
return -EINVAL;
}
if (btrfs_verify_qgroup_counts(fs_info, BTRFS_FIRST_FREE_OBJECTID,
nodesize, 0)) {
test_err("qgroup counts didn't match expected values");
return -EINVAL;
}
ret = btrfs_find_all_roots(&ctx, false);
if (ret) {
test_err("couldn't find old roots: %d", ret);
return ret;
}
old_roots = ctx.roots;
ctx.roots = NULL;
ret = remove_extent_ref(root, nodesize, nodesize, 0,
BTRFS_FIRST_FREE_OBJECTID);
if (ret) {
ulist_free(old_roots);
return ret;
}
ret = btrfs_find_all_roots(&ctx, false);
if (ret) {
ulist_free(old_roots);
test_err("couldn't find old roots: %d", ret);
return ret;
}
new_roots = ctx.roots;
ctx.roots = NULL;
ret = btrfs_qgroup_account_extent(&trans, nodesize, nodesize, old_roots,
new_roots);
if (ret) {
test_err("couldn't account space for a qgroup %d", ret);
return ret;
}
if (btrfs_verify_qgroup_counts(fs_info, BTRFS_FIRST_FREE_OBJECTID,
0, 0)) {
test_err("qgroup counts didn't match expected values");
return -EINVAL;
}
if (btrfs_verify_qgroup_counts(fs_info, BTRFS_FS_TREE_OBJECTID,
nodesize, nodesize)) {
test_err("qgroup counts didn't match expected values");
return -EINVAL;
}
return 0;
}
int btrfs_test_qgroups(u32 sectorsize, u32 nodesize)
{
struct btrfs_fs_info *fs_info = NULL;
struct btrfs_root *root;
struct btrfs_root *tmp_root;
int ret = 0;
fs_info = btrfs_alloc_dummy_fs_info(nodesize, sectorsize);
if (!fs_info) {
test_std_err(TEST_ALLOC_FS_INFO);
return -ENOMEM;
}
root = btrfs_alloc_dummy_root(fs_info);
if (IS_ERR(root)) {
test_std_err(TEST_ALLOC_ROOT);
ret = PTR_ERR(root);
goto out;
}
/* We are using this root as our extent root */
root->root_key.objectid = BTRFS_EXTENT_TREE_OBJECTID;
root->root_key.type = BTRFS_ROOT_ITEM_KEY;
root->root_key.offset = 0;
btrfs_global_root_insert(root);
/*
* Some of the paths we test assume we have a filled out fs_info, so we
* just need to add the root in there so we don't panic.
*/
root->fs_info->tree_root = root;
root->fs_info->quota_root = root;
set_bit(BTRFS_FS_QUOTA_ENABLED, &fs_info->flags);
/*
* Can't use bytenr 0, some things freak out
* *cough*backref walking code*cough*
*/
root->node = alloc_test_extent_buffer(root->fs_info, nodesize);
if (IS_ERR(root->node)) {
test_err("couldn't allocate dummy buffer");
ret = PTR_ERR(root->node);
goto out;
}
btrfs_set_header_level(root->node, 0);
btrfs_set_header_nritems(root->node, 0);
root->alloc_bytenr += 2 * nodesize;
tmp_root = btrfs_alloc_dummy_root(fs_info);
if (IS_ERR(tmp_root)) {
test_std_err(TEST_ALLOC_ROOT);
ret = PTR_ERR(tmp_root);
goto out;
}
tmp_root->root_key.objectid = BTRFS_FS_TREE_OBJECTID;
root->fs_info->fs_root = tmp_root;
ret = btrfs_insert_fs_root(root->fs_info, tmp_root);
btrfs_put_root(tmp_root);
if (ret) {
test_err("couldn't insert fs root %d", ret);
goto out;
}
tmp_root = btrfs_alloc_dummy_root(fs_info);
if (IS_ERR(tmp_root)) {
test_std_err(TEST_ALLOC_ROOT);
ret = PTR_ERR(tmp_root);
goto out;
}
tmp_root->root_key.objectid = BTRFS_FIRST_FREE_OBJECTID;
ret = btrfs_insert_fs_root(root->fs_info, tmp_root);
btrfs_put_root(tmp_root);
if (ret) {
test_err("couldn't insert subvolume root %d", ret);
goto out;
}
test_msg("running qgroup tests");
ret = test_no_shared_qgroup(root, sectorsize, nodesize);
if (ret)
goto out;
ret = test_multiple_refs(root, sectorsize, nodesize);
out:
btrfs_free_dummy_root(root);
btrfs_free_dummy_fs_info(fs_info);
return ret;
}
File diff suppressed because it is too large Load Diff
+675
View File
@@ -0,0 +1,675 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2026 Western Digital. All rights reserved.
*/
#include <linux/cleanup.h>
#include <linux/sizes.h>
#include "btrfs-tests.h"
#include "../space-info.h"
#include "../volumes.h"
#include "../zoned.h"
#define WP_MISSING_DEV ((u64)-1)
#define WP_CONVENTIONAL ((u64)-2)
#define ZONE_SIZE SZ_256M
#define HALF_STRIPE_LEN (BTRFS_STRIPE_LEN >> 1)
struct load_zone_info_test_vector {
u64 raid_type;
u64 num_stripes;
u64 alloc_offsets[8];
u64 last_alloc;
u64 bg_length;
bool degraded;
int expected_result;
u64 expected_alloc_offset;
const char *description;
};
struct zone_info {
u64 physical;
u64 capacity;
u64 alloc_offset;
};
static int test_load_zone_info(struct btrfs_fs_info *fs_info,
const struct load_zone_info_test_vector *test)
{
struct btrfs_block_group *bg __free(btrfs_free_dummy_block_group) = NULL;
struct btrfs_chunk_map *map __free(btrfs_free_chunk_map) = NULL;
struct zone_info AUTO_KFREE(zone_info);
unsigned long AUTO_KFREE(active);
int ret;
bg = btrfs_alloc_dummy_block_group(fs_info, test->bg_length);
if (!bg) {
test_std_err(TEST_ALLOC_BLOCK_GROUP);
return -ENOMEM;
}
map = btrfs_alloc_chunk_map(test->num_stripes, GFP_KERNEL);
if (!map) {
test_std_err(TEST_ALLOC_EXTENT_MAP);
return -ENOMEM;
}
zone_info = kzalloc_objs(*zone_info, test->num_stripes, GFP_KERNEL);
if (!zone_info) {
test_err("cannot allocate zone info");
return -ENOMEM;
}
active = bitmap_zalloc(test->num_stripes, GFP_KERNEL);
if (!zone_info) {
test_err("cannot allocate active bitmap");
return -ENOMEM;
}
map->type = test->raid_type;
map->num_stripes = test->num_stripes;
if (test->raid_type == BTRFS_BLOCK_GROUP_RAID10)
map->sub_stripes = 2;
for (int i = 0; i < test->num_stripes; i++) {
zone_info[i].physical = 0;
zone_info[i].alloc_offset = test->alloc_offsets[i];
zone_info[i].capacity = ZONE_SIZE;
if (zone_info[i].alloc_offset && zone_info[i].alloc_offset < ZONE_SIZE)
__set_bit(i, active);
}
if (test->degraded)
btrfs_set_opt(fs_info->mount_opt, DEGRADED);
else
btrfs_clear_opt(fs_info->mount_opt, DEGRADED);
ret = btrfs_load_block_group_by_raid_type(bg, map, zone_info, active,
test->last_alloc);
if (ret != test->expected_result) {
test_err("unexpected return value: ret %d expected %d", ret,
test->expected_result);
return -EINVAL;
}
if (!ret && bg->alloc_offset != test->expected_alloc_offset) {
test_err("unexpected alloc_offset: alloc_offset %llu expected %llu",
bg->alloc_offset, test->expected_alloc_offset);
return -EINVAL;
}
return 0;
}
static const struct load_zone_info_test_vector load_zone_info_tests[] = {
/* SINGLE */
{
.description = "SINGLE: load write pointer from sequential zone",
.raid_type = 0,
.num_stripes = 1,
.alloc_offsets = {
SZ_1M,
},
.expected_alloc_offset = SZ_1M,
},
/*
* SINGLE block group on a conventional zone sets last_alloc outside of
* btrfs_load_block_group_*(). Do not test that case.
*/
/* DUP */
/* Normal case */
{
.description = "DUP: having matching write pointers",
.raid_type = BTRFS_BLOCK_GROUP_DUP,
.num_stripes = 2,
.alloc_offsets = {
SZ_1M, SZ_1M,
},
.expected_alloc_offset = SZ_1M,
},
/*
* One sequential zone and one conventional zone, having matching
* last_alloc.
*/
{
.description = "DUP: seq zone and conv zone, matching last_alloc",
.raid_type = BTRFS_BLOCK_GROUP_DUP,
.num_stripes = 2,
.alloc_offsets = {
SZ_1M, WP_CONVENTIONAL,
},
.last_alloc = SZ_1M,
.expected_alloc_offset = SZ_1M,
},
/*
* One sequential and one conventional zone, but having smaller
* last_alloc than write pointer.
*/
{
.description = "DUP: seq zone and conv zone, smaller last_alloc",
.raid_type = BTRFS_BLOCK_GROUP_DUP,
.num_stripes = 2,
.alloc_offsets = {
SZ_1M, WP_CONVENTIONAL,
},
.last_alloc = 0,
.expected_alloc_offset = SZ_1M,
},
/* Error case: having different write pointers. */
{
.description = "DUP: fail: different write pointers",
.raid_type = BTRFS_BLOCK_GROUP_DUP,
.num_stripes = 2,
.alloc_offsets = {
SZ_1M, SZ_2M,
},
.expected_result = -EIO,
},
/* Error case: partial missing device should not happen on DUP. */
{
.description = "DUP: fail: missing device",
.raid_type = BTRFS_BLOCK_GROUP_DUP,
.num_stripes = 2,
.alloc_offsets = {
SZ_1M, WP_MISSING_DEV,
},
.expected_result = -EIO,
},
/*
* Error case: one sequential and one conventional zone, but having larger
* last_alloc than write pointer.
*/
{
.description = "DUP: fail: seq zone and conv zone, larger last_alloc",
.raid_type = BTRFS_BLOCK_GROUP_DUP,
.num_stripes = 2,
.alloc_offsets = {
SZ_1M, WP_CONVENTIONAL,
},
.last_alloc = SZ_2M,
.expected_result = -EIO,
},
/* RAID1 */
/* Normal case */
{
.description = "RAID1: having matching write pointers",
.raid_type = BTRFS_BLOCK_GROUP_RAID1,
.num_stripes = 2,
.alloc_offsets = {
SZ_1M, SZ_1M,
},
.expected_alloc_offset = SZ_1M,
},
/*
* One sequential zone and one conventional zone, having matching
* last_alloc.
*/
{
.description = "RAID1: seq zone and conv zone, matching last_alloc",
.raid_type = BTRFS_BLOCK_GROUP_RAID1,
.num_stripes = 2,
.alloc_offsets = {
SZ_1M, WP_CONVENTIONAL,
},
.last_alloc = SZ_1M,
.expected_alloc_offset = SZ_1M,
},
/*
* One sequential and one conventional zone, but having smaller
* last_alloc than write pointer.
*/
{
.description = "RAID1: seq zone and conv zone, smaller last_alloc",
.raid_type = BTRFS_BLOCK_GROUP_RAID1,
.num_stripes = 2,
.alloc_offsets = {
SZ_1M, WP_CONVENTIONAL,
},
.last_alloc = 0,
.expected_alloc_offset = SZ_1M,
},
/* Partial missing device should be recovered on DEGRADED mount */
{
.description = "RAID1: fail: missing device on DEGRADED",
.raid_type = BTRFS_BLOCK_GROUP_RAID1,
.num_stripes = 2,
.alloc_offsets = {
SZ_1M, WP_MISSING_DEV,
},
.degraded = true,
.expected_alloc_offset = SZ_1M,
},
/* Error case: having different write pointers. */
{
.description = "RAID1: fail: different write pointers",
.raid_type = BTRFS_BLOCK_GROUP_RAID1,
.num_stripes = 2,
.alloc_offsets = {
SZ_1M, SZ_2M,
},
.expected_result = -EIO,
},
/*
* Partial missing device is not allowed on non-DEGRADED mount never happen
* as it is rejected beforehand.
*/
/*
* Error case: one sequential and one conventional zone, but having larger
* last_alloc than write pointer.
*/
{
.description = "RAID1: fail: seq zone and conv zone, larger last_alloc",
.raid_type = BTRFS_BLOCK_GROUP_RAID1,
.num_stripes = 2,
.alloc_offsets = {
SZ_1M, WP_CONVENTIONAL,
},
.last_alloc = SZ_2M,
.expected_result = -EIO,
},
/* RAID0 */
/* Normal case */
{
.description = "RAID0: initial partial write",
.raid_type = BTRFS_BLOCK_GROUP_RAID0,
.num_stripes = 4,
.alloc_offsets = {
HALF_STRIPE_LEN, 0, 0, 0,
},
.expected_alloc_offset = HALF_STRIPE_LEN,
},
{
.description = "RAID0: while in second stripe",
.raid_type = BTRFS_BLOCK_GROUP_RAID0,
.num_stripes = 4,
.alloc_offsets = {
BTRFS_STRIPE_LEN * 2, BTRFS_STRIPE_LEN + HALF_STRIPE_LEN,
BTRFS_STRIPE_LEN, BTRFS_STRIPE_LEN,
},
.expected_alloc_offset = BTRFS_STRIPE_LEN * 5 + HALF_STRIPE_LEN,
},
{
.description = "RAID0: one stripe advanced",
.raid_type = BTRFS_BLOCK_GROUP_RAID0,
.num_stripes = 2,
.alloc_offsets = {
SZ_1M + BTRFS_STRIPE_LEN, SZ_1M,
},
.expected_alloc_offset = SZ_2M + BTRFS_STRIPE_LEN,
},
/* Error case: having different write pointers. */
{
.description = "RAID0: fail: disordered stripes",
.raid_type = BTRFS_BLOCK_GROUP_RAID0,
.num_stripes = 4,
.alloc_offsets = {
BTRFS_STRIPE_LEN, BTRFS_STRIPE_LEN * 2,
BTRFS_STRIPE_LEN, BTRFS_STRIPE_LEN,
},
.expected_result = -EIO,
},
{
.description = "RAID0: fail: far distance",
.raid_type = BTRFS_BLOCK_GROUP_RAID0,
.num_stripes = 4,
.alloc_offsets = {
BTRFS_STRIPE_LEN * 3, BTRFS_STRIPE_LEN,
BTRFS_STRIPE_LEN, BTRFS_STRIPE_LEN,
},
.expected_result = -EIO,
},
{
.description = "RAID0: fail: too many partial write",
.raid_type = BTRFS_BLOCK_GROUP_RAID0,
.num_stripes = 4,
.alloc_offsets = {
HALF_STRIPE_LEN, HALF_STRIPE_LEN, 0, 0,
},
.expected_result = -EIO,
},
/*
* Error case: Partial missing device is not allowed even on non-DEGRADED
* mount.
*/
{
.description = "RAID0: fail: missing device on DEGRADED",
.raid_type = BTRFS_BLOCK_GROUP_RAID0,
.num_stripes = 2,
.alloc_offsets = {
SZ_1M, WP_MISSING_DEV,
},
.degraded = true,
.expected_result = -EIO,
},
/*
* One sequential zone and one conventional zone, having matching
* last_alloc.
*/
{
.description = "RAID0: seq zone and conv zone, partially written stripe",
.raid_type = BTRFS_BLOCK_GROUP_RAID0,
.num_stripes = 2,
.alloc_offsets = {
SZ_1M, WP_CONVENTIONAL,
},
.last_alloc = SZ_2M - SZ_4K,
.expected_alloc_offset = SZ_2M - SZ_4K,
},
{
.description = "RAID0: conv zone and seq zone, partially written stripe",
.raid_type = BTRFS_BLOCK_GROUP_RAID0,
.num_stripes = 2,
.alloc_offsets = {
WP_CONVENTIONAL, SZ_1M,
},
.last_alloc = SZ_2M + SZ_4K,
.expected_alloc_offset = SZ_2M + SZ_4K,
},
/*
* Error case: one sequential and one conventional zone, but having larger
* last_alloc than write pointer.
*/
{
.description = "RAID0: fail: seq zone and conv zone, larger last_alloc",
.raid_type = BTRFS_BLOCK_GROUP_RAID0,
.num_stripes = 2,
.alloc_offsets = {
SZ_1M, WP_CONVENTIONAL,
},
.last_alloc = SZ_2M + BTRFS_STRIPE_LEN * 2,
.expected_result = -EIO,
},
/* RAID0, 4 stripes with seq zones and conv zones. */
{
.description = "RAID0: stripes [2, 2, ?, ?] last_alloc = 6",
.raid_type = BTRFS_BLOCK_GROUP_RAID0,
.num_stripes = 4,
.alloc_offsets = {
BTRFS_STRIPE_LEN * 2, BTRFS_STRIPE_LEN * 2,
WP_CONVENTIONAL, WP_CONVENTIONAL,
},
.last_alloc = BTRFS_STRIPE_LEN * 6,
.expected_alloc_offset = BTRFS_STRIPE_LEN * 6,
},
{
.description = "RAID0: stripes [2, 2, ?, ?] last_alloc = 7.5",
.raid_type = BTRFS_BLOCK_GROUP_RAID0,
.num_stripes = 4,
.alloc_offsets = {
BTRFS_STRIPE_LEN * 2, BTRFS_STRIPE_LEN * 2,
WP_CONVENTIONAL, WP_CONVENTIONAL,
},
.last_alloc = BTRFS_STRIPE_LEN * 7 + HALF_STRIPE_LEN,
.expected_alloc_offset = BTRFS_STRIPE_LEN * 7 + HALF_STRIPE_LEN,
},
{
.description = "RAID0: stripes [3, ?, ?, ?] last_alloc = 1",
.raid_type = BTRFS_BLOCK_GROUP_RAID0,
.num_stripes = 4,
.alloc_offsets = {
BTRFS_STRIPE_LEN * 3, WP_CONVENTIONAL,
WP_CONVENTIONAL, WP_CONVENTIONAL,
},
.last_alloc = BTRFS_STRIPE_LEN,
.expected_alloc_offset = BTRFS_STRIPE_LEN * 9,
},
{
.description = "RAID0: stripes [2, ?, 1, ?] last_alloc = 5",
.raid_type = BTRFS_BLOCK_GROUP_RAID0,
.num_stripes = 4,
.alloc_offsets = {
BTRFS_STRIPE_LEN * 2, WP_CONVENTIONAL,
BTRFS_STRIPE_LEN, WP_CONVENTIONAL,
},
.last_alloc = BTRFS_STRIPE_LEN * 5,
.expected_alloc_offset = BTRFS_STRIPE_LEN * 5,
},
{
.description = "RAID0: fail: stripes [2, ?, 1, ?] last_alloc = 7",
.raid_type = BTRFS_BLOCK_GROUP_RAID0,
.num_stripes = 4,
.alloc_offsets = {
BTRFS_STRIPE_LEN * 2, WP_CONVENTIONAL,
BTRFS_STRIPE_LEN, WP_CONVENTIONAL,
},
.last_alloc = BTRFS_STRIPE_LEN * 7,
.expected_result = -EIO,
},
/* RAID10 */
/* Normal case */
{
.description = "RAID10: initial partial write",
.raid_type = BTRFS_BLOCK_GROUP_RAID10,
.num_stripes = 4,
.alloc_offsets = {
HALF_STRIPE_LEN, HALF_STRIPE_LEN, 0, 0,
},
.expected_alloc_offset = HALF_STRIPE_LEN,
},
{
.description = "RAID10: while in second stripe",
.raid_type = BTRFS_BLOCK_GROUP_RAID10,
.num_stripes = 8,
.alloc_offsets = {
BTRFS_STRIPE_LEN * 2, BTRFS_STRIPE_LEN * 2,
BTRFS_STRIPE_LEN + HALF_STRIPE_LEN,
BTRFS_STRIPE_LEN + HALF_STRIPE_LEN,
BTRFS_STRIPE_LEN, BTRFS_STRIPE_LEN,
BTRFS_STRIPE_LEN, BTRFS_STRIPE_LEN,
},
.expected_alloc_offset = BTRFS_STRIPE_LEN * 5 + HALF_STRIPE_LEN,
},
{
.description = "RAID10: one stripe advanced",
.raid_type = BTRFS_BLOCK_GROUP_RAID10,
.num_stripes = 4,
.alloc_offsets = {
SZ_1M + BTRFS_STRIPE_LEN, SZ_1M + BTRFS_STRIPE_LEN,
SZ_1M, SZ_1M,
},
.expected_alloc_offset = SZ_2M + BTRFS_STRIPE_LEN,
},
{
.description = "RAID10: one stripe advanced, with conventional zone",
.raid_type = BTRFS_BLOCK_GROUP_RAID10,
.num_stripes = 4,
.alloc_offsets = {
SZ_1M + BTRFS_STRIPE_LEN, WP_CONVENTIONAL,
WP_CONVENTIONAL, SZ_1M,
},
.expected_alloc_offset = SZ_2M + BTRFS_STRIPE_LEN,
},
/* Error case: having different write pointers. */
{
.description = "RAID10: fail: disordered stripes",
.raid_type = BTRFS_BLOCK_GROUP_RAID10,
.num_stripes = 8,
.alloc_offsets = {
BTRFS_STRIPE_LEN, BTRFS_STRIPE_LEN,
BTRFS_STRIPE_LEN * 2, BTRFS_STRIPE_LEN * 2,
BTRFS_STRIPE_LEN, BTRFS_STRIPE_LEN,
BTRFS_STRIPE_LEN, BTRFS_STRIPE_LEN,
},
.expected_result = -EIO,
},
{
.description = "RAID10: fail: far distance",
.raid_type = BTRFS_BLOCK_GROUP_RAID10,
.num_stripes = 8,
.alloc_offsets = {
BTRFS_STRIPE_LEN * 3, BTRFS_STRIPE_LEN * 3,
BTRFS_STRIPE_LEN, BTRFS_STRIPE_LEN,
BTRFS_STRIPE_LEN, BTRFS_STRIPE_LEN,
BTRFS_STRIPE_LEN, BTRFS_STRIPE_LEN,
},
.expected_result = -EIO,
},
{
.description = "RAID10: fail: too many partial write",
.raid_type = BTRFS_BLOCK_GROUP_RAID10,
.num_stripes = 8,
.alloc_offsets = {
HALF_STRIPE_LEN, HALF_STRIPE_LEN,
HALF_STRIPE_LEN, HALF_STRIPE_LEN,
0, 0, 0, 0,
},
.expected_result = -EIO,
},
/*
* Error case: Partial missing device in RAID0 level is not allowed even on
* non-DEGRADED mount.
*/
{
.description = "RAID10: fail: missing device on DEGRADED",
.raid_type = BTRFS_BLOCK_GROUP_RAID10,
.num_stripes = 4,
.alloc_offsets = {
SZ_1M, SZ_1M,
WP_MISSING_DEV, WP_MISSING_DEV,
},
.degraded = true,
.expected_result = -EIO,
},
/*
* One sequential zone and one conventional zone, having matching
* last_alloc.
*/
{
.description = "RAID10: seq zone and conv zone, partially written stripe",
.raid_type = BTRFS_BLOCK_GROUP_RAID10,
.num_stripes = 4,
.alloc_offsets = {
SZ_1M, SZ_1M,
WP_CONVENTIONAL, WP_CONVENTIONAL,
},
.last_alloc = SZ_2M - SZ_4K,
.expected_alloc_offset = SZ_2M - SZ_4K,
},
{
.description = "RAID10: conv zone and seq zone, partially written stripe",
.raid_type = BTRFS_BLOCK_GROUP_RAID10,
.num_stripes = 4,
.alloc_offsets = {
WP_CONVENTIONAL, WP_CONVENTIONAL,
SZ_1M, SZ_1M,
},
.last_alloc = SZ_2M + SZ_4K,
.expected_alloc_offset = SZ_2M + SZ_4K,
},
/*
* Error case: one sequential and one conventional zone, but having larger
* last_alloc than write pointer.
*/
{
.description = "RAID10: fail: seq zone and conv zone, larger last_alloc",
.raid_type = BTRFS_BLOCK_GROUP_RAID10,
.num_stripes = 4,
.alloc_offsets = {
SZ_1M, SZ_1M,
WP_CONVENTIONAL, WP_CONVENTIONAL,
},
.last_alloc = SZ_2M + BTRFS_STRIPE_LEN * 2,
.expected_result = -EIO,
},
/* RAID10, 4 stripes with seq zones and conv zones. */
{
.description = "RAID10: stripes [2, 2, ?, ?] last_alloc = 6",
.raid_type = BTRFS_BLOCK_GROUP_RAID10,
.num_stripes = 8,
.alloc_offsets = {
BTRFS_STRIPE_LEN * 2, BTRFS_STRIPE_LEN * 2,
BTRFS_STRIPE_LEN * 2, BTRFS_STRIPE_LEN * 2,
WP_CONVENTIONAL, WP_CONVENTIONAL,
WP_CONVENTIONAL, WP_CONVENTIONAL,
},
.last_alloc = BTRFS_STRIPE_LEN * 6,
.expected_alloc_offset = BTRFS_STRIPE_LEN * 6,
},
{
.description = "RAID10: stripes [2, 2, ?, ?] last_alloc = 7.5",
.raid_type = BTRFS_BLOCK_GROUP_RAID10,
.num_stripes = 8,
.alloc_offsets = {
BTRFS_STRIPE_LEN * 2, BTRFS_STRIPE_LEN * 2,
BTRFS_STRIPE_LEN * 2, BTRFS_STRIPE_LEN * 2,
WP_CONVENTIONAL, WP_CONVENTIONAL,
WP_CONVENTIONAL, WP_CONVENTIONAL,
},
.last_alloc = BTRFS_STRIPE_LEN * 7 + HALF_STRIPE_LEN,
.expected_alloc_offset = BTRFS_STRIPE_LEN * 7 + HALF_STRIPE_LEN,
},
{
.description = "RAID10: stripes [3, ?, ?, ?] last_alloc = 1",
.raid_type = BTRFS_BLOCK_GROUP_RAID10,
.num_stripes = 8,
.alloc_offsets = {
BTRFS_STRIPE_LEN * 3, BTRFS_STRIPE_LEN * 3,
WP_CONVENTIONAL, WP_CONVENTIONAL,
WP_CONVENTIONAL, WP_CONVENTIONAL,
WP_CONVENTIONAL, WP_CONVENTIONAL,
},
.last_alloc = BTRFS_STRIPE_LEN,
.expected_alloc_offset = BTRFS_STRIPE_LEN * 9,
},
{
.description = "RAID10: stripes [2, ?, 1, ?] last_alloc = 5",
.raid_type = BTRFS_BLOCK_GROUP_RAID10,
.num_stripes = 8,
.alloc_offsets = {
BTRFS_STRIPE_LEN * 2, BTRFS_STRIPE_LEN * 2,
WP_CONVENTIONAL, WP_CONVENTIONAL,
BTRFS_STRIPE_LEN, BTRFS_STRIPE_LEN,
WP_CONVENTIONAL, WP_CONVENTIONAL,
},
.last_alloc = BTRFS_STRIPE_LEN * 5,
.expected_alloc_offset = BTRFS_STRIPE_LEN * 5,
},
{
.description = "RAID10: fail: stripes [2, ?, 1, ?] last_alloc = 7",
.raid_type = BTRFS_BLOCK_GROUP_RAID10,
.num_stripes = 8,
.alloc_offsets = {
BTRFS_STRIPE_LEN * 2, BTRFS_STRIPE_LEN * 2,
WP_CONVENTIONAL, WP_CONVENTIONAL,
BTRFS_STRIPE_LEN, BTRFS_STRIPE_LEN,
WP_CONVENTIONAL, WP_CONVENTIONAL,
},
.last_alloc = BTRFS_STRIPE_LEN * 7,
.expected_result = -EIO,
},
};
int btrfs_test_zoned(void)
{
struct btrfs_fs_info *fs_info __free(btrfs_free_dummy_fs_info) = NULL;
int ret;
test_msg("running zoned tests (error messages are expected)");
fs_info = btrfs_alloc_dummy_fs_info(PAGE_SIZE, PAGE_SIZE);
if (!fs_info) {
test_std_err(TEST_ALLOC_FS_INFO);
return -ENOMEM;
}
for (int i = 0; i < ARRAY_SIZE(load_zone_info_tests); i++) {
ret = test_load_zone_info(fs_info, &load_zone_info_tests[i]);
if (ret) {
test_err("test case \"%s\" failed", load_zone_info_tests[i].description);
return ret;
}
}
return 0;
}