#include <linux/slab.h>
#include "btrfs-tests.h"
#include "../ctree.h"
#include "../disk-io.h"
#include "../free-space-cache.h"
#include "../block-group.h"
#define BITS_PER_BITMAP (PAGE_SIZE * 8UL)
static int test_extents(struct btrfs_block_group *cache)
{
int ret = 0;
test_msg("running extent only tests");
ret = btrfs_add_free_space(cache, 0, SZ_4M);
if (ret) {
test_err("error adding initial extents %d", ret);
return ret;
}
ret = btrfs_remove_free_space(cache, 0, SZ_4M);
if (ret) {
test_err("error removing extent %d", ret);
return ret;
}
if (test_check_exists(cache, 0, SZ_4M)) {
test_err("full remove left some lingering space");
return -1;
}
ret = btrfs_add_free_space(cache, 0, SZ_4M);
if (ret) {
test_err("error adding half extent %d", ret);
return ret;
}
ret = btrfs_remove_free_space(cache, 3 * SZ_1M, SZ_1M);
if (ret) {
test_err("error removing tail end %d", ret);
return ret;
}
ret = btrfs_remove_free_space(cache, 0, SZ_1M);
if (ret) {
test_err("error removing front end %d", ret);
return ret;
}
ret = btrfs_remove_free_space(cache, SZ_2M, 4096);
if (ret) {
test_err("error removing middle piece %d", ret);
return ret;
}
if (test_check_exists(cache, 0, SZ_1M)) {
test_err("still have space at the front");
return -1;
}
if (test_check_exists(cache, SZ_2M, 4096)) {
test_err("still have space in the middle");
return -1;
}
if (test_check_exists(cache, 3 * SZ_1M, SZ_1M)) {
test_err("still have space at the end");
return -1;
}
btrfs_remove_free_space_cache(cache);
return 0;
}
static int test_bitmaps(struct btrfs_block_group *cache, u32 sectorsize)
{
u64 next_bitmap_offset;
int ret;
test_msg("running bitmap only tests");
ret = test_add_free_space_entry(cache, 0, SZ_4M, 1);
if (ret) {
test_err("couldn't create a bitmap entry %d", ret);
return ret;
}
ret = btrfs_remove_free_space(cache, 0, SZ_4M);
if (ret) {
test_err("error removing bitmap full range %d", ret);
return ret;
}
if (test_check_exists(cache, 0, SZ_4M)) {
test_err("left some space in bitmap");
return -1;
}
ret = test_add_free_space_entry(cache, 0, SZ_4M, 1);
if (ret) {
test_err("couldn't add to our bitmap entry %d", ret);
return ret;
}
ret = btrfs_remove_free_space(cache, SZ_1M, SZ_2M);
if (ret) {
test_err("couldn't remove middle chunk %d", ret);
return ret;
}
next_bitmap_offset = (u64)(BITS_PER_BITMAP * sectorsize);
ret = test_add_free_space_entry(cache, next_bitmap_offset - SZ_2M,
SZ_4M, 1);
if (ret) {
test_err("couldn't add space that straddles two bitmaps %d",
ret);
return ret;
}
ret = btrfs_remove_free_space(cache, next_bitmap_offset - SZ_1M, SZ_2M);
if (ret) {
test_err("couldn't remove overlapping space %d", ret);
return ret;
}
if (test_check_exists(cache, next_bitmap_offset - SZ_1M, SZ_2M)) {
test_err("left some space when removing overlapping");
return -1;
}
btrfs_remove_free_space_cache(cache);
return 0;
}
static int test_bitmaps_and_extents(struct btrfs_block_group *cache,
u32 sectorsize)
{
u64 bitmap_offset = (u64)(BITS_PER_BITMAP * sectorsize);
int ret;
test_msg("running bitmap and extent tests");
ret = test_add_free_space_entry(cache, SZ_4M, SZ_1M, 1);
if (ret) {
test_err("couldn't create bitmap entry %d", ret);
return ret;
}
ret = test_add_free_space_entry(cache, 0, SZ_1M, 0);
if (ret) {
test_err("couldn't add extent entry %d", ret);
return ret;
}
ret = btrfs_remove_free_space(cache, 0, SZ_1M);
if (ret) {
test_err("couldn't remove extent entry %d", ret);
return ret;
}
if (test_check_exists(cache, 0, SZ_1M)) {
test_err("left remnants after our remove");
return -1;
}
ret = test_add_free_space_entry(cache, 0, SZ_1M, 0);
if (ret) {
test_err("couldn't re-add extent entry %d", ret);
return ret;
}
ret = btrfs_remove_free_space(cache, SZ_4M, SZ_1M);
if (ret) {
test_err("couldn't remove from bitmap %d", ret);
return ret;
}
if (test_check_exists(cache, SZ_4M, SZ_1M)) {
test_err("left remnants in the bitmap");
return -1;
}
ret = test_add_free_space_entry(cache, SZ_1M, SZ_4M, 1);
if (ret) {
test_err("couldn't add to a bitmap %d", ret);
return ret;
}
ret = btrfs_remove_free_space(cache, SZ_512K, 3 * SZ_1M);
if (ret) {
test_err("couldn't remove overlapping space %d", ret);
return ret;
}
if (test_check_exists(cache, SZ_512K, 3 * SZ_1M)) {
test_err("left over pieces after removing overlapping");
return -1;
}
btrfs_remove_free_space_cache(cache);
ret = test_add_free_space_entry(cache, SZ_4M, SZ_4M, 1);
if (ret) {
test_err("couldn't add space to the bitmap %d", ret);
return ret;
}
ret = test_add_free_space_entry(cache, SZ_2M, SZ_2M, 0);
if (ret) {
test_err("couldn't add extent to the cache %d", ret);
return ret;
}
ret = btrfs_remove_free_space(cache, 3 * SZ_1M, SZ_4M);
if (ret) {
test_err("problem removing overlapping space %d", ret);
return ret;
}
if (test_check_exists(cache, 3 * SZ_1M, SZ_4M)) {
test_err("left something behind when removing space");
return -1;
}
btrfs_remove_free_space_cache(cache);
ret = test_add_free_space_entry(cache, bitmap_offset + SZ_4M, SZ_4M, 1);
if (ret) {
test_err("couldn't add bitmap %d", ret);
return ret;
}
ret = test_add_free_space_entry(cache, bitmap_offset - SZ_1M,
5 * SZ_1M, 0);
if (ret) {
test_err("couldn't add extent entry %d", ret);
return ret;
}
ret = btrfs_remove_free_space(cache, bitmap_offset + SZ_1M, 5 * SZ_1M);
if (ret) {
test_err("failed to free our space %d", ret);
return ret;
}
if (test_check_exists(cache, bitmap_offset + SZ_1M, 5 * SZ_1M)) {
test_err("left stuff over");
return -1;
}
btrfs_remove_free_space_cache(cache);
ret = test_add_free_space_entry(cache, SZ_1M, SZ_2M, 1);
if (ret) {
test_err("couldn't add bitmap entry %d", ret);
return ret;
}
ret = test_add_free_space_entry(cache, 3 * SZ_1M, SZ_1M, 0);
if (ret) {
test_err("couldn't add extent entry %d", ret);
return ret;
}
ret = btrfs_remove_free_space(cache, SZ_1M, 3 * SZ_1M);
if (ret) {
test_err("error removing bitmap and extent overlapping %d", ret);
return ret;
}
btrfs_remove_free_space_cache(cache);
return 0;
}
static bool test_use_bitmap(struct btrfs_free_space_ctl *ctl,
struct btrfs_free_space *info)
{
return ctl->free_extents > 0;
}
static int
check_num_extents_and_bitmaps(const struct btrfs_block_group *cache,
const int num_extents,
const int num_bitmaps)
{
if (cache->free_space_ctl->free_extents != num_extents) {
test_err(
"incorrect # of extent entries in the cache: %d, expected %d",
cache->free_space_ctl->free_extents, num_extents);
return -EINVAL;
}
if (cache->free_space_ctl->total_bitmaps != num_bitmaps) {
test_err(
"incorrect # of extent entries in the cache: %d, expected %d",
cache->free_space_ctl->total_bitmaps, num_bitmaps);
return -EINVAL;
}
return 0;
}
static int check_cache_empty(struct btrfs_block_group *cache)
{
u64 offset;
u64 max_extent_size;
if (cache->free_space_ctl->free_space != 0) {
test_err("cache free space is not 0");
return -EINVAL;
}
offset = btrfs_find_space_for_alloc(cache, 0, 4096, 0,
&max_extent_size);
if (offset != 0) {
test_err("space allocation did not fail, returned offset: %llu",
offset);
return -EINVAL;
}
return check_num_extents_and_bitmaps(cache, 0, 0);
}
static int
test_steal_space_from_bitmap_to_extent(struct btrfs_block_group *cache,
u32 sectorsize)
{
int ret;
u64 offset;
u64 max_extent_size;
const struct btrfs_free_space_op test_free_space_ops = {
.use_bitmap = test_use_bitmap,
};
const struct btrfs_free_space_op *orig_free_space_ops;
test_msg("running space stealing from bitmap to extent tests");
orig_free_space_ops = cache->free_space_ctl->op;
cache->free_space_ctl->op = &test_free_space_ops;
ret = test_add_free_space_entry(cache, SZ_128M - SZ_256K, SZ_128K, 0);
if (ret) {
test_err("couldn't add extent entry %d", ret);
return ret;
}
ret = test_add_free_space_entry(cache, SZ_128M + SZ_512K,
SZ_128M - SZ_512K, 1);
if (ret) {
test_err("couldn't add bitmap entry %d", ret);
return ret;
}
ret = check_num_extents_and_bitmaps(cache, 2, 1);
if (ret)
return ret;
ret = btrfs_remove_free_space(cache,
SZ_128M + 768 * SZ_1K,
SZ_128M - 768 * SZ_1K);
if (ret) {
test_err("failed to free part of bitmap space %d", ret);
return ret;
}
if (!test_check_exists(cache, SZ_128M - SZ_256K, SZ_128K)) {
test_err("free space range missing");
return -ENOENT;
}
if (!test_check_exists(cache, SZ_128M + SZ_512K, SZ_256K)) {
test_err("free space range missing");
return -ENOENT;
}
if (test_check_exists(cache, SZ_128M + 768 * SZ_1K,
SZ_128M - 768 * SZ_1K)) {
test_err("bitmap region not removed from space cache");
return -EINVAL;
}
if (test_check_exists(cache, SZ_128M + SZ_256K, SZ_256K)) {
test_err("invalid bitmap region marked as free");
return -EINVAL;
}
if (test_check_exists(cache, SZ_128M, SZ_256K)) {
test_err("invalid bitmap region marked as free");
return -EINVAL;
}
ret = btrfs_add_free_space(cache, SZ_128M, SZ_512K);
if (ret) {
test_err("error adding free space: %d", ret);
return ret;
}
if (!test_check_exists(cache, SZ_128M, SZ_512K)) {
test_err("bitmap region not marked as free");
return -ENOENT;
}
ret = check_num_extents_and_bitmaps(cache, 2, 1);
if (ret)
return ret;
ret = btrfs_add_free_space(cache, SZ_128M + SZ_16M, sectorsize);
if (ret) {
test_err("error adding free space: %d", ret);
return ret;
}
ret = check_num_extents_and_bitmaps(cache, 2, 1);
if (ret)
return ret;
ret = btrfs_add_free_space(cache, SZ_128M - SZ_128K, SZ_128K);
if (ret) {
test_err("error adding free space: %d", ret);
return ret;
}
if (!test_check_exists(cache, SZ_128M - SZ_128K, SZ_128K)) {
test_err("extent region not marked as free");
return -ENOENT;
}
ret = check_num_extents_and_bitmaps(cache, 2, 1);
if (ret)
return ret;
if (!test_check_exists(cache, SZ_128M - SZ_256K, SZ_1M)) {
test_err("expected region not marked as free");
return -ENOENT;
}
if (cache->free_space_ctl->free_space != (SZ_1M + sectorsize)) {
test_err("cache free space is not 1Mb + %u", sectorsize);
return -EINVAL;
}
offset = btrfs_find_space_for_alloc(cache,
0, SZ_1M, 0,
&max_extent_size);
if (offset != (SZ_128M - SZ_256K)) {
test_err(
"failed to allocate 1Mb from space cache, returned offset is: %llu",
offset);
return -EINVAL;
}
ret = check_num_extents_and_bitmaps(cache, 1, 1);
if (ret)
return ret;
if (cache->free_space_ctl->free_space != sectorsize) {
test_err("cache free space is not %u", sectorsize);
return -EINVAL;
}
offset = btrfs_find_space_for_alloc(cache,
0, sectorsize, 0,
&max_extent_size);
if (offset != (SZ_128M + SZ_16M)) {
test_err("failed to allocate %u, returned offset : %llu",
sectorsize, offset);
return -EINVAL;
}
ret = check_cache_empty(cache);
if (ret)
return ret;
btrfs_remove_free_space_cache(cache);
ret = test_add_free_space_entry(cache, SZ_128M + SZ_128K, SZ_128K, 0);
if (ret) {
test_err("couldn't add extent entry %d", ret);
return ret;
}
ret = test_add_free_space_entry(cache, 0, SZ_128M - SZ_512K, 1);
if (ret) {
test_err("couldn't add bitmap entry %d", ret);
return ret;
}
ret = check_num_extents_and_bitmaps(cache, 2, 1);
if (ret)
return ret;
ret = btrfs_remove_free_space(cache, 0, SZ_128M - 768 * SZ_1K);
if (ret) {
test_err("failed to free part of bitmap space %d", ret);
return ret;
}
if (!test_check_exists(cache, SZ_128M + SZ_128K, SZ_128K)) {
test_err("free space range missing");
return -ENOENT;
}
if (!test_check_exists(cache, SZ_128M - 768 * SZ_1K, SZ_256K)) {
test_err("free space range missing");
return -ENOENT;
}
if (test_check_exists(cache, 0, SZ_128M - 768 * SZ_1K)) {
test_err("bitmap region not removed from space cache");
return -EINVAL;
}
if (test_check_exists(cache, SZ_128M - SZ_512K, SZ_512K)) {
test_err("invalid bitmap region marked as free");
return -EINVAL;
}
ret = btrfs_add_free_space(cache, SZ_128M - SZ_512K, SZ_512K);
if (ret) {
test_err("error adding free space: %d", ret);
return ret;
}
if (!test_check_exists(cache, SZ_128M - SZ_512K, SZ_512K)) {
test_err("bitmap region not marked as free");
return -ENOENT;
}
ret = check_num_extents_and_bitmaps(cache, 2, 1);
if (ret)
return ret;
ret = btrfs_add_free_space(cache, SZ_32M, 2 * sectorsize);
if (ret) {
test_err("error adding free space: %d", ret);
return ret;
}
ret = btrfs_add_free_space(cache, SZ_128M, SZ_128K);
if (ret) {
test_err("error adding free space: %d", ret);
return ret;
}
if (!test_check_exists(cache, SZ_128M, SZ_128K)) {
test_err("extent region not marked as free");
return -ENOENT;
}
ret = check_num_extents_and_bitmaps(cache, 2, 1);
if (ret)
return ret;
if (!test_check_exists(cache, SZ_128M - 768 * SZ_1K, SZ_1M)) {
test_err("expected region not marked as free");
return -ENOENT;
}
if (cache->free_space_ctl->free_space != (SZ_1M + 2 * sectorsize)) {
test_err("cache free space is not 1Mb + %u", 2 * sectorsize);
return -EINVAL;
}
offset = btrfs_find_space_for_alloc(cache, 0, SZ_1M, 0,
&max_extent_size);
if (offset != (SZ_128M - 768 * SZ_1K)) {
test_err(
"failed to allocate 1Mb from space cache, returned offset is: %llu",
offset);
return -EINVAL;
}
ret = check_num_extents_and_bitmaps(cache, 1, 1);
if (ret)
return ret;
if (cache->free_space_ctl->free_space != 2 * sectorsize) {
test_err("cache free space is not %u", 2 * sectorsize);
return -EINVAL;
}
offset = btrfs_find_space_for_alloc(cache,
0, 2 * sectorsize, 0,
&max_extent_size);
if (offset != SZ_32M) {
test_err("failed to allocate %u, offset: %llu",
2 * sectorsize, offset);
return -EINVAL;
}
ret = check_cache_empty(cache);
if (ret)
return ret;
cache->free_space_ctl->op = orig_free_space_ops;
btrfs_remove_free_space_cache(cache);
return 0;
}
static bool bytes_index_use_bitmap(struct btrfs_free_space_ctl *ctl,
struct btrfs_free_space *info)
{
return true;
}
static int test_bytes_index(struct btrfs_block_group *cache, u32 sectorsize)
{
const struct btrfs_free_space_op test_free_space_ops = {
.use_bitmap = bytes_index_use_bitmap,
};
const struct btrfs_free_space_op *orig_free_space_ops;
struct btrfs_free_space_ctl *ctl = cache->free_space_ctl;
struct btrfs_free_space *entry;
struct rb_node *node;
u64 offset, max_extent_size, bytes;
int ret, i;
test_msg("running bytes index tests");
offset = 0;
for (i = 0; i < 10; i++) {
bytes = (i + 1) * SZ_1M;
ret = test_add_free_space_entry(cache, offset, bytes, 0);
if (ret) {
test_err("couldn't add extent entry %d\n", ret);
return ret;
}
offset += bytes + sectorsize;
}
for (node = rb_first_cached(&ctl->free_space_bytes), i = 9; node;
node = rb_next(node), i--) {
entry = rb_entry(node, struct btrfs_free_space, bytes_index);
bytes = (i + 1) * SZ_1M;
if (entry->bytes != bytes) {
test_err("invalid bytes index order, found %llu expected %llu",
entry->bytes, bytes);
return -EINVAL;
}
}
btrfs_remove_free_space_cache(cache);
for (i = 0; i < 2; i++) {
offset = i * BITS_PER_BITMAP * sectorsize;
bytes = (i + 1) * SZ_1M;
ret = test_add_free_space_entry(cache, offset, bytes, 1);
if (ret) {
test_err("couldn't add bitmap entry");
return ret;
}
}
for (node = rb_first_cached(&ctl->free_space_bytes), i = 1; node;
node = rb_next(node), i--) {
entry = rb_entry(node, struct btrfs_free_space, bytes_index);
bytes = (i + 1) * SZ_1M;
if (entry->bytes != bytes) {
test_err("invalid bytes index order, found %llu expected %llu",
entry->bytes, bytes);
return -EINVAL;
}
}
btrfs_remove_free_space_cache(cache);
orig_free_space_ops = cache->free_space_ctl->op;
cache->free_space_ctl->op = &test_free_space_ops;
ret = test_add_free_space_entry(cache, 0, sectorsize, 1);
if (ret) {
test_err("couldn't add bitmap entry");
return ret;
}
offset = BITS_PER_BITMAP * sectorsize;
ret = test_add_free_space_entry(cache, offset, sectorsize, 1);
if (ret) {
test_err("couldn't add bitmap_entry");
return ret;
}
for (i = 2; i < 20; i += 2) {
offset = sectorsize * i;
ret = btrfs_add_free_space(cache, offset, sectorsize);
if (ret) {
test_err("error populating sparse bitmap %d", ret);
return ret;
}
}
offset = (BITS_PER_BITMAP * sectorsize) + sectorsize;
ret = btrfs_add_free_space(cache, offset, sectorsize);
if (ret) {
test_err("error adding contiguous extent %d", ret);
return ret;
}
entry = rb_entry(rb_first_cached(&ctl->free_space_bytes),
struct btrfs_free_space, bytes_index);
if (entry->bytes != (10 * sectorsize)) {
test_err("error, wrong entry in the first slot in bytes_index");
return -EINVAL;
}
max_extent_size = 0;
offset = btrfs_find_space_for_alloc(cache, cache->start, sectorsize * 3,
0, &max_extent_size);
if (offset != 0) {
test_err("found space to alloc even though we don't have enough space");
return -EINVAL;
}
if (max_extent_size != (2 * sectorsize)) {
test_err("got the wrong max_extent size %llu expected %llu",
max_extent_size, (unsigned long long)(2 * sectorsize));
return -EINVAL;
}
entry = rb_entry(rb_first_cached(&ctl->free_space_bytes),
struct btrfs_free_space, bytes_index);
if (entry->bytes != (2 * sectorsize)) {
test_err("error, the bytes index wasn't recalculated properly");
return -EINVAL;
}
offset = (BITS_PER_BITMAP * sectorsize) - sectorsize;
ret = btrfs_add_free_space(cache, offset, sectorsize);
if (ret) {
test_err("error adding extent to the sparse entry %d", ret);
return ret;
}
entry = rb_entry(rb_first_cached(&ctl->free_space_bytes),
struct btrfs_free_space, bytes_index);
if (entry->bytes != (11 * sectorsize)) {
test_err("error, wrong entry in the first slot in bytes_index");
return -EINVAL;
}
max_extent_size = 0;
offset = btrfs_find_space_for_alloc(cache, cache->start, sectorsize * 2,
0, &max_extent_size);
if (offset != (BITS_PER_BITMAP * sectorsize)) {
test_err("error, found %llu instead of %llu for our alloc",
offset,
(unsigned long long)(BITS_PER_BITMAP * sectorsize));
return -EINVAL;
}
cache->free_space_ctl->op = orig_free_space_ops;
btrfs_remove_free_space_cache(cache);
return 0;
}
int btrfs_test_free_space_cache(u32 sectorsize, u32 nodesize)
{
struct btrfs_fs_info *fs_info;
struct btrfs_block_group *cache;
struct btrfs_root *root = NULL;
int ret = -ENOMEM;
test_msg("running btrfs free space cache tests");
fs_info = btrfs_alloc_dummy_fs_info(nodesize, sectorsize);
if (!fs_info) {
test_std_err(TEST_ALLOC_FS_INFO);
return -ENOMEM;
}
cache = btrfs_alloc_dummy_block_group(fs_info,
BITS_PER_BITMAP * sectorsize + PAGE_SIZE);
if (!cache) {
test_std_err(TEST_ALLOC_BLOCK_GROUP);
btrfs_free_dummy_fs_info(fs_info);
return 0;
}
root = btrfs_alloc_dummy_root(fs_info);
if (IS_ERR(root)) {
test_std_err(TEST_ALLOC_ROOT);
ret = PTR_ERR(root);
goto out;
}
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);
ret = test_extents(cache);
if (ret)
goto out;
ret = test_bitmaps(cache, sectorsize);
if (ret)
goto out;
ret = test_bitmaps_and_extents(cache, sectorsize);
if (ret)
goto out;
ret = test_steal_space_from_bitmap_to_extent(cache, sectorsize);
if (ret)
goto out;
ret = test_bytes_index(cache, sectorsize);
out:
btrfs_free_dummy_block_group(cache);
btrfs_free_dummy_root(root);
btrfs_free_dummy_fs_info(fs_info);
return ret;
}