From cd2d54855e3a64472632ded8cfcd2de305193279 Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Sun, 14 May 2023 19:46:23 -0500 Subject: [PATCH] Added a number of tests over mdir relocations, fixed minor bugs - lfsr_btree_isnull still used tag and not only weight for null trees - relocation forgot the mid - missed relocation when uninlining, though this fix should be cleaned up - made revision count behavior a bit more consistent Note that the new tests may be -Gnor exclusive, they rely quite a bit on exactly when compaction happens... --- lfs.c | 41 +- tests/test_mtree.toml | 1388 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 1416 insertions(+), 13 deletions(-) diff --git a/lfs.c b/lfs.c index bdc4671a..07bbc665 100644 --- a/lfs.c +++ b/lfs.c @@ -2911,7 +2911,7 @@ static inline bool lfsr_btree_isinlined(const lfsr_btree_t *btree) { } static inline bool lfsr_btree_isnull(const lfsr_btree_t *btree) { - return lfsr_btree_isinlined(btree) && btree->inlined.tag == 0; + return btree->weight == 0x80000000; } static inline lfs_size_t lfsr_btree_weight(const lfsr_btree_t *btree) { @@ -4448,7 +4448,7 @@ static int lfsr_mdir_alloc(lfs_t *lfs, lfsr_mdir_t *mdir, lfs_ssize_t mid) { // align revision count in new mdirs to our block_cycles, this makes sure // we don't immediately try to relocate the mdir if (lfs->cfg->block_cycles > 0) { - rev = lfs_alignup(rev, lfs->cfg->block_cycles); + rev = lfs_alignup(rev+1, lfs->cfg->block_cycles)-1; } // setup mdir struct @@ -4657,7 +4657,6 @@ static int lfsr_mdir_compact_(lfs_t *lfs, lfsr_mdir_t *mdir, err = lfsr_rbyd_appendall(lfs, &mdir->rbyd, start_id, end_id, attr2s, attr2_count); - printf("hey %d\n", err); if (err) { LFS_ASSERT(err != LFS_ERR_RANGE); return err; @@ -4733,15 +4732,10 @@ compact:; if (lfs->cfg->block_cycles > 0 && (mdir->rbyd.rev+1) % lfs->cfg->block_cycles == 0) { // allocate a new mdir for relocation - err = lfsr_mdir_alloc(lfs, &mdir_, 0); + err = lfsr_mdir_alloc(lfs, &mdir_, mdir->mid); if (err) { return err; } - - LFS_DEBUG("Relocating mdir 0x{%"PRIx32",%"PRIx32"} " - "-> 0x{%"PRIx32",%"PRIx32"}", - mdir->rbyd.block, mdir->other_block, - mdir_.rbyd.block, mdir_.other_block); } while (true) { @@ -5008,6 +5002,20 @@ static int lfsr_mdir_commit(lfs_t *lfs, lfsr_mdir_t *mdir, lfs_ssize_t *rid, } } + // TODO adding this here is a cludge and should definitely + // be deduplicated + // + // if we've compacted the mroot block_cycles number of times, + // trigger a relocation + if (lfs->cfg->block_cycles > 0 + && (mroot_.rbyd.rev+1) % lfs->cfg->block_cycles == 0) { + // allocate a new mdir for relocation + err = lfsr_mdir_alloc(lfs, &mroot_, -1); + if (err) { + return err; + } + } + // TODO yeah we're going to need a wide-rm err = lfsr_mdir_compact_(lfs, &mroot_, -1, 0, mdir, attrs, attr_count, @@ -5190,6 +5198,11 @@ static int lfsr_mdir_commit(lfs_t *lfs, lfsr_mdir_t *mdir, lfs_ssize_t *rid, // relocate a normal mdir } else { + LFS_DEBUG("Relocating mdir 0x{%"PRIx32",%"PRIx32"} " + "-> 0x{%"PRIx32",%"PRIx32"}", + mdir->rbyd.block, mdir->other_block, + mdir_.rbyd.block, mdir_.other_block); + // update our mtree uint8_t buf[LFSR_MPAIR_DSIZE]; lfs_ssize_t d = lfsr_mdir_todisk(lfs, &mdir_, buf); @@ -5213,7 +5226,7 @@ static int lfsr_mdir_commit(lfs_t *lfs, lfsr_mdir_t *mdir, lfs_ssize_t *rid, // need to update mtree? if (dirtymtree) { - LFS_ASSERT(mdir->mid != -1); + LFS_ASSERT(mdir_.mid != -1); // commit mtree // @@ -5259,6 +5272,11 @@ static int lfsr_mdir_commit(lfs_t *lfs, lfsr_mdir_t *mdir, lfs_ssize_t *rid, break; } + LFS_DEBUG("Relocating mroot 0x{%"PRIx32",%"PRIx32"} " + "-> 0x{%"PRIx32",%"PRIx32"}", + mchildroot.rbyd.block, mchildroot.other_block, + mchildroot_.rbyd.block, mchildroot_.other_block); + // commit mrootchild uint8_t buf[LFSR_MPAIR_DSIZE]; lfs_ssize_t d = lfsr_mdir_todisk(lfs, &mchildroot_, buf); @@ -5332,6 +5350,7 @@ static int lfsr_mdir_commit(lfs_t *lfs, lfsr_mdir_t *mdir, lfs_ssize_t *rid, // update mdir to follow requested rid lfs_ssize_t rid_ = *rid; + LFS_ASSERT(rid_ <= (lfs_ssize_t)mdir->rbyd.weight); if (rid_ < 0) { *mdir = mroot_; } else if ((lfs_size_t)rid_ < mdir_.rbyd.weight) { @@ -6671,7 +6690,7 @@ static int lfsr_formatinited(lfs_t *lfs) { for (int i = 0; i < 2; i++) { // write superblock to both rbyds in the root mroot to hopefully // avoid mounting an older filesystem on disk - lfsr_rbyd_t rbyd = {.block=i, .rev=i+1, .off=0, .trunk=0}; + lfsr_rbyd_t rbyd = {.block=i, .rev=i-1, .off=0, .trunk=0}; int err = lfsr_bd_erase(lfs, rbyd.block); if (err) { diff --git a/tests/test_mtree.toml b/tests/test_mtree.toml index 4299ad86..06f92c2f 100644 --- a/tests/test_mtree.toml +++ b/tests/test_mtree.toml @@ -24,6 +24,16 @@ code = ''' assert(memcmp(buffer, "ardvark", 7) == 0); lfsr_unmount(&lfs) => 0; + + + // check things stay sane after remount + lfsr_mount(&lfs, cfg) => 0; + + lfsr_mdir_get(&lfs, &lfs.mroot, + -1, LFSR_TAG_UATTR(1), buffer, 7) => 7; + assert(memcmp(buffer, "ardvark", 7) == 0); + + lfsr_unmount(&lfs) => 0; ''' # test a single mroot with many commits @@ -52,8 +62,19 @@ code = ''' assert(memcmp(buffer, &alphas[(N-1) % 26], 1) == 0); lfsr_unmount(&lfs) => 0; + + + // check things stay sane after remount + lfsr_mount(&lfs, cfg) => 0; + + lfsr_mdir_get(&lfs, &lfs.mroot, + -1, LFSR_TAG_UATTR(1), buffer, 4) => 1; + assert(memcmp(buffer, &alphas[(N-1) % 26], 1) == 0); + + lfsr_unmount(&lfs) => 0; ''' + ## Splitting operations ## # specific split corner cases @@ -102,6 +123,30 @@ code = ''' assert(memcmp(buffer, &alphas[1 % 26], 1) == 0); lfsr_unmount(&lfs) => 0; + + + // check things stay sane after remount + lfsr_mount(&lfs, cfg) => 0; + + // assert mdir was unininlined correctly + assert(lfsr_mtree_weight(&lfs) == 1); + // assert mroot now has no entries + assert(lfs.mroot.rbyd.weight == 0); + + // assert that our attr is still in the mroot + lfsr_mdir_get(&lfs, &lfs.mroot, -1, LFSR_TAG_UATTR(1), + buffer, SIZE) => SIZE; + assert(memcmp(buffer, &alphas[0 % 26], 1) == 0); + + // assert that our entry is still in the mtree + lfsr_mtree_lookup(&lfs, 0, &mdir) => 0; + assert(mdir.rbyd.weight == 1); + + lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_INLINED, + buffer, SIZE) => SIZE; + assert(memcmp(buffer, &alphas[1 % 26], 1) == 0); + + lfsr_unmount(&lfs) => 0; ''' [cases.test_mtree_uninline_split] @@ -148,6 +193,30 @@ code = ''' assert(memcmp(buffer, &alphas[1 % 26], 1) == 0); lfsr_unmount(&lfs) => 0; + + + // check things stay sane after remount + lfsr_mount(&lfs, cfg) => 0; + + // assert mdirs were unininlined and split + assert(lfsr_mtree_weight(&lfs) == 2); + // assert mroot now has no entries + assert(lfs.mroot.rbyd.weight == 0); + + // assert that our entries are still in the mtree + lfsr_mtree_lookup(&lfs, 0, &mdir) => 0; + assert(mdir.rbyd.weight == 1); + lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_INLINED, + buffer, SIZE) => SIZE; + assert(memcmp(buffer, &alphas[0 % 26], 1) == 0); + + lfsr_mtree_lookup(&lfs, 1, &mdir) => 0; + assert(mdir.rbyd.weight == 1); + lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_INLINED, + buffer, SIZE) => SIZE; + assert(memcmp(buffer, &alphas[1 % 26], 1) == 0); + + lfsr_unmount(&lfs) => 0; ''' [cases.test_mtree_split] @@ -216,11 +285,37 @@ code = ''' assert(memcmp(buffer, &alphas[2 % 26], 1) == 0); lfsr_unmount(&lfs) => 0; + + // check things stay sane after remount + lfsr_mount(&lfs, cfg) => 0; + + // assert mdir was split correctly + assert(lfsr_mtree_weight(&lfs) == 2); + // assert mroot still has no entries + assert(lfs.mroot.rbyd.weight == 0); + + // assert that our attr is still in the mroot + lfsr_mdir_get(&lfs, &lfs.mroot, -1, LFSR_TAG_UATTR(1), + buffer, SIZE) => SIZE; + assert(memcmp(buffer, &alphas[0 % 26], 1) == 0); + + // assert that our entries are still in the mtree + lfsr_mtree_lookup(&lfs, 0, &mdir) => 0; + assert(mdir.rbyd.weight == 1); + lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_INLINED, + buffer, SIZE) => SIZE; + assert(memcmp(buffer, &alphas[1 % 26], 1) == 0); + + lfsr_mtree_lookup(&lfs, 1, &mdir) => 0; + assert(mdir.rbyd.weight == 1); + lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_INLINED, + buffer, SIZE) => SIZE; + assert(memcmp(buffer, &alphas[2 % 26], 1) == 0); + + lfsr_unmount(&lfs) => 0; ''' -# TODO test many mroots - # try creating a range of entries that may or may not split our mtree [cases.test_mtree_split_many] defines.N = [5, 10, 20, 40, 80, 160, 320] @@ -275,6 +370,31 @@ code = ''' assert(i == N); lfsr_unmount(&lfs) => 0; + + + // check things stay sane after remount + lfsr_mount(&lfs, cfg) => 0; + + // try looking up each entry + i = 0; + for (lfs_ssize_t mid = (lfsr_mtree_isinlined(&lfs) ? -1 : 0); + mid < lfsr_mtree_weight(&lfs); + mid++) { + lfsr_mdir_t mdir; + lfsr_mtree_lookup(&lfs, mid, &mdir) => 0; + for (lfs_ssize_t rid = 0; + rid < (lfs_ssize_t)lfsr_mdir_weight(&mdir); + rid++) { + uint8_t buffer[4]; + lfsr_mdir_get(&lfs, &mdir, rid, LFSR_TAG_INLINED, + buffer, 4) => 1; + assert(memcmp(buffer, &alphas[i % 26], 1) == 0); + i += 1; + } + } + assert(i == N); + + lfsr_unmount(&lfs) => 0; ''' # create random entries @@ -358,9 +478,39 @@ code = ''' assert(count_ == count); lfsr_unmount(&lfs) => 0; + + + // check things stay sane after remount + lfsr_mount(&lfs, cfg) => 0; + + // try looking up each entry + count_ = 0; + + for (lfs_ssize_t mid = (lfsr_mtree_isinlined(&lfs) ? -1 : 0); + mid < lfsr_mtree_weight(&lfs); + mid++) { + lfsr_mdir_t mdir; + lfsr_mtree_lookup(&lfs, mid, &mdir) => 0; + for (lfs_ssize_t rid = 0; + rid < (lfs_ssize_t)lfsr_mdir_weight(&mdir); + rid++) { + uint8_t buffer[4]; + lfsr_mdir_get(&lfs, &mdir, rid, LFSR_TAG_INLINED, + buffer, 4) => 1; + + count_ += 1; + } + } + + // the mtree is a bit difficult to simulate, but we can at least test + // we ended up with the right number of entries + assert(count_ == count); + + lfsr_unmount(&lfs) => 0; } ''' + ## Dropping operations ## # specific drop corner cases @@ -412,6 +562,22 @@ code = ''' assert(memcmp(buffer, &alphas[0 % 26], 1) == 0); lfsr_unmount(&lfs) => 0; + + + // check things stay sane after remount + lfsr_mount(&lfs, cfg) => 0; + + // assert mdir was dropped + assert(lfsr_mtree_weight(&lfs) == 0); + // assert mroot still has no entries + assert(lfs.mroot.rbyd.weight == 0); + + // assert that our attr is still in the mroot + lfsr_mdir_get(&lfs, &lfs.mroot, -1, LFSR_TAG_UATTR(1), + buffer, SIZE) => SIZE; + assert(memcmp(buffer, &alphas[0 % 26], 1) == 0); + + lfsr_unmount(&lfs) => 0; ''' [cases.test_mtree_drop_compact] @@ -465,6 +631,22 @@ code = ''' assert(memcmp(buffer, &alphas[0 % 26], 1) == 0); lfsr_unmount(&lfs) => 0; + + + // check things stay sane after remount + lfsr_mount(&lfs, cfg) => 0; + + // assert mdir was dropped + assert(lfsr_mtree_weight(&lfs) == 0); + // assert mroot still has no entries + assert(lfs.mroot.rbyd.weight == 0); + + // assert that our attr is still in the mroot + lfsr_mdir_get(&lfs, &lfs.mroot, -1, LFSR_TAG_UATTR(1), + buffer, SIZE) => SIZE; + assert(memcmp(buffer, &alphas[0 % 26], 1) == 0); + + lfsr_unmount(&lfs) => 0; ''' [cases.test_mtree_drop_uninline] @@ -505,6 +687,22 @@ code = ''' assert(memcmp(buffer, &alphas[0 % 26], 1) == 0); lfsr_unmount(&lfs) => 0; + + + // check things stay sane after remount + lfsr_mount(&lfs, cfg) => 0; + + // assert mdir was dropped + assert(lfsr_mtree_weight(&lfs) == 0); + // assert mroot has no entries + assert(lfs.mroot.rbyd.weight == 0); + + // assert that our attr is still in the mroot + lfsr_mdir_get(&lfs, &lfs.mroot, -1, LFSR_TAG_UATTR(1), + buffer, SIZE) => SIZE; + assert(memcmp(buffer, &alphas[0 % 26], 1) == 0); + + lfsr_unmount(&lfs) => 0; ''' [cases.test_mtree_drop_uninline_split_l] @@ -550,6 +748,25 @@ code = ''' assert(memcmp(buffer, &alphas[1 % 26], 1) == 0); lfsr_unmount(&lfs) => 0; + + + // check things stay sane after remount + lfsr_mount(&lfs, cfg) => 0; + + // assert mdir was dropped + assert(lfsr_mtree_weight(&lfs) == 1); + // assert mroot has no entries + assert(lfs.mroot.rbyd.weight == 0); + + // assert that one entry is still in the mtree + lfsr_mtree_lookup(&lfs, 0, &mdir) => 0; + assert(mdir.rbyd.weight == 1); + + lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_INLINED, + buffer, SIZE) => SIZE; + assert(memcmp(buffer, &alphas[1 % 26], 1) == 0); + + lfsr_unmount(&lfs) => 0; ''' [cases.test_mtree_drop_uninline_split_r] @@ -595,6 +812,25 @@ code = ''' assert(memcmp(buffer, &alphas[0 % 26], 1) == 0); lfsr_unmount(&lfs) => 0; + + + // check things stay sane after remount + lfsr_mount(&lfs, cfg) => 0; + + // assert mdir was dropped + assert(lfsr_mtree_weight(&lfs) == 1); + // assert mroot has no entries + assert(lfs.mroot.rbyd.weight == 0); + + // assert that one entry is still in the mtree + lfsr_mtree_lookup(&lfs, 0, &mdir) => 0; + assert(mdir.rbyd.weight == 1); + + lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_INLINED, + buffer, SIZE) => SIZE; + assert(memcmp(buffer, &alphas[0 % 26], 1) == 0); + + lfsr_unmount(&lfs) => 0; ''' [cases.test_mtree_drop_uninline_split_both] @@ -631,6 +867,17 @@ code = ''' assert(lfs.mroot.rbyd.weight == 0); lfsr_unmount(&lfs) => 0; + + + // check things stay sane after remount + lfsr_mount(&lfs, cfg) => 0; + + // assert mdir was dropped + assert(lfsr_mtree_weight(&lfs) == 0); + // assert mroot has no entries + assert(lfs.mroot.rbyd.weight == 0); + + lfsr_unmount(&lfs) => 0; ''' [cases.test_mtree_drop_split_l] @@ -698,6 +945,30 @@ code = ''' assert(memcmp(buffer, &alphas[2 % 26], 1) == 0); lfsr_unmount(&lfs) => 0; + + + // check things stay sane after remount + lfsr_mount(&lfs, cfg) => 0; + + // assert mdir was dropped + assert(lfsr_mtree_weight(&lfs) == 1); + // assert mroot has no entries + assert(lfs.mroot.rbyd.weight == 0); + + // assert that our attr is still in the mroot + lfsr_mdir_get(&lfs, &lfs.mroot, -1, LFSR_TAG_UATTR(1), + buffer, SIZE) => SIZE; + assert(memcmp(buffer, &alphas[0 % 26], 1) == 0); + + // assert that one entry is still in the mtree + lfsr_mtree_lookup(&lfs, 0, &mdir) => 0; + assert(mdir.rbyd.weight == 1); + + lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_INLINED, + buffer, SIZE) => SIZE; + assert(memcmp(buffer, &alphas[2 % 26], 1) == 0); + + lfsr_unmount(&lfs) => 0; ''' [cases.test_mtree_drop_split_r] @@ -765,6 +1036,30 @@ code = ''' assert(memcmp(buffer, &alphas[1 % 26], 1) == 0); lfsr_unmount(&lfs) => 0; + + + // check things stay sane after remount + lfsr_mount(&lfs, cfg) => 0; + + // assert mdir was dropped + assert(lfsr_mtree_weight(&lfs) == 1); + // assert mroot has no entries + assert(lfs.mroot.rbyd.weight == 0); + + // assert that our attr is still in the mroot + lfsr_mdir_get(&lfs, &lfs.mroot, -1, LFSR_TAG_UATTR(1), + buffer, SIZE) => SIZE; + assert(memcmp(buffer, &alphas[0 % 26], 1) == 0); + + // assert that one entry is still in the mtree + lfsr_mtree_lookup(&lfs, 0, &mdir) => 0; + assert(mdir.rbyd.weight == 1); + + lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_INLINED, + buffer, SIZE) => SIZE; + assert(memcmp(buffer, &alphas[1 % 26], 1) == 0); + + lfsr_unmount(&lfs) => 0; ''' [cases.test_mtree_drop_split_both] @@ -824,6 +1119,22 @@ code = ''' assert(memcmp(buffer, &alphas[0 % 26], 1) == 0); lfsr_unmount(&lfs) => 0; + + + // check things stay sane after remount + lfsr_mount(&lfs, cfg) => 0; + + // assert mdir was dropped + assert(lfsr_mtree_weight(&lfs) == 0); + // assert mroot has no entries + assert(lfs.mroot.rbyd.weight == 0); + + // assert that our attr is still in the mroot + lfsr_mdir_get(&lfs, &lfs.mroot, -1, LFSR_TAG_UATTR(1), + buffer, SIZE) => SIZE; + assert(memcmp(buffer, &alphas[0 % 26], 1) == 0); + + lfsr_unmount(&lfs) => 0; ''' # try creating an mtree and then dropping mdirs @@ -895,6 +1206,31 @@ code = ''' assert(i == N); lfsr_unmount(&lfs) => 0; + + + // check things stay sane after remount + lfsr_mount(&lfs, cfg) => 0; + + // try looking up each entry + i = N - REMAINING; + for (lfs_ssize_t mid = (lfsr_mtree_isinlined(&lfs) ? -1 : 0); + mid < lfsr_mtree_weight(&lfs); + mid++) { + lfsr_mdir_t mdir; + lfsr_mtree_lookup(&lfs, mid, &mdir) => 0; + for (lfs_ssize_t rid = 0; + rid < (lfs_ssize_t)lfsr_mdir_weight(&mdir); + rid++) { + uint8_t buffer[4]; + lfsr_mdir_get(&lfs, &mdir, rid, LFSR_TAG_INLINED, + buffer, 4) => 1; + assert(memcmp(buffer, &alphas[i % 26], 1) == 0); + i += 1; + } + } + assert(i == N); + + lfsr_unmount(&lfs) => 0; ''' # this one has some pretty nasty corner cases @@ -970,6 +1306,15 @@ code = ''' } lfsr_unmount(&lfs) => 0; + + + // check things stay sane after remount + lfsr_mount(&lfs, cfg) => 0; + + assert(lfsr_mtree_weight(&lfs) == 0); + assert(lfsr_mdir_weight(&lfs.mroot) == 0); + + lfsr_unmount(&lfs) => 0; ''' [cases.test_mtree_drop_fuzz] @@ -1071,5 +1416,1044 @@ code = ''' assert(count_ == count); lfsr_unmount(&lfs) => 0; + + + // check things stay sane after remount + lfsr_mount(&lfs, cfg) => 0; + + // try looking up each entry + count_ = 0; + + for (lfs_ssize_t mid = (lfsr_mtree_isinlined(&lfs) ? -1 : 0); + mid < lfsr_mtree_weight(&lfs); + mid++) { + lfsr_mdir_t mdir; + lfsr_mtree_lookup(&lfs, mid, &mdir) => 0; + + // drop should make sure we never have empty mdirs + assert(mdir.mid == -1 || mdir.rbyd.weight > 0); + + for (lfs_ssize_t rid = 0; + rid < (lfs_ssize_t)lfsr_mdir_weight(&mdir); + rid++) { + uint8_t buffer[4]; + lfsr_mdir_get(&lfs, &mdir, rid, LFSR_TAG_INLINED, + buffer, 4) => 1; + + count_ += 1; + } + } + + // the mtree is a bit difficult to simulate, but we can at least test + // we ended up with the right number of entries + assert(count_ == count); + + lfsr_unmount(&lfs) => 0; + } +''' + + +## Relocation operations ## + +# specific relocation corner cases +[cases.test_mtree_relocate] +# this should be set so only one entry can fit in a metadata block +defines.SIZE = 'BLOCK_SIZE / 4' +# make it so blocks relocate every two compacts +defines.BLOCK_CYCLES = 2 +in = 'lfs.c' +code = ''' + const char *alphas = "abcdefghijklmnopqrstuvwxyz"; + lfs_t lfs; + lfsr_format(&lfs, cfg) => 0; + lfsr_mount(&lfs, cfg) => 0; + + // prepare mroot with a large attr so the next entry can not fit + uint8_t buffer[SIZE]; + memset(buffer, alphas[0 % 26], SIZE); + lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS( + LFSR_ATTR(-1, UATTR(1), 0, buffer, SIZE))) => 0; + + // create a large entry that needs to be uninlined (but not split!) + memset(buffer, alphas[1 % 26], SIZE); + lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS( + LFSR_ATTR(0, MKINLINED, +1, buffer, SIZE))) => 0; + + // force mroot to compact + lfs.mroot.rbyd.off = BLOCK_SIZE; + lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, NULL, 0) => 0; + + // assert mtree has one mdir + assert(lfsr_mtree_weight(&lfs) == 1); + // assert mroot now has no entries + assert(lfs.mroot.rbyd.weight == 0); + + // force mdir to compact twice, this should relocate + lfsr_mdir_t mdir; + lfsr_mtree_lookup(&lfs, 0, &mdir) => 0; + assert(mdir.rbyd.weight == 1); + + lfsr_mdir_t old_mdir = mdir; + + mdir.rbyd.off = BLOCK_SIZE; + lfsr_mdir_commit(&lfs, &mdir, &(lfs_ssize_t){0}, NULL, 0) => 0; + mdir.rbyd.off = BLOCK_SIZE; + memset(buffer, alphas[2 % 26], SIZE); + lfsr_mdir_commit(&lfs, &mdir, &(lfs_ssize_t){0}, LFSR_ATTRS( + LFSR_ATTR(0, INLINED, 0, buffer, SIZE))) => 0; + + // assert we relocated + assert(!lfsr_mdir_eq(&old_mdir, &mdir)); + + // assert that our attr is still in the mroot + lfsr_mdir_get(&lfs, &lfs.mroot, -1, LFSR_TAG_UATTR(1), + buffer, SIZE) => SIZE; + assert(memcmp(buffer, &alphas[0 % 26], 1) == 0); + + // assert that our entry is still in the mtree + lfsr_mtree_lookup(&lfs, 0, &mdir) => 0; + assert(mdir.rbyd.weight == 1); + + lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_INLINED, + buffer, SIZE) => SIZE; + assert(memcmp(buffer, &alphas[2 % 26], 1) == 0); + + lfsr_unmount(&lfs) => 0; + + + // check things stay sane after remount + lfsr_mount(&lfs, cfg) => 0; + + // assert mtree has one mdir + assert(lfsr_mtree_weight(&lfs) == 1); + // assert mroot now has no entries + assert(lfs.mroot.rbyd.weight == 0); + + // assert we relocated + assert(!lfsr_mdir_eq(&old_mdir, &mdir)); + + // assert that our attr is still in the mroot + lfsr_mdir_get(&lfs, &lfs.mroot, -1, LFSR_TAG_UATTR(1), + buffer, SIZE) => SIZE; + assert(memcmp(buffer, &alphas[0 % 26], 1) == 0); + + // assert that our entry is still in the mtree + lfsr_mtree_lookup(&lfs, 0, &mdir) => 0; + assert(mdir.rbyd.weight == 1); + + lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_INLINED, + buffer, SIZE) => SIZE; + assert(memcmp(buffer, &alphas[2 % 26], 1) == 0); + + lfsr_unmount(&lfs) => 0; +''' + +[cases.test_mtree_relocate_sibling_l] +# this should be set so only one entry can fit in a metadata block +defines.SIZE = 'BLOCK_SIZE / 4' +# make it so blocks relocate every two compacts +defines.BLOCK_CYCLES = 2 +in = 'lfs.c' +code = ''' + const char *alphas = "abcdefghijklmnopqrstuvwxyz"; + lfs_t lfs; + lfsr_format(&lfs, cfg) => 0; + lfsr_mount(&lfs, cfg) => 0; + + // create 2 large entries that needs to be uninlined and split + uint8_t buffer[SIZE]; + memset(buffer, alphas[0 % 26], SIZE); + lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS( + LFSR_ATTR(0, MKINLINED, +1, buffer, SIZE))) => 0; + + memset(buffer, alphas[1 % 26], SIZE); + lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS( + LFSR_ATTR(1, MKINLINED, +1, buffer, SIZE))) => 0; + + // force mroot to compact + lfs.mroot.rbyd.off = BLOCK_SIZE; + lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, NULL, 0) => 0; + + // assert mdirs were unininlined and split + assert(lfsr_mtree_weight(&lfs) == 2); + // assert mroot now has no entries + assert(lfs.mroot.rbyd.weight == 0); + + // force mdir to compact twice, this should relocate + lfsr_mdir_t mdir; + lfsr_mtree_lookup(&lfs, 0, &mdir) => 0; + assert(mdir.rbyd.weight == 1); + + lfsr_mdir_t old_mdir = mdir; + + mdir.rbyd.off = BLOCK_SIZE; + lfsr_mdir_commit(&lfs, &mdir, &(lfs_ssize_t){0}, NULL, 0) => 0; + mdir.rbyd.off = BLOCK_SIZE; + memset(buffer, alphas[2 % 26], SIZE); + lfsr_mdir_commit(&lfs, &mdir, &(lfs_ssize_t){0}, LFSR_ATTRS( + LFSR_ATTR(0, INLINED, 0, buffer, SIZE))) => 0; + + // assert we relocated + assert(!lfsr_mdir_eq(&old_mdir, &mdir)); + + // assert that our entries are still in the mtree + lfsr_mtree_lookup(&lfs, 0, &mdir) => 0; + assert(mdir.rbyd.weight == 1); + lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_INLINED, + buffer, SIZE) => SIZE; + assert(memcmp(buffer, &alphas[2 % 26], 1) == 0); + + lfsr_mtree_lookup(&lfs, 1, &mdir) => 0; + assert(mdir.rbyd.weight == 1); + lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_INLINED, + buffer, SIZE) => SIZE; + assert(memcmp(buffer, &alphas[1 % 26], 1) == 0); + + lfsr_unmount(&lfs) => 0; + + + // check things stay sane after remount + lfsr_mount(&lfs, cfg) => 0; + + // assert mdirs were unininlined and split + assert(lfsr_mtree_weight(&lfs) == 2); + // assert mroot now has no entries + assert(lfs.mroot.rbyd.weight == 0); + + // assert we relocated + assert(!lfsr_mdir_eq(&old_mdir, &mdir)); + + // assert that our entries are still in the mtree + lfsr_mtree_lookup(&lfs, 0, &mdir) => 0; + assert(mdir.rbyd.weight == 1); + lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_INLINED, + buffer, SIZE) => SIZE; + assert(memcmp(buffer, &alphas[2 % 26], 1) == 0); + + lfsr_mtree_lookup(&lfs, 1, &mdir) => 0; + assert(mdir.rbyd.weight == 1); + lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_INLINED, + buffer, SIZE) => SIZE; + assert(memcmp(buffer, &alphas[1 % 26], 1) == 0); + + lfsr_unmount(&lfs) => 0; +''' + +[cases.test_mtree_relocate_sibling_r] +# this should be set so only one entry can fit in a metadata block +defines.SIZE = 'BLOCK_SIZE / 4' +# make it so blocks relocate every two compacts +defines.BLOCK_CYCLES = 2 +in = 'lfs.c' +code = ''' + const char *alphas = "abcdefghijklmnopqrstuvwxyz"; + lfs_t lfs; + lfsr_format(&lfs, cfg) => 0; + lfsr_mount(&lfs, cfg) => 0; + + // create 2 large entries that needs to be uninlined and split + uint8_t buffer[SIZE]; + memset(buffer, alphas[0 % 26], SIZE); + lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS( + LFSR_ATTR(0, MKINLINED, +1, buffer, SIZE))) => 0; + + memset(buffer, alphas[1 % 26], SIZE); + lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS( + LFSR_ATTR(1, MKINLINED, +1, buffer, SIZE))) => 0; + + // force mroot to compact + lfs.mroot.rbyd.off = BLOCK_SIZE; + lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, NULL, 0) => 0; + + // assert mdirs were unininlined and split + assert(lfsr_mtree_weight(&lfs) == 2); + // assert mroot now has no entries + assert(lfs.mroot.rbyd.weight == 0); + + // force mdir to compact twice, this should relocate + lfsr_mdir_t mdir; + lfsr_mtree_lookup(&lfs, 1, &mdir) => 0; + assert(mdir.rbyd.weight == 1); + + lfsr_mdir_t old_mdir = mdir; + + mdir.rbyd.off = BLOCK_SIZE; + lfsr_mdir_commit(&lfs, &mdir, &(lfs_ssize_t){0}, NULL, 0) => 0; + mdir.rbyd.off = BLOCK_SIZE; + memset(buffer, alphas[2 % 26], SIZE); + lfsr_mdir_commit(&lfs, &mdir, &(lfs_ssize_t){0}, LFSR_ATTRS( + LFSR_ATTR(0, INLINED, 0, buffer, SIZE))) => 0; + + // assert we relocated + assert(!lfsr_mdir_eq(&old_mdir, &mdir)); + + // assert that our entries are still in the mtree + lfsr_mtree_lookup(&lfs, 0, &mdir) => 0; + assert(mdir.rbyd.weight == 1); + lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_INLINED, + buffer, SIZE) => SIZE; + assert(memcmp(buffer, &alphas[0 % 26], 1) == 0); + + lfsr_mtree_lookup(&lfs, 1, &mdir) => 0; + assert(mdir.rbyd.weight == 1); + lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_INLINED, + buffer, SIZE) => SIZE; + assert(memcmp(buffer, &alphas[2 % 26], 1) == 0); + + lfsr_unmount(&lfs) => 0; + + + // check things stay sane after remount + lfsr_mount(&lfs, cfg) => 0; + + // assert mdirs were unininlined and split + assert(lfsr_mtree_weight(&lfs) == 2); + // assert mroot now has no entries + assert(lfs.mroot.rbyd.weight == 0); + + // assert we relocated + assert(!lfsr_mdir_eq(&old_mdir, &mdir)); + + // assert that our entries are still in the mtree + lfsr_mtree_lookup(&lfs, 0, &mdir) => 0; + assert(mdir.rbyd.weight == 1); + lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_INLINED, + buffer, SIZE) => SIZE; + assert(memcmp(buffer, &alphas[0 % 26], 1) == 0); + + lfsr_mtree_lookup(&lfs, 1, &mdir) => 0; + assert(mdir.rbyd.weight == 1); + lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_INLINED, + buffer, SIZE) => SIZE; + assert(memcmp(buffer, &alphas[2 % 26], 1) == 0); + + lfsr_unmount(&lfs) => 0; +''' + +[cases.test_mtree_extend] +# this should be set so only one entry can fit in a metadata block +defines.SIZE = 'BLOCK_SIZE / 4' +# make it so blocks relocate every two compacts +defines.BLOCK_CYCLES = 2 +in = 'lfs.c' +code = ''' + const char *alphas = "abcdefghijklmnopqrstuvwxyz"; + lfs_t lfs; + lfsr_format(&lfs, cfg) => 0; + lfsr_mount(&lfs, cfg) => 0; + + // prepare mroot with an attr + uint8_t buffer[SIZE]; + memset(buffer, alphas[0 % 26], SIZE); + lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS( + LFSR_ATTR(-1, UATTR(1), 0, buffer, SIZE))) => 0; + + // force mroot to compact twice, this should extend the mroot + lfsr_mdir_t old_mroot = lfs.mroot; + + lfs.mroot.rbyd.off = BLOCK_SIZE; + lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, NULL, 0) => 0; + lfs.mroot.rbyd.off = BLOCK_SIZE; + memset(buffer, alphas[1 % 26], SIZE); + lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS( + LFSR_ATTR(-1, UATTR(1), 0, buffer, SIZE))) => 0; + + // assert we relocated + assert(!lfsr_mdir_eq(&old_mroot, &lfs.mroot)); + + // assert that our attr is still in the mroot + lfsr_mdir_get(&lfs, &lfs.mroot, -1, LFSR_TAG_UATTR(1), + buffer, SIZE) => SIZE; + assert(memcmp(buffer, &alphas[1 % 26], 1) == 0); + + lfsr_unmount(&lfs) => 0; + + + // check things stay sane after remount + lfsr_mount(&lfs, cfg) => 0; + + // assert we relocated + assert(!lfsr_mdir_eq(&old_mroot, &lfs.mroot)); + + // assert that our attr is still in the mroot + lfsr_mdir_get(&lfs, &lfs.mroot, -1, LFSR_TAG_UATTR(1), + buffer, SIZE) => SIZE; + assert(memcmp(buffer, &alphas[1 % 26], 1) == 0); + + lfsr_unmount(&lfs) => 0; +''' + +[cases.test_mtree_relocate_mroot] +# this should be set so only one entry can fit in a metadata block +defines.SIZE = 'BLOCK_SIZE / 4' +# make it so blocks relocate every two compacts +defines.BLOCK_CYCLES = 2 +in = 'lfs.c' +code = ''' + const char *alphas = "abcdefghijklmnopqrstuvwxyz"; + lfs_t lfs; + lfsr_format(&lfs, cfg) => 0; + lfsr_mount(&lfs, cfg) => 0; + + // prepare mroot with an attr + uint8_t buffer[SIZE]; + memset(buffer, alphas[0 % 26], SIZE); + lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS( + LFSR_ATTR(-1, UATTR(1), 0, buffer, SIZE))) => 0; + + // force mroot to compact twice, this should extend the mroot + lfsr_mdir_t old_mroot = lfs.mroot; + + lfs.mroot.rbyd.off = BLOCK_SIZE; + lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, NULL, 0) => 0; + lfs.mroot.rbyd.off = BLOCK_SIZE; + lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, NULL, 0) => 0; + + // assert we relocated + assert(!lfsr_mdir_eq(&old_mroot, &lfs.mroot)); + + // force mroot to compact twice again, this should relocate the mroot + old_mroot = lfs.mroot; + + lfs.mroot.rbyd.off = BLOCK_SIZE; + lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, NULL, 0) => 0; + lfs.mroot.rbyd.off = BLOCK_SIZE; + memset(buffer, alphas[1 % 26], SIZE); + lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS( + LFSR_ATTR(-1, UATTR(1), 0, buffer, SIZE))) => 0; + + // assert we relocated + assert(!lfsr_mdir_eq(&old_mroot, &lfs.mroot)); + + // assert that our attr is still in the mroot + lfsr_mdir_get(&lfs, &lfs.mroot, -1, LFSR_TAG_UATTR(1), + buffer, SIZE) => SIZE; + assert(memcmp(buffer, &alphas[1 % 26], 1) == 0); + + lfsr_unmount(&lfs) => 0; + + + // check things stay sane after remount + lfsr_mount(&lfs, cfg) => 0; + + // assert we relocated + assert(!lfsr_mdir_eq(&old_mroot, &lfs.mroot)); + + // assert that our attr is still in the mroot + lfsr_mdir_get(&lfs, &lfs.mroot, -1, LFSR_TAG_UATTR(1), + buffer, SIZE) => SIZE; + assert(memcmp(buffer, &alphas[1 % 26], 1) == 0); + + lfsr_unmount(&lfs) => 0; +''' + +[cases.test_mtree_relocate_extend] +# this should be set so only one entry can fit in a metadata block +defines.SIZE = 'BLOCK_SIZE / 4' +# make it so blocks relocate every two compacts +defines.BLOCK_CYCLES = 2 +in = 'lfs.c' +code = ''' + const char *alphas = "abcdefghijklmnopqrstuvwxyz"; + lfs_t lfs; + lfsr_format(&lfs, cfg) => 0; + lfsr_mount(&lfs, cfg) => 0; + + // prepare mroot with a large attr so the next entry can not fit + uint8_t buffer[SIZE]; + memset(buffer, alphas[0 % 26], SIZE); + lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS( + LFSR_ATTR(-1, UATTR(1), 0, buffer, SIZE))) => 0; + + // create a large entry that needs to be uninlined (but not split!) + memset(buffer, alphas[1 % 26], SIZE); + lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS( + LFSR_ATTR(0, MKINLINED, +1, buffer, SIZE))) => 0; + + // force mroot to compact + lfs.mroot.rbyd.off = BLOCK_SIZE; + lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, NULL, 0) => 0; + + // assert mtree has one mdir + assert(lfsr_mtree_weight(&lfs) == 1); + // assert mroot now has no entries + assert(lfs.mroot.rbyd.weight == 0); + + // setup mroot to need to compact, this should trigger a relocation when + // we relocate the mdir below + lfs.mroot.rbyd.off = BLOCK_SIZE; + lfsr_mdir_t old_mroot = lfs.mroot; + + // force mdir to compact twice, this should relocate + lfsr_mdir_t mdir; + lfsr_mtree_lookup(&lfs, 0, &mdir) => 0; + assert(mdir.rbyd.weight == 1); + + lfsr_mdir_t old_mdir = mdir; + + mdir.rbyd.off = BLOCK_SIZE; + lfsr_mdir_commit(&lfs, &mdir, &(lfs_ssize_t){0}, NULL, 0) => 0; + mdir.rbyd.off = BLOCK_SIZE; + memset(buffer, alphas[2 % 26], SIZE); + lfsr_mdir_commit(&lfs, &mdir, &(lfs_ssize_t){0}, LFSR_ATTRS( + LFSR_ATTR(0, INLINED, 0, buffer, SIZE))) => 0; + + // assert we relocated our mdir + assert(!lfsr_mdir_eq(&old_mdir, &mdir)); + + // assert we relocated our mroot + assert(!lfsr_mdir_eq(&old_mroot, &lfs.mroot)); + + // assert that our attr is still in the mroot + lfsr_mdir_get(&lfs, &lfs.mroot, -1, LFSR_TAG_UATTR(1), + buffer, SIZE) => SIZE; + assert(memcmp(buffer, &alphas[0 % 26], 1) == 0); + + // assert that our entry is still in the mtree + lfsr_mtree_lookup(&lfs, 0, &mdir) => 0; + assert(mdir.rbyd.weight == 1); + + lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_INLINED, + buffer, SIZE) => SIZE; + assert(memcmp(buffer, &alphas[2 % 26], 1) == 0); + + lfsr_unmount(&lfs) => 0; + + + // check things stay sane after remount + lfsr_mount(&lfs, cfg) => 0; + + // assert mtree has one mdir + assert(lfsr_mtree_weight(&lfs) == 1); + // assert mroot now has no entries + assert(lfs.mroot.rbyd.weight == 0); + + // assert we relocated our mdir + assert(!lfsr_mdir_eq(&old_mdir, &mdir)); + + // assert we relocated our mroot + assert(!lfsr_mdir_eq(&old_mroot, &lfs.mroot)); + + // assert that our attr is still in the mroot + lfsr_mdir_get(&lfs, &lfs.mroot, -1, LFSR_TAG_UATTR(1), + buffer, SIZE) => SIZE; + assert(memcmp(buffer, &alphas[0 % 26], 1) == 0); + + // assert that our entry is still in the mtree + lfsr_mtree_lookup(&lfs, 0, &mdir) => 0; + assert(mdir.rbyd.weight == 1); + + lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_INLINED, + buffer, SIZE) => SIZE; + assert(memcmp(buffer, &alphas[2 % 26], 1) == 0); + + lfsr_unmount(&lfs) => 0; +''' + +[cases.test_mtree_split_extend] +# this should be set so only one entry can fit in a metadata block +defines.SIZE = 'BLOCK_SIZE / 4' +# make it so blocks relocate every two compacts +defines.BLOCK_CYCLES = 2 +in = 'lfs.c' +code = ''' + const char *alphas = "abcdefghijklmnopqrstuvwxyz"; + lfs_t lfs; + lfsr_format(&lfs, cfg) => 0; + lfsr_mount(&lfs, cfg) => 0; + + // create an uninlined mdir + uint8_t buffer[SIZE]; + memset(buffer, alphas[0 % 26], SIZE); + lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS( + LFSR_ATTR(-1, UATTR(1), 0, buffer, SIZE))) => 0; + + memset(buffer, alphas[1 % 26], SIZE); + lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS( + LFSR_ATTR(0, MKINLINED, +1, buffer, SIZE))) => 0; + + // force mroot to compact + lfs.mroot.rbyd.off = BLOCK_SIZE; + lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, NULL, 0) => 0; + + // assert mdir was unininlined correctly + assert(lfsr_mtree_weight(&lfs) == 1); + // assert mroot now has no entries + assert(lfs.mroot.rbyd.weight == 0); + + // setup mroot to need to compact, this should trigger a relocation when + // we relocate the mdir below + lfs.mroot.rbyd.off = BLOCK_SIZE; + lfsr_mdir_t old_mroot = lfs.mroot; + + // now add another large entry to the mdir, forcing a split + lfsr_mdir_t mdir; + lfsr_mtree_lookup(&lfs, 0, &mdir) => 0; + assert(mdir.rbyd.weight == 1); + + memset(buffer, alphas[2 % 26], SIZE); + lfsr_mdir_commit(&lfs, &mdir, &(lfs_ssize_t){1}, LFSR_ATTRS( + LFSR_ATTR(1, MKINLINED, +1, buffer, SIZE))) => 0; + + // force mdir to compact + mdir.rbyd.off = BLOCK_SIZE; + lfsr_mdir_commit(&lfs, &mdir, &(lfs_ssize_t){1}, NULL, 0) => 0; + + // assert mdir was split correctly + assert(lfsr_mtree_weight(&lfs) == 2); + // assert mroot still has no entries + assert(lfs.mroot.rbyd.weight == 0); + + // assert we relocated our mroot + assert(!lfsr_mdir_eq(&old_mroot, &lfs.mroot)); + + // assert that our attr is still in the mroot + lfsr_mdir_get(&lfs, &lfs.mroot, -1, LFSR_TAG_UATTR(1), + buffer, SIZE) => SIZE; + assert(memcmp(buffer, &alphas[0 % 26], 1) == 0); + + // assert that our entries are still in the mtree + lfsr_mtree_lookup(&lfs, 0, &mdir) => 0; + assert(mdir.rbyd.weight == 1); + lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_INLINED, + buffer, SIZE) => SIZE; + assert(memcmp(buffer, &alphas[1 % 26], 1) == 0); + + lfsr_mtree_lookup(&lfs, 1, &mdir) => 0; + assert(mdir.rbyd.weight == 1); + lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_INLINED, + buffer, SIZE) => SIZE; + assert(memcmp(buffer, &alphas[2 % 26], 1) == 0); + + lfsr_unmount(&lfs) => 0; + + + // check things stay sane after remount + lfsr_mount(&lfs, cfg) => 0; + + // assert mdir was split correctly + assert(lfsr_mtree_weight(&lfs) == 2); + // assert mroot still has no entries + assert(lfs.mroot.rbyd.weight == 0); + + // assert we relocated our mroot + assert(!lfsr_mdir_eq(&old_mroot, &lfs.mroot)); + + // assert that our attr is still in the mroot + lfsr_mdir_get(&lfs, &lfs.mroot, -1, LFSR_TAG_UATTR(1), + buffer, SIZE) => SIZE; + assert(memcmp(buffer, &alphas[0 % 26], 1) == 0); + + // assert that our entries are still in the mtree + lfsr_mtree_lookup(&lfs, 0, &mdir) => 0; + assert(mdir.rbyd.weight == 1); + lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_INLINED, + buffer, SIZE) => SIZE; + assert(memcmp(buffer, &alphas[1 % 26], 1) == 0); + + lfsr_mtree_lookup(&lfs, 1, &mdir) => 0; + assert(mdir.rbyd.weight == 1); + lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_INLINED, + buffer, SIZE) => SIZE; + assert(memcmp(buffer, &alphas[2 % 26], 1) == 0); + + lfsr_unmount(&lfs) => 0; +''' + +[cases.test_mtree_drop_extend] +# this should be set so only one entry can fit in a metadata block +defines.SIZE = 'BLOCK_SIZE / 4' +# make it so blocks relocate every two compacts +defines.BLOCK_CYCLES = 2 +in = 'lfs.c' +code = ''' + const char *alphas = "abcdefghijklmnopqrstuvwxyz"; + lfs_t lfs; + lfsr_format(&lfs, cfg) => 0; + lfsr_mount(&lfs, cfg) => 0; + + // create an uninlined mdir + uint8_t buffer[SIZE]; + memset(buffer, alphas[0 % 26], SIZE); + lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS( + LFSR_ATTR(-1, UATTR(1), 0, buffer, SIZE))) => 0; + + memset(buffer, alphas[1 % 26], SIZE); + lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS( + LFSR_ATTR(0, MKINLINED, +1, buffer, SIZE))) => 0; + + // force mroot to compact + lfs.mroot.rbyd.off = BLOCK_SIZE; + lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, NULL, 0) => 0; + + // assert mdir was unininlined correctly + assert(lfsr_mtree_weight(&lfs) == 1); + // assert mroot now has no entries + assert(lfs.mroot.rbyd.weight == 0); + + // setup mroot to need to compact, this should trigger a relocation when + // we relocate the mdir below + lfs.mroot.rbyd.off = BLOCK_SIZE; + lfsr_mdir_t old_mroot = lfs.mroot; + + // remove the entry, forcing the mdir to be dropped + lfsr_mdir_t mdir; + lfsr_mtree_lookup(&lfs, 0, &mdir) => 0; + assert(mdir.rbyd.weight == 1); + + lfsr_mdir_commit(&lfs, &mdir, &(lfs_ssize_t){0}, LFSR_ATTRS( + LFSR_ATTR(0, MKUNR, -1, NULL, 0))) => 0; + + // assert mdir was dropped + assert(lfsr_mtree_weight(&lfs) == 0); + // assert mroot still has no entries + assert(lfs.mroot.rbyd.weight == 0); + + // assert we relocated our mroot + assert(!lfsr_mdir_eq(&old_mroot, &lfs.mroot)); + + // assert that our attr is still in the mroot + lfsr_mdir_get(&lfs, &lfs.mroot, -1, LFSR_TAG_UATTR(1), + buffer, SIZE) => SIZE; + assert(memcmp(buffer, &alphas[0 % 26], 1) == 0); + + lfsr_unmount(&lfs) => 0; + + + // check things stay sane after remount + lfsr_mount(&lfs, cfg) => 0; + + // assert mdir was dropped + assert(lfsr_mtree_weight(&lfs) == 0); + // assert mroot still has no entries + assert(lfs.mroot.rbyd.weight == 0); + + // assert we relocated our mroot + assert(!lfsr_mdir_eq(&old_mroot, &lfs.mroot)); + + // assert that our attr is still in the mroot + lfsr_mdir_get(&lfs, &lfs.mroot, -1, LFSR_TAG_UATTR(1), + buffer, SIZE) => SIZE; + assert(memcmp(buffer, &alphas[0 % 26], 1) == 0); + + lfsr_unmount(&lfs) => 0; +''' + +[cases.test_mtree_uninline_extend] +# this should be set so only one entry can fit in a metadata block +defines.SIZE = 'BLOCK_SIZE / 4' +# make it so blocks relocate every two compacts +defines.BLOCK_CYCLES = 2 +in = 'lfs.c' +code = ''' + const char *alphas = "abcdefghijklmnopqrstuvwxyz"; + lfs_t lfs; + lfsr_format(&lfs, cfg) => 0; + lfsr_mount(&lfs, cfg) => 0; + + // force mroot to compact once, so the second compact below will trigger + // a relocation + lfs.mroot.rbyd.off = BLOCK_SIZE; + lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, NULL, 0) => 0; + + // prepare mroot with a large attr so the next entry can not fit + uint8_t buffer[SIZE]; + memset(buffer, alphas[0 % 26], SIZE); + lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS( + LFSR_ATTR(-1, UATTR(1), 0, buffer, SIZE))) => 0; + + // create a large entry that needs to be uninlined (but not split!) + memset(buffer, alphas[1 % 26], SIZE); + lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS( + LFSR_ATTR(0, MKINLINED, +1, buffer, SIZE))) => 0; + + // force mroot to compact, this should trigger a relocation + lfsr_mdir_t old_mroot = lfs.mroot; + lfs.mroot.rbyd.off = BLOCK_SIZE; + lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, NULL, 0) => 0; + + // assert mdir was unininlined correctly + assert(lfsr_mtree_weight(&lfs) == 1); + // assert mroot now has no entries + assert(lfs.mroot.rbyd.weight == 0); + + // assert we relocated our mroot + assert(!lfsr_mdir_eq(&old_mroot, &lfs.mroot)); + + // assert that our attr is still in the mroot + lfsr_mdir_get(&lfs, &lfs.mroot, -1, LFSR_TAG_UATTR(1), + buffer, SIZE) => SIZE; + assert(memcmp(buffer, &alphas[0 % 26], 1) == 0); + + // assert that our entry is still in the mtree + lfsr_mdir_t mdir; + lfsr_mtree_lookup(&lfs, 0, &mdir) => 0; + assert(mdir.rbyd.weight == 1); + + lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_INLINED, + buffer, SIZE) => SIZE; + assert(memcmp(buffer, &alphas[1 % 26], 1) == 0); + + lfsr_unmount(&lfs) => 0; + + + // check things stay sane after remount + lfsr_mount(&lfs, cfg) => 0; + + // assert mdir was unininlined correctly + assert(lfsr_mtree_weight(&lfs) == 1); + // assert mroot now has no entries + assert(lfs.mroot.rbyd.weight == 0); + + // assert we relocated our mroot + assert(!lfsr_mdir_eq(&old_mroot, &lfs.mroot)); + + // assert that our attr is still in the mroot + lfsr_mdir_get(&lfs, &lfs.mroot, -1, LFSR_TAG_UATTR(1), + buffer, SIZE) => SIZE; + assert(memcmp(buffer, &alphas[0 % 26], 1) == 0); + + // assert that our entry is still in the mtree + lfsr_mtree_lookup(&lfs, 0, &mdir) => 0; + assert(mdir.rbyd.weight == 1); + + lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_INLINED, + buffer, SIZE) => SIZE; + assert(memcmp(buffer, &alphas[1 % 26], 1) == 0); + + lfsr_unmount(&lfs) => 0; +''' + +[cases.test_mtree_uninline_split_extend] +# this should be set so only one entry can fit in a metadata block +defines.SIZE = 'BLOCK_SIZE / 4' +# make it so blocks relocate every two compacts +defines.BLOCK_CYCLES = 2 +in = 'lfs.c' +code = ''' + const char *alphas = "abcdefghijklmnopqrstuvwxyz"; + lfs_t lfs; + lfsr_format(&lfs, cfg) => 0; + lfsr_mount(&lfs, cfg) => 0; + + // force mroot to compact once, so the second compact below will trigger + // a relocation + lfs.mroot.rbyd.off = BLOCK_SIZE; + lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, NULL, 0) => 0; + + // create 2 large entries that needs to be uninlined and split + uint8_t buffer[SIZE]; + memset(buffer, alphas[0 % 26], SIZE); + lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS( + LFSR_ATTR(0, MKINLINED, +1, buffer, SIZE))) => 0; + + memset(buffer, alphas[1 % 26], SIZE); + lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS( + LFSR_ATTR(1, MKINLINED, +1, buffer, SIZE))) => 0; + + // force mroot to compact, this should trigger a relocation + lfsr_mdir_t old_mroot = lfs.mroot; + lfs.mroot.rbyd.off = BLOCK_SIZE; + lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, NULL, 0) => 0; + + // assert mdirs were unininlined and split + assert(lfsr_mtree_weight(&lfs) == 2); + // assert mroot now has no entries + assert(lfs.mroot.rbyd.weight == 0); + + // assert we relocated our mroot + assert(!lfsr_mdir_eq(&old_mroot, &lfs.mroot)); + + // assert that our entries are still in the mtree + lfsr_mdir_t mdir; + lfsr_mtree_lookup(&lfs, 0, &mdir) => 0; + assert(mdir.rbyd.weight == 1); + lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_INLINED, + buffer, SIZE) => SIZE; + assert(memcmp(buffer, &alphas[0 % 26], 1) == 0); + + lfsr_mtree_lookup(&lfs, 1, &mdir) => 0; + assert(mdir.rbyd.weight == 1); + lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_INLINED, + buffer, SIZE) => SIZE; + assert(memcmp(buffer, &alphas[1 % 26], 1) == 0); + + lfsr_unmount(&lfs) => 0; + + + // check things stay sane after remount + lfsr_mount(&lfs, cfg) => 0; + + // assert mdirs were unininlined and split + assert(lfsr_mtree_weight(&lfs) == 2); + // assert mroot now has no entries + assert(lfs.mroot.rbyd.weight == 0); + + // assert we relocated our mroot + assert(!lfsr_mdir_eq(&old_mroot, &lfs.mroot)); + + // assert that our entries are still in the mtree + lfsr_mtree_lookup(&lfs, 0, &mdir) => 0; + assert(mdir.rbyd.weight == 1); + lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_INLINED, + buffer, SIZE) => SIZE; + assert(memcmp(buffer, &alphas[0 % 26], 1) == 0); + + lfsr_mtree_lookup(&lfs, 1, &mdir) => 0; + assert(mdir.rbyd.weight == 1); + lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_INLINED, + buffer, SIZE) => SIZE; + assert(memcmp(buffer, &alphas[1 % 26], 1) == 0); + + lfsr_unmount(&lfs) => 0; +''' + +# this fuzz covers a lot of configuratinos +[cases.test_mtree_relocating_fuzz] +defines.N = [5, 10, 20, 40] +defines.FORCE_COMPACTION = [false, true] +defines.BLOCK_CYCLES = [5, 2, 1] +defines.SAMPLES = 500 +# -1 => all pseudo-random seeds +# n => reproduce a specific seed +defines.SEED = -1 +in = 'lfs.c' +code = ''' + const char *alphas = "abcdefghijklmnopqrstuvwxyz"; + + // iterate through severals seeds that we can reproduce easily + for (uint32_t seed = (SEED == -1 ? 1 : SEED); + (SEED == -1 ? seed < SAMPLES+1 : seed == SEED); + seed++) { + printf("--- seed: %d ---\n", seed); + // create lfs here since we need to reset each iteration, we're + // space constrained and we can't expect gc to work at this point + lfs_t lfs; + lfsr_format(&lfs, cfg) => 0; + lfsr_mount(&lfs, cfg) => 0; + + // at least keep track of the number of entries we expect + lfs_size_t count = 0; + + uint32_t prng = seed; + for (lfs_size_t i = 0; i < N; i++) { + // choose a pseudo-random mid + lfs_ssize_t mid = lfsr_mtree_weight(&lfs) == 0 + ? -1 + : (lfs_ssize_t)(TEST_PRNG(&prng) % lfsr_mtree_weight(&lfs)); + // fetch mdir + lfsr_mdir_t mdir; + lfsr_mtree_lookup(&lfs, mid, &mdir) => 0; + // choose a pseudo-random rid + lfs_ssize_t rid = TEST_PRNG(&prng) % (lfsr_mdir_weight(&mdir)+1); + // choose to create or delete + uint8_t op = (lfs_size_t)rid == lfsr_mdir_weight(&mdir) + ? 0 + : TEST_PRNG(&prng) % 3; + + // force a compaction? + if (FORCE_COMPACTION) { + mdir.rbyd.off = cfg->block_size; + lfs.mroot.rbyd.off = cfg->block_size; + } + + // create + if (op == 0) { + // add to rbyd + lfsr_mdir_commit(&lfs, &mdir, &rid, LFSR_ATTRS( + LFSR_ATTR(rid, MKINLINED, +1, + &alphas[i % 26], 1))) => 0; + + // make sure we can look up the new entry + uint8_t buffer[4]; + lfsr_mdir_get(&lfs, &mdir, rid, LFSR_TAG_INLINED, + buffer, 4) => 1; + assert(memcmp(buffer, &alphas[i % 26], 1) == 0); + + count += 1; + + // update + } else if (op == 1) { + // update rbyd + lfsr_mdir_commit(&lfs, &mdir, &rid, LFSR_ATTRS( + LFSR_ATTR(rid, INLINED, 0, + &alphas[i % 26], 1))) => 0; + + // make sure we can look up the new entry + uint8_t buffer[4]; + lfsr_mdir_get(&lfs, &mdir, rid, LFSR_TAG_INLINED, + buffer, 4) => 1; + assert(memcmp(buffer, &alphas[i % 26], 1) == 0); + + // delete + } else { + lfsr_mdir_commit(&lfs, &mdir, &rid, LFSR_ATTRS( + LFSR_ATTR(rid, MKUNR, -1, NULL, 0))) => 0; + + count -= 1; + } + } + + // try looking up each entry + lfs_size_t count_ = 0; + + for (lfs_ssize_t mid = (lfsr_mtree_isinlined(&lfs) ? -1 : 0); + mid < lfsr_mtree_weight(&lfs); + mid++) { + lfsr_mdir_t mdir; + lfsr_mtree_lookup(&lfs, mid, &mdir) => 0; + + // drop should make sure we never have empty mdirs + assert(mdir.mid == -1 || mdir.rbyd.weight > 0); + + for (lfs_ssize_t rid = 0; + rid < (lfs_ssize_t)lfsr_mdir_weight(&mdir); + rid++) { + uint8_t buffer[4]; + lfsr_mdir_get(&lfs, &mdir, rid, LFSR_TAG_INLINED, + buffer, 4) => 1; + + count_ += 1; + } + } + + // the mtree is a bit difficult to simulate, but we can at least test + // we ended up with the right number of entries + assert(count_ == count); + + lfsr_unmount(&lfs) => 0; + + + // check things stay sane after remount + lfsr_mount(&lfs, cfg) => 0; + + // try looking up each entry + count_ = 0; + + for (lfs_ssize_t mid = (lfsr_mtree_isinlined(&lfs) ? -1 : 0); + mid < lfsr_mtree_weight(&lfs); + mid++) { + lfsr_mdir_t mdir; + lfsr_mtree_lookup(&lfs, mid, &mdir) => 0; + + // drop should make sure we never have empty mdirs + assert(mdir.mid == -1 || mdir.rbyd.weight > 0); + + for (lfs_ssize_t rid = 0; + rid < (lfs_ssize_t)lfsr_mdir_weight(&mdir); + rid++) { + uint8_t buffer[4]; + lfsr_mdir_get(&lfs, &mdir, rid, LFSR_TAG_INLINED, + buffer, 4) => 1; + + count_ += 1; + } + } + + // the mtree is a bit difficult to simulate, but we can at least test + // we ended up with the right number of entries + assert(count_ == count); + + lfsr_unmount(&lfs) => 0; } '''