@@ -42,4 +42,5 @@ btrfs-$(CONFIG_FS_VERITY) += verity.o
btrfs-$(CONFIG_BTRFS_FS_RUN_SANITY_TESTS) += tests/free-space-tests.o \
tests/extent-buffer-tests.o tests/btrfs-tests.o \
tests/extent-io-tests.o tests/inode-tests.o tests/qgroup-tests.o \
- tests/free-space-tree-tests.o tests/extent-map-tests.o
+ tests/free-space-tree-tests.o tests/extent-map-tests.o \
+ tests/write-intent-bitmaps-tests.o
@@ -27,6 +27,7 @@ const char *test_error[] = {
[TEST_ALLOC_INODE] = "cannot allocate inode",
[TEST_ALLOC_BLOCK_GROUP] = "cannot allocate block group",
[TEST_ALLOC_EXTENT_MAP] = "cannot allocate extent map",
+ [TEST_ALLOC_WRITE_INTENT_CTRL] = "cannot allocate write intent control",
};
static const struct super_operations btrfs_test_super_ops = {
@@ -279,6 +280,9 @@ int btrfs_run_sanity_tests(void)
}
}
ret = btrfs_test_extent_map();
+ if (ret)
+ goto out;
+ ret = btrfs_test_write_intent_bitmaps();
out:
btrfs_destroy_test_fs();
@@ -23,6 +23,7 @@ enum {
TEST_ALLOC_INODE,
TEST_ALLOC_BLOCK_GROUP,
TEST_ALLOC_EXTENT_MAP,
+ TEST_ALLOC_WRITE_INTENT_CTRL,
};
extern const char *test_error[];
@@ -37,6 +38,7 @@ 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_extent_map(void);
+int btrfs_test_write_intent_bitmaps(void);
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);
new file mode 100644
@@ -0,0 +1,245 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include "../ctree.h"
+#include "../volumes.h"
+#include "../write-intent.h"
+#include "btrfs-tests.h"
+
+static struct write_intent_ctrl *alloc_dummy_ctrl(void)
+{
+ struct write_intent_ctrl *ctrl;
+ struct write_intent_super *wis;
+
+ ctrl = kzalloc(sizeof(*ctrl), GFP_NOFS);
+ if (!ctrl)
+ return NULL;
+ /*
+ * For dummy tests, we only need the primary page, no need for the
+ * commit page.
+ */
+ ctrl->page = alloc_page(GFP_NOFS);
+ if (!ctrl->page) {
+ kfree(ctrl);
+ return NULL;
+ }
+ ctrl->blocksize = BTRFS_STRIPE_LEN;
+ atomic64_set(&ctrl->event, 1);
+ spin_lock_init(&ctrl->lock);
+ memzero_page(ctrl->page, 0, WRITE_INTENT_BITMAPS_SIZE);
+ wis = page_address(ctrl->page);
+ wi_set_super_magic(wis, WRITE_INTENT_SUPER_MAGIC);
+ wi_set_super_csum_type(wis, 0);
+ wi_set_super_events(wis, 1);
+ wi_set_super_flags(wis, WRITE_INTENT_FLAGS_SUPPORTED);
+ wi_set_super_size(wis, WRITE_INTENT_BITMAPS_SIZE);
+ wi_set_super_blocksize(wis, ctrl->blocksize);
+ wi_set_super_nr_entries(wis, 0);
+ return ctrl;
+}
+
+static void zero_bitmaps(struct write_intent_ctrl *ctrl)
+{
+ struct write_intent_super *wis = page_address(ctrl->page);
+
+ memzero_page(ctrl->page, sizeof(struct write_intent_super),
+ WRITE_INTENT_BITMAPS_SIZE -
+ sizeof(struct write_intent_super));
+ wi_set_super_nr_entries(wis, 0);
+}
+
+static int compare_bitmaps(struct write_intent_ctrl *ctrl,
+ int nr_entries, u64 *bytenrs, u64 *bitmaps)
+{
+ struct write_intent_super *wis = page_address(ctrl->page);
+ struct write_intent_entry empty = {0};
+ int i;
+
+ if (wi_super_nr_entries(wis) != nr_entries) {
+ test_err("nr entries mismatch, has %llu expect %u",
+ wi_super_nr_entries(wis), nr_entries);
+ goto err;
+ }
+
+ for (i = 0; i < nr_entries; i++) {
+ struct write_intent_entry *entry =
+ write_intent_entry_nr(ctrl, i);
+
+ if (wi_entry_bytenr(entry) != bytenrs[i]) {
+ test_err("bytenr mismatch, has %llu expect %llu",
+ wi_entry_bytenr(entry), bytenrs[i]);
+ goto err;
+ }
+ if (wi_entry_raw_bitmap(entry) != bitmaps[i]) {
+ test_err("bitmap mismatch, has 0x%016llx expect 0x%016llx",
+ wi_entry_raw_bitmap(entry), bitmaps[i]);
+ goto err;
+ }
+ }
+
+ /* The unused entries should all be zero. */
+ for (i = nr_entries; i < WRITE_INTENT_INTERNAL_BITMAPS_MAX_ENTRIES;
+ i++) {
+ if (memcmp(write_intent_entry_nr(ctrl, i), &empty,
+ sizeof(empty))) {
+ test_err(
+ "unused entry is not empty, entry %u nr_entries %u",
+ i, nr_entries);
+ goto err;
+ }
+ }
+ return 0;
+err:
+ /* Dump the bitmaps for better debugging. */
+ test_err("dumping bitmaps, nr_entries=%llu:", wi_super_nr_entries(wis));
+ for (i = 0; i < wi_super_nr_entries(wis); i++) {
+ struct write_intent_entry *entry =
+ write_intent_entry_nr(ctrl, i);
+
+ test_err(" entry=%u bytenr=%llu bitmap=0x%016llx\n",
+ i, wi_entry_bytenr(entry), wi_entry_raw_bitmap(entry));
+ }
+ return -EUCLEAN;
+}
+
+static void free_dummy_ctrl(struct write_intent_ctrl *ctrl)
+{
+ __free_page(ctrl->page);
+ ASSERT(ctrl->commit_page == NULL);
+ kfree(ctrl);
+}
+
+/*
+ * Basic tests to ensure set and clear can properly handle bits set/clear in
+ * one entry.
+ */
+static int test_case_simple_entry(struct write_intent_ctrl *ctrl)
+{
+ const u32 blocksize = BTRFS_STRIPE_LEN;
+ u64 bitmaps[1] = { 0 };
+ u64 bytenrs[1] = { 0 };
+ int ret;
+
+ zero_bitmaps(ctrl);
+
+ write_intent_set_bits(ctrl, 0, blocksize * 3);
+
+ bitmaps[0] = 0x7;
+ bytenrs[0] = 0;
+ ret = compare_bitmaps(ctrl, 1, bytenrs, bitmaps);
+ if (ret < 0)
+ return ret;
+
+ write_intent_clear_bits(ctrl, 0, blocksize * 3);
+ ret = compare_bitmaps(ctrl, 0, bytenrs, bitmaps);
+ if (ret < 0)
+ return ret;
+
+ write_intent_set_bits(ctrl, blocksize * 8, blocksize * 3);
+
+ bitmaps[0] = 0x700;
+ bytenrs[0] = 0;
+ ret = compare_bitmaps(ctrl, 1, bytenrs, bitmaps);
+ if (ret < 0)
+ return ret;
+
+ write_intent_clear_bits(ctrl, blocksize * 9, blocksize * 2);
+ bitmaps[0] = 0x100;
+ bytenrs[0] = 0;
+ ret = compare_bitmaps(ctrl, 1, bytenrs, bitmaps);
+ if (ret < 0)
+ return ret;
+
+ write_intent_clear_bits(ctrl, blocksize * 8, blocksize * 1);
+ ret = compare_bitmaps(ctrl, 0, bytenrs, bitmaps);
+ if (ret < 0)
+ return ret;
+
+ /* Tests at high bits. */
+ write_intent_set_bits(ctrl, blocksize * 61, blocksize * 3);
+ bitmaps[0] = 0xe000000000000000L;
+ bytenrs[0] = 0;
+ ret = compare_bitmaps(ctrl, 1, bytenrs, bitmaps);
+ if (ret < 0)
+ return ret;
+ write_intent_clear_bits(ctrl, blocksize * 61, blocksize * 1);
+ bitmaps[0] = 0xc000000000000000L;
+ bytenrs[0] = 0;
+ ret = compare_bitmaps(ctrl, 1, bytenrs, bitmaps);
+ if (ret < 0)
+ return ret;
+ write_intent_clear_bits(ctrl, blocksize * 62, blocksize * 2);
+ ret = compare_bitmaps(ctrl, 0, bytenrs, bitmaps);
+ if (ret < 0)
+ return ret;
+ return 0;
+}
+
+/* Tests set/clear that cross entry boundaries. */
+static int test_case_cross_entries(struct write_intent_ctrl *ctrl)
+{
+ const u32 blocksize = BTRFS_STRIPE_LEN;
+ u64 bitmaps[3] = { 0 };
+ u64 bytenrs[3] = { 0 };
+ int ret;
+
+ zero_bitmaps(ctrl);
+
+ write_intent_set_bits(ctrl, blocksize * 32, blocksize * 64);
+ bitmaps[0] = 0xffffffff00000000L;
+ bytenrs[0] = 0;
+ bitmaps[1] = 0x00000000ffffffffL;
+ bytenrs[1] = 4194304;
+ ret = compare_bitmaps(ctrl, 2, bytenrs, bitmaps);
+ if (ret < 0)
+ return ret;
+
+ write_intent_set_bits(ctrl, blocksize * 96, blocksize * 64);
+ bitmaps[0] = 0xffffffff00000000L;
+ bytenrs[0] = 0;
+ bitmaps[1] = 0xffffffffffffffffL;
+ bytenrs[1] = 4194304;
+ bitmaps[2] = 0x00000000ffffffffL;
+ bytenrs[2] = 8388608;
+ ret = compare_bitmaps(ctrl, 3, bytenrs, bitmaps);
+ if (ret < 0)
+ return ret;
+
+ write_intent_clear_bits(ctrl, blocksize * 33, blocksize * 126);
+ bitmaps[0] = 0x0000000100000000L;
+ bytenrs[0] = 0;
+ bitmaps[1] = 0x0000000080000000L;
+ bytenrs[1] = 8388608;
+ ret = compare_bitmaps(ctrl, 2, bytenrs, bitmaps);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+int btrfs_test_write_intent_bitmaps(void)
+{
+ struct write_intent_ctrl *ctrl;
+ int ret;
+
+ ctrl = alloc_dummy_ctrl();
+ if (!ctrl) {
+ test_std_err(TEST_ALLOC_WRITE_INTENT_CTRL);
+ return -ENOMEM;
+ }
+ test_msg("running extent_map tests");
+
+ ret = test_case_simple_entry(ctrl);
+ if (ret < 0) {
+ test_err("failed set/clear tests in one simple entry");
+ goto out;
+ }
+
+ ret = test_case_cross_entries(ctrl);
+ if (ret < 0) {
+ test_err("failed set/clear tests across entry boundaries");
+ goto out;
+ }
+out:
+ free_dummy_ctrl(ctrl);
+ return ret;
+}
@@ -216,16 +216,6 @@ static int write_intent_init(struct btrfs_fs_info *fs_info)
return 0;
}
-static struct write_intent_entry *write_intent_entry_nr(
- struct write_intent_ctrl *ctrl, int nr)
-{
-
- ASSERT(nr < WRITE_INTENT_INTERNAL_BITMAPS_MAX_ENTRIES);
- return (page_address(ctrl->page) +
- sizeof(struct write_intent_super) +
- nr * sizeof(struct write_intent_entry));
-}
-
/*
* Return <0 if the bytenr is before the given entry.
* Return 0 if the bytenr is inside the given entry.
@@ -197,6 +197,8 @@ WRITE_INTENT_SETGET_FUNCS(super_blocksize, struct write_intent_super,
WRITE_INTENT_SETGET_FUNCS(super_csum_type, struct write_intent_super,
csum_type, 16);
WRITE_INTENT_SETGET_FUNCS(entry_bytenr, struct write_intent_entry, bytenr, 64);
+WRITE_INTENT_SETGET_FUNCS(entry_raw_bitmap, struct write_intent_entry,
+ bitmap, 64);
static inline u32 write_intent_entry_size(struct write_intent_ctrl *ctrl)
{
@@ -205,6 +207,16 @@ static inline u32 write_intent_entry_size(struct write_intent_ctrl *ctrl)
return wi_super_blocksize(wis) * WRITE_INTENT_BITS_PER_ENTRY;
}
+static inline struct write_intent_entry *write_intent_entry_nr(
+ struct write_intent_ctrl *ctrl, int nr)
+{
+
+ ASSERT(nr < WRITE_INTENT_INTERNAL_BITMAPS_MAX_ENTRIES);
+ return (page_address(ctrl->page) +
+ sizeof(struct write_intent_super) +
+ nr * sizeof(struct write_intent_entry));
+}
+
static inline void wie_get_bitmap(struct write_intent_entry *entry,
unsigned long *bitmap)
{
It turns out such sparse bitmap still has a lot of things to go wrong, definitely needs some tests to cover all the different corner cases. Signed-off-by: Qu Wenruo <wqu@suse.com> --- fs/btrfs/Makefile | 3 +- fs/btrfs/tests/btrfs-tests.c | 4 + fs/btrfs/tests/btrfs-tests.h | 2 + fs/btrfs/tests/write-intent-bitmaps-tests.c | 245 ++++++++++++++++++++ fs/btrfs/write-intent.c | 10 - fs/btrfs/write-intent.h | 12 + 6 files changed, 265 insertions(+), 11 deletions(-) create mode 100644 fs/btrfs/tests/write-intent-bitmaps-tests.c