# maximize lookahead buffer, we don't actually gc so we only get one pass # of the disk for these tests defines.LOOKAHEAD_SIZE = 'BLOCK_COUNT / 8' # test a single mroot [cases.test_mtree_one_mroot] code = ''' lfs_t lfs; lfsr_format(&lfs, cfg) => 0; lfsr_mount(&lfs, cfg) => 0; lfsr_unmount(&lfs) => 0; ''' # test a single mroot with a custom attribute [cases.test_mtree_one_mroot_attr] in = 'lfs.c' code = ''' lfs_t lfs; lfsr_format(&lfs, cfg) => 0; lfsr_mount(&lfs, cfg) => 0; lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS( LFSR_ATTR(-1, UATTR(1), 0, "ardvark", 7))) => 0; uint8_t buffer[7]; lfsr_mdir_get(&lfs, &lfs.mroot, -1, LFSR_TAG_UATTR(1), buffer, 7) => 7; 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 [cases.test_mtree_one_mroot_many_commits] defines.N = [5, 5000] in = 'lfs.c' code = ''' const char *alphas = "abcdefghijklmnopqrstuvwxyz"; lfs_t lfs; lfsr_format(&lfs, cfg) => 0; lfsr_mount(&lfs, cfg) => 0; for (lfs_size_t i = 0; i < N; i++) { lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS( LFSR_ATTR(-1, UATTR(1), 0, &alphas[i % 26], 1))) => 0; uint8_t buffer[4]; lfsr_mdir_get(&lfs, &lfs.mroot, -1, LFSR_TAG_UATTR(1), buffer, 4) => 1; assert(memcmp(buffer, &alphas[i % 26], 1) == 0); } uint8_t buffer[4]; 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; // 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 [cases.test_mtree_uninline] # this should be set so only one entry can fit in a metadata block defines.SIZE = 'BLOCK_SIZE / 4' 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 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_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 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] # this should be set so only one entry can fit in a metadata block defines.SIZE = 'BLOCK_SIZE / 4' 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); // 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 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] # this should be set so only one entry can fit in a metadata block defines.SIZE = 'BLOCK_SIZE / 4' 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); // 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 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 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; ''' # 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] defines.FORCE_COMPACTION = [false, true] in = 'lfs.c' code = ''' const char *alphas = "abcdefghijklmnopqrstuvwxyz"; lfs_t lfs; lfsr_format(&lfs, cfg) => 0; lfsr_mount(&lfs, cfg) => 0; // create entries lfsr_mdir_t mdir; lfsr_mtree_lookup(&lfs, lfsr_mtree_weight(&lfs)-1, &mdir) => 0; lfs_ssize_t rid = 0; for (lfs_size_t i = 0; i < N; i++) { // force a compaction? if (FORCE_COMPACTION) { mdir.rbyd.off = cfg->block_size; lfs.mroot.rbyd.off = cfg->block_size; } lfsr_mdir_commit(&lfs, &mdir, &rid, LFSR_ATTRS( LFSR_ATTR(rid, MKINLINED, +1, &alphas[i % 26], 1))) => 0; uint8_t buffer[4]; lfsr_mdir_get(&lfs, &mdir, rid, LFSR_TAG_INLINED, buffer, 4) => 1; assert(memcmp(buffer, &alphas[i % 26], 1) == 0); rid += 1; } // try looking up each entry lfs_size_t 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; // 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 [cases.test_mtree_split_fuzz] defines.N = [5, 10, 20, 40, 80, 160] defines.FORCE_COMPACTION = [false, true] defines.SAMPLES = 100 # -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); // force a compaction? if (FORCE_COMPACTION) { mdir.rbyd.off = cfg->block_size; lfs.mroot.rbyd.off = cfg->block_size; } // add to rbyd, potentially splitting the mdir 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; } // 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; 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; 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 [cases.test_mtree_drop] # this should be set so only one entry can fit in a metadata block defines.SIZE = 'BLOCK_SIZE / 4' 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); // 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){1}, 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 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 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] # this should be set so only one entry can fit in a metadata block defines.SIZE = 'BLOCK_SIZE / 4' 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); // 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); // force mdir to compact while we're removing mdir.rbyd.off = BLOCK_SIZE; lfsr_mdir_commit(&lfs, &mdir, &(lfs_ssize_t){1}, 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 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 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] # this should be set so only one entry can fit in a metadata block defines.SIZE = 'BLOCK_SIZE / 4' 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; // remove the entry as we compact, forcing the mdir to be dropped lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS( LFSR_ATTR(0, MKUNR, -1, NULL, 0))) => 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; // 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] # this should be set so only one entry can fit in a metadata block defines.SIZE = 'BLOCK_SIZE / 4' in = 'lfs.c' code = ''' const char *alphas = "abcdefghijklmnopqrstuvwxyz"; lfs_t lfs; lfsr_format(&lfs, cfg) => 0; lfsr_mount(&lfs, cfg) => 0; // create an mdir that needs to be 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; // remove the left entry as we compact, forcing the left // mdir to be dropped lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS( LFSR_ATTR(0, MKUNR, -1, NULL, 0))) => 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_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 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] # this should be set so only one entry can fit in a metadata block defines.SIZE = 'BLOCK_SIZE / 4' in = 'lfs.c' code = ''' const char *alphas = "abcdefghijklmnopqrstuvwxyz"; lfs_t lfs; lfsr_format(&lfs, cfg) => 0; lfsr_mount(&lfs, cfg) => 0; // create an mdir that needs to be 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; // remove the right entry as we compact, forcing the right mdir // to be dropped lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS( LFSR_ATTR(1, MKUNR, -1, NULL, 0))) => 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_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_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] # this should be set so only one entry can fit in a metadata block defines.SIZE = 'BLOCK_SIZE / 4' in = 'lfs.c' code = ''' const char *alphas = "abcdefghijklmnopqrstuvwxyz"; lfs_t lfs; lfsr_format(&lfs, cfg) => 0; lfsr_mount(&lfs, cfg) => 0; // create an mdir that needs to be 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; // remove both entries as we compact, forcing both mdirs to be dropped lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS( LFSR_ATTR(0, MKUNR, -1, NULL, 0), LFSR_ATTR(0, MKUNR, -1, NULL, 0))) => 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; // 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] # this should be set so only one entry can fit in a metadata block defines.SIZE = 'BLOCK_SIZE / 4' 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); // 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; // remove the left entry as we compact, forcing the left // mdir to be dropped 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) == 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; // 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] # this should be set so only one entry can fit in a metadata block defines.SIZE = 'BLOCK_SIZE / 4' 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); // 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; // remove the right entry as we compact, forcing the right // mdir to be dropped lfsr_mdir_commit(&lfs, &mdir, &(lfs_ssize_t){1}, LFSR_ATTRS( LFSR_ATTR(1, MKUNR, -1, NULL, 0))) => 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; // 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] # this should be set so only one entry can fit in a metadata block defines.SIZE = 'BLOCK_SIZE / 4' 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); // 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; // remove both entries as we compact, forcing both mdirs to be dropped lfsr_mdir_commit(&lfs, &mdir, &(lfs_ssize_t){1}, LFSR_ATTRS( LFSR_ATTR(0, MKUNR, -1, NULL, 0), LFSR_ATTR(0, MKUNR, -1, NULL, 0))) => 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; // 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 [cases.test_mtree_drop_many] defines.N = [5, 10, 20, 40, 80, 160, 320] defines.REMAINING = [20, 5, 1, 0] if = 'N > REMAINING' defines.FORCE_COMPACTION = [false, true] in = 'lfs.c' code = ''' const char *alphas = "abcdefghijklmnopqrstuvwxyz"; lfs_t lfs; lfsr_format(&lfs, cfg) => 0; lfsr_mount(&lfs, cfg) => 0; // create entries lfsr_mdir_t mdir; lfsr_mtree_lookup(&lfs, lfsr_mtree_weight(&lfs)-1, &mdir) => 0; lfs_ssize_t rid = 0; for (lfs_size_t i = 0; i < N; i++) { lfsr_mdir_commit(&lfs, &mdir, &rid, LFSR_ATTRS( LFSR_ATTR(rid, MKINLINED, +1, &alphas[i % 26], 1))) => 0; uint8_t buffer[4]; lfsr_mdir_get(&lfs, &mdir, rid, LFSR_TAG_INLINED, buffer, 4) => 1; assert(memcmp(buffer, &alphas[i % 26], 1) == 0); rid += 1; } // remove entries for (lfs_size_t i = 0; i < N - REMAINING; i++) { lfs_ssize_t mid = (lfsr_mtree_isinlined(&lfs) ? -1 : 0); 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); // force a compaction? if (FORCE_COMPACTION) { mdir.rbyd.off = cfg->block_size; lfs.mroot.rbyd.off = cfg->block_size; } lfsr_mdir_commit(&lfs, &mdir, &(lfs_ssize_t){0}, LFSR_ATTRS( LFSR_ATTR(0, MKUNR, -1, NULL, 0))) => 0; } // try looking up each entry lfs_size_t 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; // 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 [cases.test_mtree_repeated_drop] defines.N = [5, 10, 20, 40] defines.FORCE_COMPACTION = [false, true] defines.CYCLES = 10 in = 'lfs.c' code = ''' const char *alphas = "abcdefghijklmnopqrstuvwxyz"; lfs_t lfs; lfsr_format(&lfs, cfg) => 0; lfsr_mount(&lfs, cfg) => 0; for (lfs_size_t cycle = 0; cycle < CYCLES; cycle++) { // create entries lfsr_mdir_t mdir; lfsr_mtree_lookup(&lfs, lfsr_mtree_weight(&lfs)-1, &mdir) => 0; lfs_ssize_t rid = 0; for (lfs_size_t i = 0; i < N; i++) { lfsr_mdir_commit(&lfs, &mdir, &rid, LFSR_ATTRS( LFSR_ATTR(rid, MKINLINED, +1, &alphas[i % 26], 1))) => 0; uint8_t buffer[4]; lfsr_mdir_get(&lfs, &mdir, rid, LFSR_TAG_INLINED, buffer, 4) => 1; assert(memcmp(buffer, &alphas[i % 26], 1) == 0); rid += 1; } // try looking up each entry lfs_size_t 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); // remove entries for (lfs_size_t i = 0; i < N; i++) { lfs_ssize_t mid = (lfsr_mtree_isinlined(&lfs) ? -1 : 0); 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); // force a compaction? if (FORCE_COMPACTION) { mdir.rbyd.off = cfg->block_size; lfs.mroot.rbyd.off = cfg->block_size; } lfsr_mdir_commit(&lfs, &mdir, &(lfs_ssize_t){0}, LFSR_ATTRS( LFSR_ATTR(0, MKUNR, -1, NULL, 0))) => 0; } assert(lfsr_mtree_weight(&lfs) == 0); assert(lfsr_mdir_weight(&lfs.mroot) == 0); } 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] defines.N = [5, 10, 20, 40, 80, 160] defines.FORCE_COMPACTION = [false, true] defines.SAMPLES = 100 # -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) % 2; // 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, potentially splitting the mdir 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; // 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; } ''' ## 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; } ''' ## Neighboring mdir updates ## [cases.test_mtree_neighbor] in = 'lfs.c' code = ''' const char *alphas = "abcdefghijklmnopqrstuvwxyz"; lfs_t lfs; lfsr_format(&lfs, cfg) => 0; lfsr_mount(&lfs, cfg) => 0; // setup our neighbors lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS( LFSR_ATTR(0, MKINLINED, +1, &alphas[0 % 26], 1), LFSR_ATTR(1, MKINLINED, +1, &alphas[1 % 26], 1))) => 0; // this test only works if these all fit in the mroot assert(lfsr_mtree_isinlined(&lfs)); lfsr_openedmdir_t left_neighbor = {.rid=0, .mdir=lfs.mroot}; lfsr_openedmdir_t right_neighbor = {.rid=1, .mdir=lfs.mroot}; lfsr_mdir_addopened(&lfs, &left_neighbor); lfsr_mdir_addopened(&lfs, &right_neighbor); // insert a new entry, this should update our neighbors lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS( LFSR_ATTR(1, MKINLINED, +1, &alphas[2 % 26], 1))) => 0; // assert that our entry is still in the mtree assert(lfs.mroot.rbyd.weight == 3); uint8_t buffer[1]; lfsr_mdir_get(&lfs, &lfs.mroot, 1, LFSR_TAG_INLINED, buffer, 1) => 1; assert(memcmp(buffer, &alphas[2 % 26], 1) == 0); // assert that our neighbors were updated correctly assert(left_neighbor.rid == 0); assert(left_neighbor.mdir.mid == -1); assert(memcmp(&left_neighbor.mdir, &lfs.mroot, sizeof(lfsr_mdir_t)) == 0); assert(right_neighbor.rid == 2); assert(right_neighbor.mdir.mid == -1); assert(memcmp(&right_neighbor.mdir, &lfs.mroot, sizeof(lfsr_mdir_t)) == 0); lfsr_mdir_removeopened(&lfs, &left_neighbor); lfsr_mdir_removeopened(&lfs, &right_neighbor); lfsr_unmount(&lfs) => 0; ''' [cases.test_mtree_neighbor_remove_l] in = 'lfs.c' code = ''' const char *alphas = "abcdefghijklmnopqrstuvwxyz"; lfs_t lfs; lfsr_format(&lfs, cfg) => 0; lfsr_mount(&lfs, cfg) => 0; // setup our neighbors lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS( LFSR_ATTR(0, MKINLINED, +1, &alphas[0 % 26], 1), LFSR_ATTR(1, MKINLINED, +1, &alphas[1 % 26], 1))) => 0; // this test only works if these all fit in the mroot assert(lfsr_mtree_isinlined(&lfs)); lfsr_openedmdir_t left_neighbor = {.rid=0, .mdir=lfs.mroot}; lfsr_openedmdir_t right_neighbor = {.rid=1, .mdir=lfs.mroot}; lfsr_mdir_addopened(&lfs, &left_neighbor); lfsr_mdir_addopened(&lfs, &right_neighbor); // try removing our left entry lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS( LFSR_ATTR(0, MKUNR, -1, NULL, 0))) => 0; // assert that an entry was removed assert(lfs.mroot.rbyd.weight == 1); // assert that our neighbors were updated correctly assert(left_neighbor.rid == -2); assert(left_neighbor.mdir.mid == -2); assert(right_neighbor.rid == 0); assert(right_neighbor.mdir.mid == -1); assert(memcmp(&right_neighbor.mdir, &lfs.mroot, sizeof(lfsr_mdir_t)) == 0); lfsr_mdir_removeopened(&lfs, &left_neighbor); lfsr_mdir_removeopened(&lfs, &right_neighbor); lfsr_unmount(&lfs) => 0; ''' [cases.test_mtree_neighbor_remove_r] in = 'lfs.c' code = ''' const char *alphas = "abcdefghijklmnopqrstuvwxyz"; lfs_t lfs; lfsr_format(&lfs, cfg) => 0; lfsr_mount(&lfs, cfg) => 0; // setup our neighbors lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS( LFSR_ATTR(0, MKINLINED, +1, &alphas[0 % 26], 1), LFSR_ATTR(1, MKINLINED, +1, &alphas[1 % 26], 1))) => 0; // this test only works if these all fit in the mroot assert(lfsr_mtree_isinlined(&lfs)); lfsr_openedmdir_t left_neighbor = {.rid=0, .mdir=lfs.mroot}; lfsr_openedmdir_t right_neighbor = {.rid=1, .mdir=lfs.mroot}; lfsr_mdir_addopened(&lfs, &left_neighbor); lfsr_mdir_addopened(&lfs, &right_neighbor); // try removing our left entry lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS( LFSR_ATTR(1, MKUNR, -1, NULL, 0))) => 0; // assert that an entry was removed assert(lfs.mroot.rbyd.weight == 1); // assert that our neighbors were updated correctly assert(left_neighbor.rid == 0); assert(left_neighbor.mdir.mid == -1); assert(memcmp(&left_neighbor.mdir, &lfs.mroot, sizeof(lfsr_mdir_t)) == 0); assert(right_neighbor.rid == -2); assert(right_neighbor.mdir.mid == -2); lfsr_mdir_removeopened(&lfs, &left_neighbor); lfsr_mdir_removeopened(&lfs, &right_neighbor); lfsr_unmount(&lfs) => 0; ''' [cases.test_mtree_neighbor_uninline] # this should be set so only one entry can fit in a metadata block defines.SIZE = 'BLOCK_SIZE / 4' in = 'lfs.c' code = ''' const char *alphas = "abcdefghijklmnopqrstuvwxyz"; lfs_t lfs; lfsr_format(&lfs, cfg) => 0; lfsr_mount(&lfs, cfg) => 0; // setup our neighbors lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS( LFSR_ATTR(0, MKINLINED, +1, &alphas[0 % 26], 1), LFSR_ATTR(1, MKINLINED, +1, &alphas[1 % 26], 1))) => 0; // this test only works if these all fit in the mroot assert(lfsr_mtree_isinlined(&lfs)); lfsr_openedmdir_t left_neighbor = {.rid=0, .mdir=lfs.mroot}; lfsr_openedmdir_t right_neighbor = {.rid=1, .mdir=lfs.mroot}; lfsr_mdir_addopened(&lfs, &left_neighbor); lfsr_mdir_addopened(&lfs, &right_neighbor); // prepare mroot with a large attr so the next entry can not fit uint8_t buffer[SIZE]; memset(buffer, alphas[2 % 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[3 % 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 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[2 % 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 == 3); lfsr_mdir_get(&lfs, &mdir, 1, LFSR_TAG_INLINED, buffer, SIZE) => SIZE; assert(memcmp(buffer, &alphas[3 % 26], 1) == 0); // assert that our neighbors were updated correctly assert(left_neighbor.rid == 0); assert(left_neighbor.mdir.mid == 0); assert(memcmp(&left_neighbor.mdir, &mdir, sizeof(lfsr_mdir_t)) == 0); assert(right_neighbor.rid == 2); assert(right_neighbor.mdir.mid == 0); assert(memcmp(&right_neighbor.mdir, &mdir, sizeof(lfsr_mdir_t)) == 0); lfsr_mdir_removeopened(&lfs, &left_neighbor); lfsr_mdir_removeopened(&lfs, &right_neighbor); lfsr_unmount(&lfs) => 0; ''' [cases.test_mtree_neighbor_uninline_split] # this should be set so only one entry can fit in a metadata block defines.SIZE = 'BLOCK_SIZE / 4' in = 'lfs.c' code = ''' const char *alphas = "abcdefghijklmnopqrstuvwxyz"; lfs_t lfs; lfsr_format(&lfs, cfg) => 0; lfsr_mount(&lfs, cfg) => 0; // setup our neighbors lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS( LFSR_ATTR(0, MKINLINED, +1, &alphas[0 % 26], 1), LFSR_ATTR(1, MKINLINED, +1, &alphas[1 % 26], 1))) => 0; // this test only works if these all fit in the mroot assert(lfsr_mtree_isinlined(&lfs)); lfsr_openedmdir_t left_neighbor = {.rid=0, .mdir=lfs.mroot}; lfsr_openedmdir_t right_neighbor = {.rid=1, .mdir=lfs.mroot}; lfsr_mdir_addopened(&lfs, &left_neighbor); lfsr_mdir_addopened(&lfs, &right_neighbor); // create 2 large entries that needs to be uninlined and split uint8_t buffer[SIZE]; memset(buffer, alphas[2 % 26], SIZE); lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS( LFSR_ATTR(1, MKINLINED, +1, buffer, SIZE))) => 0; memset(buffer, alphas[3 % 26], SIZE); lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS( LFSR_ATTR(2, 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); // assert that our entries are still in the mtree lfsr_mdir_t mdir; lfsr_mtree_lookup(&lfs, 0, &mdir) => 0; assert(mdir.rbyd.weight == 2); lfsr_mdir_get(&lfs, &mdir, 1, LFSR_TAG_INLINED, buffer, SIZE) => SIZE; assert(memcmp(buffer, &alphas[2 % 26], 1) == 0); lfsr_mdir_t msibling; lfsr_mtree_lookup(&lfs, 1, &msibling) => 0; assert(msibling.rbyd.weight == 2); lfsr_mdir_get(&lfs, &msibling, 0, LFSR_TAG_INLINED, buffer, SIZE) => SIZE; assert(memcmp(buffer, &alphas[3 % 26], 1) == 0); // assert that our neighbors were updated correctly assert(left_neighbor.rid == 0); assert(left_neighbor.mdir.mid == 0); assert(memcmp(&left_neighbor.mdir, &mdir, sizeof(lfsr_mdir_t)) == 0); assert(right_neighbor.rid == 1); assert(right_neighbor.mdir.mid == 1); assert(memcmp(&right_neighbor.mdir, &msibling, sizeof(lfsr_mdir_t)) == 0); lfsr_mdir_removeopened(&lfs, &left_neighbor); lfsr_mdir_removeopened(&lfs, &right_neighbor); lfsr_unmount(&lfs) => 0; ''' [cases.test_mtree_neighbor_split] # this should be set so only one entry can fit in a metadata block defines.SIZE = 'BLOCK_SIZE / 4' in = 'lfs.c' code = ''' const char *alphas = "abcdefghijklmnopqrstuvwxyz"; lfs_t lfs; lfsr_format(&lfs, cfg) => 0; lfsr_mount(&lfs, cfg) => 0; // setup our neighbors lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS( LFSR_ATTR(0, MKINLINED, +1, &alphas[0 % 26], 1), LFSR_ATTR(1, MKINLINED, +1, &alphas[1 % 26], 1))) => 0; // this test only works if these all fit in the mroot assert(lfsr_mtree_isinlined(&lfs)); lfsr_openedmdir_t left_neighbor = {.rid=0, .mdir=lfs.mroot}; lfsr_openedmdir_t right_neighbor = {.rid=1, .mdir=lfs.mroot}; lfsr_mdir_addopened(&lfs, &left_neighbor); lfsr_mdir_addopened(&lfs, &right_neighbor); // create an uninlined mdir uint8_t buffer[SIZE]; memset(buffer, alphas[2 % 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[3 % 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 mdir was unininlined correctly assert(lfsr_mtree_weight(&lfs) == 1); // assert mroot now has no entries assert(lfs.mroot.rbyd.weight == 0); // 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 == 3); memset(buffer, alphas[4 % 26], SIZE); lfsr_mdir_commit(&lfs, &mdir, &(lfs_ssize_t){2}, LFSR_ATTRS( LFSR_ATTR(2, MKINLINED, +1, buffer, SIZE))) => 0; // force mdir to compact mdir.rbyd.off = BLOCK_SIZE; lfsr_mdir_commit(&lfs, &mdir, &(lfs_ssize_t){2}, 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 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[2 % 26], 1) == 0); // assert that our entries are still in the mtree lfsr_mtree_lookup(&lfs, 0, &mdir) => 0; assert(mdir.rbyd.weight == 2); lfsr_mdir_get(&lfs, &mdir, 1, LFSR_TAG_INLINED, buffer, SIZE) => SIZE; assert(memcmp(buffer, &alphas[3 % 26], 1) == 0); lfsr_mdir_t msibling; lfsr_mtree_lookup(&lfs, 1, &msibling) => 0; assert(msibling.rbyd.weight == 2); lfsr_mdir_get(&lfs, &msibling, 0, LFSR_TAG_INLINED, buffer, SIZE) => SIZE; assert(memcmp(buffer, &alphas[4 % 26], 1) == 0); // assert that our neighbors were updated correctly assert(left_neighbor.rid == 0); assert(left_neighbor.mdir.mid == 0); assert(memcmp(&left_neighbor.mdir, &mdir, sizeof(lfsr_mdir_t)) == 0); assert(right_neighbor.rid == 1); assert(right_neighbor.mdir.mid == 1); assert(memcmp(&right_neighbor.mdir, &msibling, sizeof(lfsr_mdir_t)) == 0); lfsr_mdir_removeopened(&lfs, &left_neighbor); lfsr_mdir_removeopened(&lfs, &right_neighbor); lfsr_unmount(&lfs) => 0; ''' [cases.test_mtree_neighbor_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; // setup our neighbors lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS( LFSR_ATTR(0, MKINLINED, +1, &alphas[0 % 26], 1), LFSR_ATTR(1, MKINLINED, +1, &alphas[1 % 26], 1))) => 0; // this test only works if these all fit in the mroot assert(lfsr_mtree_isinlined(&lfs)); lfsr_openedmdir_t left_neighbor = {.rid=0, .mdir=lfs.mroot}; lfsr_openedmdir_t right_neighbor = {.rid=1, .mdir=lfs.mroot}; lfsr_mdir_addopened(&lfs, &left_neighbor); lfsr_mdir_addopened(&lfs, &right_neighbor); // 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); // assert that our neighbors were updated correctly assert(left_neighbor.rid == 0); assert(left_neighbor.mdir.mid == -1); assert(memcmp(&left_neighbor.mdir, &lfs.mroot, sizeof(lfsr_mdir_t)) == 0); assert(right_neighbor.rid == 1); assert(right_neighbor.mdir.mid == -1); assert(memcmp(&right_neighbor.mdir, &lfs.mroot, sizeof(lfsr_mdir_t)) == 0); lfsr_mdir_removeopened(&lfs, &left_neighbor); lfsr_mdir_removeopened(&lfs, &right_neighbor); lfsr_unmount(&lfs) => 0; ''' [cases.test_mtree_neighbor_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; // setup our neighbors lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS( LFSR_ATTR(0, MKINLINED, +1, &alphas[0 % 26], 1), LFSR_ATTR(1, MKINLINED, +1, &alphas[1 % 26], 1))) => 0; // this test only works if these all fit in the mroot assert(lfsr_mtree_isinlined(&lfs)); lfsr_openedmdir_t left_neighbor = {.rid=0, .mdir=lfs.mroot}; lfsr_openedmdir_t right_neighbor = {.rid=1, .mdir=lfs.mroot}; lfsr_mdir_addopened(&lfs, &left_neighbor); lfsr_mdir_addopened(&lfs, &right_neighbor); // prepare mroot with a large attr so the next entry can not fit uint8_t buffer[SIZE]; memset(buffer, alphas[2 % 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[3 % 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 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 == 3); 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[4 % 26], SIZE); lfsr_mdir_commit(&lfs, &mdir, &(lfs_ssize_t){0}, LFSR_ATTRS( LFSR_ATTR(1, 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[2 % 26], 1) == 0); // assert that our entry is still in the mtree lfsr_mtree_lookup(&lfs, 0, &mdir) => 0; assert(mdir.rbyd.weight == 3); lfsr_mdir_get(&lfs, &mdir, 1, LFSR_TAG_INLINED, buffer, SIZE) => SIZE; assert(memcmp(buffer, &alphas[4 % 26], 1) == 0); // assert that our neighbors were updated correctly assert(left_neighbor.rid == 0); assert(left_neighbor.mdir.mid == 0); assert(memcmp(&left_neighbor.mdir, &mdir, sizeof(lfsr_mdir_t)) == 0); assert(right_neighbor.rid == 2); assert(right_neighbor.mdir.mid == 0); assert(memcmp(&right_neighbor.mdir, &mdir, sizeof(lfsr_mdir_t)) == 0); lfsr_mdir_removeopened(&lfs, &left_neighbor); lfsr_mdir_removeopened(&lfs, &right_neighbor); lfsr_unmount(&lfs) => 0; ''' ## mtree traversal ## # test specific corner cases [cases.test_mtree_traversal] in = 'lfs.c' code = ''' const char *alphas = "abcdefghijklmnopqrstuvwxyz"; lfs_t lfs; lfsr_format(&lfs, cfg) => 0; lfsr_mount(&lfs, cfg) => 0; // insert a new entry, this should update our neighbors lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS( LFSR_ATTR(0, MKINLINED, +1, &alphas[0 % 26], 1))) => 0; // assert that our entry is still in the mtree assert(lfs.mroot.rbyd.weight == 1); uint8_t buffer[1]; lfsr_mdir_get(&lfs, &lfs.mroot, 0, LFSR_TAG_INLINED, buffer, 1) => 1; assert(memcmp(buffer, &alphas[0 % 26], 1) == 0); // test that we can traverse the tree, keeping track of all blocks we see uint8_t *seen = malloc((BLOCK_COUNT+7)/8); memset(seen, 0, (BLOCK_COUNT+7)/8); lfsr_mtree_traversal_t traversal = LFSR_MTREE_TRAVERSAL_INIT; for (lfs_block_t i = 0;; i++) { // a bit hacky, but this catches infinite loops assert(i < 2*1); lfs_size_t mid_; lfsr_tag_t tag_; lfsr_data_t data_; int err = lfsr_mtree_traversal_next(&lfs, &traversal, &mid_, &tag_, &data_); assert(!err || err == LFS_ERR_NOENT); if (err == LFS_ERR_NOENT) { break; } if (tag_ == LFSR_TAG_BTREE) { lfsr_rbyd_t *branch = (lfsr_rbyd_t *)data_.buf.buffer; printf("traversal: %d 0x%x btree 0x%x.%x\n", mid_, tag_, branch->block, branch->trunk); // keep track of seen blocks seen[branch->block / 8] |= 1 << (branch->block % 8); } else if (tag_ == LFSR_TAG_MDIR) { lfsr_mdir_t *mdir = (lfsr_mdir_t*)data_.buf.buffer; printf("traversal: %d 0x%x mdir 0x{%x,%x}\n", mid_, tag_, mdir->rbyd.block, mdir->other_block); // keep track of seen blocks seen[mdir->rbyd.block / 8] |= 1 << (mdir->rbyd.block % 8); seen[mdir->other_block / 8] |= 1 << (mdir->other_block % 8); } else { // this shouldn't happen printf("traversal: %d 0x%x %d\n", mid_, tag_, lfsr_data_size(data_)); assert(false); } } // if traversal worked, we should be able to clobber all other blocks uint8_t buffer_[BLOCK_SIZE]; memset(buffer_, 0xcc, BLOCK_SIZE); for (lfs_block_t block = 0; block < BLOCK_COUNT; block++) { if (!(seen[block / 8] & (1 << (block % 8)))) { cfg->erase(cfg, block) => 0; cfg->prog(cfg, block, 0, buffer_, BLOCK_SIZE) => 0; } } free(seen); // and the tree should still work // assert that our entry is still in the mtree assert(lfs.mroot.rbyd.weight == 1); lfsr_mdir_get(&lfs, &lfs.mroot, 0, LFSR_TAG_INLINED, buffer, 1) => 1; assert(memcmp(buffer, &alphas[0 % 26], 1) == 0); lfsr_unmount(&lfs) => 0; ''' [cases.test_mtree_traversal_uninline] # this should be set so only one entry can fit in a metadata block defines.SIZE = 'BLOCK_SIZE / 4' 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 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_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); // test that we can traverse the tree, keeping track of all blocks we see uint8_t *seen = malloc((BLOCK_COUNT+7)/8); memset(seen, 0, (BLOCK_COUNT+7)/8); lfsr_mtree_traversal_t traversal = LFSR_MTREE_TRAVERSAL_INIT; for (lfs_block_t i = 0;; i++) { // a bit hacky, but this catches infinite loops assert(i < 2*2); lfs_size_t mid_; lfsr_tag_t tag_; lfsr_data_t data_; int err = lfsr_mtree_traversal_next(&lfs, &traversal, &mid_, &tag_, &data_); assert(!err || err == LFS_ERR_NOENT); if (err == LFS_ERR_NOENT) { break; } if (tag_ == LFSR_TAG_BTREE) { lfsr_rbyd_t *branch = (lfsr_rbyd_t *)data_.buf.buffer; printf("traversal: %d 0x%x btree 0x%x.%x\n", mid_, tag_, branch->block, branch->trunk); // keep track of seen blocks seen[branch->block / 8] |= 1 << (branch->block % 8); } else if (tag_ == LFSR_TAG_MDIR) { lfsr_mdir_t *mdir = (lfsr_mdir_t*)data_.buf.buffer; printf("traversal: %d 0x%x mdir 0x{%x,%x}\n", mid_, tag_, mdir->rbyd.block, mdir->other_block); // keep track of seen blocks seen[mdir->rbyd.block / 8] |= 1 << (mdir->rbyd.block % 8); seen[mdir->other_block / 8] |= 1 << (mdir->other_block % 8); } else { // this shouldn't happen printf("traversal: %d 0x%x %d\n", mid_, tag_, lfsr_data_size(data_)); assert(false); } } // if traversal worked, we should be able to clobber all other blocks uint8_t buffer_[BLOCK_SIZE]; memset(buffer_, 0xcc, BLOCK_SIZE); for (lfs_block_t block = 0; block < BLOCK_COUNT; block++) { if (!(seen[block / 8] & (1 << (block % 8)))) { cfg->erase(cfg, block) => 0; cfg->prog(cfg, block, 0, buffer_, BLOCK_SIZE) => 0; } } free(seen); // and the tree should still work // 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_traversal_split] # this should be set so only one entry can fit in a metadata block defines.SIZE = 'BLOCK_SIZE / 4' 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); // 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_mdir_t msibling; lfsr_mtree_lookup(&lfs, 1, &msibling) => 0; assert(msibling.rbyd.weight == 1); lfsr_mdir_get(&lfs, &msibling, 0, LFSR_TAG_INLINED, buffer, SIZE) => SIZE; assert(memcmp(buffer, &alphas[1 % 26], 1) == 0); // test that we can traverse the tree, keeping track of all blocks we see uint8_t *seen = malloc((BLOCK_COUNT+7)/8); memset(seen, 0, (BLOCK_COUNT+7)/8); lfsr_mtree_traversal_t traversal = LFSR_MTREE_TRAVERSAL_INIT; for (lfs_block_t i = 0;; i++) { // a bit hacky, but this catches infinite loops assert(i < 2*3); lfs_size_t mid_; lfsr_tag_t tag_; lfsr_data_t data_; int err = lfsr_mtree_traversal_next(&lfs, &traversal, &mid_, &tag_, &data_); assert(!err || err == LFS_ERR_NOENT); if (err == LFS_ERR_NOENT) { break; } if (tag_ == LFSR_TAG_BTREE) { lfsr_rbyd_t *branch = (lfsr_rbyd_t *)data_.buf.buffer; printf("traversal: %d 0x%x btree 0x%x.%x\n", mid_, tag_, branch->block, branch->trunk); // keep track of seen blocks seen[branch->block / 8] |= 1 << (branch->block % 8); } else if (tag_ == LFSR_TAG_MDIR) { lfsr_mdir_t *mdir = (lfsr_mdir_t*)data_.buf.buffer; printf("traversal: %d 0x%x mdir 0x{%x,%x}\n", mid_, tag_, mdir->rbyd.block, mdir->other_block); // keep track of seen blocks seen[mdir->rbyd.block / 8] |= 1 << (mdir->rbyd.block % 8); seen[mdir->other_block / 8] |= 1 << (mdir->other_block % 8); } else { // this shouldn't happen printf("traversal: %d 0x%x %d\n", mid_, tag_, lfsr_data_size(data_)); assert(false); } } // if traversal worked, we should be able to clobber all other blocks uint8_t buffer_[BLOCK_SIZE]; memset(buffer_, 0xcc, BLOCK_SIZE); for (lfs_block_t block = 0; block < BLOCK_COUNT; block++) { if (!(seen[block / 8] & (1 << (block % 8)))) { cfg->erase(cfg, block) => 0; cfg->prog(cfg, block, 0, buffer_, BLOCK_SIZE) => 0; } } free(seen); // and the tree should still work // 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, &msibling) => 0; assert(msibling.rbyd.weight == 1); lfsr_mdir_get(&lfs, &msibling, 0, LFSR_TAG_INLINED, buffer, SIZE) => SIZE; assert(memcmp(buffer, &alphas[1 % 26], 1) == 0); lfsr_unmount(&lfs) => 0; ''' [cases.test_mtree_traversal_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); // test that we can traverse the tree, keeping track of all blocks we see uint8_t *seen = malloc((BLOCK_COUNT+7)/8); memset(seen, 0, (BLOCK_COUNT+7)/8); lfsr_mtree_traversal_t traversal = LFSR_MTREE_TRAVERSAL_INIT; for (lfs_block_t i = 0;; i++) { // a bit hacky, but this catches infinite loops assert(i < 2*3); lfs_size_t mid_; lfsr_tag_t tag_; lfsr_data_t data_; int err = lfsr_mtree_traversal_next(&lfs, &traversal, &mid_, &tag_, &data_); assert(!err || err == LFS_ERR_NOENT); if (err == LFS_ERR_NOENT) { break; } if (tag_ == LFSR_TAG_BTREE) { lfsr_rbyd_t *branch = (lfsr_rbyd_t *)data_.buf.buffer; printf("traversal: %d 0x%x btree 0x%x.%x\n", mid_, tag_, branch->block, branch->trunk); // keep track of seen blocks seen[branch->block / 8] |= 1 << (branch->block % 8); } else if (tag_ == LFSR_TAG_MDIR) { lfsr_mdir_t *mdir = (lfsr_mdir_t*)data_.buf.buffer; printf("traversal: %d 0x%x mdir 0x{%x,%x}\n", mid_, tag_, mdir->rbyd.block, mdir->other_block); // keep track of seen blocks seen[mdir->rbyd.block / 8] |= 1 << (mdir->rbyd.block % 8); seen[mdir->other_block / 8] |= 1 << (mdir->other_block % 8); } else { // this shouldn't happen printf("traversal: %d 0x%x %d\n", mid_, tag_, lfsr_data_size(data_)); assert(false); } } // if traversal worked, we should be able to clobber all other blocks uint8_t buffer_[BLOCK_SIZE]; memset(buffer_, 0xcc, BLOCK_SIZE); for (lfs_block_t block = 0; block < BLOCK_COUNT; block++) { if (!(seen[block / 8] & (1 << (block % 8)))) { cfg->erase(cfg, block) => 0; cfg->prog(cfg, block, 0, buffer_, BLOCK_SIZE) => 0; } } free(seen); // and the tree should still work // 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; ''' # larger traversal tests [cases.test_mtree_traversal_many] defines.N = [5, 10, 20, 40, 80, 160, 320] defines.FORCE_COMPACTION = [false, true] in = 'lfs.c' code = ''' const char *alphas = "abcdefghijklmnopqrstuvwxyz"; lfs_t lfs; lfsr_format(&lfs, cfg) => 0; lfsr_mount(&lfs, cfg) => 0; // create entries lfsr_mdir_t mdir; lfsr_mtree_lookup(&lfs, lfsr_mtree_weight(&lfs)-1, &mdir) => 0; lfs_ssize_t rid = 0; for (lfs_size_t i = 0; i < N; i++) { // force a compaction? if (FORCE_COMPACTION) { mdir.rbyd.off = cfg->block_size; lfs.mroot.rbyd.off = cfg->block_size; } lfsr_mdir_commit(&lfs, &mdir, &rid, LFSR_ATTRS( LFSR_ATTR(rid, MKINLINED, +1, &alphas[i % 26], 1))) => 0; uint8_t buffer[4]; lfsr_mdir_get(&lfs, &mdir, rid, LFSR_TAG_INLINED, buffer, 4) => 1; assert(memcmp(buffer, &alphas[i % 26], 1) == 0); rid += 1; } // try looking up each entry lfs_size_t 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); // test that we can traverse the tree, keeping track of all blocks we see uint8_t *seen = malloc((BLOCK_COUNT+7)/8); memset(seen, 0, (BLOCK_COUNT+7)/8); lfsr_mtree_traversal_t traversal = LFSR_MTREE_TRAVERSAL_INIT; for (lfs_block_t i = 0;; i++) { // a bit hacky, but this catches infinite loops assert(i < 2*(1+N)); lfs_size_t mid_; lfsr_tag_t tag_; lfsr_data_t data_; int err = lfsr_mtree_traversal_next(&lfs, &traversal, &mid_, &tag_, &data_); assert(!err || err == LFS_ERR_NOENT); if (err == LFS_ERR_NOENT) { break; } if (tag_ == LFSR_TAG_BTREE) { lfsr_rbyd_t *branch = (lfsr_rbyd_t *)data_.buf.buffer; printf("traversal: %d 0x%x btree 0x%x.%x\n", mid_, tag_, branch->block, branch->trunk); // keep track of seen blocks seen[branch->block / 8] |= 1 << (branch->block % 8); } else if (tag_ == LFSR_TAG_MDIR) { lfsr_mdir_t *mdir = (lfsr_mdir_t*)data_.buf.buffer; printf("traversal: %d 0x%x mdir 0x{%x,%x}\n", mid_, tag_, mdir->rbyd.block, mdir->other_block); // keep track of seen blocks seen[mdir->rbyd.block / 8] |= 1 << (mdir->rbyd.block % 8); seen[mdir->other_block / 8] |= 1 << (mdir->other_block % 8); } else { // this shouldn't happen printf("traversal: %d 0x%x %d\n", mid_, tag_, lfsr_data_size(data_)); assert(false); } } // if traversal worked, we should be able to clobber all other blocks uint8_t buffer_[BLOCK_SIZE]; memset(buffer_, 0xcc, BLOCK_SIZE); for (lfs_block_t block = 0; block < BLOCK_COUNT; block++) { if (!(seen[block / 8] & (1 << (block % 8)))) { cfg->erase(cfg, block) => 0; cfg->prog(cfg, block, 0, buffer_, BLOCK_SIZE) => 0; } } free(seen); // and the tree should still work // 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; ''' [cases.test_mtree_traversal_fuzz] defines.N = [5, 10, 20, 40, 80, 160] defines.FORCE_COMPACTION = [false, true] defines.SAMPLES = 100 # -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); // force a compaction? if (FORCE_COMPACTION) { mdir.rbyd.off = cfg->block_size; lfs.mroot.rbyd.off = cfg->block_size; } // add to rbyd, potentially splitting the mdir 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; } // 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; 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); // test that we can traverse the tree, keeping track of all blocks we see uint8_t *seen = malloc((BLOCK_COUNT+7)/8); memset(seen, 0, (BLOCK_COUNT+7)/8); lfsr_mtree_traversal_t traversal = LFSR_MTREE_TRAVERSAL_INIT; for (lfs_block_t i = 0;; i++) { // a bit hacky, but this catches infinite loops assert(i < 2*(1+N)); lfs_size_t mid_; lfsr_tag_t tag_; lfsr_data_t data_; int err = lfsr_mtree_traversal_next(&lfs, &traversal, &mid_, &tag_, &data_); assert(!err || err == LFS_ERR_NOENT); if (err == LFS_ERR_NOENT) { break; } if (tag_ == LFSR_TAG_BTREE) { lfsr_rbyd_t *branch = (lfsr_rbyd_t *)data_.buf.buffer; printf("traversal: %d 0x%x btree 0x%x.%x\n", mid_, tag_, branch->block, branch->trunk); // keep track of seen blocks seen[branch->block / 8] |= 1 << (branch->block % 8); } else if (tag_ == LFSR_TAG_MDIR) { lfsr_mdir_t *mdir = (lfsr_mdir_t*)data_.buf.buffer; printf("traversal: %d 0x%x mdir 0x{%x,%x}\n", mid_, tag_, mdir->rbyd.block, mdir->other_block); // keep track of seen blocks seen[mdir->rbyd.block / 8] |= 1 << (mdir->rbyd.block % 8); seen[mdir->other_block / 8] |= 1 << (mdir->other_block % 8); } else { // this shouldn't happen printf("traversal: %d 0x%x %d\n", mid_, tag_, lfsr_data_size(data_)); assert(false); } } // if traversal worked, we should be able to clobber all other blocks uint8_t buffer_[BLOCK_SIZE]; memset(buffer_, 0xcc, BLOCK_SIZE); for (lfs_block_t block = 0; block < BLOCK_COUNT; block++) { if (!(seen[block / 8] & (1 << (block % 8)))) { cfg->erase(cfg, block) => 0; cfg->prog(cfg, block, 0, buffer_, BLOCK_SIZE) => 0; } } free(seen); // and the tree should still work // 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; } '''