30a9a62620
Originally, the intention of this rework was to make it possible to
shrub the mtree, i.e. allow an mshrub, i.e. inline the root rbyd of the
mtree to be inlined in the mroot.
This would allow small mtrees, 2, 3, etc mdirs, to save a block that
would be needed for the mtree's root.
But as the mshrub was progressing, minor problems kept unfolding, and
ultimately I've decided to shelve the idea of mshrubs for now. They add
quite a bit of complexity for relatively little gain:
- bshrubs are just complicated to update. They require a call to
lfsr_mdir_commit to update the inlined-root, which is a bit of a
problem when your mshrub needs to be updated inside lfsr_mdir_commit,
and your system disallows recursion...
Recursion _can_ be avoided by separate bshrub commit variants that go
through either lfsr_mdir_commit or lfsr_mdir_commit_, but this
complicates things and requires some code duplication, weakening the
value of reusing the bshrub data-structure.
- It's not always possible to compact the mshrub's backing mroot when
we need to modify the mshrub.
If an mroot becomes full and needs to split, for example, we need to
allocate the new mdirs, update the (new) mshrub, and then commit
everything into the mroot when we compact. But the "update the (new)
mshrub" step can't be done until after we compact, because the mroot
is by definition full.
This _can_ also be worked around, by building an attr list containing
all of the mshrub changes, and committing the mshrub/mroot changes in
the same transaction, but this complicates things and increases the
stack cost for the current hot-path.
- Every shrub needs a configurable shrub size, and the mshrub is no
exception. This adds another config option and complicates shared
shrub eviction code.
- The value for mshrubs is not actually that great.
Unlike file bshrubs, there's only one mshrub in the filesystem, and
I'm not sure there's a situation where a filesystem has >1 mdirs and
the exact number of allocated blocks is critical.
And this complexity is reflected in code cost and robustness, not to
mention developer time. I think for littlefs this is just not worth
doing. At least not now.
We can always introduce mshrubs in a backwards compatible manner if
needed.
---
But this rework did lead to better code organization around mdir commits
and how they update the mtree/mroot, so I'm keeping those changes.
In general lfsr_mdir_commit has been broken up into mtree/mroot specific
functions that _do_ propagate in-device changes. Any commit to the mroot
changes the on-disk state of the filesystem anyways, so the mroot commit
_must_ be the last thing lfsr_mdir_commit does.
This leads to some duplicated updates, but that's not really a problem.
Here's the new call graph inside lfsr_mdir_commit:
lfsr_mdir_commit
.---------' | | | '-----------------.
v | | '-----------------. |
lfsr_mtree_commit | '--------. | |
'---------. | | | |
v v | | |
lfsr_mroot_commit | | |
| '--------. | | |
| v v | |
| lfsr_mdir_commit_ | |
| .--------' '--------. | |
| | .-----------------|-' |
v v v v v
lfsr_mdir_commit__ lfsr_mdir_compact__
This rework didn't really impact code/stack that much. It added a bit of
code, but saved a bit of RAM. The real value is that the narrower-scoped
functions contain more focused logic:
code stack
before: 30780 2504
after: 31096 (+1.0%) 2480 (-1.0%)
4414 lines
145 KiB
TOML
4414 lines
145 KiB
TOML
# Test the high-level metadata tree in the core of littlefs
|
|
after = ['test_rbyd', 'test_btree']
|
|
|
|
# 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'
|
|
|
|
# some helper functions
|
|
in = 'lfs.c'
|
|
code = '''
|
|
static lfs_ssize_t lfsr_mdir_get(lfs_t *lfs, const lfsr_mdir_t *mdir,
|
|
lfs_ssize_t rid, lfsr_tag_t tag, void *buffer, lfs_size_t size) {
|
|
lfsr_data_t data;
|
|
int err = lfsr_mdir_lookup(lfs, mdir, rid, tag, NULL, &data);
|
|
if (err) {
|
|
return err;
|
|
}
|
|
|
|
return lfsr_data_read(lfs, &data, buffer, size);
|
|
}
|
|
'''
|
|
|
|
# test a single mroot
|
|
[cases.test_mtree_mroot]
|
|
code = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, CFG) => 0;
|
|
lfsr_mount(&lfs, CFG) => 0;
|
|
lfsr_unmount(&lfs) => 0;
|
|
'''
|
|
|
|
# test a single mroot with attributes
|
|
[cases.test_mtree_mroot_attrs]
|
|
defines.N = [1, 3]
|
|
in = 'lfs.c'
|
|
code = '''
|
|
const char *alphas = "abcdefghijklmnopqrstuvwxyz";
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, CFG) => 0;
|
|
lfsr_mount(&lfs, CFG) => 0;
|
|
lfs_alloc_ack(&lfs);
|
|
|
|
for (lfs_size_t i = 0; i < N; i++) {
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(-1, UATTR(i), 0, BUF(&alphas[i % 26], 1)))) => 0;
|
|
}
|
|
|
|
for (lfs_size_t i = 0; i < N; i++) {
|
|
uint8_t buffer[1];
|
|
lfsr_mdir_get(&lfs, &lfs.mroot,
|
|
-1, LFSR_TAG_UATTR(i), buffer, 1) => 1;
|
|
assert(memcmp(buffer, &alphas[i % 26], 1) == 0);
|
|
}
|
|
|
|
lfsr_unmount(&lfs) => 0;
|
|
|
|
|
|
// check things stay sane after remount
|
|
lfsr_mount(&lfs, CFG) => 0;
|
|
|
|
for (lfs_size_t i = 0; i < N; i++) {
|
|
uint8_t buffer[1];
|
|
lfsr_mdir_get(&lfs, &lfs.mroot,
|
|
-1, LFSR_TAG_UATTR(i), buffer, 1) => 1;
|
|
assert(memcmp(buffer, &alphas[i % 26], 1) == 0);
|
|
}
|
|
|
|
lfsr_unmount(&lfs) => 0;
|
|
'''
|
|
|
|
# test a single mroot with forced compaction
|
|
[cases.test_mtree_mroot_compact]
|
|
defines.N = [1, 3]
|
|
in = 'lfs.c'
|
|
code = '''
|
|
const char *alphas = "abcdefghijklmnopqrstuvwxyz";
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, CFG) => 0;
|
|
lfsr_mount(&lfs, CFG) => 0;
|
|
lfs_alloc_ack(&lfs);
|
|
|
|
for (lfs_size_t i = 0; i < N; i++) {
|
|
// force mroot to compact
|
|
lfs.mroot.u.rbyd.eoff = -1;
|
|
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(-1, UATTR(i), 0, BUF(&alphas[i % 26], 1)))) => 0;
|
|
}
|
|
|
|
for (lfs_size_t i = 0; i < N; i++) {
|
|
uint8_t buffer[1];
|
|
lfsr_mdir_get(&lfs, &lfs.mroot,
|
|
-1, LFSR_TAG_UATTR(i), buffer, 1) => 1;
|
|
assert(memcmp(buffer, &alphas[i % 26], 1) == 0);
|
|
}
|
|
|
|
lfsr_unmount(&lfs) => 0;
|
|
|
|
|
|
// check things stay sane after remount
|
|
lfsr_mount(&lfs, CFG) => 0;
|
|
|
|
for (lfs_size_t i = 0; i < N; i++) {
|
|
uint8_t buffer[1];
|
|
lfsr_mdir_get(&lfs, &lfs.mroot,
|
|
-1, LFSR_TAG_UATTR(i), buffer, 1) => 1;
|
|
assert(memcmp(buffer, &alphas[i % 26], 1) == 0);
|
|
}
|
|
|
|
lfsr_unmount(&lfs) => 0;
|
|
'''
|
|
|
|
# test a single mroot with many commits
|
|
[cases.test_mtree_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;
|
|
lfs_alloc_ack(&lfs);
|
|
|
|
for (lfs_size_t i = 0; i < N; i++) {
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(-1, UATTR(1), 0, BUF(&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 = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, CFG) => 0;
|
|
lfsr_mount(&lfs, CFG) => 0;
|
|
lfs_alloc_ack(&lfs);
|
|
// remove root bookmark for now
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(0, RM, -1, NULL))) => 0;
|
|
|
|
// prepare mroot with a large attr so the next entry can not fit
|
|
uint8_t buffer[SIZE];
|
|
memset(buffer, 'a', SIZE);
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(-1, UATTR(1), 0, BUF(buffer, SIZE)))) => 0;
|
|
|
|
// create a large entry that needs to be uninlined (but not split!)
|
|
memset(buffer, 'b', SIZE);
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(0, REG, +1, BUF(buffer, SIZE)))) => 0;
|
|
|
|
// force mroot to compact
|
|
lfs.mroot.u.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
|
|
// assert mdir was unininlined correctly
|
|
assert(lfsr_mtree_weight(&lfs.mtree) == 1*lfsr_mleafweight(&lfs));
|
|
// assert mroot now has no entries
|
|
assert(lfs.mroot.u.m.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, "a", 1) == 0);
|
|
|
|
// assert that our entry is still in the mtree
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0*lfsr_mleafweight(&lfs)+0, &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
|
|
lfsr_mdir_get(&lfs, &mdir, mdir.mid, LFSR_TAG_REG,
|
|
buffer, SIZE) => SIZE;
|
|
assert(memcmp(buffer, "b", 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.mtree) == 1*lfsr_mleafweight(&lfs));
|
|
// assert mroot now has no entries
|
|
assert(lfs.mroot.u.m.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, "a", 1) == 0);
|
|
|
|
// assert that our entry is still in the mtree
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0*lfsr_mleafweight(&lfs)+0, &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
|
|
lfsr_mdir_get(&lfs, &mdir, mdir.mid, LFSR_TAG_REG,
|
|
buffer, SIZE) => SIZE;
|
|
assert(memcmp(buffer, "b", 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 = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, CFG) => 0;
|
|
lfsr_mount(&lfs, CFG) => 0;
|
|
lfs_alloc_ack(&lfs);
|
|
// remove root bookmark for now
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(0, RM, -1, NULL))) => 0;
|
|
|
|
// create 2 large entries that needs to be uninlined and split
|
|
uint8_t buffer[SIZE];
|
|
memset(buffer, 'a', SIZE);
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(0, REG, +1, BUF(buffer, SIZE)))) => 0;
|
|
|
|
memset(buffer, 'b', SIZE);
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(1, REG, +1, BUF(buffer, SIZE)))) => 0;
|
|
|
|
// force mroot to compact
|
|
lfs.mroot.u.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
|
|
// assert mdirs were unininlined and split
|
|
assert(lfsr_mtree_weight(&lfs.mtree) == 2*lfsr_mleafweight(&lfs));
|
|
// assert mroot now has no entries
|
|
assert(lfs.mroot.u.m.weight == 0);
|
|
|
|
// assert that our entries are still in the mtree
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0*lfsr_mleafweight(&lfs)+0, &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
lfsr_mdir_get(&lfs, &mdir, mdir.mid, LFSR_TAG_REG,
|
|
buffer, SIZE) => SIZE;
|
|
assert(memcmp(buffer, "a", 1) == 0);
|
|
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 1*lfsr_mleafweight(&lfs)+0, &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
lfsr_mdir_get(&lfs, &mdir, mdir.mid, LFSR_TAG_REG,
|
|
buffer, SIZE) => SIZE;
|
|
assert(memcmp(buffer, "b", 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.mtree) == 2*lfsr_mleafweight(&lfs));
|
|
// assert mroot now has no entries
|
|
assert(lfs.mroot.u.m.weight == 0);
|
|
|
|
// assert that our entries are still in the mtree
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0*lfsr_mleafweight(&lfs)+0, &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
lfsr_mdir_get(&lfs, &mdir, mdir.mid, LFSR_TAG_REG,
|
|
buffer, SIZE) => SIZE;
|
|
assert(memcmp(buffer, "a", 1) == 0);
|
|
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 1*lfsr_mleafweight(&lfs)+0, &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
lfsr_mdir_get(&lfs, &mdir, mdir.mid, LFSR_TAG_REG,
|
|
buffer, SIZE) => SIZE;
|
|
assert(memcmp(buffer, "b", 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 = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, CFG) => 0;
|
|
lfsr_mount(&lfs, CFG) => 0;
|
|
lfs_alloc_ack(&lfs);
|
|
// remove root bookmark for now
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(0, RM, -1, NULL))) => 0;
|
|
|
|
// create an uninlined mdir
|
|
uint8_t buffer[SIZE];
|
|
memset(buffer, 'a', SIZE);
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(-1, UATTR(1), 0, BUF(buffer, SIZE)))) => 0;
|
|
|
|
memset(buffer, 'b', SIZE);
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(0, REG, +1, BUF(buffer, SIZE)))) => 0;
|
|
|
|
// force mroot to compact
|
|
lfs.mroot.u.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
|
|
// assert mdir was unininlined correctly
|
|
assert(lfsr_mtree_weight(&lfs.mtree) == 1*lfsr_mleafweight(&lfs));
|
|
// assert mroot now has no entries
|
|
assert(lfs.mroot.u.m.weight == 0);
|
|
|
|
// now add another large entry to the mdir, forcing a split
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0*lfsr_mleafweight(&lfs)+1, &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
|
|
memset(buffer, 'c', SIZE);
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(mdir.mid, REG, +1, BUF(buffer, SIZE)))) => 0;
|
|
|
|
// force mdir to compact
|
|
mdir.u.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &mdir, NULL, 0) => 0;
|
|
|
|
// assert mdir was split correctly
|
|
assert(lfsr_mtree_weight(&lfs.mtree) == 2*lfsr_mleafweight(&lfs));
|
|
// assert mroot still has no entries
|
|
assert(lfs.mroot.u.m.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, "a", 1) == 0);
|
|
|
|
// assert that our entries are still in the mtree
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0*lfsr_mleafweight(&lfs)+0, &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
lfsr_mdir_get(&lfs, &mdir, mdir.mid, LFSR_TAG_REG,
|
|
buffer, SIZE) => SIZE;
|
|
assert(memcmp(buffer, "b", 1) == 0);
|
|
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 1*lfsr_mleafweight(&lfs)+0, &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
lfsr_mdir_get(&lfs, &mdir, mdir.mid, LFSR_TAG_REG,
|
|
buffer, SIZE) => SIZE;
|
|
assert(memcmp(buffer, "c", 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.mtree) == 2*lfsr_mleafweight(&lfs));
|
|
// assert mroot still has no entries
|
|
assert(lfs.mroot.u.m.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, "a", 1) == 0);
|
|
|
|
// assert that our entries are still in the mtree
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0*lfsr_mleafweight(&lfs)+0, &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
lfsr_mdir_get(&lfs, &mdir, mdir.mid, LFSR_TAG_REG,
|
|
buffer, SIZE) => SIZE;
|
|
assert(memcmp(buffer, "b", 1) == 0);
|
|
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 1*lfsr_mleafweight(&lfs)+0, &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
lfsr_mdir_get(&lfs, &mdir, mdir.mid, LFSR_TAG_REG,
|
|
buffer, SIZE) => SIZE;
|
|
assert(memcmp(buffer, "c", 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;
|
|
lfs_alloc_ack(&lfs);
|
|
// remove root bookmark for now
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(0, RM, -1, NULL))) => 0;
|
|
|
|
// create entries
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree,
|
|
lfs_smax32(
|
|
lfsr_mtree_weight(&lfs.mtree) - lfsr_mleafweight(&lfs),
|
|
0),
|
|
&mdir) => 0;
|
|
for (lfs_size_t i = 0; i < N; i++) {
|
|
// force a compaction?
|
|
if (FORCE_COMPACTION) {
|
|
mdir.u.rbyd.eoff = -1;
|
|
lfs.mroot.u.rbyd.eoff = -1;
|
|
}
|
|
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(mdir.mid, REG, +1,
|
|
BUF(&alphas[i % 26], 1)))) => 0;
|
|
|
|
uint8_t buffer[4];
|
|
lfsr_mdir_get(&lfs, &mdir, mdir.mid, LFSR_TAG_REG,
|
|
buffer, 4) => 1;
|
|
assert(memcmp(buffer, &alphas[i % 26], 1) == 0);
|
|
|
|
mdir.mid += 1;
|
|
}
|
|
|
|
// try looking up each entry
|
|
lfs_size_t i = 0;
|
|
for (lfs_ssize_t mid = 0;
|
|
mid < lfs_smax32(
|
|
lfsr_mtree_weight(&lfs.mtree),
|
|
lfsr_mleafweight(&lfs));
|
|
mid += lfsr_mleafweight(&lfs)) {
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, mid, &mdir) => 0;
|
|
for (; (mdir.mid & lfsr_midrmask(&lfs))
|
|
< (lfs_ssize_t)mdir.u.m.weight;
|
|
mdir.mid += 1) {
|
|
uint8_t buffer[4];
|
|
lfsr_mdir_get(&lfs, &mdir, mdir.mid, LFSR_TAG_REG,
|
|
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 = 0;
|
|
mid < lfs_smax32(
|
|
lfsr_mtree_weight(&lfs.mtree),
|
|
lfsr_mleafweight(&lfs));
|
|
mid += lfsr_mleafweight(&lfs)) {
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, mid, &mdir) => 0;
|
|
for (; (mdir.mid & lfsr_midrmask(&lfs))
|
|
< (lfs_ssize_t)mdir.u.m.weight;
|
|
mdir.mid += 1) {
|
|
uint8_t buffer[4];
|
|
lfsr_mdir_get(&lfs, &mdir, mdir.mid, LFSR_TAG_REG,
|
|
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.SEED = 'range(100)'
|
|
in = 'lfs.c'
|
|
code = '''
|
|
const char *alphas = "abcdefghijklmnopqrstuvwxyz";
|
|
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, CFG) => 0;
|
|
lfsr_mount(&lfs, CFG) => 0;
|
|
lfs_alloc_ack(&lfs);
|
|
// remove root bookmark for now
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(0, RM, -1, NULL))) => 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 = (lfs_ssize_t)(
|
|
TEST_PRNG(&prng) % lfs_max32(
|
|
lfsr_mtree_weight(&lfs.mtree),
|
|
lfsr_mleafweight(&lfs)));
|
|
// fetch mdir
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, mid, &mdir) => 0;
|
|
// limit our mid to our mdir's weight
|
|
mdir.mid = (mdir.mid & lfsr_midbmask(&lfs))
|
|
| (mdir.mid % (mdir.u.m.weight+1));
|
|
|
|
// force a compaction?
|
|
if (FORCE_COMPACTION) {
|
|
mdir.u.rbyd.eoff = -1;
|
|
lfs.mroot.u.rbyd.eoff = -1;
|
|
}
|
|
|
|
// add to rbyd, potentially splitting the mdir
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(mdir.mid, REG, +1,
|
|
BUF(&alphas[i % 26], 1)))) => 0;
|
|
|
|
// make sure we can look up the new entry
|
|
uint8_t buffer[4];
|
|
lfsr_mdir_get(&lfs, &mdir, mdir.mid, LFSR_TAG_REG,
|
|
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 = 0;
|
|
mid < lfs_smax32(
|
|
lfsr_mtree_weight(&lfs.mtree),
|
|
lfsr_mleafweight(&lfs));
|
|
mid += lfsr_mleafweight(&lfs)) {
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, mid, &mdir) => 0;
|
|
for (; (mdir.mid & lfsr_midrmask(&lfs))
|
|
< (lfs_ssize_t)mdir.u.m.weight;
|
|
mdir.mid += 1) {
|
|
uint8_t buffer[4];
|
|
lfsr_mdir_get(&lfs, &mdir, mdir.mid, LFSR_TAG_REG,
|
|
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 = 0;
|
|
mid < lfs_smax32(
|
|
lfsr_mtree_weight(&lfs.mtree),
|
|
lfsr_mleafweight(&lfs));
|
|
mid += lfsr_mleafweight(&lfs)) {
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, mid, &mdir) => 0;
|
|
for (; (mdir.mid & lfsr_midrmask(&lfs))
|
|
< (lfs_ssize_t)mdir.u.m.weight;
|
|
mdir.mid += 1) {
|
|
uint8_t buffer[4];
|
|
lfsr_mdir_get(&lfs, &mdir, mdir.mid, LFSR_TAG_REG,
|
|
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 = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, CFG) => 0;
|
|
lfsr_mount(&lfs, CFG) => 0;
|
|
lfs_alloc_ack(&lfs);
|
|
// remove root bookmark for now
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(0, RM, -1, NULL))) => 0;
|
|
|
|
// create an uninlined mdir
|
|
uint8_t buffer[SIZE];
|
|
memset(buffer, 'a', SIZE);
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(-1, UATTR(1), 0, BUF(buffer, SIZE)))) => 0;
|
|
|
|
memset(buffer, 'b', SIZE);
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(0, REG, +1, BUF(buffer, SIZE)))) => 0;
|
|
|
|
// force mroot to compact
|
|
lfs.mroot.u.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
|
|
// assert mdir was unininlined correctly
|
|
assert(lfsr_mtree_weight(&lfs.mtree) == 1*lfsr_mleafweight(&lfs));
|
|
// assert mroot now has no entries
|
|
assert(lfs.mroot.u.m.weight == 0);
|
|
|
|
// remove the entry, forcing the mdir to be dropped
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0*lfsr_mleafweight(&lfs)+0, &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(0, RM, -1, NULL))) => 0;
|
|
|
|
// assert mdir was dropped
|
|
assert(lfsr_mtree_weight(&lfs.mtree) == 0*lfsr_mleafweight(&lfs));
|
|
// assert mroot still has no entries
|
|
assert(lfs.mroot.u.m.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, "a", 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.mtree) == 0*lfsr_mleafweight(&lfs));
|
|
// assert mroot still has no entries
|
|
assert(lfs.mroot.u.m.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, "a", 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 = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, CFG) => 0;
|
|
lfsr_mount(&lfs, CFG) => 0;
|
|
lfs_alloc_ack(&lfs);
|
|
// remove root bookmark for now
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(0, RM, -1, NULL))) => 0;
|
|
|
|
// create an uninlined mdir
|
|
uint8_t buffer[SIZE];
|
|
memset(buffer, 'a', SIZE);
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(-1, UATTR(1), 0, BUF(buffer, SIZE)))) => 0;
|
|
|
|
memset(buffer, 'b', SIZE);
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(0, REG, +1, BUF(buffer, SIZE)))) => 0;
|
|
|
|
// force mroot to compact
|
|
lfs.mroot.u.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
|
|
// assert mdir was unininlined correctly
|
|
assert(lfsr_mtree_weight(&lfs.mtree) == 1*lfsr_mleafweight(&lfs));
|
|
// assert mroot now has no entries
|
|
assert(lfs.mroot.u.m.weight == 0);
|
|
|
|
// remove the entry, forcing the mdir to be dropped
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0*lfsr_mleafweight(&lfs)+0, &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
|
|
// force mdir to compact while we're removing
|
|
mdir.u.rbyd.eoff = -1;
|
|
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(0, RM, -1, NULL))) => 0;
|
|
|
|
// assert mdir was dropped
|
|
assert(lfsr_mtree_weight(&lfs.mtree) == 0*lfsr_mleafweight(&lfs));
|
|
// assert mroot still has no entries
|
|
assert(lfs.mroot.u.m.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, "a", 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.mtree) == 0*lfsr_mleafweight(&lfs));
|
|
// assert mroot still has no entries
|
|
assert(lfs.mroot.u.m.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, "a", 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 = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, CFG) => 0;
|
|
lfsr_mount(&lfs, CFG) => 0;
|
|
lfs_alloc_ack(&lfs);
|
|
// remove root bookmark for now
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(0, RM, -1, NULL))) => 0;
|
|
|
|
// create an uninlined mdir
|
|
uint8_t buffer[SIZE];
|
|
memset(buffer, 'a', SIZE);
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(-1, UATTR(1), 0, BUF(buffer, SIZE)))) => 0;
|
|
|
|
memset(buffer, 'b', SIZE);
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(0, REG, +1, BUF(buffer, SIZE)))) => 0;
|
|
|
|
// force mroot to compact
|
|
lfs.mroot.u.rbyd.eoff = -1;
|
|
|
|
// remove the entry as we compact, forcing the mdir to be dropped
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(0, RM, -1, NULL))) => 0;
|
|
|
|
// assert mdir was dropped
|
|
assert(lfsr_mtree_weight(&lfs.mtree) == 0*lfsr_mleafweight(&lfs));
|
|
// assert mroot has no entries
|
|
assert(lfs.mroot.u.m.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, "a", 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.mtree) == 0*lfsr_mleafweight(&lfs));
|
|
// assert mroot has no entries
|
|
assert(lfs.mroot.u.m.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, "a", 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 = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, CFG) => 0;
|
|
lfsr_mount(&lfs, CFG) => 0;
|
|
lfs_alloc_ack(&lfs);
|
|
// remove root bookmark for now
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(0, RM, -1, NULL))) => 0;
|
|
|
|
// create an mdir that needs to be split
|
|
uint8_t buffer[SIZE];
|
|
memset(buffer, 'a', SIZE);
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(0, REG, +1, BUF(buffer, SIZE)))) => 0;
|
|
|
|
memset(buffer, 'b', SIZE);
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(1, REG, +1, BUF(buffer, SIZE)))) => 0;
|
|
|
|
// force mroot to compact
|
|
lfs.mroot.u.rbyd.eoff = -1;
|
|
|
|
// remove the left entry as we compact, forcing the left
|
|
// mdir to be dropped
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(0, RM, -1, NULL))) => 0;
|
|
|
|
// assert mdir was dropped
|
|
assert(lfsr_mtree_weight(&lfs.mtree) == 1*lfsr_mleafweight(&lfs));
|
|
// assert mroot has no entries
|
|
assert(lfs.mroot.u.m.weight == 0);
|
|
|
|
// assert that one entry is still in the mtree
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0*lfsr_mleafweight(&lfs)+0, &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
|
|
lfsr_mdir_get(&lfs, &mdir, mdir.mid, LFSR_TAG_REG,
|
|
buffer, SIZE) => SIZE;
|
|
assert(memcmp(buffer, "b", 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.mtree) == 1*lfsr_mleafweight(&lfs));
|
|
// assert mroot has no entries
|
|
assert(lfs.mroot.u.m.weight == 0);
|
|
|
|
// assert that one entry is still in the mtree
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0*lfsr_mleafweight(&lfs)+0, &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
|
|
lfsr_mdir_get(&lfs, &mdir, mdir.mid, LFSR_TAG_REG,
|
|
buffer, SIZE) => SIZE;
|
|
assert(memcmp(buffer, "b", 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 = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, CFG) => 0;
|
|
lfsr_mount(&lfs, CFG) => 0;
|
|
lfs_alloc_ack(&lfs);
|
|
// remove root bookmark for now
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(0, RM, -1, NULL))) => 0;
|
|
|
|
// create an mdir that needs to be split
|
|
uint8_t buffer[SIZE];
|
|
memset(buffer, 'a', SIZE);
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(0, REG, +1, BUF(buffer, SIZE)))) => 0;
|
|
|
|
memset(buffer, 'b', SIZE);
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(1, REG, +1, BUF(buffer, SIZE)))) => 0;
|
|
|
|
// force mroot to compact
|
|
lfs.mroot.u.rbyd.eoff = -1;
|
|
|
|
// remove the right entry as we compact, forcing the right mdir
|
|
// to be dropped
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(1, RM, -1, NULL))) => 0;
|
|
|
|
// assert mdir was dropped
|
|
assert(lfsr_mtree_weight(&lfs.mtree) == 1*lfsr_mleafweight(&lfs));
|
|
// assert mroot has no entries
|
|
assert(lfs.mroot.u.m.weight == 0);
|
|
|
|
// assert that one entry is still in the mtree
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0*lfsr_mleafweight(&lfs)+0, &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
|
|
lfsr_mdir_get(&lfs, &mdir, mdir.mid, LFSR_TAG_REG,
|
|
buffer, SIZE) => SIZE;
|
|
assert(memcmp(buffer, "a", 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.mtree) == 1*lfsr_mleafweight(&lfs));
|
|
// assert mroot has no entries
|
|
assert(lfs.mroot.u.m.weight == 0);
|
|
|
|
// assert that one entry is still in the mtree
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0*lfsr_mleafweight(&lfs)+0, &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
|
|
lfsr_mdir_get(&lfs, &mdir, mdir.mid, LFSR_TAG_REG,
|
|
buffer, SIZE) => SIZE;
|
|
assert(memcmp(buffer, "a", 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 = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, CFG) => 0;
|
|
lfsr_mount(&lfs, CFG) => 0;
|
|
lfs_alloc_ack(&lfs);
|
|
// remove root bookmark for now
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(0, RM, -1, NULL))) => 0;
|
|
|
|
// create an mdir that needs to be split
|
|
uint8_t buffer[SIZE];
|
|
memset(buffer, 'a', SIZE);
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(0, REG, +1, BUF(buffer, SIZE)))) => 0;
|
|
|
|
memset(buffer, 'b', SIZE);
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(1, REG, +1, BUF(buffer, SIZE)))) => 0;
|
|
|
|
// force mroot to compact
|
|
lfs.mroot.u.rbyd.eoff = -1;
|
|
|
|
// remove both entries as we compact, forcing both mdirs to be dropped
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(0, RM, -1, NULL),
|
|
LFSR_ATTR(0, RM, -1, NULL))) => 0;
|
|
|
|
// assert mdir was dropped
|
|
assert(lfsr_mtree_weight(&lfs.mtree) == 0*lfsr_mleafweight(&lfs));
|
|
// assert mroot has no entries
|
|
assert(lfs.mroot.u.m.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.mtree) == 0*lfsr_mleafweight(&lfs));
|
|
// assert mroot has no entries
|
|
assert(lfs.mroot.u.m.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 = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, CFG) => 0;
|
|
lfsr_mount(&lfs, CFG) => 0;
|
|
lfs_alloc_ack(&lfs);
|
|
// remove root bookmark for now
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(0, RM, -1, NULL))) => 0;
|
|
|
|
// create an uninlined mdir
|
|
uint8_t buffer[SIZE];
|
|
memset(buffer, 'a', SIZE);
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(-1, UATTR(1), 0, BUF(buffer, SIZE)))) => 0;
|
|
|
|
memset(buffer, 'b', SIZE);
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(0, REG, +1, BUF(buffer, SIZE)))) => 0;
|
|
|
|
// force mroot to compact
|
|
lfs.mroot.u.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
|
|
// assert mdir was unininlined correctly
|
|
assert(lfsr_mtree_weight(&lfs.mtree) == 1*lfsr_mleafweight(&lfs));
|
|
// assert mroot now has no entries
|
|
assert(lfs.mroot.u.m.weight == 0);
|
|
|
|
// now add another large entry to the mdir, forcing a split
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0*lfsr_mleafweight(&lfs)+1, &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
|
|
memset(buffer, 'c', SIZE);
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(mdir.mid, REG, +1, BUF(buffer, SIZE)))) => 0;
|
|
|
|
// force mdir to compact
|
|
mdir.u.rbyd.eoff = -1;
|
|
|
|
// remove the left entry as we compact, forcing the left
|
|
// mdir to be dropped
|
|
mdir.mid = 0*lfsr_mleafweight(&lfs)+0;
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(mdir.mid, RM, -1, NULL))) => 0;
|
|
|
|
// assert mdir was dropped
|
|
assert(lfsr_mtree_weight(&lfs.mtree) == 1*lfsr_mleafweight(&lfs));
|
|
// assert mroot has no entries
|
|
assert(lfs.mroot.u.m.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, "a", 1) == 0);
|
|
|
|
// assert that one entry is still in the mtree
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0*lfsr_mleafweight(&lfs)+0, &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
|
|
lfsr_mdir_get(&lfs, &mdir, mdir.mid, LFSR_TAG_REG,
|
|
buffer, SIZE) => SIZE;
|
|
assert(memcmp(buffer, "c", 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.mtree) == 1*lfsr_mleafweight(&lfs));
|
|
// assert mroot has no entries
|
|
assert(lfs.mroot.u.m.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, "a", 1) == 0);
|
|
|
|
// assert that one entry is still in the mtree
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0*lfsr_mleafweight(&lfs)+0, &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
|
|
lfsr_mdir_get(&lfs, &mdir, mdir.mid, LFSR_TAG_REG,
|
|
buffer, SIZE) => SIZE;
|
|
assert(memcmp(buffer, "c", 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 = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, CFG) => 0;
|
|
lfsr_mount(&lfs, CFG) => 0;
|
|
lfs_alloc_ack(&lfs);
|
|
// remove root bookmark for now
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(0, RM, -1, NULL))) => 0;
|
|
|
|
// create an uninlined mdir
|
|
uint8_t buffer[SIZE];
|
|
memset(buffer, 'a', SIZE);
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(-1, UATTR(1), 0, BUF(buffer, SIZE)))) => 0;
|
|
|
|
memset(buffer, 'b', SIZE);
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(0, REG, +1, BUF(buffer, SIZE)))) => 0;
|
|
|
|
// force mroot to compact
|
|
lfs.mroot.u.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
|
|
// assert mdir was unininlined correctly
|
|
assert(lfsr_mtree_weight(&lfs.mtree) == 1*lfsr_mleafweight(&lfs));
|
|
// assert mroot now has no entries
|
|
assert(lfs.mroot.u.m.weight == 0);
|
|
|
|
// now add another large entry to the mdir, forcing a split
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0*lfsr_mleafweight(&lfs)+1, &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
|
|
memset(buffer, 'c', SIZE);
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(mdir.mid, REG, +1, BUF(buffer, SIZE)))) => 0;
|
|
|
|
// force mdir to compact
|
|
mdir.u.rbyd.eoff = -1;
|
|
|
|
// remove the right entry as we compact, forcing the right
|
|
// mdir to be dropped
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(1, RM, -1, NULL))) => 0;
|
|
|
|
// assert mdir was dropped
|
|
assert(lfsr_mtree_weight(&lfs.mtree) == 1*lfsr_mleafweight(&lfs));
|
|
// assert mroot has no entries
|
|
assert(lfs.mroot.u.m.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, "a", 1) == 0);
|
|
|
|
// assert that one entry is still in the mtree
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0*lfsr_mleafweight(&lfs)+0, &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
|
|
lfsr_mdir_get(&lfs, &mdir, mdir.mid, LFSR_TAG_REG,
|
|
buffer, SIZE) => SIZE;
|
|
assert(memcmp(buffer, "b", 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.mtree) == 1*lfsr_mleafweight(&lfs));
|
|
// assert mroot has no entries
|
|
assert(lfs.mroot.u.m.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, "a", 1) == 0);
|
|
|
|
// assert that one entry is still in the mtree
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0*lfsr_mleafweight(&lfs)+0, &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
|
|
lfsr_mdir_get(&lfs, &mdir, mdir.mid, LFSR_TAG_REG,
|
|
buffer, SIZE) => SIZE;
|
|
assert(memcmp(buffer, "b", 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 = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, CFG) => 0;
|
|
lfsr_mount(&lfs, CFG) => 0;
|
|
lfs_alloc_ack(&lfs);
|
|
// remove root bookmark for now
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(0, RM, -1, NULL))) => 0;
|
|
|
|
// create an uninlined mdir
|
|
uint8_t buffer[SIZE];
|
|
memset(buffer, 'a', SIZE);
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(-1, UATTR(1), 0, BUF(buffer, SIZE)))) => 0;
|
|
|
|
memset(buffer, 'b', SIZE);
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(0, REG, +1, BUF(buffer, SIZE)))) => 0;
|
|
|
|
// force mroot to compact
|
|
lfs.mroot.u.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
|
|
// assert mdir was unininlined correctly
|
|
assert(lfsr_mtree_weight(&lfs.mtree) == 1*lfsr_mleafweight(&lfs));
|
|
// assert mroot now has no entries
|
|
assert(lfs.mroot.u.m.weight == 0);
|
|
|
|
// now add another large entry to the mdir, forcing a split
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0*lfsr_mleafweight(&lfs)+1, &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
|
|
memset(buffer, 'c', SIZE);
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(mdir.mid, REG, +1, BUF(buffer, SIZE)))) => 0;
|
|
|
|
// force mdir to compact
|
|
mdir.u.rbyd.eoff = -1;
|
|
|
|
// remove both entries as we compact, forcing both mdirs to be dropped
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(0, RM, -1, NULL),
|
|
LFSR_ATTR(0, RM, -1, NULL))) => 0;
|
|
|
|
// assert mdir was dropped
|
|
assert(lfsr_mtree_weight(&lfs.mtree) == 0*lfsr_mleafweight(&lfs));
|
|
// assert mroot has no entries
|
|
assert(lfs.mroot.u.m.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, "a", 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.mtree) == 0*lfsr_mleafweight(&lfs));
|
|
// assert mroot has no entries
|
|
assert(lfs.mroot.u.m.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, "a", 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]
|
|
# note we never drop all the way to zero, our mtree does not support this
|
|
defines.REMAINING = [20, 5, 1]
|
|
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;
|
|
lfs_alloc_ack(&lfs);
|
|
// remove root bookmark for now
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(0, RM, -1, NULL))) => 0;
|
|
|
|
// create entries
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree,
|
|
lfs_smax32(
|
|
lfsr_mtree_weight(&lfs.mtree) - lfsr_mleafweight(&lfs),
|
|
0),
|
|
&mdir) => 0;
|
|
for (lfs_size_t i = 0; i < N; i++) {
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(mdir.mid, REG, +1,
|
|
BUF(&alphas[i % 26], 1)))) => 0;
|
|
|
|
uint8_t buffer[4];
|
|
lfsr_mdir_get(&lfs, &mdir, mdir.mid, LFSR_TAG_REG,
|
|
buffer, 4) => 1;
|
|
assert(memcmp(buffer, &alphas[i % 26], 1) == 0);
|
|
|
|
mdir.mid += 1;
|
|
}
|
|
|
|
// remove entries
|
|
for (lfs_size_t i = 0; i < N - REMAINING; i++) {
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree,
|
|
0*lfsr_mleafweight(&lfs)+0, &mdir) => 0;
|
|
|
|
// drop should make sure we never have empty mdirs
|
|
assert(lfsr_mtree_ismptr(&lfs.mtree) || mdir.u.m.weight > 0);
|
|
|
|
// force a compaction?
|
|
if (FORCE_COMPACTION) {
|
|
mdir.u.rbyd.eoff = -1;
|
|
lfs.mroot.u.rbyd.eoff = -1;
|
|
}
|
|
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(0, RM, -1, NULL))) => 0;
|
|
}
|
|
|
|
// try looking up each entry
|
|
lfs_size_t i = N - REMAINING;
|
|
for (lfs_ssize_t mid = 0;
|
|
mid < lfs_smax32(
|
|
lfsr_mtree_weight(&lfs.mtree),
|
|
lfsr_mleafweight(&lfs));
|
|
mid += lfsr_mleafweight(&lfs)) {
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, mid, &mdir) => 0;
|
|
for (; (mdir.mid & lfsr_midrmask(&lfs))
|
|
< (lfs_ssize_t)mdir.u.m.weight;
|
|
mdir.mid += 1) {
|
|
uint8_t buffer[4];
|
|
lfsr_mdir_get(&lfs, &mdir, mdir.mid, LFSR_TAG_REG,
|
|
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 = 0;
|
|
mid < lfs_smax32(
|
|
lfsr_mtree_weight(&lfs.mtree),
|
|
lfsr_mleafweight(&lfs));
|
|
mid += lfsr_mleafweight(&lfs)) {
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, mid, &mdir) => 0;
|
|
for (; (mdir.mid & lfsr_midrmask(&lfs))
|
|
< (lfs_ssize_t)mdir.u.m.weight;
|
|
mdir.mid += 1) {
|
|
uint8_t buffer[4];
|
|
lfsr_mdir_get(&lfs, &mdir, mdir.mid, LFSR_TAG_REG,
|
|
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]
|
|
# note we never drop all the way to zero, our mtree does not support this
|
|
defines.REMAINING = 1
|
|
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;
|
|
lfs_alloc_ack(&lfs);
|
|
// remove root bookmark for now
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(0, RM, -1, NULL))) => 0;
|
|
|
|
for (lfs_size_t cycle = 0; cycle < CYCLES; cycle++) {
|
|
// create entries, note we may have leftovers from the previous cycle
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree,
|
|
lfs_smax32(
|
|
lfsr_mtree_weight(&lfs.mtree) - lfsr_mleafweight(&lfs),
|
|
0),
|
|
&mdir) => 0;
|
|
for (lfs_size_t i = 0; i < (cycle == 0 ? N : N-1); i++) {
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(mdir.mid, REG, +1,
|
|
BUF(&alphas[i % 26], 1)))) => 0;
|
|
|
|
uint8_t buffer[4];
|
|
lfsr_mdir_get(&lfs, &mdir, mdir.mid, LFSR_TAG_REG,
|
|
buffer, 4) => 1;
|
|
assert(memcmp(buffer, &alphas[i % 26], 1) == 0);
|
|
|
|
mdir.mid += 1;
|
|
}
|
|
|
|
// try looking up each entry
|
|
lfs_size_t i = 0;
|
|
for (lfs_ssize_t mid = 0;
|
|
mid < lfs_smax32(
|
|
lfsr_mtree_weight(&lfs.mtree),
|
|
lfsr_mleafweight(&lfs));
|
|
mid += lfsr_mleafweight(&lfs)) {
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, mid, &mdir) => 0;
|
|
for (; (mdir.mid & lfsr_midrmask(&lfs))
|
|
< (lfs_ssize_t)mdir.u.m.weight;
|
|
mdir.mid += 1) {
|
|
uint8_t buffer[4];
|
|
lfsr_mdir_get(&lfs, &mdir, mdir.mid, LFSR_TAG_REG,
|
|
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-1; i++) {
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree,
|
|
0*lfsr_mleafweight(&lfs)+0, &mdir) => 0;
|
|
|
|
// drop should make sure we never have empty mdirs
|
|
assert(lfsr_mtree_ismptr(&lfs.mtree) || mdir.u.m.weight > 0);
|
|
|
|
// force a compaction?
|
|
if (FORCE_COMPACTION) {
|
|
mdir.u.rbyd.eoff = -1;
|
|
lfs.mroot.u.rbyd.eoff = -1;
|
|
}
|
|
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(0, RM, -1, NULL))) => 0;
|
|
}
|
|
|
|
// try looking up each entry
|
|
i = N - REMAINING;
|
|
for (lfs_ssize_t mid = 0;
|
|
mid < lfs_smax32(
|
|
lfsr_mtree_weight(&lfs.mtree),
|
|
lfsr_mleafweight(&lfs));
|
|
mid += lfsr_mleafweight(&lfs)) {
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, mid, &mdir) => 0;
|
|
for (; (mdir.mid & lfsr_midrmask(&lfs))
|
|
< (lfs_ssize_t)mdir.u.m.weight;
|
|
mdir.mid += 1) {
|
|
uint8_t buffer[4];
|
|
lfsr_mdir_get(&lfs, &mdir, mdir.mid, LFSR_TAG_REG,
|
|
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
|
|
lfs_size_t i = N - REMAINING;
|
|
for (lfs_ssize_t mid = 0;
|
|
mid < lfs_smax32(
|
|
lfsr_mtree_weight(&lfs.mtree),
|
|
lfsr_mleafweight(&lfs));
|
|
mid += lfsr_mleafweight(&lfs)) {
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, mid, &mdir) => 0;
|
|
for (; (mdir.mid & lfsr_midrmask(&lfs))
|
|
< (lfs_ssize_t)mdir.u.m.weight;
|
|
mdir.mid += 1) {
|
|
uint8_t buffer[4];
|
|
lfsr_mdir_get(&lfs, &mdir, mdir.mid, LFSR_TAG_REG,
|
|
buffer, 4) => 1;
|
|
assert(memcmp(buffer, &alphas[i % 26], 1) == 0);
|
|
i += 1;
|
|
}
|
|
}
|
|
assert(i == N);
|
|
|
|
lfsr_unmount(&lfs) => 0;
|
|
'''
|
|
|
|
[cases.test_mtree_drop_fuzz]
|
|
defines.N = [5, 10, 20, 40, 80, 160]
|
|
defines.FORCE_COMPACTION = [false, true]
|
|
defines.SEED = 'range(100)'
|
|
in = 'lfs.c'
|
|
code = '''
|
|
const char *alphas = "abcdefghijklmnopqrstuvwxyz";
|
|
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, CFG) => 0;
|
|
lfsr_mount(&lfs, CFG) => 0;
|
|
lfs_alloc_ack(&lfs);
|
|
// remove root bookmark for now
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(0, RM, -1, NULL))) => 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 = (lfs_ssize_t)(
|
|
TEST_PRNG(&prng) % lfs_max32(
|
|
lfsr_mtree_weight(&lfs.mtree),
|
|
lfsr_mleafweight(&lfs)));
|
|
// fetch mdir
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, mid, &mdir) => 0;
|
|
// limit our mid to our mdir's weight
|
|
mdir.mid = (mdir.mid & lfsr_midbmask(&lfs))
|
|
| (mdir.mid % (mdir.u.m.weight+1));
|
|
// choose to create or delete, note we make sure to never delete to zero
|
|
uint8_t op = ((mdir.mid & lfsr_midrmask(&lfs)) == mdir.u.m.weight
|
|
|| ((mdir.mid & lfsr_midrmask(&lfs)) == mdir.u.m.weight-1
|
|
&& lfsr_mtree_weight(&lfs.mtree)
|
|
== lfsr_mleafweight(&lfs))
|
|
? 0
|
|
: TEST_PRNG(&prng) % 2);
|
|
|
|
// force a compaction?
|
|
if (FORCE_COMPACTION) {
|
|
mdir.u.rbyd.eoff = -1;
|
|
lfs.mroot.u.rbyd.eoff = -1;
|
|
}
|
|
|
|
// create
|
|
if (op == 0) {
|
|
// add to rbyd, potentially splitting the mdir
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(mdir.mid, REG, +1,
|
|
BUF(&alphas[i % 26], 1)))) => 0;
|
|
|
|
// make sure we can look up the new entry
|
|
uint8_t buffer[4];
|
|
lfsr_mdir_get(&lfs, &mdir, mdir.mid, LFSR_TAG_REG,
|
|
buffer, 4) => 1;
|
|
assert(memcmp(buffer, &alphas[i % 26], 1) == 0);
|
|
|
|
count += 1;
|
|
|
|
// delete
|
|
} else {
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(mdir.mid, RM, -1, NULL))) => 0;
|
|
|
|
count -= 1;
|
|
}
|
|
}
|
|
|
|
// try looking up each entry
|
|
lfs_size_t count_ = 0;
|
|
|
|
for (lfs_ssize_t mid = 0;
|
|
mid < lfs_smax32(
|
|
lfsr_mtree_weight(&lfs.mtree),
|
|
lfsr_mleafweight(&lfs));
|
|
mid += lfsr_mleafweight(&lfs)) {
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, mid, &mdir) => 0;
|
|
|
|
// drop should make sure we never have empty mdirs
|
|
assert(lfsr_mtree_ismptr(&lfs.mtree) || mdir.u.m.weight > 0);
|
|
|
|
for (; (mdir.mid & lfsr_midrmask(&lfs))
|
|
< (lfs_ssize_t)mdir.u.m.weight;
|
|
mdir.mid += 1) {
|
|
uint8_t buffer[4];
|
|
lfsr_mdir_get(&lfs, &mdir, mdir.mid, LFSR_TAG_REG,
|
|
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 = 0;
|
|
mid < lfs_smax32(
|
|
lfsr_mtree_weight(&lfs.mtree),
|
|
lfsr_mleafweight(&lfs));
|
|
mid += lfsr_mleafweight(&lfs)) {
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, mid, &mdir) => 0;
|
|
|
|
// drop should make sure we never have empty mdirs
|
|
assert(lfsr_mtree_ismptr(&lfs.mtree) || mdir.u.m.weight > 0);
|
|
|
|
for (; (mdir.mid & lfsr_midrmask(&lfs))
|
|
< (lfs_ssize_t)mdir.u.m.weight;
|
|
mdir.mid += 1) {
|
|
uint8_t buffer[4];
|
|
lfsr_mdir_get(&lfs, &mdir, mdir.mid, LFSR_TAG_REG,
|
|
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 = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, CFG) => 0;
|
|
lfsr_mount(&lfs, CFG) => 0;
|
|
lfs_alloc_ack(&lfs);
|
|
// remove root bookmark for now
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(0, RM, -1, NULL))) => 0;
|
|
|
|
// prepare mroot with a large attr so the next entry can not fit
|
|
uint8_t buffer[SIZE];
|
|
memset(buffer, 'a', SIZE);
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(-1, UATTR(1), 0, BUF(buffer, SIZE)))) => 0;
|
|
|
|
// create a large entry that needs to be uninlined (but not split!)
|
|
memset(buffer, 'b', SIZE);
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(0, REG, +1, BUF(buffer, SIZE)))) => 0;
|
|
|
|
// force mroot to compact
|
|
lfs.mroot.u.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
|
|
// assert mtree has one mdir
|
|
assert(lfsr_mtree_weight(&lfs.mtree) == 1*lfsr_mleafweight(&lfs));
|
|
// assert mroot now has no entries
|
|
assert(lfs.mroot.u.m.weight == 0);
|
|
|
|
// force mdir to compact twice, this should relocate
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0*lfsr_mleafweight(&lfs)+0, &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
|
|
lfsr_mdir_t old_mdir = mdir;
|
|
|
|
mdir.u.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &mdir, NULL, 0) => 0;
|
|
mdir.u.rbyd.eoff = -1;
|
|
memset(buffer, 'c', SIZE);
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(mdir.mid, REG, 0, BUF(buffer, SIZE)))) => 0;
|
|
|
|
// assert we relocated
|
|
assert(lfsr_mdir_cmp(&old_mdir, &mdir) != 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, "a", 1) == 0);
|
|
|
|
// assert that our entry is still in the mtree
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0*lfsr_mleafweight(&lfs)+0, &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
|
|
lfsr_mdir_get(&lfs, &mdir, mdir.mid, LFSR_TAG_REG,
|
|
buffer, SIZE) => SIZE;
|
|
assert(memcmp(buffer, "c", 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.mtree) == 1*lfsr_mleafweight(&lfs));
|
|
// assert mroot now has no entries
|
|
assert(lfs.mroot.u.m.weight == 0);
|
|
|
|
// assert we relocated
|
|
assert(lfsr_mdir_cmp(&old_mdir, &mdir) != 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, "a", 1) == 0);
|
|
|
|
// assert that our entry is still in the mtree
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0*lfsr_mleafweight(&lfs)+0, &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
|
|
lfsr_mdir_get(&lfs, &mdir, mdir.mid, LFSR_TAG_REG,
|
|
buffer, SIZE) => SIZE;
|
|
assert(memcmp(buffer, "c", 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 = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, CFG) => 0;
|
|
lfsr_mount(&lfs, CFG) => 0;
|
|
lfs_alloc_ack(&lfs);
|
|
// remove root bookmark for now
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(0, RM, -1, NULL))) => 0;
|
|
|
|
// create 2 large entries that needs to be uninlined and split
|
|
uint8_t buffer[SIZE];
|
|
memset(buffer, 'a', SIZE);
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(0, REG, +1, BUF(buffer, SIZE)))) => 0;
|
|
|
|
memset(buffer, 'b', SIZE);
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(1, REG, +1, BUF(buffer, SIZE)))) => 0;
|
|
|
|
// force mroot to compact
|
|
lfs.mroot.u.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
|
|
// assert mdirs were unininlined and split
|
|
assert(lfsr_mtree_weight(&lfs.mtree) == 2*lfsr_mleafweight(&lfs));
|
|
// assert mroot now has no entries
|
|
assert(lfs.mroot.u.m.weight == 0);
|
|
|
|
// force mdir to compact twice, this should relocate
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0*lfsr_mleafweight(&lfs)+0, &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
|
|
lfsr_mdir_t old_mdir = mdir;
|
|
|
|
mdir.u.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &mdir, NULL, 0) => 0;
|
|
mdir.u.rbyd.eoff = -1;
|
|
memset(buffer, 'c', SIZE);
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(mdir.mid, REG, 0, BUF(buffer, SIZE)))) => 0;
|
|
|
|
// assert we relocated
|
|
assert(lfsr_mdir_cmp(&old_mdir, &mdir) != 0);
|
|
|
|
// assert that our entries are still in the mtree
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0*lfsr_mleafweight(&lfs)+0, &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
lfsr_mdir_get(&lfs, &mdir, mdir.mid, LFSR_TAG_REG,
|
|
buffer, SIZE) => SIZE;
|
|
assert(memcmp(buffer, "c", 1) == 0);
|
|
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 1*lfsr_mleafweight(&lfs)+0, &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
lfsr_mdir_get(&lfs, &mdir, mdir.mid, LFSR_TAG_REG,
|
|
buffer, SIZE) => SIZE;
|
|
assert(memcmp(buffer, "b", 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.mtree) == 2*lfsr_mleafweight(&lfs));
|
|
// assert mroot now has no entries
|
|
assert(lfs.mroot.u.m.weight == 0);
|
|
|
|
// assert we relocated
|
|
assert(lfsr_mdir_cmp(&old_mdir, &mdir) != 0);
|
|
|
|
// assert that our entries are still in the mtree
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0*lfsr_mleafweight(&lfs)+0, &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
lfsr_mdir_get(&lfs, &mdir, mdir.mid, LFSR_TAG_REG,
|
|
buffer, SIZE) => SIZE;
|
|
assert(memcmp(buffer, "c", 1) == 0);
|
|
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 1*lfsr_mleafweight(&lfs)+0, &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
lfsr_mdir_get(&lfs, &mdir, mdir.mid, LFSR_TAG_REG,
|
|
buffer, SIZE) => SIZE;
|
|
assert(memcmp(buffer, "b", 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 = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, CFG) => 0;
|
|
lfsr_mount(&lfs, CFG) => 0;
|
|
lfs_alloc_ack(&lfs);
|
|
// remove root bookmark for now
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(0, RM, -1, NULL))) => 0;
|
|
|
|
// create 2 large entries that needs to be uninlined and split
|
|
uint8_t buffer[SIZE];
|
|
memset(buffer, 'a', SIZE);
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(0, REG, +1, BUF(buffer, SIZE)))) => 0;
|
|
|
|
memset(buffer, 'b', SIZE);
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(1, REG, +1, BUF(buffer, SIZE)))) => 0;
|
|
|
|
// force mroot to compact
|
|
lfs.mroot.u.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
|
|
// assert mdirs were unininlined and split
|
|
assert(lfsr_mtree_weight(&lfs.mtree) == 2*lfsr_mleafweight(&lfs));
|
|
// assert mroot now has no entries
|
|
assert(lfs.mroot.u.m.weight == 0);
|
|
|
|
// force mdir to compact twice, this should relocate
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 1*lfsr_mleafweight(&lfs)+0, &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
|
|
lfsr_mdir_t old_mdir = mdir;
|
|
|
|
mdir.u.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &mdir, NULL, 0) => 0;
|
|
mdir.u.rbyd.eoff = -1;
|
|
memset(buffer, 'c', SIZE);
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(mdir.mid, REG, 0, BUF(buffer, SIZE)))) => 0;
|
|
|
|
// assert we relocated
|
|
assert(lfsr_mdir_cmp(&old_mdir, &mdir) != 0);
|
|
|
|
// assert that our entries are still in the mtree
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0*lfsr_mleafweight(&lfs)+0, &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
lfsr_mdir_get(&lfs, &mdir, mdir.mid, LFSR_TAG_REG,
|
|
buffer, SIZE) => SIZE;
|
|
assert(memcmp(buffer, "a", 1) == 0);
|
|
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 1*lfsr_mleafweight(&lfs)+0, &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
lfsr_mdir_get(&lfs, &mdir, mdir.mid, LFSR_TAG_REG,
|
|
buffer, SIZE) => SIZE;
|
|
assert(memcmp(buffer, "c", 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.mtree) == 2*lfsr_mleafweight(&lfs));
|
|
// assert mroot now has no entries
|
|
assert(lfs.mroot.u.m.weight == 0);
|
|
|
|
// assert we relocated
|
|
assert(lfsr_mdir_cmp(&old_mdir, &mdir) != 0);
|
|
|
|
// assert that our entries are still in the mtree
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0*lfsr_mleafweight(&lfs)+0, &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
lfsr_mdir_get(&lfs, &mdir, mdir.mid, LFSR_TAG_REG,
|
|
buffer, SIZE) => SIZE;
|
|
assert(memcmp(buffer, "a", 1) == 0);
|
|
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 1*lfsr_mleafweight(&lfs)+0, &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
lfsr_mdir_get(&lfs, &mdir, mdir.mid, LFSR_TAG_REG,
|
|
buffer, SIZE) => SIZE;
|
|
assert(memcmp(buffer, "c", 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 = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, CFG) => 0;
|
|
lfsr_mount(&lfs, CFG) => 0;
|
|
lfs_alloc_ack(&lfs);
|
|
// remove root bookmark for now
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(0, RM, -1, NULL))) => 0;
|
|
|
|
// prepare mroot with an attr
|
|
uint8_t buffer[SIZE];
|
|
memset(buffer, 'a', SIZE);
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(-1, UATTR(1), 0, BUF(buffer, SIZE)))) => 0;
|
|
|
|
// force mroot to compact twice, this should extend the mroot
|
|
lfsr_mdir_t old_mroot = lfs.mroot;
|
|
|
|
lfs.mroot.u.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
lfs.mroot.u.rbyd.eoff = -1;
|
|
memset(buffer, 'b', SIZE);
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(-1, UATTR(1), 0, BUF(buffer, SIZE)))) => 0;
|
|
|
|
// assert we relocated
|
|
assert(lfsr_mdir_cmp(&old_mroot, &lfs.mroot) != 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, "b", 1) == 0);
|
|
|
|
lfsr_unmount(&lfs) => 0;
|
|
|
|
|
|
// check things stay sane after remount
|
|
lfsr_mount(&lfs, CFG) => 0;
|
|
|
|
// assert we relocated
|
|
assert(lfsr_mdir_cmp(&old_mroot, &lfs.mroot) != 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, "b", 1) == 0);
|
|
|
|
lfsr_unmount(&lfs) => 0;
|
|
'''
|
|
|
|
[cases.test_mtree_extend_twice]
|
|
# 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
|
|
# force our block to compact by setting prog_size=block_size, we don't have
|
|
# any way to indirectly force the intermediary mroots to compact otherwise
|
|
defines.PROG_SIZE = 'BLOCK_SIZE'
|
|
in = 'lfs.c'
|
|
code = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, CFG) => 0;
|
|
lfsr_mount(&lfs, CFG) => 0;
|
|
lfs_alloc_ack(&lfs);
|
|
// remove root bookmark for now
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(0, RM, -1, NULL))) => 0;
|
|
|
|
// prepare mroot with an attr
|
|
uint8_t buffer[SIZE];
|
|
memset(buffer, 'a', SIZE);
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(-1, UATTR(1), 0, BUF(buffer, SIZE)))) => 0;
|
|
|
|
// force mroot to compact 2x2 times, this should extend the mroot twice
|
|
lfsr_mdir_t old_mroot = lfs.mroot;
|
|
|
|
for (int i = 0; i < 4; i++) {
|
|
lfs.mroot.u.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
}
|
|
lfs.mroot.u.rbyd.eoff = -1;
|
|
memset(buffer, 'b', SIZE);
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(-1, UATTR(1), 0, BUF(buffer, SIZE)))) => 0;
|
|
|
|
// assert we relocated
|
|
assert(lfsr_mdir_cmp(&old_mroot, &lfs.mroot) != 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, "b", 1) == 0);
|
|
|
|
lfsr_unmount(&lfs) => 0;
|
|
|
|
|
|
// check things stay sane after remount
|
|
lfsr_mount(&lfs, CFG) => 0;
|
|
|
|
// assert we relocated
|
|
assert(lfsr_mdir_cmp(&old_mroot, &lfs.mroot) != 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, "b", 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 = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, CFG) => 0;
|
|
lfsr_mount(&lfs, CFG) => 0;
|
|
lfs_alloc_ack(&lfs);
|
|
// remove root bookmark for now
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(0, RM, -1, NULL))) => 0;
|
|
|
|
// prepare mroot with an attr
|
|
uint8_t buffer[SIZE];
|
|
memset(buffer, 'a', SIZE);
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(-1, UATTR(1), 0, BUF(buffer, SIZE)))) => 0;
|
|
|
|
// force mroot to compact twice, this should extend the mroot
|
|
lfsr_mdir_t old_mroot = lfs.mroot;
|
|
|
|
lfs.mroot.u.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
lfs.mroot.u.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
|
|
// assert we relocated
|
|
assert(lfsr_mdir_cmp(&old_mroot, &lfs.mroot) != 0);
|
|
|
|
// force mroot to compact twice again, this should relocate the mroot
|
|
old_mroot = lfs.mroot;
|
|
|
|
lfs.mroot.u.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
lfs.mroot.u.rbyd.eoff = -1;
|
|
memset(buffer, 'b', SIZE);
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(-1, UATTR(1), 0, BUF(buffer, SIZE)))) => 0;
|
|
|
|
// assert we relocated
|
|
assert(lfsr_mdir_cmp(&old_mroot, &lfs.mroot) != 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, "b", 1) == 0);
|
|
|
|
lfsr_unmount(&lfs) => 0;
|
|
|
|
|
|
// check things stay sane after remount
|
|
lfsr_mount(&lfs, CFG) => 0;
|
|
|
|
// assert we relocated
|
|
assert(lfsr_mdir_cmp(&old_mroot, &lfs.mroot) != 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, "b", 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 = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, CFG) => 0;
|
|
lfsr_mount(&lfs, CFG) => 0;
|
|
lfs_alloc_ack(&lfs);
|
|
// remove root bookmark for now
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(0, RM, -1, NULL))) => 0;
|
|
|
|
// prepare mroot with a large attr so the next entry can not fit
|
|
uint8_t buffer[SIZE];
|
|
memset(buffer, 'a', SIZE);
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(-1, UATTR(1), 0, BUF(buffer, SIZE)))) => 0;
|
|
|
|
// create a large entry that needs to be uninlined (but not split!)
|
|
memset(buffer, 'b', SIZE);
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(0, REG, +1, BUF(buffer, SIZE)))) => 0;
|
|
|
|
// force mroot to compact
|
|
lfs.mroot.u.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
|
|
// assert mtree has one mdir
|
|
assert(lfsr_mtree_weight(&lfs.mtree) == 1*lfsr_mleafweight(&lfs));
|
|
// assert mroot now has no entries
|
|
assert(lfs.mroot.u.m.weight == 0);
|
|
|
|
// setup mroot to need to compact, this should trigger a relocation when
|
|
// we relocate the mdir below
|
|
lfs.mroot.u.rbyd.eoff = -1;
|
|
lfsr_mdir_t old_mroot = lfs.mroot;
|
|
|
|
// force mdir to compact twice, this should relocate
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0*lfsr_mleafweight(&lfs)+0, &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
|
|
lfsr_mdir_t old_mdir = mdir;
|
|
|
|
mdir.u.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &mdir, NULL, 0) => 0;
|
|
mdir.u.rbyd.eoff = -1;
|
|
memset(buffer, 'c', SIZE);
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(mdir.mid, REG, 0, BUF(buffer, SIZE)))) => 0;
|
|
|
|
// assert we relocated our mdir
|
|
assert(lfsr_mdir_cmp(&old_mdir, &mdir) != 0);
|
|
|
|
// assert we relocated our mroot
|
|
assert(lfsr_mdir_cmp(&old_mroot, &lfs.mroot) != 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, "a", 1) == 0);
|
|
|
|
// assert that our entry is still in the mtree
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0*lfsr_mleafweight(&lfs)+0, &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
|
|
lfsr_mdir_get(&lfs, &mdir, mdir.mid, LFSR_TAG_REG,
|
|
buffer, SIZE) => SIZE;
|
|
assert(memcmp(buffer, "c", 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.mtree) == 1*lfsr_mleafweight(&lfs));
|
|
// assert mroot now has no entries
|
|
assert(lfs.mroot.u.m.weight == 0);
|
|
|
|
// assert we relocated our mdir
|
|
assert(lfsr_mdir_cmp(&old_mdir, &mdir) != 0);
|
|
|
|
// assert we relocated our mroot
|
|
assert(lfsr_mdir_cmp(&old_mroot, &lfs.mroot) != 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, "a", 1) == 0);
|
|
|
|
// assert that our entry is still in the mtree
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0*lfsr_mleafweight(&lfs)+0, &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
|
|
lfsr_mdir_get(&lfs, &mdir, mdir.mid, LFSR_TAG_REG,
|
|
buffer, SIZE) => SIZE;
|
|
assert(memcmp(buffer, "c", 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 = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, CFG) => 0;
|
|
lfsr_mount(&lfs, CFG) => 0;
|
|
lfs_alloc_ack(&lfs);
|
|
// remove root bookmark for now
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(0, RM, -1, NULL))) => 0;
|
|
|
|
// create an uninlined mdir
|
|
uint8_t buffer[SIZE];
|
|
memset(buffer, 'a', SIZE);
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(-1, UATTR(1), 0, BUF(buffer, SIZE)))) => 0;
|
|
|
|
memset(buffer, 'b', SIZE);
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(0, REG, +1, BUF(buffer, SIZE)))) => 0;
|
|
|
|
// force mroot to compact
|
|
lfs.mroot.u.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
|
|
// assert mdir was unininlined correctly
|
|
assert(lfsr_mtree_weight(&lfs.mtree) == 1*lfsr_mleafweight(&lfs));
|
|
// assert mroot now has no entries
|
|
assert(lfs.mroot.u.m.weight == 0);
|
|
|
|
// setup mroot to need to compact, this should trigger a relocation when
|
|
// we relocate the mdir below
|
|
lfs.mroot.u.rbyd.eoff = -1;
|
|
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, &lfs.mtree, 0*lfsr_mleafweight(&lfs)+1, &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
|
|
memset(buffer, 'c', SIZE);
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(mdir.mid, REG, +1, BUF(buffer, SIZE)))) => 0;
|
|
|
|
// force mdir to compact
|
|
mdir.u.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &mdir, NULL, 0) => 0;
|
|
|
|
// assert mdir was split correctly
|
|
assert(lfsr_mtree_weight(&lfs.mtree) == 2*lfsr_mleafweight(&lfs));
|
|
// assert mroot still has no entries
|
|
assert(lfs.mroot.u.m.weight == 0);
|
|
|
|
// assert we relocated our mroot
|
|
assert(lfsr_mdir_cmp(&old_mroot, &lfs.mroot) != 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, "a", 1) == 0);
|
|
|
|
// assert that our entries are still in the mtree
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0*lfsr_mleafweight(&lfs)+0, &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
lfsr_mdir_get(&lfs, &mdir, mdir.mid, LFSR_TAG_REG,
|
|
buffer, SIZE) => SIZE;
|
|
assert(memcmp(buffer, "b", 1) == 0);
|
|
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 1*lfsr_mleafweight(&lfs)+0, &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
lfsr_mdir_get(&lfs, &mdir, mdir.mid, LFSR_TAG_REG,
|
|
buffer, SIZE) => SIZE;
|
|
assert(memcmp(buffer, "c", 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.mtree) == 2*lfsr_mleafweight(&lfs));
|
|
// assert mroot still has no entries
|
|
assert(lfs.mroot.u.m.weight == 0);
|
|
|
|
// assert we relocated our mroot
|
|
assert(lfsr_mdir_cmp(&old_mroot, &lfs.mroot) != 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, "a", 1) == 0);
|
|
|
|
// assert that our entries are still in the mtree
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0*lfsr_mleafweight(&lfs)+0, &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
lfsr_mdir_get(&lfs, &mdir, mdir.mid, LFSR_TAG_REG,
|
|
buffer, SIZE) => SIZE;
|
|
assert(memcmp(buffer, "b", 1) == 0);
|
|
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 1*lfsr_mleafweight(&lfs)+0, &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
lfsr_mdir_get(&lfs, &mdir, mdir.mid, LFSR_TAG_REG,
|
|
buffer, SIZE) => SIZE;
|
|
assert(memcmp(buffer, "c", 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 = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, CFG) => 0;
|
|
lfsr_mount(&lfs, CFG) => 0;
|
|
lfs_alloc_ack(&lfs);
|
|
// remove root bookmark for now
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(0, RM, -1, NULL))) => 0;
|
|
|
|
// create an uninlined mdir
|
|
uint8_t buffer[SIZE];
|
|
memset(buffer, 'a', SIZE);
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(-1, UATTR(1), 0, BUF(buffer, SIZE)))) => 0;
|
|
|
|
memset(buffer, 'b', SIZE);
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(0, REG, +1, BUF(buffer, SIZE)))) => 0;
|
|
|
|
// force mroot to compact
|
|
lfs.mroot.u.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
|
|
// assert mdir was unininlined correctly
|
|
assert(lfsr_mtree_weight(&lfs.mtree) == 1*lfsr_mleafweight(&lfs));
|
|
// assert mroot now has no entries
|
|
assert(lfs.mroot.u.m.weight == 0);
|
|
|
|
// setup mroot to need to compact, this should trigger a relocation when
|
|
// we relocate the mdir below
|
|
lfs.mroot.u.rbyd.eoff = -1;
|
|
lfsr_mdir_t old_mroot = lfs.mroot;
|
|
|
|
// remove the entry, forcing the mdir to be dropped
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0*lfsr_mleafweight(&lfs)+0, &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(mdir.mid, RM, -1, NULL))) => 0;
|
|
|
|
// assert mdir was dropped
|
|
assert(lfsr_mtree_weight(&lfs.mtree) == 0*lfsr_mleafweight(&lfs));
|
|
// assert mroot still has no entries
|
|
assert(lfs.mroot.u.m.weight == 0);
|
|
|
|
// assert we relocated our mroot
|
|
assert(lfsr_mdir_cmp(&old_mroot, &lfs.mroot) != 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, "a", 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.mtree) == 0*lfsr_mleafweight(&lfs));
|
|
// assert mroot still has no entries
|
|
assert(lfs.mroot.u.m.weight == 0);
|
|
|
|
// assert we relocated our mroot
|
|
assert(lfsr_mdir_cmp(&old_mroot, &lfs.mroot) != 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, "a", 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 = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, CFG) => 0;
|
|
lfsr_mount(&lfs, CFG) => 0;
|
|
lfs_alloc_ack(&lfs);
|
|
// remove root bookmark for now
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(0, RM, -1, NULL))) => 0;
|
|
|
|
// force mroot to compact once, so the second compact below will trigger
|
|
// a relocation
|
|
lfs.mroot.u.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
|
|
// prepare mroot with a large attr so the next entry can not fit
|
|
uint8_t buffer[SIZE];
|
|
memset(buffer, 'a', SIZE);
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(-1, UATTR(1), 0, BUF(buffer, SIZE)))) => 0;
|
|
|
|
// create a large entry that needs to be uninlined (but not split!)
|
|
memset(buffer, 'b', SIZE);
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(0, REG, +1, BUF(buffer, SIZE)))) => 0;
|
|
|
|
// force mroot to compact, this should trigger a relocation
|
|
lfsr_mdir_t old_mroot = lfs.mroot;
|
|
lfs.mroot.u.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
|
|
// assert mdir was unininlined correctly
|
|
assert(lfsr_mtree_weight(&lfs.mtree) == 1*lfsr_mleafweight(&lfs));
|
|
// assert mroot now has no entries
|
|
assert(lfs.mroot.u.m.weight == 0);
|
|
|
|
// assert we relocated our mroot
|
|
assert(lfsr_mdir_cmp(&old_mroot, &lfs.mroot) != 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, "a", 1) == 0);
|
|
|
|
// assert that our entry is still in the mtree
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0*lfsr_mleafweight(&lfs)+0, &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
|
|
lfsr_mdir_get(&lfs, &mdir, mdir.mid, LFSR_TAG_REG,
|
|
buffer, SIZE) => SIZE;
|
|
assert(memcmp(buffer, "b", 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.mtree) == 1*lfsr_mleafweight(&lfs));
|
|
// assert mroot now has no entries
|
|
assert(lfs.mroot.u.m.weight == 0);
|
|
|
|
// assert we relocated our mroot
|
|
assert(lfsr_mdir_cmp(&old_mroot, &lfs.mroot) != 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, "a", 1) == 0);
|
|
|
|
// assert that our entry is still in the mtree
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0*lfsr_mleafweight(&lfs)+0, &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
|
|
lfsr_mdir_get(&lfs, &mdir, mdir.mid, LFSR_TAG_REG,
|
|
buffer, SIZE) => SIZE;
|
|
assert(memcmp(buffer, "b", 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 = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, CFG) => 0;
|
|
lfsr_mount(&lfs, CFG) => 0;
|
|
lfs_alloc_ack(&lfs);
|
|
// remove root bookmark for now
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(0, RM, -1, NULL))) => 0;
|
|
|
|
// force mroot to compact once, so the second compact below will trigger
|
|
// a relocation
|
|
lfs.mroot.u.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
|
|
// create 2 large entries that needs to be uninlined and split
|
|
uint8_t buffer[SIZE];
|
|
memset(buffer, 'a', SIZE);
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(0, REG, +1, BUF(buffer, SIZE)))) => 0;
|
|
|
|
memset(buffer, 'b', SIZE);
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(1, REG, +1, BUF(buffer, SIZE)))) => 0;
|
|
|
|
// force mroot to compact, this should trigger a relocation
|
|
lfsr_mdir_t old_mroot = lfs.mroot;
|
|
lfs.mroot.u.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
|
|
// assert mdirs were unininlined and split
|
|
assert(lfsr_mtree_weight(&lfs.mtree) == 2*lfsr_mleafweight(&lfs));
|
|
// assert mroot now has no entries
|
|
assert(lfs.mroot.u.m.weight == 0);
|
|
|
|
// assert we relocated our mroot
|
|
assert(lfsr_mdir_cmp(&old_mroot, &lfs.mroot) != 0);
|
|
|
|
// assert that our entries are still in the mtree
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0*lfsr_mleafweight(&lfs)+0, &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
lfsr_mdir_get(&lfs, &mdir, mdir.mid, LFSR_TAG_REG,
|
|
buffer, SIZE) => SIZE;
|
|
assert(memcmp(buffer, "a", 1) == 0);
|
|
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 1*lfsr_mleafweight(&lfs)+0, &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
lfsr_mdir_get(&lfs, &mdir, mdir.mid, LFSR_TAG_REG,
|
|
buffer, SIZE) => SIZE;
|
|
assert(memcmp(buffer, "b", 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.mtree) == 2*lfsr_mleafweight(&lfs));
|
|
// assert mroot now has no entries
|
|
assert(lfs.mroot.u.m.weight == 0);
|
|
|
|
// assert we relocated our mroot
|
|
assert(lfsr_mdir_cmp(&old_mroot, &lfs.mroot) != 0);
|
|
|
|
// assert that our entries are still in the mtree
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0*lfsr_mleafweight(&lfs)+0, &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
lfsr_mdir_get(&lfs, &mdir, mdir.mid, LFSR_TAG_REG,
|
|
buffer, SIZE) => SIZE;
|
|
assert(memcmp(buffer, "a", 1) == 0);
|
|
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 1*lfsr_mleafweight(&lfs)+0, &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
lfsr_mdir_get(&lfs, &mdir, mdir.mid, LFSR_TAG_REG,
|
|
buffer, SIZE) => SIZE;
|
|
assert(memcmp(buffer, "b", 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.SEED = 'range(500)'
|
|
in = 'lfs.c'
|
|
code = '''
|
|
const char *alphas = "abcdefghijklmnopqrstuvwxyz";
|
|
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, CFG) => 0;
|
|
lfsr_mount(&lfs, CFG) => 0;
|
|
lfs_alloc_ack(&lfs);
|
|
// remove root bookmark for now
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(0, RM, -1, NULL))) => 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 = (lfs_ssize_t)(
|
|
TEST_PRNG(&prng) % lfs_max32(
|
|
lfsr_mtree_weight(&lfs.mtree),
|
|
lfsr_mleafweight(&lfs)));
|
|
// fetch mdir
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, mid, &mdir) => 0;
|
|
// limit our mid to our mdir's weight
|
|
mdir.mid = (mdir.mid & lfsr_midbmask(&lfs))
|
|
| (mdir.mid % (mdir.u.m.weight+1));
|
|
// choose to create or delete, note we make sure to never delete to zero
|
|
uint8_t op = ((mdir.mid & lfsr_midrmask(&lfs)) == mdir.u.m.weight
|
|
|| ((mdir.mid & lfsr_midrmask(&lfs)) == mdir.u.m.weight-1
|
|
&& lfsr_mtree_weight(&lfs.mtree)
|
|
== lfsr_mleafweight(&lfs))
|
|
? 0
|
|
: TEST_PRNG(&prng) % 3);
|
|
|
|
// force a compaction?
|
|
if (FORCE_COMPACTION) {
|
|
mdir.u.rbyd.eoff = -1;
|
|
lfs.mroot.u.rbyd.eoff = -1;
|
|
}
|
|
|
|
// create
|
|
if (op == 0) {
|
|
// add to rbyd
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(mdir.mid, REG, +1,
|
|
BUF(&alphas[i % 26], 1)))) => 0;
|
|
|
|
// make sure we can look up the new entry
|
|
uint8_t buffer[4];
|
|
lfsr_mdir_get(&lfs, &mdir, mdir.mid, LFSR_TAG_REG,
|
|
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, LFSR_ATTRS(
|
|
LFSR_ATTR(mdir.mid, REG, 0,
|
|
BUF(&alphas[i % 26], 1)))) => 0;
|
|
|
|
// make sure we can look up the new entry
|
|
uint8_t buffer[4];
|
|
lfsr_mdir_get(&lfs, &mdir, mdir.mid, LFSR_TAG_REG,
|
|
buffer, 4) => 1;
|
|
assert(memcmp(buffer, &alphas[i % 26], 1) == 0);
|
|
|
|
// delete
|
|
} else {
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(mdir.mid, RM, -1, NULL))) => 0;
|
|
|
|
count -= 1;
|
|
}
|
|
}
|
|
|
|
// try looking up each entry
|
|
lfs_size_t count_ = 0;
|
|
|
|
for (lfs_ssize_t mid = 0;
|
|
mid < lfs_smax32(
|
|
lfsr_mtree_weight(&lfs.mtree),
|
|
lfsr_mleafweight(&lfs));
|
|
mid += lfsr_mleafweight(&lfs)) {
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, mid, &mdir) => 0;
|
|
|
|
// drop should make sure we never have empty mdirs
|
|
assert(lfsr_mtree_ismptr(&lfs.mtree) || mdir.u.m.weight > 0);
|
|
|
|
for (; (mdir.mid & lfsr_midrmask(&lfs))
|
|
< (lfs_ssize_t)mdir.u.m.weight;
|
|
mdir.mid += 1) {
|
|
uint8_t buffer[4];
|
|
lfsr_mdir_get(&lfs, &mdir, mdir.mid, LFSR_TAG_REG,
|
|
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 = 0;
|
|
mid < lfs_smax32(
|
|
lfsr_mtree_weight(&lfs.mtree),
|
|
lfsr_mleafweight(&lfs));
|
|
mid += lfsr_mleafweight(&lfs)) {
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, mid, &mdir) => 0;
|
|
|
|
// drop should make sure we never have empty mdirs
|
|
assert(lfsr_mtree_ismptr(&lfs.mtree) || mdir.u.m.weight > 0);
|
|
|
|
for (; (mdir.mid & lfsr_midrmask(&lfs))
|
|
< (lfs_ssize_t)mdir.u.m.weight;
|
|
mdir.mid += 1) {
|
|
uint8_t buffer[4];
|
|
lfsr_mdir_get(&lfs, &mdir, mdir.mid, LFSR_TAG_REG,
|
|
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 = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, CFG) => 0;
|
|
lfsr_mount(&lfs, CFG) => 0;
|
|
lfs_alloc_ack(&lfs);
|
|
// remove root bookmark for now
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(0, RM, -1, NULL))) => 0;
|
|
|
|
// setup our neighbors
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(0, REG, +1, BUF("a", 1)),
|
|
LFSR_ATTR(1, REG, +1, BUF("b", 1)))) => 0;
|
|
// this test only works if these all fit in the mroot
|
|
assert(lfsr_mtree_ismptr(&lfs.mtree));
|
|
|
|
lfsr_openedmdir_t left_neighbor = {
|
|
.mdir={.mid=0, .u.m=lfs.mroot.u.m}};
|
|
lfsr_openedmdir_t right_neighbor = {
|
|
.mdir={.mid=1, .u.m=lfs.mroot.u.m}};
|
|
lfsr_mdir_addopened(&lfs, LFS_TYPE_INTERNAL, &left_neighbor);
|
|
lfsr_mdir_addopened(&lfs, LFS_TYPE_INTERNAL, &right_neighbor);
|
|
|
|
// insert a new entry, this should update our neighbors
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(1, REG, +1, BUF("c", 1)))) => 0;
|
|
|
|
// assert that our entry is still in the mtree
|
|
assert(lfs.mroot.u.m.weight == 3);
|
|
|
|
uint8_t buffer[1];
|
|
lfsr_mdir_get(&lfs, &lfs.mroot, 1, LFSR_TAG_REG,
|
|
buffer, 1) => 1;
|
|
assert(memcmp(buffer, "c", 1) == 0);
|
|
|
|
// assert that our neighbors were updated correctly
|
|
assert(left_neighbor.mdir.mid == 0);
|
|
assert(memcmp(&left_neighbor.mdir.u.m, &lfs.mroot.u.m,
|
|
sizeof(lfs.mroot.u.m)) == 0);
|
|
assert(right_neighbor.mdir.mid == 2);
|
|
assert(memcmp(&right_neighbor.mdir.u.m, &lfs.mroot.u.m,
|
|
sizeof(lfs.mroot.u.m)) == 0);
|
|
|
|
lfsr_mdir_removeopened(&lfs, LFS_TYPE_INTERNAL, &left_neighbor);
|
|
lfsr_mdir_removeopened(&lfs, LFS_TYPE_INTERNAL, &right_neighbor);
|
|
lfsr_unmount(&lfs) => 0;
|
|
'''
|
|
|
|
[cases.test_mtree_neighbor_remove_l]
|
|
in = 'lfs.c'
|
|
code = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, CFG) => 0;
|
|
lfsr_mount(&lfs, CFG) => 0;
|
|
lfs_alloc_ack(&lfs);
|
|
// remove root bookmark for now
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(0, RM, -1, NULL))) => 0;
|
|
|
|
// setup our neighbors
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(0, REG, +1, BUF("a", 1)),
|
|
LFSR_ATTR(1, REG, +1, BUF("b", 1)))) => 0;
|
|
// this test only works if these all fit in the mroot
|
|
assert(lfsr_mtree_ismptr(&lfs.mtree));
|
|
|
|
lfsr_openedmdir_t left_neighbor = {
|
|
.mdir={.mid=0, .u.m=lfs.mroot.u.m}};
|
|
lfsr_openedmdir_t right_neighbor = {
|
|
.mdir={.mid=1, .u.m=lfs.mroot.u.m}};
|
|
lfsr_mdir_addopened(&lfs, LFS_TYPE_INTERNAL, &left_neighbor);
|
|
lfsr_mdir_addopened(&lfs, LFS_TYPE_INTERNAL, &right_neighbor);
|
|
|
|
// try removing our left entry
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(0, RM, -1, NULL))) => 0;
|
|
|
|
// assert that an entry was removed
|
|
assert(lfs.mroot.u.m.weight == 1);
|
|
|
|
// assert that our neighbors were updated correctly
|
|
assert(left_neighbor.mdir.mid == -1);
|
|
assert(right_neighbor.mdir.mid == 0);
|
|
assert(memcmp(&right_neighbor.mdir.u.m, &lfs.mroot.u.m,
|
|
sizeof(lfs.mroot.u.m)) == 0);
|
|
|
|
lfsr_mdir_removeopened(&lfs, LFS_TYPE_INTERNAL, &left_neighbor);
|
|
lfsr_mdir_removeopened(&lfs, LFS_TYPE_INTERNAL, &right_neighbor);
|
|
lfsr_unmount(&lfs) => 0;
|
|
'''
|
|
|
|
[cases.test_mtree_neighbor_remove_r]
|
|
in = 'lfs.c'
|
|
code = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, CFG) => 0;
|
|
lfsr_mount(&lfs, CFG) => 0;
|
|
lfs_alloc_ack(&lfs);
|
|
// remove root bookmark for now
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(0, RM, -1, NULL))) => 0;
|
|
|
|
// setup our neighbors
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(0, REG, +1, BUF("a", 1)),
|
|
LFSR_ATTR(1, REG, +1, BUF("b", 1)))) => 0;
|
|
// this test only works if these all fit in the mroot
|
|
assert(lfsr_mtree_ismptr(&lfs.mtree));
|
|
|
|
lfsr_openedmdir_t left_neighbor = {
|
|
.mdir={.mid=0, .u.m=lfs.mroot.u.m}};
|
|
lfsr_openedmdir_t right_neighbor = {
|
|
.mdir={.mid=1, .u.m=lfs.mroot.u.m}};
|
|
lfsr_mdir_addopened(&lfs, LFS_TYPE_INTERNAL, &left_neighbor);
|
|
lfsr_mdir_addopened(&lfs, LFS_TYPE_INTERNAL, &right_neighbor);
|
|
|
|
// try removing our left entry
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(1, RM, -1, NULL))) => 0;
|
|
|
|
// assert that an entry was removed
|
|
assert(lfs.mroot.u.m.weight == 1);
|
|
|
|
// assert that our neighbors were updated correctly
|
|
assert(left_neighbor.mdir.mid == 0);
|
|
assert(memcmp(&left_neighbor.mdir.u.m, &lfs.mroot.u.m,
|
|
sizeof(lfs.mroot.u.m)) == 0);
|
|
assert(right_neighbor.mdir.mid == -1);
|
|
|
|
lfsr_mdir_removeopened(&lfs, LFS_TYPE_INTERNAL, &left_neighbor);
|
|
lfsr_mdir_removeopened(&lfs, LFS_TYPE_INTERNAL, &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 = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, CFG) => 0;
|
|
lfsr_mount(&lfs, CFG) => 0;
|
|
lfs_alloc_ack(&lfs);
|
|
// remove root bookmark for now
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(0, RM, -1, NULL))) => 0;
|
|
|
|
// setup our neighbors
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(0, REG, +1, BUF("a", 1)),
|
|
LFSR_ATTR(1, REG, +1, BUF("b", 1)))) => 0;
|
|
// this test only works if these all fit in the mroot
|
|
assert(lfsr_mtree_ismptr(&lfs.mtree));
|
|
|
|
lfsr_openedmdir_t left_neighbor = {
|
|
.mdir={.mid=0, .u.m=lfs.mroot.u.m}};
|
|
lfsr_openedmdir_t right_neighbor = {
|
|
.mdir={.mid=1, .u.m=lfs.mroot.u.m}};
|
|
lfsr_mdir_addopened(&lfs, LFS_TYPE_INTERNAL, &left_neighbor);
|
|
lfsr_mdir_addopened(&lfs, LFS_TYPE_INTERNAL, &right_neighbor);
|
|
|
|
// prepare mroot with a large attr so the next entry can not fit
|
|
uint8_t buffer[SIZE];
|
|
memset(buffer, 'c', SIZE);
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(-1, UATTR(1), 0, BUF(buffer, SIZE)))) => 0;
|
|
|
|
// create a large entry that needs to be uninlined (but not split!)
|
|
memset(buffer, 'd', SIZE);
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(1, REG, +1, BUF(buffer, SIZE)))) => 0;
|
|
|
|
// force mroot to compact
|
|
lfs.mroot.u.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
|
|
// assert mdir was unininlined correctly
|
|
assert(lfsr_mtree_weight(&lfs.mtree) == 2*lfsr_mleafweight(&lfs));
|
|
// assert mroot now has no entries
|
|
assert(lfs.mroot.u.m.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, "c", 1) == 0);
|
|
|
|
// assert that our entry is still in the mtree
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0*lfsr_mleafweight(&lfs)+1, &mdir) => 0;
|
|
assert(mdir.u.m.weight == 2);
|
|
lfsr_mdir_get(&lfs, &mdir, mdir.mid, LFSR_TAG_REG,
|
|
buffer, SIZE) => SIZE;
|
|
assert(memcmp(buffer, "d", 1) == 0);
|
|
|
|
// note that our current implementation splits here, which is suboptimal
|
|
// but saves on code size
|
|
lfsr_mdir_t msibling;
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree,
|
|
1*lfsr_mleafweight(&lfs)+0, &msibling) => 0;
|
|
assert(msibling.u.m.weight == 1);
|
|
|
|
// assert that our neighbors were updated correctly
|
|
assert(left_neighbor.mdir.mid == 0*lfsr_mleafweight(&lfs)+0);
|
|
assert(memcmp(&left_neighbor.mdir.u.m, &mdir.u.m,
|
|
sizeof(mdir.u.m)) == 0);
|
|
assert(right_neighbor.mdir.mid == 1*lfsr_mleafweight(&lfs)+0);
|
|
assert(memcmp(&right_neighbor.mdir.u.m, &msibling.u.m,
|
|
sizeof(msibling.u.m)) == 0);
|
|
|
|
lfsr_mdir_removeopened(&lfs, LFS_TYPE_INTERNAL, &left_neighbor);
|
|
lfsr_mdir_removeopened(&lfs, LFS_TYPE_INTERNAL, &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 = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, CFG) => 0;
|
|
lfsr_mount(&lfs, CFG) => 0;
|
|
lfs_alloc_ack(&lfs);
|
|
// remove root bookmark for now
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(0, RM, -1, NULL))) => 0;
|
|
|
|
// setup our neighbors
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(0, REG, +1, BUF("a", 1)),
|
|
LFSR_ATTR(1, REG, +1, BUF("b", 1)))) => 0;
|
|
// this test only works if these all fit in the mroot
|
|
assert(lfsr_mtree_ismptr(&lfs.mtree));
|
|
|
|
lfsr_openedmdir_t left_neighbor = {
|
|
.mdir={.mid=0, .u.m=lfs.mroot.u.m}};
|
|
lfsr_openedmdir_t right_neighbor = {
|
|
.mdir={.mid=1, .u.m=lfs.mroot.u.m}};
|
|
lfsr_mdir_addopened(&lfs, LFS_TYPE_INTERNAL, &left_neighbor);
|
|
lfsr_mdir_addopened(&lfs, LFS_TYPE_INTERNAL, &right_neighbor);
|
|
|
|
// create 2 large entries that needs to be uninlined and split
|
|
uint8_t buffer[SIZE];
|
|
memset(buffer, 'c', SIZE);
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(1, REG, +1, BUF(buffer, SIZE)))) => 0;
|
|
|
|
memset(buffer, 'd', SIZE);
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(2, REG, +1, BUF(buffer, SIZE)))) => 0;
|
|
|
|
// force mroot to compact
|
|
lfs.mroot.u.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
|
|
// assert mdirs were unininlined and split
|
|
assert(lfsr_mtree_weight(&lfs.mtree) == 2*lfsr_mleafweight(&lfs));
|
|
// assert mroot now has no entries
|
|
assert(lfs.mroot.u.m.weight == 0);
|
|
|
|
// assert that our entries are still in the mtree
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0*lfsr_mleafweight(&lfs)+1, &mdir) => 0;
|
|
assert(mdir.u.m.weight == 2);
|
|
lfsr_mdir_get(&lfs, &mdir, mdir.mid, LFSR_TAG_REG,
|
|
buffer, SIZE) => SIZE;
|
|
assert(memcmp(buffer, "c", 1) == 0);
|
|
|
|
lfsr_mdir_t msibling;
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree,
|
|
1*lfsr_mleafweight(&lfs)+0, &msibling) => 0;
|
|
assert(msibling.u.m.weight == 2);
|
|
lfsr_mdir_get(&lfs, &msibling, msibling.mid, LFSR_TAG_REG,
|
|
buffer, SIZE) => SIZE;
|
|
assert(memcmp(buffer, "d", 1) == 0);
|
|
|
|
// assert that our neighbors were updated correctly
|
|
assert(left_neighbor.mdir.mid == 0*lfsr_mleafweight(&lfs)+0);
|
|
assert(memcmp(&left_neighbor.mdir.u.m, &mdir.u.m,
|
|
sizeof(mdir.u.m)) == 0);
|
|
assert(right_neighbor.mdir.mid == 1*lfsr_mleafweight(&lfs)+1);
|
|
assert(memcmp(&right_neighbor.mdir.u.m, &msibling.u.m,
|
|
sizeof(msibling.u.m)) == 0);
|
|
|
|
lfsr_mdir_removeopened(&lfs, LFS_TYPE_INTERNAL, &left_neighbor);
|
|
lfsr_mdir_removeopened(&lfs, LFS_TYPE_INTERNAL, &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 = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, CFG) => 0;
|
|
lfsr_mount(&lfs, CFG) => 0;
|
|
lfs_alloc_ack(&lfs);
|
|
// remove root bookmark for now
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(0, RM, -1, NULL))) => 0;
|
|
|
|
// create an uninlined mdir
|
|
uint8_t buffer[SIZE];
|
|
memset(buffer, 'c', SIZE);
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(-1, UATTR(1), 0, BUF(buffer, SIZE)))) => 0;
|
|
|
|
memset(buffer, 'd', SIZE);
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(0, REG, +1, BUF(buffer, SIZE)))) => 0;
|
|
|
|
// force mroot to compact
|
|
lfs.mroot.u.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
|
|
// assert mdir was unininlined correctly
|
|
assert(lfsr_mtree_weight(&lfs.mtree) == 1*lfsr_mleafweight(&lfs));
|
|
// assert mroot now has no entries
|
|
assert(lfs.mroot.u.m.weight == 0);
|
|
|
|
// setup our neighbors
|
|
//
|
|
// note we do this after uninlining! this is because uninlining may
|
|
// aggresively split the mtree if there are already neighbors in the mdir
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0*lfsr_mleafweight(&lfs)+0, &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(mdir.mid+0, REG, +1, BUF("a", 1)),
|
|
LFSR_ATTR(mdir.mid+2, REG, +1, BUF("b", 1)))) => 0;
|
|
// this test only works if these all fit in the mdir
|
|
assert(lfsr_mtree_weight(&lfs.mtree) == 1*lfsr_mleafweight(&lfs));
|
|
assert(mdir.u.m.weight == 3);
|
|
|
|
lfsr_openedmdir_t left_neighbor = {
|
|
.mdir={.mid=mdir.mid+0, .u.m=mdir.u.m}};
|
|
lfsr_openedmdir_t right_neighbor = {
|
|
.mdir={.mid=mdir.mid+2, .u.m=mdir.u.m}};
|
|
lfsr_mdir_addopened(&lfs, LFS_TYPE_INTERNAL, &left_neighbor);
|
|
lfsr_mdir_addopened(&lfs, LFS_TYPE_INTERNAL, &right_neighbor);
|
|
|
|
// now add another large entry to the mdir, forcing a split
|
|
memset(buffer, 'e', SIZE);
|
|
mdir.mid = 0*lfsr_mleafweight(&lfs)+2;
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(mdir.mid, REG, +1, BUF(buffer, SIZE)))) => 0;
|
|
|
|
// force mdir to compact
|
|
mdir.u.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &mdir, NULL, 0) => 0;
|
|
|
|
// assert mdir was split correctly
|
|
assert(lfsr_mtree_weight(&lfs.mtree) == 2*lfsr_mleafweight(&lfs));
|
|
// assert mroot still has no entries
|
|
assert(lfs.mroot.u.m.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, "c", 1) == 0);
|
|
|
|
// assert that our entries are still in the mtree
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0*lfsr_mleafweight(&lfs)+1, &mdir) => 0;
|
|
assert(mdir.u.m.weight == 2);
|
|
lfsr_mdir_get(&lfs, &mdir, mdir.mid, LFSR_TAG_REG,
|
|
buffer, SIZE) => SIZE;
|
|
assert(memcmp(buffer, "d", 1) == 0);
|
|
|
|
lfsr_mdir_t msibling;
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree,
|
|
1*lfsr_mleafweight(&lfs)+0, &msibling) => 0;
|
|
assert(msibling.u.m.weight == 2);
|
|
lfsr_mdir_get(&lfs, &msibling, msibling.mid, LFSR_TAG_REG,
|
|
buffer, SIZE) => SIZE;
|
|
assert(memcmp(buffer, "e", 1) == 0);
|
|
|
|
// assert that our neighbors were updated correctly
|
|
assert(left_neighbor.mdir.mid == 0*lfsr_mleafweight(&lfs)+0);
|
|
assert(memcmp(&left_neighbor.mdir.u.m, &mdir.u.m,
|
|
sizeof(mdir.u.m)) == 0);
|
|
assert(right_neighbor.mdir.mid == 1*lfsr_mleafweight(&lfs)+1);
|
|
assert(memcmp(&right_neighbor.mdir.u.m, &msibling.u.m,
|
|
sizeof(msibling.u.m)) == 0);
|
|
|
|
lfsr_mdir_removeopened(&lfs, LFS_TYPE_INTERNAL, &left_neighbor);
|
|
lfsr_mdir_removeopened(&lfs, LFS_TYPE_INTERNAL, &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 = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, CFG) => 0;
|
|
lfsr_mount(&lfs, CFG) => 0;
|
|
lfs_alloc_ack(&lfs);
|
|
// remove root bookmark for now
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(0, RM, -1, NULL))) => 0;
|
|
|
|
// setup our neighbors
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(0, REG, +1, BUF("a", 1)),
|
|
LFSR_ATTR(1, REG, +1, BUF("b", 1)))) => 0;
|
|
// this test only works if these all fit in the mroot
|
|
assert(lfsr_mtree_ismptr(&lfs.mtree));
|
|
|
|
lfsr_openedmdir_t left_neighbor = {
|
|
.mdir={.mid=0, .u.m=lfs.mroot.u.m}};
|
|
lfsr_openedmdir_t right_neighbor = {
|
|
.mdir={.mid=1, .u.m=lfs.mroot.u.m}};
|
|
lfsr_mdir_addopened(&lfs, LFS_TYPE_INTERNAL, &left_neighbor);
|
|
lfsr_mdir_addopened(&lfs, LFS_TYPE_INTERNAL, &right_neighbor);
|
|
|
|
// prepare mroot with an attr
|
|
uint8_t buffer[SIZE];
|
|
memset(buffer, 'a', SIZE);
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(-1, UATTR(1), 0, BUF(buffer, SIZE)))) => 0;
|
|
|
|
// force mroot to compact twice, this should extend the mroot
|
|
lfsr_mdir_t old_mroot = lfs.mroot;
|
|
|
|
lfs.mroot.u.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
lfs.mroot.u.rbyd.eoff = -1;
|
|
memset(buffer, 'b', SIZE);
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(-1, UATTR(1), 0, BUF(buffer, SIZE)))) => 0;
|
|
|
|
// assert we relocated
|
|
assert(lfsr_mdir_cmp(&old_mroot, &lfs.mroot) != 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, "b", 1) == 0);
|
|
|
|
// assert that our neighbors were updated correctly
|
|
assert(left_neighbor.mdir.mid == 0);
|
|
assert(memcmp(&left_neighbor.mdir.u.m, &lfs.mroot.u.m,
|
|
sizeof(lfs.mroot.u.m)) == 0);
|
|
assert(right_neighbor.mdir.mid == 1);
|
|
assert(memcmp(&right_neighbor.mdir.u.m, &lfs.mroot.u.m,
|
|
sizeof(lfs.mroot.u.m)) == 0);
|
|
|
|
lfsr_mdir_removeopened(&lfs, LFS_TYPE_INTERNAL, &left_neighbor);
|
|
lfsr_mdir_removeopened(&lfs, LFS_TYPE_INTERNAL, &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 = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, CFG) => 0;
|
|
lfsr_mount(&lfs, CFG) => 0;
|
|
lfs_alloc_ack(&lfs);
|
|
// remove root bookmark for now
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(0, RM, -1, NULL))) => 0;
|
|
|
|
// create an uninlined mdir
|
|
uint8_t buffer[SIZE];
|
|
memset(buffer, 'c', SIZE);
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(-1, UATTR(1), 0, BUF(buffer, SIZE)))) => 0;
|
|
|
|
memset(buffer, 'd', SIZE);
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(0, REG, +1, BUF(buffer, SIZE)))) => 0;
|
|
|
|
// force mroot to compact
|
|
lfs.mroot.u.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
|
|
// assert mdir was unininlined correctly
|
|
assert(lfsr_mtree_weight(&lfs.mtree) == 1*lfsr_mleafweight(&lfs));
|
|
// assert mroot now has no entries
|
|
assert(lfs.mroot.u.m.weight == 0);
|
|
|
|
// setup our neighbors
|
|
//
|
|
// note we do this after uninlining! this is because uninlining may
|
|
// aggresively split the mtree if there are already neighbors in the mdir
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0*lfsr_mleafweight(&lfs)+0, &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(mdir.mid+0, REG, +1, BUF("a", 1)),
|
|
LFSR_ATTR(mdir.mid+2, REG, +1, BUF("b", 1)))) => 0;
|
|
// this test only works if these all fit in the mdir
|
|
assert(lfsr_mtree_weight(&lfs.mtree) == 1*lfsr_mleafweight(&lfs));
|
|
assert(mdir.u.m.weight == 3);
|
|
|
|
lfsr_openedmdir_t left_neighbor = {
|
|
.mdir={.mid=mdir.mid+0, .u.m=mdir.u.m}};
|
|
lfsr_openedmdir_t right_neighbor = {
|
|
.mdir={.mid=mdir.mid+2, .u.m=mdir.u.m}};
|
|
lfsr_mdir_addopened(&lfs, LFS_TYPE_INTERNAL, &left_neighbor);
|
|
lfsr_mdir_addopened(&lfs, LFS_TYPE_INTERNAL, &right_neighbor);
|
|
|
|
// force mdir to compact twice, this should relocate
|
|
lfsr_mdir_t old_mdir = mdir;
|
|
|
|
mdir.u.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &mdir, NULL, 0) => 0;
|
|
mdir.u.rbyd.eoff = -1;
|
|
memset(buffer, 'e', SIZE);
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(1, REG, 0, BUF(buffer, SIZE)))) => 0;
|
|
|
|
// assert we relocated
|
|
assert(lfsr_mdir_cmp(&old_mdir, &mdir) != 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, "c", 1) == 0);
|
|
|
|
// assert that our entry is still in the mtree
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0*lfsr_mleafweight(&lfs)+1, &mdir) => 0;
|
|
assert(mdir.u.m.weight == 3);
|
|
|
|
lfsr_mdir_get(&lfs, &mdir, mdir.mid, LFSR_TAG_REG,
|
|
buffer, SIZE) => SIZE;
|
|
assert(memcmp(buffer, "e", 1) == 0);
|
|
|
|
// assert that our neighbors were updated correctly
|
|
assert(left_neighbor.mdir.mid == 0*lfsr_mleafweight(&lfs)+0);
|
|
assert(memcmp(&left_neighbor.mdir.u.m, &mdir.u.m, sizeof(mdir.u.m)) == 0);
|
|
assert(right_neighbor.mdir.mid == 0*lfsr_mleafweight(&lfs)+2);
|
|
assert(memcmp(&right_neighbor.mdir.u.m, &mdir.u.m, sizeof(mdir.u.m)) == 0);
|
|
|
|
lfsr_mdir_removeopened(&lfs, LFS_TYPE_INTERNAL, &left_neighbor);
|
|
lfsr_mdir_removeopened(&lfs, LFS_TYPE_INTERNAL, &right_neighbor);
|
|
lfsr_unmount(&lfs) => 0;
|
|
'''
|
|
|
|
[cases.test_mtree_neighbor_middle_split]
|
|
# 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 = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, CFG) => 0;
|
|
lfsr_mount(&lfs, CFG) => 0;
|
|
lfs_alloc_ack(&lfs);
|
|
// remove root bookmark for now
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(0, RM, -1, NULL))) => 0;
|
|
|
|
//// create a situation where we have 3 mdirs in our tree
|
|
|
|
// first force mroot to uninlined+split
|
|
uint8_t buffer[SIZE];
|
|
memset(buffer, 'a', SIZE);
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(0, REG, +1, BUF(buffer, SIZE)))) => 0;
|
|
memset(buffer, 'b', SIZE);
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(1, REG, +1, BUF(buffer, SIZE)))) => 0;
|
|
|
|
// force mroot to compact
|
|
lfs.mroot.u.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
|
|
// we should now have 2 mdirs
|
|
assert(lfsr_mtree_weight(&lfs.mtree) == 2*lfsr_mleafweight(&lfs));
|
|
|
|
// now force one of our siblings to split
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 1*lfsr_mleafweight(&lfs)+1, &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
memset(buffer, 'c', SIZE);
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(mdir.mid, REG, +1, BUF(buffer, SIZE)))) => 0;
|
|
|
|
// force mdir to compact
|
|
mdir.u.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &mdir, NULL, 0) => 0;
|
|
|
|
// we should now have 3 mdirs
|
|
assert(lfsr_mtree_weight(&lfs.mtree) == 3*lfsr_mleafweight(&lfs));
|
|
|
|
|
|
//// Now test splitting updates mids correctly
|
|
|
|
// setup our neighbors
|
|
lfsr_openedmdir_t left_neighbor;
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0*lfsr_mleafweight(&lfs)+0,
|
|
&left_neighbor.mdir) => 0;
|
|
assert(left_neighbor.mdir.u.m.weight == 1);
|
|
|
|
lfsr_openedmdir_t right_neighbor;
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 2*lfsr_mleafweight(&lfs)+0,
|
|
&right_neighbor.mdir) => 0;
|
|
assert(right_neighbor.mdir.u.m.weight == 1);
|
|
|
|
lfsr_mdir_addopened(&lfs, LFS_TYPE_INTERNAL, &left_neighbor);
|
|
lfsr_mdir_addopened(&lfs, LFS_TYPE_INTERNAL, &right_neighbor);
|
|
|
|
// cause middle mdir to split
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 1*lfsr_mleafweight(&lfs)+1, &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
memset(buffer, 'd', SIZE);
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(mdir.mid, REG, +1, BUF(buffer, SIZE)))) => 0;
|
|
|
|
// force mdir to compact
|
|
mdir.u.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &mdir, NULL, 0) => 0;
|
|
|
|
// we should now have 4 mdirs
|
|
assert(lfsr_mtree_weight(&lfs.mtree) == 4*lfsr_mleafweight(&lfs));
|
|
|
|
// assert that our neighbors were updated correctly
|
|
assert(left_neighbor.mdir.mid == 0*lfsr_mleafweight(&lfs)+0);
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0*lfsr_mleafweight(&lfs)+0, &mdir) => 0;
|
|
assert(memcmp(&left_neighbor.mdir.u.m, &mdir.u.m, sizeof(mdir.u.m)) == 0);
|
|
|
|
assert(right_neighbor.mdir.mid == 3*lfsr_mleafweight(&lfs)+0);
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 3*lfsr_mleafweight(&lfs)+0, &mdir) => 0;
|
|
assert(memcmp(&right_neighbor.mdir.u.m, &mdir.u.m, sizeof(mdir.u.m)) == 0);
|
|
|
|
lfsr_mdir_removeopened(&lfs, LFS_TYPE_INTERNAL, &left_neighbor);
|
|
lfsr_mdir_removeopened(&lfs, LFS_TYPE_INTERNAL, &right_neighbor);
|
|
lfsr_unmount(&lfs) => 0;
|
|
'''
|
|
|
|
[cases.test_mtree_neighbor_middle_drop]
|
|
# 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 = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, CFG) => 0;
|
|
lfsr_mount(&lfs, CFG) => 0;
|
|
lfs_alloc_ack(&lfs);
|
|
// remove root bookmark for now
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(0, RM, -1, NULL))) => 0;
|
|
|
|
//// create a situation where we have 3 mdirs in our tree
|
|
|
|
// first force mroot to uninlined+split
|
|
uint8_t buffer[SIZE];
|
|
memset(buffer, 'a', SIZE);
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(0, REG, +1, BUF(buffer, SIZE)))) => 0;
|
|
memset(buffer, 'b', SIZE);
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(1, REG, +1, BUF(buffer, SIZE)))) => 0;
|
|
|
|
// force mroot to compact
|
|
lfs.mroot.u.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
|
|
// we should now have 2 mdirs
|
|
assert(lfsr_mtree_weight(&lfs.mtree) == 2*lfsr_mleafweight(&lfs));
|
|
|
|
// now force one of our siblings to split
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 1*lfsr_mleafweight(&lfs)+1, &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
memset(buffer, 'c', SIZE);
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(mdir.mid, REG, +1, BUF(buffer, SIZE)))) => 0;
|
|
|
|
// force mdir to compact
|
|
mdir.u.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &mdir, NULL, 0) => 0;
|
|
|
|
// we should now have 3 mdirs
|
|
assert(lfsr_mtree_weight(&lfs.mtree) == 3*lfsr_mleafweight(&lfs));
|
|
|
|
|
|
//// Now test dropping updates mids correctly
|
|
|
|
// setup our neighbors
|
|
lfsr_openedmdir_t left_neighbor;
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0*lfsr_mleafweight(&lfs)+0,
|
|
&left_neighbor.mdir) => 0;
|
|
assert(left_neighbor.mdir.u.m.weight == 1);
|
|
|
|
lfsr_openedmdir_t right_neighbor;
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 2*lfsr_mleafweight(&lfs)+0,
|
|
&right_neighbor.mdir) => 0;
|
|
assert(right_neighbor.mdir.u.m.weight == 1);
|
|
|
|
lfsr_mdir_addopened(&lfs, LFS_TYPE_INTERNAL, &left_neighbor);
|
|
lfsr_mdir_addopened(&lfs, LFS_TYPE_INTERNAL, &right_neighbor);
|
|
|
|
// cause middle mdir to drop
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 1*lfsr_mleafweight(&lfs)+0, &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(mdir.mid, RM, -1, NULL))) => 0;
|
|
|
|
// we should now have 2 mdirs
|
|
assert(lfsr_mtree_weight(&lfs.mtree) == 2*lfsr_mleafweight(&lfs));
|
|
|
|
// assert that our neighbors were updated correctly
|
|
assert(left_neighbor.mdir.mid == 0*lfsr_mleafweight(&lfs)+0);
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0*lfsr_mleafweight(&lfs)+0, &mdir) => 0;
|
|
assert(memcmp(&left_neighbor.mdir.u.m, &mdir.u.m, sizeof(mdir.u.m)) == 0);
|
|
|
|
assert(right_neighbor.mdir.mid == 1*lfsr_mleafweight(&lfs)+0);
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 1*lfsr_mleafweight(&lfs)+0, &mdir) => 0;
|
|
assert(memcmp(&right_neighbor.mdir.u.m, &mdir.u.m, sizeof(mdir.u.m)) == 0);
|
|
|
|
lfsr_mdir_removeopened(&lfs, LFS_TYPE_INTERNAL, &left_neighbor);
|
|
lfsr_mdir_removeopened(&lfs, LFS_TYPE_INTERNAL, &right_neighbor);
|
|
lfsr_unmount(&lfs) => 0;
|
|
'''
|
|
|
|
|
|
## mtree traversal ##
|
|
|
|
# test specific corner cases
|
|
[cases.test_mtree_traversal]
|
|
defines.VALIDATE = [false, true]
|
|
in = 'lfs.c'
|
|
code = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, CFG) => 0;
|
|
lfsr_mount(&lfs, CFG) => 0;
|
|
lfs_alloc_ack(&lfs);
|
|
// remove root bookmark for now
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(0, RM, -1, NULL))) => 0;
|
|
|
|
// insert a new entry, this should update our neighbors
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(0, REG, +1, BUF("a", 1)))) => 0;
|
|
|
|
// assert that our entry is still in the mtree
|
|
assert(lfs.mroot.u.m.weight == 1);
|
|
|
|
uint8_t buffer[1];
|
|
lfsr_mdir_get(&lfs, &lfs.mroot, 0, LFSR_TAG_REG,
|
|
buffer, 1) => 1;
|
|
assert(memcmp(buffer, "a", 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_traversal_t traversal = LFSR_TRAVERSAL(
|
|
VALIDATE ? LFSR_TRAVERSAL_VALIDATE : 0);
|
|
for (lfs_block_t i = 0;; i++) {
|
|
// a bit hacky, but this catches infinite loops
|
|
assert(i < 2*BLOCK_COUNT);
|
|
|
|
lfsr_tinfo_t tinfo;
|
|
int err = lfsr_traversal_read(&lfs, &traversal, &tinfo);
|
|
assert(!err || err == LFS_ERR_NOENT);
|
|
if (err == LFS_ERR_NOENT) {
|
|
break;
|
|
}
|
|
|
|
if (tinfo.tag == LFSR_TAG_MDIR) {
|
|
printf("traversal: 0x%x mdir 0x{%x,%x}\n",
|
|
tinfo.tag,
|
|
tinfo.u.mdir.u.m.blocks[0], tinfo.u.mdir.u.m.blocks[1]);
|
|
|
|
// keep track of seen blocks
|
|
seen[tinfo.u.mdir.u.m.blocks[1] / 8]
|
|
|= 1 << (tinfo.u.mdir.u.m.blocks[1] % 8);
|
|
seen[tinfo.u.mdir.u.m.blocks[0] / 8]
|
|
|= 1 << (tinfo.u.mdir.u.m.blocks[0] % 8);
|
|
|
|
} else if (tinfo.tag == LFSR_TAG_BRANCH) {
|
|
printf("traversal: 0x%x btree 0x%x.%x\n",
|
|
tinfo.tag,
|
|
tinfo.u.rbyd.block, tinfo.u.rbyd.trunk);
|
|
|
|
// keep track of seen blocks
|
|
seen[tinfo.u.rbyd.block / 8] |= 1 << (tinfo.u.rbyd.block % 8);
|
|
|
|
} else {
|
|
// this shouldn't happen
|
|
printf("traversal: 0x%x\n", tinfo.tag);
|
|
assert(false);
|
|
}
|
|
}
|
|
|
|
// if traversal worked, we should be able to clobber all other blocks
|
|
uint8_t clobber_buf[BLOCK_SIZE];
|
|
memset(clobber_buf, 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, clobber_buf, BLOCK_SIZE) => 0;
|
|
}
|
|
}
|
|
free(seen);
|
|
|
|
// and the tree should still work
|
|
|
|
// assert that our entry is still in the mtree
|
|
assert(lfs.mroot.u.m.weight == 1);
|
|
|
|
lfsr_mdir_get(&lfs, &lfs.mroot, 0, LFSR_TAG_REG,
|
|
buffer, 1) => 1;
|
|
assert(memcmp(buffer, "a", 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'
|
|
defines.VALIDATE = [false, true]
|
|
in = 'lfs.c'
|
|
code = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, CFG) => 0;
|
|
lfsr_mount(&lfs, CFG) => 0;
|
|
lfs_alloc_ack(&lfs);
|
|
// remove root bookmark for now
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(0, RM, -1, NULL))) => 0;
|
|
|
|
// prepare mroot with a large attr so the next entry can not fit
|
|
uint8_t buffer[SIZE];
|
|
memset(buffer, 'a', SIZE);
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(-1, UATTR(1), 0, BUF(buffer, SIZE)))) => 0;
|
|
|
|
// create a large entry that needs to be uninlined (but not split!)
|
|
memset(buffer, 'b', SIZE);
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(0, REG, +1, BUF(buffer, SIZE)))) => 0;
|
|
|
|
// force mroot to compact
|
|
lfs.mroot.u.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
|
|
// assert mdir was unininlined correctly
|
|
assert(lfsr_mtree_weight(&lfs.mtree) == 1*lfsr_mleafweight(&lfs));
|
|
// assert mroot now has no entries
|
|
assert(lfs.mroot.u.m.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, "a", 1) == 0);
|
|
|
|
// assert that our entry is still in the mtree
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0*lfsr_mleafweight(&lfs)+0, &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
|
|
lfsr_mdir_get(&lfs, &mdir, mdir.mid, LFSR_TAG_REG,
|
|
buffer, SIZE) => SIZE;
|
|
assert(memcmp(buffer, "b", 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_traversal_t traversal = LFSR_TRAVERSAL(
|
|
VALIDATE ? LFSR_TRAVERSAL_VALIDATE : 0);
|
|
for (lfs_block_t i = 0;; i++) {
|
|
// a bit hacky, but this catches infinite loops
|
|
assert(i < 2*BLOCK_COUNT);
|
|
|
|
lfsr_tinfo_t tinfo;
|
|
int err = lfsr_traversal_read(&lfs, &traversal, &tinfo);
|
|
assert(!err || err == LFS_ERR_NOENT);
|
|
if (err == LFS_ERR_NOENT) {
|
|
break;
|
|
}
|
|
|
|
if (tinfo.tag == LFSR_TAG_MDIR) {
|
|
printf("traversal: 0x%x mdir 0x{%x,%x}\n",
|
|
tinfo.tag,
|
|
tinfo.u.mdir.u.m.blocks[0], tinfo.u.mdir.u.m.blocks[1]);
|
|
|
|
// keep track of seen blocks
|
|
seen[tinfo.u.mdir.u.m.blocks[1] / 8]
|
|
|= 1 << (tinfo.u.mdir.u.m.blocks[1] % 8);
|
|
seen[tinfo.u.mdir.u.m.blocks[0] / 8]
|
|
|= 1 << (tinfo.u.mdir.u.m.blocks[0] % 8);
|
|
|
|
} else if (tinfo.tag == LFSR_TAG_BRANCH) {
|
|
printf("traversal: 0x%x btree 0x%x.%x\n",
|
|
tinfo.tag,
|
|
tinfo.u.rbyd.block, tinfo.u.rbyd.trunk);
|
|
|
|
// keep track of seen blocks
|
|
seen[tinfo.u.rbyd.block / 8] |= 1 << (tinfo.u.rbyd.block % 8);
|
|
|
|
} else {
|
|
// this shouldn't happen
|
|
printf("traversal: 0x%x\n", tinfo.tag);
|
|
assert(false);
|
|
}
|
|
}
|
|
|
|
// if traversal worked, we should be able to clobber all other blocks
|
|
uint8_t clobber_buf[BLOCK_SIZE];
|
|
memset(clobber_buf, 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, clobber_buf, BLOCK_SIZE) => 0;
|
|
}
|
|
}
|
|
free(seen);
|
|
|
|
// and the tree should still work
|
|
|
|
// assert mdir was unininlined correctly
|
|
assert(lfsr_mtree_weight(&lfs.mtree) == 1*lfsr_mleafweight(&lfs));
|
|
// assert mroot now has no entries
|
|
assert(lfs.mroot.u.m.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, "a", 1) == 0);
|
|
|
|
// assert that our entry is still in the mtree
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0*lfsr_mleafweight(&lfs)+0, &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
|
|
lfsr_mdir_get(&lfs, &mdir, mdir.mid, LFSR_TAG_REG,
|
|
buffer, SIZE) => SIZE;
|
|
assert(memcmp(buffer, "b", 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'
|
|
defines.VALIDATE = [false, true]
|
|
in = 'lfs.c'
|
|
code = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, CFG) => 0;
|
|
lfsr_mount(&lfs, CFG) => 0;
|
|
lfs_alloc_ack(&lfs);
|
|
// remove root bookmark for now
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(0, RM, -1, NULL))) => 0;
|
|
|
|
// create 2 large entries that needs to be uninlined and split
|
|
uint8_t buffer[SIZE];
|
|
memset(buffer, 'a', SIZE);
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(0, REG, +1, BUF(buffer, SIZE)))) => 0;
|
|
|
|
memset(buffer, 'b', SIZE);
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(1, REG, +1, BUF(buffer, SIZE)))) => 0;
|
|
|
|
// force mroot to compact
|
|
lfs.mroot.u.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
|
|
// assert mdirs were unininlined and split
|
|
assert(lfsr_mtree_weight(&lfs.mtree) == 2*lfsr_mleafweight(&lfs));
|
|
// assert mroot now has no entries
|
|
assert(lfs.mroot.u.m.weight == 0);
|
|
|
|
// assert that our entries are still in the mtree
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0*lfsr_mleafweight(&lfs)+0, &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
lfsr_mdir_get(&lfs, &mdir, mdir.mid, LFSR_TAG_REG,
|
|
buffer, SIZE) => SIZE;
|
|
assert(memcmp(buffer, "a", 1) == 0);
|
|
|
|
lfsr_mdir_t msibling;
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree,
|
|
1*lfsr_mleafweight(&lfs)+0, &msibling) => 0;
|
|
assert(msibling.u.m.weight == 1);
|
|
lfsr_mdir_get(&lfs, &msibling, msibling.mid, LFSR_TAG_REG,
|
|
buffer, SIZE) => SIZE;
|
|
assert(memcmp(buffer, "b", 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_traversal_t traversal = LFSR_TRAVERSAL(
|
|
VALIDATE ? LFSR_TRAVERSAL_VALIDATE : 0);
|
|
for (lfs_block_t i = 0;; i++) {
|
|
// a bit hacky, but this catches infinite loops
|
|
assert(i < 2*BLOCK_COUNT);
|
|
|
|
lfsr_tinfo_t tinfo;
|
|
int err = lfsr_traversal_read(&lfs, &traversal, &tinfo);
|
|
assert(!err || err == LFS_ERR_NOENT);
|
|
if (err == LFS_ERR_NOENT) {
|
|
break;
|
|
}
|
|
|
|
if (tinfo.tag == LFSR_TAG_MDIR) {
|
|
printf("traversal: 0x%x mdir 0x{%x,%x}\n",
|
|
tinfo.tag,
|
|
tinfo.u.mdir.u.m.blocks[0], tinfo.u.mdir.u.m.blocks[1]);
|
|
|
|
// keep track of seen blocks
|
|
seen[tinfo.u.mdir.u.m.blocks[1] / 8]
|
|
|= 1 << (tinfo.u.mdir.u.m.blocks[1] % 8);
|
|
seen[tinfo.u.mdir.u.m.blocks[0] / 8]
|
|
|= 1 << (tinfo.u.mdir.u.m.blocks[0] % 8);
|
|
|
|
} else if (tinfo.tag == LFSR_TAG_BRANCH) {
|
|
printf("traversal: 0x%x btree 0x%x.%x\n",
|
|
tinfo.tag,
|
|
tinfo.u.rbyd.block, tinfo.u.rbyd.trunk);
|
|
|
|
// keep track of seen blocks
|
|
seen[tinfo.u.rbyd.block / 8] |= 1 << (tinfo.u.rbyd.block % 8);
|
|
|
|
} else {
|
|
// this shouldn't happen
|
|
printf("traversal: 0x%x\n", tinfo.tag);
|
|
assert(false);
|
|
}
|
|
}
|
|
|
|
// if traversal worked, we should be able to clobber all other blocks
|
|
uint8_t clobber_buf[BLOCK_SIZE];
|
|
memset(clobber_buf, 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, clobber_buf, BLOCK_SIZE) => 0;
|
|
}
|
|
}
|
|
free(seen);
|
|
|
|
// and the tree should still work
|
|
|
|
// assert mdirs were unininlined and split
|
|
assert(lfsr_mtree_weight(&lfs.mtree) == 2*lfsr_mleafweight(&lfs));
|
|
// assert mroot now has no entries
|
|
assert(lfs.mroot.u.m.weight == 0);
|
|
|
|
// assert that our entries are still in the mtree
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0*lfsr_mleafweight(&lfs)+0, &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
lfsr_mdir_get(&lfs, &mdir, mdir.mid, LFSR_TAG_REG,
|
|
buffer, SIZE) => SIZE;
|
|
assert(memcmp(buffer, "a", 1) == 0);
|
|
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree,
|
|
1*lfsr_mleafweight(&lfs)+0, &msibling) => 0;
|
|
assert(msibling.u.m.weight == 1);
|
|
lfsr_mdir_get(&lfs, &msibling, msibling.mid, LFSR_TAG_REG,
|
|
buffer, SIZE) => SIZE;
|
|
assert(memcmp(buffer, "b", 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
|
|
defines.VALIDATE = [false, true]
|
|
in = 'lfs.c'
|
|
code = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, CFG) => 0;
|
|
lfsr_mount(&lfs, CFG) => 0;
|
|
lfs_alloc_ack(&lfs);
|
|
// remove root bookmark for now
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(0, RM, -1, NULL))) => 0;
|
|
|
|
// prepare mroot with an attr
|
|
uint8_t buffer[SIZE];
|
|
memset(buffer, 'a', SIZE);
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(-1, UATTR(1), 0, BUF(buffer, SIZE)))) => 0;
|
|
|
|
// force mroot to compact twice, this should extend the mroot
|
|
lfsr_mdir_t old_mroot = lfs.mroot;
|
|
|
|
lfs.mroot.u.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
lfs.mroot.u.rbyd.eoff = -1;
|
|
memset(buffer, 'b', SIZE);
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(-1, UATTR(1), 0, BUF(buffer, SIZE)))) => 0;
|
|
|
|
// assert we relocated
|
|
assert(lfsr_mdir_cmp(&old_mroot, &lfs.mroot) != 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, "b", 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_traversal_t traversal = LFSR_TRAVERSAL(
|
|
VALIDATE ? LFSR_TRAVERSAL_VALIDATE : 0);
|
|
for (lfs_block_t i = 0;; i++) {
|
|
// a bit hacky, but this catches infinite loops
|
|
assert(i < 2*BLOCK_COUNT);
|
|
|
|
lfsr_tinfo_t tinfo;
|
|
int err = lfsr_traversal_read(&lfs, &traversal, &tinfo);
|
|
assert(!err || err == LFS_ERR_NOENT);
|
|
if (err == LFS_ERR_NOENT) {
|
|
break;
|
|
}
|
|
|
|
if (tinfo.tag == LFSR_TAG_MDIR) {
|
|
printf("traversal: 0x%x mdir 0x{%x,%x}\n",
|
|
tinfo.tag,
|
|
tinfo.u.mdir.u.m.blocks[0], tinfo.u.mdir.u.m.blocks[1]);
|
|
|
|
// keep track of seen blocks
|
|
seen[tinfo.u.mdir.u.m.blocks[1] / 8]
|
|
|= 1 << (tinfo.u.mdir.u.m.blocks[1] % 8);
|
|
seen[tinfo.u.mdir.u.m.blocks[0] / 8]
|
|
|= 1 << (tinfo.u.mdir.u.m.blocks[0] % 8);
|
|
|
|
} else if (tinfo.tag == LFSR_TAG_BRANCH) {
|
|
printf("traversal: 0x%x btree 0x%x.%x\n",
|
|
tinfo.tag,
|
|
tinfo.u.rbyd.block, tinfo.u.rbyd.trunk);
|
|
|
|
// keep track of seen blocks
|
|
seen[tinfo.u.rbyd.block / 8] |= 1 << (tinfo.u.rbyd.block % 8);
|
|
|
|
} else {
|
|
// this shouldn't happen
|
|
printf("traversal: 0x%x\n", tinfo.tag);
|
|
assert(false);
|
|
}
|
|
}
|
|
|
|
// if traversal worked, we should be able to clobber all other blocks
|
|
uint8_t clobber_buf[BLOCK_SIZE];
|
|
memset(clobber_buf, 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, clobber_buf, BLOCK_SIZE) => 0;
|
|
}
|
|
}
|
|
free(seen);
|
|
|
|
// and the tree should still work
|
|
|
|
// assert we relocated
|
|
assert(lfsr_mdir_cmp(&old_mroot, &lfs.mroot) != 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, "b", 1) == 0);
|
|
|
|
lfsr_unmount(&lfs) => 0;
|
|
'''
|
|
|
|
# larger traversal tests
|
|
[cases.test_mtree_traversal_many]
|
|
defines.N = [5, 10, 20, 40, 80, 160, 320]
|
|
defines.VALIDATE = [false, true]
|
|
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;
|
|
lfs_alloc_ack(&lfs);
|
|
// remove root bookmark for now
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(0, RM, -1, NULL))) => 0;
|
|
|
|
// create entries
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree,
|
|
lfs_smax32(
|
|
lfsr_mtree_weight(&lfs.mtree) - lfsr_mleafweight(&lfs),
|
|
0),
|
|
&mdir) => 0;
|
|
for (lfs_size_t i = 0; i < N; i++) {
|
|
// force a compaction?
|
|
if (FORCE_COMPACTION) {
|
|
mdir.u.rbyd.eoff = -1;
|
|
lfs.mroot.u.rbyd.eoff = -1;
|
|
}
|
|
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(mdir.mid, REG, +1,
|
|
BUF(&alphas[i % 26], 1)))) => 0;
|
|
|
|
uint8_t buffer[4];
|
|
lfsr_mdir_get(&lfs, &mdir, mdir.mid, LFSR_TAG_REG,
|
|
buffer, 4) => 1;
|
|
assert(memcmp(buffer, &alphas[i % 26], 1) == 0);
|
|
|
|
mdir.mid += 1;
|
|
}
|
|
|
|
// try looking up each entry
|
|
lfs_size_t i = 0;
|
|
for (lfs_ssize_t mid = 0;
|
|
mid < lfs_smax32(
|
|
lfsr_mtree_weight(&lfs.mtree),
|
|
lfsr_mleafweight(&lfs));
|
|
mid += lfsr_mleafweight(&lfs)) {
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, mid, &mdir) => 0;
|
|
for (; (mdir.mid & lfsr_midrmask(&lfs))
|
|
< (lfs_ssize_t)mdir.u.m.weight;
|
|
mdir.mid += 1) {
|
|
uint8_t buffer[4];
|
|
lfsr_mdir_get(&lfs, &mdir, mdir.mid, LFSR_TAG_REG,
|
|
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_traversal_t traversal = LFSR_TRAVERSAL(
|
|
VALIDATE ? LFSR_TRAVERSAL_VALIDATE : 0);
|
|
for (lfs_block_t i = 0;; i++) {
|
|
// a bit hacky, but this catches infinite loops
|
|
assert(i < 2*BLOCK_COUNT);
|
|
|
|
lfsr_tinfo_t tinfo;
|
|
int err = lfsr_traversal_read(&lfs, &traversal, &tinfo);
|
|
assert(!err || err == LFS_ERR_NOENT);
|
|
if (err == LFS_ERR_NOENT) {
|
|
break;
|
|
}
|
|
|
|
if (tinfo.tag == LFSR_TAG_MDIR) {
|
|
printf("traversal: 0x%x mdir 0x{%x,%x}\n",
|
|
tinfo.tag,
|
|
tinfo.u.mdir.u.m.blocks[0], tinfo.u.mdir.u.m.blocks[1]);
|
|
|
|
// keep track of seen blocks
|
|
seen[tinfo.u.mdir.u.m.blocks[1] / 8]
|
|
|= 1 << (tinfo.u.mdir.u.m.blocks[1] % 8);
|
|
seen[tinfo.u.mdir.u.m.blocks[0] / 8]
|
|
|= 1 << (tinfo.u.mdir.u.m.blocks[0] % 8);
|
|
|
|
} else if (tinfo.tag == LFSR_TAG_BRANCH) {
|
|
printf("traversal: 0x%x btree 0x%x.%x\n",
|
|
tinfo.tag,
|
|
tinfo.u.rbyd.block, tinfo.u.rbyd.trunk);
|
|
|
|
// keep track of seen blocks
|
|
seen[tinfo.u.rbyd.block / 8] |= 1 << (tinfo.u.rbyd.block % 8);
|
|
|
|
} else {
|
|
// this shouldn't happen
|
|
printf("traversal: 0x%x\n", tinfo.tag);
|
|
assert(false);
|
|
}
|
|
}
|
|
|
|
// if traversal worked, we should be able to clobber all other blocks
|
|
uint8_t clobber_buf[BLOCK_SIZE];
|
|
memset(clobber_buf, 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, clobber_buf, BLOCK_SIZE) => 0;
|
|
}
|
|
}
|
|
free(seen);
|
|
|
|
// and the tree should still work
|
|
|
|
// try looking up each entry
|
|
i = 0;
|
|
for (lfs_ssize_t mid = 0;
|
|
mid < lfs_smax32(
|
|
lfsr_mtree_weight(&lfs.mtree),
|
|
lfsr_mleafweight(&lfs));
|
|
mid += lfsr_mleafweight(&lfs)) {
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, mid, &mdir) => 0;
|
|
for (; (mdir.mid & lfsr_midrmask(&lfs))
|
|
< (lfs_ssize_t)mdir.u.m.weight;
|
|
mdir.mid += 1) {
|
|
uint8_t buffer[4];
|
|
lfsr_mdir_get(&lfs, &mdir, mdir.mid, LFSR_TAG_REG,
|
|
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.VALIDATE = [false, true]
|
|
defines.FORCE_COMPACTION = [false, true]
|
|
defines.SEED = 'range(100)'
|
|
in = 'lfs.c'
|
|
code = '''
|
|
const char *alphas = "abcdefghijklmnopqrstuvwxyz";
|
|
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, CFG) => 0;
|
|
lfsr_mount(&lfs, CFG) => 0;
|
|
lfs_alloc_ack(&lfs);
|
|
// remove root bookmark for now
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(0, RM, -1, NULL))) => 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 = (lfs_ssize_t)(
|
|
TEST_PRNG(&prng) % lfs_max32(
|
|
lfsr_mtree_weight(&lfs.mtree),
|
|
lfsr_mleafweight(&lfs)));
|
|
// fetch mdir
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, mid, &mdir) => 0;
|
|
// limit our mid to our mdir's weight
|
|
mdir.mid = (mdir.mid & lfsr_midbmask(&lfs))
|
|
| (mdir.mid % (mdir.u.m.weight+1));
|
|
|
|
// force a compaction?
|
|
if (FORCE_COMPACTION) {
|
|
mdir.u.rbyd.eoff = -1;
|
|
lfs.mroot.u.rbyd.eoff = -1;
|
|
}
|
|
|
|
// add to rbyd, potentially splitting the mdir
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(mdir.mid, REG, +1,
|
|
BUF(&alphas[i % 26], 1)))) => 0;
|
|
|
|
// make sure we can look up the new entry
|
|
uint8_t buffer[4];
|
|
lfsr_mdir_get(&lfs, &mdir, mdir.mid, LFSR_TAG_REG,
|
|
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 = 0;
|
|
mid < lfs_smax32(
|
|
lfsr_mtree_weight(&lfs.mtree),
|
|
lfsr_mleafweight(&lfs));
|
|
mid += lfsr_mleafweight(&lfs)) {
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, mid, &mdir) => 0;
|
|
for (; (mdir.mid & lfsr_midrmask(&lfs))
|
|
< (lfs_ssize_t)mdir.u.m.weight;
|
|
mdir.mid += 1) {
|
|
uint8_t buffer[4];
|
|
lfsr_mdir_get(&lfs, &mdir, mdir.mid, LFSR_TAG_REG,
|
|
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_traversal_t traversal = LFSR_TRAVERSAL(
|
|
VALIDATE ? LFSR_TRAVERSAL_VALIDATE : 0);
|
|
for (lfs_block_t i = 0;; i++) {
|
|
// a bit hacky, but this catches infinite loops
|
|
assert(i < 2*BLOCK_COUNT);
|
|
|
|
lfsr_tinfo_t tinfo;
|
|
int err = lfsr_traversal_read(&lfs, &traversal, &tinfo);
|
|
assert(!err || err == LFS_ERR_NOENT);
|
|
if (err == LFS_ERR_NOENT) {
|
|
break;
|
|
}
|
|
|
|
if (tinfo.tag == LFSR_TAG_MDIR) {
|
|
printf("traversal: 0x%x mdir 0x{%x,%x}\n",
|
|
tinfo.tag,
|
|
tinfo.u.mdir.u.m.blocks[0], tinfo.u.mdir.u.m.blocks[1]);
|
|
|
|
// keep track of seen blocks
|
|
seen[tinfo.u.mdir.u.m.blocks[1] / 8]
|
|
|= 1 << (tinfo.u.mdir.u.m.blocks[1] % 8);
|
|
seen[tinfo.u.mdir.u.m.blocks[0] / 8]
|
|
|= 1 << (tinfo.u.mdir.u.m.blocks[0] % 8);
|
|
|
|
} else if (tinfo.tag == LFSR_TAG_BRANCH) {
|
|
printf("traversal: 0x%x btree 0x%x.%x\n",
|
|
tinfo.tag,
|
|
tinfo.u.rbyd.block, tinfo.u.rbyd.trunk);
|
|
|
|
// keep track of seen blocks
|
|
seen[tinfo.u.rbyd.block / 8] |= 1 << (tinfo.u.rbyd.block % 8);
|
|
|
|
} else {
|
|
// this shouldn't happen
|
|
printf("traversal: 0x%x\n", tinfo.tag);
|
|
assert(false);
|
|
}
|
|
}
|
|
|
|
// if traversal worked, we should be able to clobber all other blocks
|
|
uint8_t clobber_buf[BLOCK_SIZE];
|
|
memset(clobber_buf, 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, clobber_buf, BLOCK_SIZE) => 0;
|
|
}
|
|
}
|
|
free(seen);
|
|
|
|
// and the tree should still work
|
|
|
|
// try looking up each entry
|
|
count_ = 0;
|
|
|
|
for (lfs_ssize_t mid = 0;
|
|
mid < lfs_smax32(
|
|
lfsr_mtree_weight(&lfs.mtree),
|
|
lfsr_mleafweight(&lfs));
|
|
mid += lfsr_mleafweight(&lfs)) {
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, mid, &mdir) => 0;
|
|
for (; (mdir.mid & lfsr_midrmask(&lfs))
|
|
< (lfs_ssize_t)mdir.u.m.weight;
|
|
mdir.mid += 1) {
|
|
uint8_t buffer[4];
|
|
lfsr_mdir_get(&lfs, &mdir, mdir.mid, LFSR_TAG_REG,
|
|
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;
|
|
'''
|
|
|
|
|
|
## Cycle detection? ##
|
|
|
|
# test that our cycle detector at least works in common cases
|
|
[cases.test_mtree_traversal_mroot_cycle]
|
|
in = 'lfs.c'
|
|
code = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, CFG) => 0;
|
|
lfsr_mount(&lfs, CFG) => 0;
|
|
lfs_alloc_ack(&lfs);
|
|
|
|
uint8_t buf[LFSR_MPTR_DSIZE];
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(-1,
|
|
MROOT, 0, FROMMPTR(&LFSR_MPTR_MROOTANCHOR(), buf)))) => 0;
|
|
|
|
// technically, cycle detection only needs to work when we're validating
|
|
lfsr_traversal_t traversal = LFSR_TRAVERSAL(LFSR_TRAVERSAL_VALIDATE);
|
|
for (lfs_block_t i = 0;; i++) {
|
|
// assert that we detect the cycle in a reasonable number of iterations
|
|
assert(i < 2*BLOCK_COUNT);
|
|
|
|
lfsr_tinfo_t tinfo;
|
|
int err = lfsr_traversal_read(&lfs, &traversal, &tinfo);
|
|
assert(!err || err == LFS_ERR_CORRUPT);
|
|
if (err == LFS_ERR_CORRUPT) {
|
|
break;
|
|
}
|
|
|
|
if (tinfo.tag == LFSR_TAG_MDIR) {
|
|
printf("traversal: 0x%x mdir 0x{%x,%x}\n",
|
|
tinfo.tag,
|
|
tinfo.u.mdir.u.m.blocks[0], tinfo.u.mdir.u.m.blocks[1]);
|
|
|
|
} else if (tinfo.tag == LFSR_TAG_BRANCH) {
|
|
printf("traversal: 0x%x btree 0x%x.%x\n",
|
|
tinfo.tag,
|
|
tinfo.u.rbyd.block, tinfo.u.rbyd.trunk);
|
|
|
|
} else {
|
|
// this shouldn't happen
|
|
printf("traversal: 0x%x\n", tinfo.tag);
|
|
assert(false);
|
|
}
|
|
}
|
|
|
|
lfsr_unmount(&lfs) => 0;
|
|
'''
|
|
|
|
|
|
## Magic consistency ##
|
|
|
|
# make sure our magic string ("littlefs") shows up in the same place (off=8)
|
|
[cases.test_mtree_magic]
|
|
# this should be set so only one entry can fit in a metadata block
|
|
defines.SIZE = 'BLOCK_SIZE / 4'
|
|
code = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, CFG) => 0;
|
|
|
|
// check our magic string
|
|
//
|
|
// note if we lose power we may not have the magic string in both blocks!
|
|
// but we don't lose power in this test so we can assert the magic string
|
|
// is present in both
|
|
uint8_t magic[lfs_max(16, READ_SIZE)];
|
|
CFG->read(CFG, 0, 0, magic, lfs_max(16, READ_SIZE)) => 0;
|
|
assert(memcmp(&magic[8], "littlefs", 8) == 0);
|
|
CFG->read(CFG, 1, 0, magic, lfs_max(16, READ_SIZE)) => 0;
|
|
assert(memcmp(&magic[8], "littlefs", 8) == 0);
|
|
'''
|
|
|
|
[cases.test_mtree_magic_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 = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, CFG) => 0;
|
|
lfsr_mount(&lfs, CFG) => 0;
|
|
lfs_alloc_ack(&lfs);
|
|
|
|
// prepare mroot with an attr
|
|
uint8_t buffer[SIZE];
|
|
memset(buffer, 'a', SIZE);
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(-1, UATTR(1), 0, BUF(buffer, SIZE)))) => 0;
|
|
|
|
// force mroot to compact twice, this should extend the mroot
|
|
lfsr_mdir_t old_mroot = lfs.mroot;
|
|
|
|
lfs.mroot.u.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
lfs.mroot.u.rbyd.eoff = -1;
|
|
memset(buffer, 'b', SIZE);
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(-1, UATTR(1), 0, BUF(buffer, SIZE)))) => 0;
|
|
|
|
// assert we relocated
|
|
assert(lfsr_mdir_cmp(&old_mroot, &lfs.mroot) != 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, "b", 1) == 0);
|
|
|
|
lfsr_unmount(&lfs) => 0;
|
|
|
|
|
|
// check our magic string
|
|
//
|
|
// note if we lose power we may not have the magic string in both blocks!
|
|
// but we don't lose power in this test so we can assert the magic string
|
|
// is present in both
|
|
uint8_t magic[lfs_max(16, READ_SIZE)];
|
|
CFG->read(CFG, 0, 0, magic, lfs_max(16, READ_SIZE)) => 0;
|
|
assert(memcmp(&magic[8], "littlefs", 8) == 0);
|
|
CFG->read(CFG, 1, 0, magic, lfs_max(16, READ_SIZE)) => 0;
|
|
assert(memcmp(&magic[8], "littlefs", 8) == 0);
|
|
'''
|
|
|
|
[cases.test_mtree_magic_extend_twice]
|
|
# 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
|
|
# force our block to compact by setting prog_size=block_size, we don't have
|
|
# any way to indirectly force the intermediary mroots to compact otherwise
|
|
defines.PROG_SIZE = 'BLOCK_SIZE'
|
|
in = 'lfs.c'
|
|
code = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, CFG) => 0;
|
|
lfsr_mount(&lfs, CFG) => 0;
|
|
lfs_alloc_ack(&lfs);
|
|
|
|
// prepare mroot with an attr
|
|
uint8_t buffer[SIZE];
|
|
memset(buffer, 'a', SIZE);
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(-1, UATTR(1), 0, BUF(buffer, SIZE)))) => 0;
|
|
|
|
// force mroot to compact 2x2 times, this should extend the mroot twice
|
|
lfsr_mdir_t old_mroot = lfs.mroot;
|
|
|
|
for (int i = 0; i < 4; i++) {
|
|
lfs.mroot.u.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
}
|
|
lfs.mroot.u.rbyd.eoff = -1;
|
|
memset(buffer, 'b', SIZE);
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(-1, UATTR(1), 0, BUF(buffer, SIZE)))) => 0;
|
|
|
|
// assert we relocated
|
|
assert(lfsr_mdir_cmp(&old_mroot, &lfs.mroot) != 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, "b", 1) == 0);
|
|
|
|
lfsr_unmount(&lfs) => 0;
|
|
|
|
|
|
// check our magic string
|
|
//
|
|
// note if we lose power we may not have the magic string in both blocks!
|
|
// but we don't lose power in this test so we can assert the magic string
|
|
// is present in both
|
|
uint8_t magic[lfs_max(16, READ_SIZE)];
|
|
CFG->read(CFG, 0, 0, magic, lfs_max(16, READ_SIZE)) => 0;
|
|
assert(memcmp(&magic[8], "littlefs", 8) == 0);
|
|
CFG->read(CFG, 1, 0, magic, lfs_max(16, READ_SIZE)) => 0;
|
|
assert(memcmp(&magic[8], "littlefs", 8) == 0);
|
|
'''
|
|
|