9f18b1fd50
This is an intermediate commit as a part of a tangent into compressed
mids.
The idea here, is instead of using bid=-1 as a special value for mroots,
use only the top bit to indicate mroots. This allows you to compare
against the grm/other uninlined mids by masking instead of signed
comparison.
This is valuable for compressed mids since extracting bids relies on
knowledge of the block size, and becomes quite a bit more expensive.
mroot bid mroot cmp
before: 0xffffffff lfs_smax32(a, 0) == lfs_smax32(b, 0)
after: 0x80000000 (a & 0x7fffffff) == (b & 0x7fffffff)
The implementation here is a bit clumsy. I think GCC may be not that
great at optimizing out copies of structs being passed around via
inlined functions. But this is only a proof-of-concept.
4423 lines
142 KiB
TOML
4423 lines
142 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.r.rbyd.eoff = BLOCK_SIZE;
|
|
|
|
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 dstart 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.r.rbyd.eoff = BLOCK_SIZE;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
|
|
// assert mdir was unininlined correctly
|
|
assert(lfsr_mtree_weight(&lfs) == 1);
|
|
// 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, LFSR_MID(0, -1), &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
|
|
lfsr_mdir_get(&lfs, &mdir, 0, 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) == 1);
|
|
// 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, LFSR_MID(0, -1), &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
|
|
lfsr_mdir_get(&lfs, &mdir, 0, 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 dstart 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.r.rbyd.eoff = BLOCK_SIZE;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
|
|
// assert mdirs were unininlined and split
|
|
assert(lfsr_mtree_weight(&lfs) == 2);
|
|
// 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, LFSR_MID(0, -1), &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_REG,
|
|
buffer, SIZE) => SIZE;
|
|
assert(memcmp(buffer, "a", 1) == 0);
|
|
|
|
lfsr_mtree_lookup(&lfs, LFSR_MID(1, -1), &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
lfsr_mdir_get(&lfs, &mdir, 0, 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) == 2);
|
|
// 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, LFSR_MID(0, -1), &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_REG,
|
|
buffer, SIZE) => SIZE;
|
|
assert(memcmp(buffer, "a", 1) == 0);
|
|
|
|
lfsr_mtree_lookup(&lfs, LFSR_MID(1, -1), &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
lfsr_mdir_get(&lfs, &mdir, 0, 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 dstart 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.r.rbyd.eoff = BLOCK_SIZE;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
|
|
// assert mdir was unininlined correctly
|
|
assert(lfsr_mtree_weight(&lfs) == 1);
|
|
// 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, LFSR_MID(0, -1), &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
|
|
memset(buffer, 'c', SIZE);
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(1, REG, +1, BUF(buffer, SIZE)))) => 0;
|
|
|
|
// force mdir to compact
|
|
mdir.u.r.rbyd.eoff = BLOCK_SIZE;
|
|
lfsr_mdir_commit(&lfs, &mdir, NULL, 0) => 0;
|
|
|
|
// assert mdir was split correctly
|
|
assert(lfsr_mtree_weight(&lfs) == 2);
|
|
// 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, LFSR_MID(0, -1), &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_REG,
|
|
buffer, SIZE) => SIZE;
|
|
assert(memcmp(buffer, "b", 1) == 0);
|
|
|
|
lfsr_mtree_lookup(&lfs, LFSR_MID(1, -1), &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
lfsr_mdir_get(&lfs, &mdir, 0, 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) == 2);
|
|
// 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, LFSR_MID(0, -1), &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_REG,
|
|
buffer, SIZE) => SIZE;
|
|
assert(memcmp(buffer, "b", 1) == 0);
|
|
|
|
lfsr_mtree_lookup(&lfs, LFSR_MID(1, -1), &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
lfsr_mdir_get(&lfs, &mdir, 0, 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 dstart 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, LFSR_MID(lfs_smax32(lfsr_mtree_weight(&lfs)-1, 0), -1),
|
|
&mdir) => 0;
|
|
|
|
mdir.mid.rid = 0;
|
|
for (lfs_size_t i = 0; i < N; i++) {
|
|
// force a compaction?
|
|
if (FORCE_COMPACTION) {
|
|
mdir.u.r.rbyd.eoff = -1;
|
|
lfs.mroot.u.r.rbyd.eoff = -1;
|
|
}
|
|
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(mdir.mid.rid, REG, +1,
|
|
BUF(&alphas[i % 26], 1)))) => 0;
|
|
|
|
uint8_t buffer[4];
|
|
lfsr_mdir_get(&lfs, &mdir, mdir.mid.rid, LFSR_TAG_REG,
|
|
buffer, 4) => 1;
|
|
assert(memcmp(buffer, &alphas[i % 26], 1) == 0);
|
|
|
|
mdir.mid.rid += 1;
|
|
}
|
|
|
|
// try looking up each entry
|
|
lfs_size_t i = 0;
|
|
for (lfs_ssize_t mid = 0;
|
|
mid < lfs_smax32(lfsr_mtree_weight(&lfs), 1);
|
|
mid++) {
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_lookup(&lfs, LFSR_MID(mid, -1), &mdir) => 0;
|
|
for (mdir.mid.rid = 0;
|
|
mdir.mid.rid < (lfs_ssize_t)mdir.u.m.weight;
|
|
mdir.mid.rid++) {
|
|
uint8_t buffer[4];
|
|
lfsr_mdir_get(&lfs, &mdir, mdir.mid.rid, 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), 1);
|
|
mid++) {
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_lookup(&lfs, LFSR_MID(mid, -1), &mdir) => 0;
|
|
for (mdir.mid.rid = 0;
|
|
mdir.mid.rid < (lfs_ssize_t)mdir.u.m.weight;
|
|
mdir.mid.rid++) {
|
|
uint8_t buffer[4];
|
|
lfsr_mdir_get(&lfs, &mdir, mdir.mid.rid, 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 dstart 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), 1));
|
|
// fetch mdir
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_lookup(&lfs, LFSR_MID(mid, -1), &mdir) => 0;
|
|
// choose a pseudo-random rid
|
|
mdir.mid.rid = TEST_PRNG(&prng) % (mdir.u.m.weight+1);
|
|
|
|
// force a compaction?
|
|
if (FORCE_COMPACTION) {
|
|
mdir.u.r.rbyd.eoff = -1;
|
|
lfs.mroot.u.r.rbyd.eoff = -1;
|
|
}
|
|
|
|
// add to rbyd, potentially splitting the mdir
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(mdir.mid.rid, 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.rid, 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), 1);
|
|
mid++) {
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_lookup(&lfs, LFSR_MID(mid, -1), &mdir) => 0;
|
|
for (mdir.mid.rid = 0;
|
|
mdir.mid.rid < (lfs_ssize_t)mdir.u.m.weight;
|
|
mdir.mid.rid++) {
|
|
uint8_t buffer[4];
|
|
lfsr_mdir_get(&lfs, &mdir, mdir.mid.rid, 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), 1);
|
|
mid++) {
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_lookup(&lfs, LFSR_MID(mid, -1), &mdir) => 0;
|
|
for (mdir.mid.rid = 0;
|
|
mdir.mid.rid < (lfs_ssize_t)mdir.u.m.weight;
|
|
mdir.mid.rid++) {
|
|
uint8_t buffer[4];
|
|
lfsr_mdir_get(&lfs, &mdir, mdir.mid.rid, 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 dstart 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.r.rbyd.eoff = BLOCK_SIZE;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
|
|
// assert mdir was unininlined correctly
|
|
assert(lfsr_mtree_weight(&lfs) == 1);
|
|
// 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, LFSR_MID(0, -1), &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) == 0);
|
|
// 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) == 0);
|
|
// 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 dstart 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.r.rbyd.eoff = BLOCK_SIZE;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
|
|
// assert mdir was unininlined correctly
|
|
assert(lfsr_mtree_weight(&lfs) == 1);
|
|
// 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, LFSR_MID(0, -1), &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
|
|
// force mdir to compact while we're removing
|
|
mdir.u.r.rbyd.eoff = BLOCK_SIZE;
|
|
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(0, RM, -1, NULL))) => 0;
|
|
|
|
// assert mdir was dropped
|
|
assert(lfsr_mtree_weight(&lfs) == 0);
|
|
// 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) == 0);
|
|
// 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 dstart 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.r.rbyd.eoff = BLOCK_SIZE;
|
|
|
|
// 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) == 0);
|
|
// 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) == 0);
|
|
// 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 dstart 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.r.rbyd.eoff = BLOCK_SIZE;
|
|
|
|
// 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) == 1);
|
|
// 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, LFSR_MID(0, -1), &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
|
|
lfsr_mdir_get(&lfs, &mdir, 0, 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) == 1);
|
|
// 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, LFSR_MID(0, -1), &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
|
|
lfsr_mdir_get(&lfs, &mdir, 0, 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 dstart 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.r.rbyd.eoff = BLOCK_SIZE;
|
|
|
|
// 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) == 1);
|
|
// 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, LFSR_MID(0, -1), &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
|
|
lfsr_mdir_get(&lfs, &mdir, 0, 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) == 1);
|
|
// 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, LFSR_MID(0, -1), &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
|
|
lfsr_mdir_get(&lfs, &mdir, 0, 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 dstart 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.r.rbyd.eoff = BLOCK_SIZE;
|
|
|
|
// 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) == 0);
|
|
// 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) == 0);
|
|
// 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 dstart 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.r.rbyd.eoff = BLOCK_SIZE;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
|
|
// assert mdir was unininlined correctly
|
|
assert(lfsr_mtree_weight(&lfs) == 1);
|
|
// 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, LFSR_MID(0, -1), &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
|
|
memset(buffer, 'c', SIZE);
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(1, REG, +1, BUF(buffer, SIZE)))) => 0;
|
|
|
|
// force mdir to compact
|
|
mdir.u.r.rbyd.eoff = BLOCK_SIZE;
|
|
|
|
// remove the left entry as we compact, forcing the left
|
|
// mdir to be dropped
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(0, RM, -1, NULL))) => 0;
|
|
|
|
// assert mdir was dropped
|
|
assert(lfsr_mtree_weight(&lfs) == 1);
|
|
// 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, LFSR_MID(0, -1), &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
|
|
lfsr_mdir_get(&lfs, &mdir, 0, 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) == 1);
|
|
// 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, LFSR_MID(0, -1), &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
|
|
lfsr_mdir_get(&lfs, &mdir, 0, 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 dstart 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.r.rbyd.eoff = BLOCK_SIZE;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
|
|
// assert mdir was unininlined correctly
|
|
assert(lfsr_mtree_weight(&lfs) == 1);
|
|
// 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, LFSR_MID(0, -1), &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
|
|
memset(buffer, 'c', SIZE);
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(1, REG, +1, BUF(buffer, SIZE)))) => 0;
|
|
|
|
// force mdir to compact
|
|
mdir.u.r.rbyd.eoff = BLOCK_SIZE;
|
|
|
|
// 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) == 1);
|
|
// 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, LFSR_MID(0, -1), &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
|
|
lfsr_mdir_get(&lfs, &mdir, 0, 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) == 1);
|
|
// 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, LFSR_MID(0, -1), &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
|
|
lfsr_mdir_get(&lfs, &mdir, 0, 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 dstart 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.r.rbyd.eoff = BLOCK_SIZE;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
|
|
// assert mdir was unininlined correctly
|
|
assert(lfsr_mtree_weight(&lfs) == 1);
|
|
// 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, LFSR_MID(0, -1), &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
|
|
memset(buffer, 'c', SIZE);
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(1, REG, +1, BUF(buffer, SIZE)))) => 0;
|
|
|
|
// force mdir to compact
|
|
mdir.u.r.rbyd.eoff = BLOCK_SIZE;
|
|
|
|
// 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) == 0);
|
|
// 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) == 0);
|
|
// 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]
|
|
defines.REMAINING = [20, 5, 1, 0]
|
|
if = 'N > REMAINING'
|
|
defines.FORCE_COMPACTION = [false, true]
|
|
in = 'lfs.c'
|
|
code = '''
|
|
const char *alphas = "abcdefghijklmnopqrstuvwxyz";
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, CFG) => 0;
|
|
lfsr_mount(&lfs, CFG) => 0;
|
|
lfs_alloc_ack(&lfs);
|
|
// remove root dstart 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, LFSR_MID(lfs_smax32(lfsr_mtree_weight(&lfs)-1, 0), -1),
|
|
&mdir) => 0;
|
|
|
|
mdir.mid.rid = 0;
|
|
for (lfs_size_t i = 0; i < N; i++) {
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(mdir.mid.rid, REG, +1,
|
|
BUF(&alphas[i % 26], 1)))) => 0;
|
|
|
|
uint8_t buffer[4];
|
|
lfsr_mdir_get(&lfs, &mdir, mdir.mid.rid, LFSR_TAG_REG,
|
|
buffer, 4) => 1;
|
|
assert(memcmp(buffer, &alphas[i % 26], 1) == 0);
|
|
|
|
mdir.mid.rid += 1;
|
|
}
|
|
|
|
// remove entries
|
|
for (lfs_size_t i = 0; i < N - REMAINING; i++) {
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_lookup(&lfs, LFSR_MID(0, -1), &mdir) => 0;
|
|
|
|
// drop should make sure we never have empty mdirs
|
|
assert(lfsr_mdir_ismroot(&mdir) || mdir.u.m.weight > 0);
|
|
|
|
// force a compaction?
|
|
if (FORCE_COMPACTION) {
|
|
mdir.u.r.rbyd.eoff = -1;
|
|
lfs.mroot.u.r.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), 1);
|
|
mid++) {
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_lookup(&lfs, LFSR_MID(mid, -1), &mdir) => 0;
|
|
for (mdir.mid.rid = 0;
|
|
mdir.mid.rid < (lfs_ssize_t)mdir.u.m.weight;
|
|
mdir.mid.rid++) {
|
|
uint8_t buffer[4];
|
|
lfsr_mdir_get(&lfs, &mdir, mdir.mid.rid, 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), 1);
|
|
mid++) {
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_lookup(&lfs, LFSR_MID(mid, -1), &mdir) => 0;
|
|
for (mdir.mid.rid = 0;
|
|
mdir.mid.rid < (lfs_ssize_t)mdir.u.m.weight;
|
|
mdir.mid.rid++) {
|
|
uint8_t buffer[4];
|
|
lfsr_mdir_get(&lfs, &mdir, mdir.mid.rid, 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]
|
|
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 dstart 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
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_lookup(&lfs, LFSR_MID(lfs_smax32(lfsr_mtree_weight(&lfs)-1, 0), -1),
|
|
&mdir) => 0;
|
|
|
|
mdir.mid.rid = 0;
|
|
for (lfs_size_t i = 0; i < N; i++) {
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(mdir.mid.rid, REG, +1,
|
|
BUF(&alphas[i % 26], 1)))) => 0;
|
|
|
|
uint8_t buffer[4];
|
|
lfsr_mdir_get(&lfs, &mdir, mdir.mid.rid, LFSR_TAG_REG,
|
|
buffer, 4) => 1;
|
|
assert(memcmp(buffer, &alphas[i % 26], 1) == 0);
|
|
|
|
mdir.mid.rid += 1;
|
|
}
|
|
|
|
// try looking up each entry
|
|
lfs_size_t i = 0;
|
|
for (lfs_ssize_t mid = 0;
|
|
mid < lfs_smax32(lfsr_mtree_weight(&lfs), 1);
|
|
mid++) {
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_lookup(&lfs, LFSR_MID(mid, -1), &mdir) => 0;
|
|
for (mdir.mid.rid = 0;
|
|
mdir.mid.rid < (lfs_ssize_t)mdir.u.m.weight;
|
|
mdir.mid.rid++) {
|
|
uint8_t buffer[4];
|
|
lfsr_mdir_get(&lfs, &mdir, mdir.mid.rid, 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; i++) {
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_lookup(&lfs, LFSR_MID(0, -1), &mdir) => 0;
|
|
|
|
// drop should make sure we never have empty mdirs
|
|
assert(lfsr_mdir_ismroot(&mdir) || mdir.u.m.weight > 0);
|
|
|
|
// force a compaction?
|
|
if (FORCE_COMPACTION) {
|
|
mdir.u.r.rbyd.eoff = -1;
|
|
lfs.mroot.u.r.rbyd.eoff = -1;
|
|
}
|
|
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(0, RM, -1, NULL))) => 0;
|
|
}
|
|
|
|
assert(lfsr_mtree_weight(&lfs) == 0);
|
|
assert(lfs.mroot.u.m.weight == 0);
|
|
}
|
|
|
|
lfsr_unmount(&lfs) => 0;
|
|
|
|
|
|
// check things stay sane after remount
|
|
lfsr_mount(&lfs, CFG) => 0;
|
|
|
|
assert(lfsr_mtree_weight(&lfs) == 0);
|
|
assert(lfs.mroot.u.m.weight == 0);
|
|
|
|
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 dstart 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), 1));
|
|
// fetch mdir
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_lookup(&lfs, LFSR_MID(mid, -1), &mdir) => 0;
|
|
// choose a pseudo-random rid
|
|
mdir.mid.rid = TEST_PRNG(&prng) % (mdir.u.m.weight+1);
|
|
// choose to create or delete
|
|
uint8_t op = (lfs_size_t)mdir.mid.rid == mdir.u.m.weight
|
|
? 0
|
|
: TEST_PRNG(&prng) % 2;
|
|
|
|
// force a compaction?
|
|
if (FORCE_COMPACTION) {
|
|
mdir.u.r.rbyd.eoff = -1;
|
|
lfs.mroot.u.r.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.rid, 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.rid, 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.rid, 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), 1);
|
|
mid++) {
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_lookup(&lfs, LFSR_MID(mid, -1), &mdir) => 0;
|
|
|
|
// drop should make sure we never have empty mdirs
|
|
assert(lfsr_mdir_ismroot(&mdir) || mdir.u.m.weight > 0);
|
|
|
|
for (mdir.mid.rid = 0;
|
|
mdir.mid.rid < (lfs_ssize_t)mdir.u.m.weight;
|
|
mdir.mid.rid++) {
|
|
uint8_t buffer[4];
|
|
lfsr_mdir_get(&lfs, &mdir, mdir.mid.rid, 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), 1);
|
|
mid++) {
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_lookup(&lfs, LFSR_MID(mid, -1), &mdir) => 0;
|
|
|
|
// drop should make sure we never have empty mdirs
|
|
assert(lfsr_mdir_ismroot(&mdir) || mdir.u.m.weight > 0);
|
|
|
|
for (mdir.mid.rid = 0;
|
|
mdir.mid.rid < (lfs_ssize_t)mdir.u.m.weight;
|
|
mdir.mid.rid++) {
|
|
uint8_t buffer[4];
|
|
lfsr_mdir_get(&lfs, &mdir, mdir.mid.rid, 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 dstart 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.r.rbyd.eoff = BLOCK_SIZE;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
|
|
// assert mtree has one mdir
|
|
assert(lfsr_mtree_weight(&lfs) == 1);
|
|
// 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, LFSR_MID(0, -1), &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
|
|
lfsr_mdir_t old_mdir = mdir;
|
|
|
|
mdir.u.r.rbyd.eoff = BLOCK_SIZE;
|
|
lfsr_mdir_commit(&lfs, &mdir, NULL, 0) => 0;
|
|
mdir.u.r.rbyd.eoff = BLOCK_SIZE;
|
|
memset(buffer, 'c', SIZE);
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(0, REG, 0, BUF(buffer, SIZE)))) => 0;
|
|
|
|
// assert we relocated
|
|
assert(lfsr_mdir_cmp(old_mdir.u.m.blocks, mdir.u.m.blocks) != 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, LFSR_MID(0, -1), &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
|
|
lfsr_mdir_get(&lfs, &mdir, 0, 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) == 1);
|
|
// assert mroot now has no entries
|
|
assert(lfs.mroot.u.m.weight == 0);
|
|
|
|
// assert we relocated
|
|
assert(lfsr_mdir_cmp(old_mdir.u.m.blocks, mdir.u.m.blocks) != 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, LFSR_MID(0, -1), &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
|
|
lfsr_mdir_get(&lfs, &mdir, 0, 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 dstart 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.r.rbyd.eoff = BLOCK_SIZE;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
|
|
// assert mdirs were unininlined and split
|
|
assert(lfsr_mtree_weight(&lfs) == 2);
|
|
// 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, LFSR_MID(0, -1), &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
|
|
lfsr_mdir_t old_mdir = mdir;
|
|
|
|
mdir.u.r.rbyd.eoff = BLOCK_SIZE;
|
|
lfsr_mdir_commit(&lfs, &mdir, NULL, 0) => 0;
|
|
mdir.u.r.rbyd.eoff = BLOCK_SIZE;
|
|
memset(buffer, 'c', SIZE);
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(0, REG, 0, BUF(buffer, SIZE)))) => 0;
|
|
|
|
// assert we relocated
|
|
assert(lfsr_mdir_cmp(old_mdir.u.m.blocks, mdir.u.m.blocks) != 0);
|
|
|
|
// assert that our entries are still in the mtree
|
|
lfsr_mtree_lookup(&lfs, LFSR_MID(0, -1), &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_REG,
|
|
buffer, SIZE) => SIZE;
|
|
assert(memcmp(buffer, "c", 1) == 0);
|
|
|
|
lfsr_mtree_lookup(&lfs, LFSR_MID(1, -1), &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
lfsr_mdir_get(&lfs, &mdir, 0, 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) == 2);
|
|
// assert mroot now has no entries
|
|
assert(lfs.mroot.u.m.weight == 0);
|
|
|
|
// assert we relocated
|
|
assert(lfsr_mdir_cmp(old_mdir.u.m.blocks, mdir.u.m.blocks) != 0);
|
|
|
|
// assert that our entries are still in the mtree
|
|
lfsr_mtree_lookup(&lfs, LFSR_MID(0, -1), &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_REG,
|
|
buffer, SIZE) => SIZE;
|
|
assert(memcmp(buffer, "c", 1) == 0);
|
|
|
|
lfsr_mtree_lookup(&lfs, LFSR_MID(1, -1), &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
lfsr_mdir_get(&lfs, &mdir, 0, 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 dstart 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.r.rbyd.eoff = BLOCK_SIZE;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
|
|
// assert mdirs were unininlined and split
|
|
assert(lfsr_mtree_weight(&lfs) == 2);
|
|
// 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, LFSR_MID(1, -1), &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
|
|
lfsr_mdir_t old_mdir = mdir;
|
|
|
|
mdir.u.r.rbyd.eoff = BLOCK_SIZE;
|
|
lfsr_mdir_commit(&lfs, &mdir, NULL, 0) => 0;
|
|
mdir.u.r.rbyd.eoff = BLOCK_SIZE;
|
|
memset(buffer, 'c', SIZE);
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(0, REG, 0, BUF(buffer, SIZE)))) => 0;
|
|
|
|
// assert we relocated
|
|
assert(lfsr_mdir_cmp(old_mdir.u.m.blocks, mdir.u.m.blocks) != 0);
|
|
|
|
// assert that our entries are still in the mtree
|
|
lfsr_mtree_lookup(&lfs, LFSR_MID(0, -1), &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_REG,
|
|
buffer, SIZE) => SIZE;
|
|
assert(memcmp(buffer, "a", 1) == 0);
|
|
|
|
lfsr_mtree_lookup(&lfs, LFSR_MID(1, -1), &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
lfsr_mdir_get(&lfs, &mdir, 0, 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) == 2);
|
|
// assert mroot now has no entries
|
|
assert(lfs.mroot.u.m.weight == 0);
|
|
|
|
// assert we relocated
|
|
assert(lfsr_mdir_cmp(old_mdir.u.m.blocks, mdir.u.m.blocks) != 0);
|
|
|
|
// assert that our entries are still in the mtree
|
|
lfsr_mtree_lookup(&lfs, LFSR_MID(0, -1), &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_REG,
|
|
buffer, SIZE) => SIZE;
|
|
assert(memcmp(buffer, "a", 1) == 0);
|
|
|
|
lfsr_mtree_lookup(&lfs, LFSR_MID(1, -1), &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
lfsr_mdir_get(&lfs, &mdir, 0, 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 dstart 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.r.rbyd.eoff = BLOCK_SIZE;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
lfs.mroot.u.r.rbyd.eoff = BLOCK_SIZE;
|
|
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.u.m.blocks, lfs.mroot.u.m.blocks) != 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.u.m.blocks, lfs.mroot.u.m.blocks) != 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 dstart 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.r.rbyd.eoff = BLOCK_SIZE;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
}
|
|
lfs.mroot.u.r.rbyd.eoff = BLOCK_SIZE;
|
|
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.u.m.blocks, lfs.mroot.u.m.blocks) != 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.u.m.blocks, lfs.mroot.u.m.blocks) != 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 dstart 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.r.rbyd.eoff = BLOCK_SIZE;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
lfs.mroot.u.r.rbyd.eoff = BLOCK_SIZE;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
|
|
// assert we relocated
|
|
assert(lfsr_mdir_cmp(old_mroot.u.m.blocks, lfs.mroot.u.m.blocks) != 0);
|
|
|
|
// force mroot to compact twice again, this should relocate the mroot
|
|
old_mroot = lfs.mroot;
|
|
|
|
lfs.mroot.u.r.rbyd.eoff = BLOCK_SIZE;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
lfs.mroot.u.r.rbyd.eoff = BLOCK_SIZE;
|
|
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.u.m.blocks, lfs.mroot.u.m.blocks) != 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.u.m.blocks, lfs.mroot.u.m.blocks) != 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 dstart 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.r.rbyd.eoff = BLOCK_SIZE;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
|
|
// assert mtree has one mdir
|
|
assert(lfsr_mtree_weight(&lfs) == 1);
|
|
// 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.r.rbyd.eoff = BLOCK_SIZE;
|
|
lfsr_mdir_t old_mroot = lfs.mroot;
|
|
|
|
// force mdir to compact twice, this should relocate
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_lookup(&lfs, LFSR_MID(0, -1), &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
|
|
lfsr_mdir_t old_mdir = mdir;
|
|
|
|
mdir.u.r.rbyd.eoff = BLOCK_SIZE;
|
|
lfsr_mdir_commit(&lfs, &mdir, NULL, 0) => 0;
|
|
mdir.u.r.rbyd.eoff = BLOCK_SIZE;
|
|
memset(buffer, 'c', SIZE);
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(0, REG, 0, BUF(buffer, SIZE)))) => 0;
|
|
|
|
// assert we relocated our mdir
|
|
assert(lfsr_mdir_cmp(old_mdir.u.m.blocks, mdir.u.m.blocks) != 0);
|
|
|
|
// assert we relocated our mroot
|
|
assert(lfsr_mdir_cmp(old_mroot.u.m.blocks, lfs.mroot.u.m.blocks) != 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, LFSR_MID(0, -1), &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
|
|
lfsr_mdir_get(&lfs, &mdir, 0, 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) == 1);
|
|
// assert mroot now has no entries
|
|
assert(lfs.mroot.u.m.weight == 0);
|
|
|
|
// assert we relocated our mdir
|
|
assert(lfsr_mdir_cmp(old_mdir.u.m.blocks, mdir.u.m.blocks) != 0);
|
|
|
|
// assert we relocated our mroot
|
|
assert(lfsr_mdir_cmp(old_mroot.u.m.blocks, lfs.mroot.u.m.blocks) != 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, LFSR_MID(0, -1), &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
|
|
lfsr_mdir_get(&lfs, &mdir, 0, 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 dstart 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.r.rbyd.eoff = BLOCK_SIZE;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
|
|
// assert mdir was unininlined correctly
|
|
assert(lfsr_mtree_weight(&lfs) == 1);
|
|
// 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.r.rbyd.eoff = BLOCK_SIZE;
|
|
lfsr_mdir_t old_mroot = lfs.mroot;
|
|
|
|
// now add another large entry to the mdir, forcing a split
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_lookup(&lfs, LFSR_MID(0, -1), &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
|
|
memset(buffer, 'c', SIZE);
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(1, REG, +1, BUF(buffer, SIZE)))) => 0;
|
|
|
|
// force mdir to compact
|
|
mdir.u.r.rbyd.eoff = BLOCK_SIZE;
|
|
lfsr_mdir_commit(&lfs, &mdir, NULL, 0) => 0;
|
|
|
|
// assert mdir was split correctly
|
|
assert(lfsr_mtree_weight(&lfs) == 2);
|
|
// assert mroot still has no entries
|
|
assert(lfs.mroot.u.m.weight == 0);
|
|
|
|
// assert we relocated our mroot
|
|
assert(lfsr_mdir_cmp(old_mroot.u.m.blocks, lfs.mroot.u.m.blocks) != 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, LFSR_MID(0, -1), &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_REG,
|
|
buffer, SIZE) => SIZE;
|
|
assert(memcmp(buffer, "b", 1) == 0);
|
|
|
|
lfsr_mtree_lookup(&lfs, LFSR_MID(1, -1), &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
lfsr_mdir_get(&lfs, &mdir, 0, 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) == 2);
|
|
// assert mroot still has no entries
|
|
assert(lfs.mroot.u.m.weight == 0);
|
|
|
|
// assert we relocated our mroot
|
|
assert(lfsr_mdir_cmp(old_mroot.u.m.blocks, lfs.mroot.u.m.blocks) != 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, LFSR_MID(0, -1), &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_REG,
|
|
buffer, SIZE) => SIZE;
|
|
assert(memcmp(buffer, "b", 1) == 0);
|
|
|
|
lfsr_mtree_lookup(&lfs, LFSR_MID(1, -1), &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
lfsr_mdir_get(&lfs, &mdir, 0, 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 dstart 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.r.rbyd.eoff = BLOCK_SIZE;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
|
|
// assert mdir was unininlined correctly
|
|
assert(lfsr_mtree_weight(&lfs) == 1);
|
|
// 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.r.rbyd.eoff = BLOCK_SIZE;
|
|
lfsr_mdir_t old_mroot = lfs.mroot;
|
|
|
|
// remove the entry, forcing the mdir to be dropped
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_lookup(&lfs, LFSR_MID(0, -1), &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) == 0);
|
|
// assert mroot still has no entries
|
|
assert(lfs.mroot.u.m.weight == 0);
|
|
|
|
// assert we relocated our mroot
|
|
assert(lfsr_mdir_cmp(old_mroot.u.m.blocks, lfs.mroot.u.m.blocks) != 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) == 0);
|
|
// assert mroot still has no entries
|
|
assert(lfs.mroot.u.m.weight == 0);
|
|
|
|
// assert we relocated our mroot
|
|
assert(lfsr_mdir_cmp(old_mroot.u.m.blocks, lfs.mroot.u.m.blocks) != 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 dstart 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.r.rbyd.eoff = BLOCK_SIZE;
|
|
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.r.rbyd.eoff = BLOCK_SIZE;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
|
|
// assert mdir was unininlined correctly
|
|
assert(lfsr_mtree_weight(&lfs) == 1);
|
|
// assert mroot now has no entries
|
|
assert(lfs.mroot.u.m.weight == 0);
|
|
|
|
// assert we relocated our mroot
|
|
assert(lfsr_mdir_cmp(old_mroot.u.m.blocks, lfs.mroot.u.m.blocks) != 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, LFSR_MID(0, -1), &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
|
|
lfsr_mdir_get(&lfs, &mdir, 0, 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) == 1);
|
|
// assert mroot now has no entries
|
|
assert(lfs.mroot.u.m.weight == 0);
|
|
|
|
// assert we relocated our mroot
|
|
assert(lfsr_mdir_cmp(old_mroot.u.m.blocks, lfs.mroot.u.m.blocks) != 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, LFSR_MID(0, -1), &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
|
|
lfsr_mdir_get(&lfs, &mdir, 0, 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 dstart 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.r.rbyd.eoff = BLOCK_SIZE;
|
|
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.r.rbyd.eoff = BLOCK_SIZE;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
|
|
// assert mdirs were unininlined and split
|
|
assert(lfsr_mtree_weight(&lfs) == 2);
|
|
// assert mroot now has no entries
|
|
assert(lfs.mroot.u.m.weight == 0);
|
|
|
|
// assert we relocated our mroot
|
|
assert(lfsr_mdir_cmp(old_mroot.u.m.blocks, lfs.mroot.u.m.blocks) != 0);
|
|
|
|
// assert that our entries are still in the mtree
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_lookup(&lfs, LFSR_MID(0, -1), &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_REG,
|
|
buffer, SIZE) => SIZE;
|
|
assert(memcmp(buffer, "a", 1) == 0);
|
|
|
|
lfsr_mtree_lookup(&lfs, LFSR_MID(1, -1), &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
lfsr_mdir_get(&lfs, &mdir, 0, 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) == 2);
|
|
// assert mroot now has no entries
|
|
assert(lfs.mroot.u.m.weight == 0);
|
|
|
|
// assert we relocated our mroot
|
|
assert(lfsr_mdir_cmp(old_mroot.u.m.blocks, lfs.mroot.u.m.blocks) != 0);
|
|
|
|
// assert that our entries are still in the mtree
|
|
lfsr_mtree_lookup(&lfs, LFSR_MID(0, -1), &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_REG,
|
|
buffer, SIZE) => SIZE;
|
|
assert(memcmp(buffer, "a", 1) == 0);
|
|
|
|
lfsr_mtree_lookup(&lfs, LFSR_MID(1, -1), &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
lfsr_mdir_get(&lfs, &mdir, 0, 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 dstart 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), 1));
|
|
// fetch mdir
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_lookup(&lfs, LFSR_MID(mid, -1), &mdir) => 0;
|
|
// choose a pseudo-random rid
|
|
mdir.mid.rid = TEST_PRNG(&prng) % (mdir.u.m.weight+1);
|
|
// choose to create or delete
|
|
uint8_t op = (lfs_size_t)mdir.mid.rid == mdir.u.m.weight
|
|
? 0
|
|
: TEST_PRNG(&prng) % 3;
|
|
|
|
// force a compaction?
|
|
if (FORCE_COMPACTION) {
|
|
mdir.u.r.rbyd.eoff = -1;
|
|
lfs.mroot.u.r.rbyd.eoff = -1;
|
|
}
|
|
|
|
// create
|
|
if (op == 0) {
|
|
// add to rbyd
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(mdir.mid.rid, 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.rid, 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.rid, 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.rid, 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.rid, 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), 1);
|
|
mid++) {
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_lookup(&lfs, LFSR_MID(mid, -1), &mdir) => 0;
|
|
|
|
// drop should make sure we never have empty mdirs
|
|
assert(lfsr_mdir_ismroot(&mdir) || mdir.u.m.weight > 0);
|
|
|
|
for (mdir.mid.rid = 0;
|
|
mdir.mid.rid < (lfs_ssize_t)mdir.u.m.weight;
|
|
mdir.mid.rid++) {
|
|
uint8_t buffer[4];
|
|
lfsr_mdir_get(&lfs, &mdir, mdir.mid.rid, 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), 1);
|
|
mid++) {
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_lookup(&lfs, LFSR_MID(mid, -1), &mdir) => 0;
|
|
|
|
// drop should make sure we never have empty mdirs
|
|
assert(lfsr_mdir_ismroot(&mdir) || mdir.u.m.weight > 0);
|
|
|
|
for (mdir.mid.rid = 0;
|
|
mdir.mid.rid < (lfs_ssize_t)mdir.u.m.weight;
|
|
mdir.mid.rid++) {
|
|
uint8_t buffer[4];
|
|
lfsr_mdir_get(&lfs, &mdir, mdir.mid.rid, 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 dstart 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_isinlined(&lfs));
|
|
|
|
lfsr_openedmdir_t left_neighbor = {
|
|
.mdir={.mid=LFSR_MID(lfs.mroot.mid.bid, 0), .u=lfs.mroot.u}};
|
|
lfsr_openedmdir_t right_neighbor = {
|
|
.mdir={.mid=LFSR_MID(lfs.mroot.mid.bid, 1), .u=lfs.mroot.u}};
|
|
lfsr_mdir_addopened(&lfs, LFS_TYPE_REG, &left_neighbor);
|
|
lfsr_mdir_addopened(&lfs, LFS_TYPE_REG, &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(!lfsr_mdir_isdropped(&left_neighbor.mdir));
|
|
assert(left_neighbor.mdir.mid.bid == lfs.mroot.mid.bid);
|
|
assert(left_neighbor.mdir.mid.rid == 0);
|
|
assert(memcmp(&left_neighbor.mdir.u, &lfs.mroot.u,
|
|
sizeof(lfs.mroot.u)) == 0);
|
|
assert(!lfsr_mdir_isdropped(&right_neighbor.mdir));
|
|
assert(right_neighbor.mdir.mid.bid == lfs.mroot.mid.bid);
|
|
assert(right_neighbor.mdir.mid.rid == 2);
|
|
assert(memcmp(&right_neighbor.mdir.u, &lfs.mroot.u,
|
|
sizeof(lfs.mroot.u)) == 0);
|
|
|
|
lfsr_mdir_removeopened(&lfs, LFS_TYPE_REG, &left_neighbor);
|
|
lfsr_mdir_removeopened(&lfs, LFS_TYPE_REG, &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 dstart 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_isinlined(&lfs));
|
|
|
|
lfsr_openedmdir_t left_neighbor = {
|
|
.mdir={.mid=LFSR_MID(lfs.mroot.mid.bid, 0), .u=lfs.mroot.u}};
|
|
lfsr_openedmdir_t right_neighbor = {
|
|
.mdir={.mid=LFSR_MID(lfs.mroot.mid.bid, 1), .u=lfs.mroot.u}};
|
|
lfsr_mdir_addopened(&lfs, LFS_TYPE_REG, &left_neighbor);
|
|
lfsr_mdir_addopened(&lfs, LFS_TYPE_REG, &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(lfsr_mdir_isdropped(&left_neighbor.mdir));
|
|
assert(!lfsr_mdir_isdropped(&right_neighbor.mdir));
|
|
assert(right_neighbor.mdir.mid.bid == lfs.mroot.mid.bid);
|
|
assert(right_neighbor.mdir.mid.rid == 0);
|
|
assert(memcmp(&right_neighbor.mdir.u, &lfs.mroot.u,
|
|
sizeof(lfs.mroot.u)) == 0);
|
|
|
|
lfsr_mdir_removeopened(&lfs, LFS_TYPE_REG, &left_neighbor);
|
|
lfsr_mdir_removeopened(&lfs, LFS_TYPE_REG, &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 dstart 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_isinlined(&lfs));
|
|
|
|
lfsr_openedmdir_t left_neighbor = {
|
|
.mdir={.mid=LFSR_MID(lfs.mroot.mid.bid, 0), .u=lfs.mroot.u}};
|
|
lfsr_openedmdir_t right_neighbor = {
|
|
.mdir={.mid=LFSR_MID(lfs.mroot.mid.bid, 1), .u=lfs.mroot.u}};
|
|
lfsr_mdir_addopened(&lfs, LFS_TYPE_REG, &left_neighbor);
|
|
lfsr_mdir_addopened(&lfs, LFS_TYPE_REG, &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(!lfsr_mdir_isdropped(&left_neighbor.mdir));
|
|
assert(left_neighbor.mdir.mid.bid == lfs.mroot.mid.bid);
|
|
assert(left_neighbor.mdir.mid.rid == 0);
|
|
assert(memcmp(&left_neighbor.mdir.u, &lfs.mroot.u,
|
|
sizeof(lfs.mroot.u)) == 0);
|
|
assert(lfsr_mdir_isdropped(&right_neighbor.mdir));
|
|
|
|
lfsr_mdir_removeopened(&lfs, LFS_TYPE_REG, &left_neighbor);
|
|
lfsr_mdir_removeopened(&lfs, LFS_TYPE_REG, &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 dstart 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_isinlined(&lfs));
|
|
|
|
lfsr_openedmdir_t left_neighbor = {
|
|
.mdir={.mid=LFSR_MID(lfs.mroot.mid.bid, 0), .u=lfs.mroot.u}};
|
|
lfsr_openedmdir_t right_neighbor = {
|
|
.mdir={.mid=LFSR_MID(lfs.mroot.mid.bid, 1), .u=lfs.mroot.u}};
|
|
lfsr_mdir_addopened(&lfs, LFS_TYPE_REG, &left_neighbor);
|
|
lfsr_mdir_addopened(&lfs, LFS_TYPE_REG, &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.r.rbyd.eoff = BLOCK_SIZE;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
|
|
// assert mdir was unininlined correctly
|
|
assert(lfsr_mtree_weight(&lfs) == 2);
|
|
// 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, LFSR_MID(0, -1), &mdir) => 0;
|
|
assert(mdir.u.m.weight == 2);
|
|
lfsr_mdir_get(&lfs, &mdir, 1, 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, LFSR_MID(1, -1), &msibling) => 0;
|
|
assert(msibling.u.m.weight == 1);
|
|
|
|
// assert that our neighbors were updated correctly
|
|
assert(!lfsr_mdir_isdropped(&left_neighbor.mdir));
|
|
assert(left_neighbor.mdir.mid.bid == 0);
|
|
assert(left_neighbor.mdir.mid.rid == 0);
|
|
assert(memcmp(&left_neighbor.mdir.u, &mdir.u,
|
|
sizeof(mdir.u)) == 0);
|
|
assert(!lfsr_mdir_isdropped(&right_neighbor.mdir));
|
|
assert(right_neighbor.mdir.mid.bid == 1);
|
|
assert(right_neighbor.mdir.mid.rid == 0);
|
|
assert(memcmp(&right_neighbor.mdir.u, &msibling.u,
|
|
sizeof(msibling.u)) == 0);
|
|
|
|
lfsr_mdir_removeopened(&lfs, LFS_TYPE_REG, &left_neighbor);
|
|
lfsr_mdir_removeopened(&lfs, LFS_TYPE_REG, &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 dstart 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_isinlined(&lfs));
|
|
|
|
lfsr_openedmdir_t left_neighbor = {
|
|
.mdir={.mid=LFSR_MID(lfs.mroot.mid.bid, 0), .u=lfs.mroot.u}};
|
|
lfsr_openedmdir_t right_neighbor = {
|
|
.mdir={.mid=LFSR_MID(lfs.mroot.mid.bid, 1), .u=lfs.mroot.u}};
|
|
lfsr_mdir_addopened(&lfs, LFS_TYPE_REG, &left_neighbor);
|
|
lfsr_mdir_addopened(&lfs, LFS_TYPE_REG, &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.r.rbyd.eoff = BLOCK_SIZE;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
|
|
// assert mdirs were unininlined and split
|
|
assert(lfsr_mtree_weight(&lfs) == 2);
|
|
// 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, LFSR_MID(0, -1), &mdir) => 0;
|
|
assert(mdir.u.m.weight == 2);
|
|
lfsr_mdir_get(&lfs, &mdir, 1, LFSR_TAG_REG,
|
|
buffer, SIZE) => SIZE;
|
|
assert(memcmp(buffer, "c", 1) == 0);
|
|
|
|
lfsr_mdir_t msibling;
|
|
lfsr_mtree_lookup(&lfs, LFSR_MID(1, -1), &msibling) => 0;
|
|
assert(msibling.u.m.weight == 2);
|
|
lfsr_mdir_get(&lfs, &msibling, 0, LFSR_TAG_REG,
|
|
buffer, SIZE) => SIZE;
|
|
assert(memcmp(buffer, "d", 1) == 0);
|
|
|
|
// assert that our neighbors were updated correctly
|
|
assert(!lfsr_mdir_isdropped(&left_neighbor.mdir));
|
|
assert(left_neighbor.mdir.mid.bid == 0);
|
|
assert(left_neighbor.mdir.mid.rid == 0);
|
|
assert(memcmp(&left_neighbor.mdir.u, &mdir.u,
|
|
sizeof(mdir.u)) == 0);
|
|
assert(!lfsr_mdir_isdropped(&right_neighbor.mdir));
|
|
assert(right_neighbor.mdir.mid.bid == 1);
|
|
assert(right_neighbor.mdir.mid.rid == 1);
|
|
assert(memcmp(&right_neighbor.mdir.u, &msibling.u,
|
|
sizeof(msibling.u)) == 0);
|
|
|
|
lfsr_mdir_removeopened(&lfs, LFS_TYPE_REG, &left_neighbor);
|
|
lfsr_mdir_removeopened(&lfs, LFS_TYPE_REG, &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 dstart 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.r.rbyd.eoff = BLOCK_SIZE;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
|
|
// assert mdir was unininlined correctly
|
|
assert(lfsr_mtree_weight(&lfs) == 1);
|
|
// 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, LFSR_MID(0, -1), &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(0, REG, +1, BUF("a", 1)),
|
|
LFSR_ATTR(2, REG, +1, BUF("b", 1)))) => 0;
|
|
// this test only works if these all fit in the mdir
|
|
assert(lfsr_mtree_weight(&lfs) == 1);
|
|
assert(mdir.u.m.weight == 3);
|
|
|
|
lfsr_openedmdir_t left_neighbor = {
|
|
.mdir={.mid=LFSR_MID(mdir.mid.bid, 0), .u=mdir.u}};
|
|
lfsr_openedmdir_t right_neighbor = {
|
|
.mdir={.mid=LFSR_MID(mdir.mid.bid, 2), .u=mdir.u}};
|
|
lfsr_mdir_addopened(&lfs, LFS_TYPE_REG, &left_neighbor);
|
|
lfsr_mdir_addopened(&lfs, LFS_TYPE_REG, &right_neighbor);
|
|
|
|
// now add another large entry to the mdir, forcing a split
|
|
memset(buffer, 'e', SIZE);
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(2, REG, +1, BUF(buffer, SIZE)))) => 0;
|
|
|
|
// force mdir to compact
|
|
mdir.u.r.rbyd.eoff = BLOCK_SIZE;
|
|
lfsr_mdir_commit(&lfs, &mdir, NULL, 0) => 0;
|
|
|
|
// assert mdir was split correctly
|
|
assert(lfsr_mtree_weight(&lfs) == 2);
|
|
// 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, LFSR_MID(0, -1), &mdir) => 0;
|
|
assert(mdir.u.m.weight == 2);
|
|
lfsr_mdir_get(&lfs, &mdir, 1, LFSR_TAG_REG,
|
|
buffer, SIZE) => SIZE;
|
|
assert(memcmp(buffer, "d", 1) == 0);
|
|
|
|
lfsr_mdir_t msibling;
|
|
lfsr_mtree_lookup(&lfs, LFSR_MID(1, -1), &msibling) => 0;
|
|
assert(msibling.u.m.weight == 2);
|
|
lfsr_mdir_get(&lfs, &msibling, 0, LFSR_TAG_REG,
|
|
buffer, SIZE) => SIZE;
|
|
assert(memcmp(buffer, "e", 1) == 0);
|
|
|
|
// assert that our neighbors were updated correctly
|
|
assert(!lfsr_mdir_isdropped(&left_neighbor.mdir));
|
|
assert(left_neighbor.mdir.mid.bid == 0);
|
|
assert(left_neighbor.mdir.mid.rid == 0);
|
|
assert(memcmp(&left_neighbor.mdir.u, &mdir.u,
|
|
sizeof(mdir.u)) == 0);
|
|
assert(!lfsr_mdir_isdropped(&right_neighbor.mdir));
|
|
assert(right_neighbor.mdir.mid.bid == 1);
|
|
assert(right_neighbor.mdir.mid.rid == 1);
|
|
assert(memcmp(&right_neighbor.mdir.u, &msibling.u,
|
|
sizeof(msibling.u)) == 0);
|
|
|
|
lfsr_mdir_removeopened(&lfs, LFS_TYPE_REG, &left_neighbor);
|
|
lfsr_mdir_removeopened(&lfs, LFS_TYPE_REG, &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 dstart 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_isinlined(&lfs));
|
|
|
|
lfsr_openedmdir_t left_neighbor = {
|
|
.mdir={.mid=LFSR_MID(lfs.mroot.mid.bid, 0), .u=lfs.mroot.u}};
|
|
lfsr_openedmdir_t right_neighbor = {
|
|
.mdir={.mid=LFSR_MID(lfs.mroot.mid.bid, 1), .u=lfs.mroot.u}};
|
|
lfsr_mdir_addopened(&lfs, LFS_TYPE_REG, &left_neighbor);
|
|
lfsr_mdir_addopened(&lfs, LFS_TYPE_REG, &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.r.rbyd.eoff = BLOCK_SIZE;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
lfs.mroot.u.r.rbyd.eoff = BLOCK_SIZE;
|
|
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.u.m.blocks, lfs.mroot.u.m.blocks) != 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(!lfsr_mdir_isdropped(&left_neighbor.mdir));
|
|
assert(left_neighbor.mdir.mid.bid == lfs.mroot.mid.bid);
|
|
assert(left_neighbor.mdir.mid.rid == 0);
|
|
assert(memcmp(&left_neighbor.mdir.u, &lfs.mroot.u,
|
|
sizeof(lfs.mroot.u)) == 0);
|
|
assert(!lfsr_mdir_isdropped(&right_neighbor.mdir));
|
|
assert(right_neighbor.mdir.mid.bid == lfs.mroot.mid.bid);
|
|
assert(right_neighbor.mdir.mid.rid == 1);
|
|
assert(memcmp(&right_neighbor.mdir.u, &lfs.mroot.u,
|
|
sizeof(lfs.mroot.u)) == 0);
|
|
|
|
lfsr_mdir_removeopened(&lfs, LFS_TYPE_REG, &left_neighbor);
|
|
lfsr_mdir_removeopened(&lfs, LFS_TYPE_REG, &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 dstart 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.r.rbyd.eoff = BLOCK_SIZE;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
|
|
// assert mdir was unininlined correctly
|
|
assert(lfsr_mtree_weight(&lfs) == 1);
|
|
// 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, LFSR_MID(0, -1), &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(0, REG, +1, BUF("a", 1)),
|
|
LFSR_ATTR(2, REG, +1, BUF("b", 1)))) => 0;
|
|
// this test only works if these all fit in the mdir
|
|
assert(lfsr_mtree_weight(&lfs) == 1);
|
|
assert(mdir.u.m.weight == 3);
|
|
|
|
lfsr_openedmdir_t left_neighbor = {
|
|
.mdir={.mid=LFSR_MID(mdir.mid.bid, 0), .u=mdir.u}};
|
|
lfsr_openedmdir_t right_neighbor = {
|
|
.mdir={.mid=LFSR_MID(mdir.mid.bid, 2), .u=mdir.u}};
|
|
lfsr_mdir_addopened(&lfs, LFS_TYPE_REG, &left_neighbor);
|
|
lfsr_mdir_addopened(&lfs, LFS_TYPE_REG, &right_neighbor);
|
|
|
|
// force mdir to compact twice, this should relocate
|
|
lfsr_mdir_t old_mdir = mdir;
|
|
|
|
mdir.u.r.rbyd.eoff = BLOCK_SIZE;
|
|
lfsr_mdir_commit(&lfs, &mdir, NULL, 0) => 0;
|
|
mdir.u.r.rbyd.eoff = BLOCK_SIZE;
|
|
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.u.m.blocks, mdir.u.m.blocks) != 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, LFSR_MID(0, -1), &mdir) => 0;
|
|
assert(mdir.u.m.weight == 3);
|
|
|
|
lfsr_mdir_get(&lfs, &mdir, 1, LFSR_TAG_REG,
|
|
buffer, SIZE) => SIZE;
|
|
assert(memcmp(buffer, "e", 1) == 0);
|
|
|
|
// assert that our neighbors were updated correctly
|
|
assert(!lfsr_mdir_isdropped(&left_neighbor.mdir));
|
|
assert(left_neighbor.mdir.mid.bid == 0);
|
|
assert(left_neighbor.mdir.mid.rid == 0);
|
|
assert(memcmp(&left_neighbor.mdir.u, &mdir.u, sizeof(mdir.u)) == 0);
|
|
assert(!lfsr_mdir_isdropped(&right_neighbor.mdir));
|
|
assert(right_neighbor.mdir.mid.bid == 0);
|
|
assert(right_neighbor.mdir.mid.rid == 2);
|
|
assert(memcmp(&right_neighbor.mdir.u, &mdir.u, sizeof(mdir.u)) == 0);
|
|
|
|
lfsr_mdir_removeopened(&lfs, LFS_TYPE_REG, &left_neighbor);
|
|
lfsr_mdir_removeopened(&lfs, LFS_TYPE_REG, &right_neighbor);
|
|
lfsr_unmount(&lfs) => 0;
|
|
'''
|
|
|
|
[cases.test_mtree_neighbor_mid_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 dstart 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.r.rbyd.eoff = BLOCK_SIZE;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
|
|
// we should now have 2 mdirs
|
|
assert(lfsr_mtree_weight(&lfs) == 2);
|
|
|
|
// now force one of our siblings to split
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_lookup(&lfs, LFSR_MID(1, -1), &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
memset(buffer, 'c', SIZE);
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(1, REG, +1, BUF(buffer, SIZE)))) => 0;
|
|
|
|
// force mdir to compact
|
|
mdir.u.r.rbyd.eoff = BLOCK_SIZE;
|
|
lfsr_mdir_commit(&lfs, &mdir, NULL, 0) => 0;
|
|
|
|
// we should now have 3 mdirs
|
|
assert(lfsr_mtree_weight(&lfs) == 3);
|
|
|
|
|
|
//// Now test splitting updates mids correctly
|
|
|
|
// setup our neighbors
|
|
lfsr_openedmdir_t left_neighbor;
|
|
lfsr_mtree_lookup(&lfs, LFSR_MID(0, -1), &left_neighbor.mdir) => 0;
|
|
assert(left_neighbor.mdir.u.m.weight == 1);
|
|
left_neighbor.mdir.mid.rid = 0;
|
|
|
|
lfsr_openedmdir_t right_neighbor;
|
|
lfsr_mtree_lookup(&lfs, LFSR_MID(2, -1), &right_neighbor.mdir) => 0;
|
|
assert(right_neighbor.mdir.u.m.weight == 1);
|
|
right_neighbor.mdir.mid.rid = 0;
|
|
|
|
lfsr_mdir_addopened(&lfs, LFS_TYPE_REG, &left_neighbor);
|
|
lfsr_mdir_addopened(&lfs, LFS_TYPE_REG, &right_neighbor);
|
|
|
|
// cause middle mdir to split
|
|
lfsr_mtree_lookup(&lfs, LFSR_MID(1, -1), &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
memset(buffer, 'd', SIZE);
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(1, REG, +1, BUF(buffer, SIZE)))) => 0;
|
|
|
|
// force mdir to compact
|
|
mdir.u.r.rbyd.eoff = BLOCK_SIZE;
|
|
lfsr_mdir_commit(&lfs, &mdir, NULL, 0) => 0;
|
|
|
|
// we should now have 4 mdirs
|
|
assert(lfsr_mtree_weight(&lfs) == 4);
|
|
|
|
// assert that our neighbors were updated correctly
|
|
assert(!lfsr_mdir_isdropped(&left_neighbor.mdir));
|
|
assert(left_neighbor.mdir.mid.bid == 0);
|
|
assert(left_neighbor.mdir.mid.rid == 0);
|
|
lfsr_mtree_lookup(&lfs, LFSR_MID(0, -1), &mdir) => 0;
|
|
assert(memcmp(&left_neighbor.mdir.u, &mdir.u, sizeof(mdir.u)) == 0);
|
|
|
|
assert(right_neighbor.mdir.mid.bid == 3);
|
|
assert(right_neighbor.mdir.mid.rid == 0);
|
|
lfsr_mtree_lookup(&lfs, LFSR_MID(3, -1), &mdir) => 0;
|
|
assert(memcmp(&right_neighbor.mdir.u, &mdir.u, sizeof(mdir.u)) == 0);
|
|
|
|
lfsr_mdir_removeopened(&lfs, LFS_TYPE_REG, &left_neighbor);
|
|
lfsr_mdir_removeopened(&lfs, LFS_TYPE_REG, &right_neighbor);
|
|
lfsr_unmount(&lfs) => 0;
|
|
'''
|
|
|
|
[cases.test_mtree_neighbor_mid_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 dstart 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.r.rbyd.eoff = BLOCK_SIZE;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
|
|
// we should now have 2 mdirs
|
|
assert(lfsr_mtree_weight(&lfs) == 2);
|
|
|
|
// now force one of our siblings to split
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_lookup(&lfs, LFSR_MID(1, -1), &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
memset(buffer, 'c', SIZE);
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(1, REG, +1, BUF(buffer, SIZE)))) => 0;
|
|
|
|
// force mdir to compact
|
|
mdir.u.r.rbyd.eoff = BLOCK_SIZE;
|
|
lfsr_mdir_commit(&lfs, &mdir, NULL, 0) => 0;
|
|
|
|
// we should now have 3 mdirs
|
|
assert(lfsr_mtree_weight(&lfs) == 3);
|
|
|
|
|
|
//// Now test dropping updates mids correctly
|
|
|
|
// setup our neighbors
|
|
lfsr_openedmdir_t left_neighbor;
|
|
lfsr_mtree_lookup(&lfs, LFSR_MID(0, -1), &left_neighbor.mdir) => 0;
|
|
assert(left_neighbor.mdir.u.m.weight == 1);
|
|
left_neighbor.mdir.mid.rid = 0;
|
|
|
|
lfsr_openedmdir_t right_neighbor;
|
|
lfsr_mtree_lookup(&lfs, LFSR_MID(2, -1), &right_neighbor.mdir) => 0;
|
|
assert(right_neighbor.mdir.u.m.weight == 1);
|
|
right_neighbor.mdir.mid.rid = 0;
|
|
|
|
lfsr_mdir_addopened(&lfs, LFS_TYPE_REG, &left_neighbor);
|
|
lfsr_mdir_addopened(&lfs, LFS_TYPE_REG, &right_neighbor);
|
|
|
|
// cause middle mdir to drop
|
|
lfsr_mtree_lookup(&lfs, LFSR_MID(1, -1), &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(0, RM, -1, NULL))) => 0;
|
|
|
|
// we should now have 2 mdirs
|
|
assert(lfsr_mtree_weight(&lfs) == 2);
|
|
|
|
// assert that our neighbors were updated correctly
|
|
assert(!lfsr_mdir_isdropped(&left_neighbor.mdir));
|
|
assert(left_neighbor.mdir.mid.bid == 0);
|
|
assert(left_neighbor.mdir.mid.rid == 0);
|
|
lfsr_mtree_lookup(&lfs, LFSR_MID(0, -1), &mdir) => 0;
|
|
assert(memcmp(&left_neighbor.mdir.u, &mdir.u, sizeof(mdir.u)) == 0);
|
|
|
|
assert(!lfsr_mdir_isdropped(&right_neighbor.mdir));
|
|
assert(right_neighbor.mdir.mid.bid == 1);
|
|
assert(right_neighbor.mdir.mid.rid == 0);
|
|
lfsr_mtree_lookup(&lfs, LFSR_MID(1, -1), &mdir) => 0;
|
|
assert(memcmp(&right_neighbor.mdir.u, &mdir.u, sizeof(mdir.u)) == 0);
|
|
|
|
lfsr_mdir_removeopened(&lfs, LFS_TYPE_REG, &left_neighbor);
|
|
lfsr_mdir_removeopened(&lfs, LFS_TYPE_REG, &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 dstart 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_mtree_traversal_t traversal = LFSR_MTREE_TRAVERSAL(
|
|
VALIDATE ? LFSR_MTREE_TRAVERSAL_VALIDATE : 0);
|
|
|
|
for (lfs_block_t i = 0;; i++) {
|
|
// a bit hacky, but this catches infinite loops
|
|
assert(i < 2*1);
|
|
|
|
lfsr_mid_t mid_;
|
|
lfsr_tag_t tag_;
|
|
lfsr_data_t data_;
|
|
int err = lfsr_mtree_traversal_next(&lfs, &traversal,
|
|
&mid_, &tag_, &data_);
|
|
assert(!err || err == LFS_ERR_NOENT);
|
|
if (err == LFS_ERR_NOENT) {
|
|
break;
|
|
}
|
|
|
|
if (tag_ == LFSR_TAG_BTREE) {
|
|
lfsr_rbyd_t *branch = (lfsr_rbyd_t *)data_.u.b.buffer;
|
|
printf("traversal: %d.%d 0x%x btree 0x%x.%x\n",
|
|
mid_.bid,
|
|
mid_.rid,
|
|
tag_,
|
|
branch->block, branch->trunk);
|
|
|
|
// keep track of seen blocks
|
|
seen[branch->block / 8] |= 1 << (branch->block % 8);
|
|
} else if (tag_ == LFSR_TAG_MDIR) {
|
|
lfsr_mdir_t *mdir = (lfsr_mdir_t*)data_.u.b.buffer;
|
|
printf("traversal: %d.%d 0x%x mdir 0x{%x,%x}\n",
|
|
mid_.bid,
|
|
mid_.rid,
|
|
tag_,
|
|
mdir->u.m.blocks[0], mdir->u.m.blocks[1]);
|
|
|
|
// keep track of seen blocks
|
|
seen[mdir->u.m.blocks[1] / 8] |= 1 << (mdir->u.m.blocks[1] % 8);
|
|
seen[mdir->u.m.blocks[0] / 8] |= 1 << (mdir->u.m.blocks[0] % 8);
|
|
} else {
|
|
// this shouldn't happen
|
|
printf("traversal: %d.%d 0x%x %d\n",
|
|
mid_.bid,
|
|
mid_.rid,
|
|
tag_,
|
|
lfsr_data_size(&data_));
|
|
assert(false);
|
|
}
|
|
}
|
|
|
|
// if traversal worked, we should be able to clobber all other blocks
|
|
uint8_t buffer_[BLOCK_SIZE];
|
|
memset(buffer_, 0xcc, BLOCK_SIZE);
|
|
for (lfs_block_t block = 0; block < BLOCK_COUNT; block++) {
|
|
if (!(seen[block / 8] & (1 << (block % 8)))) {
|
|
CFG->erase(CFG, block) => 0;
|
|
CFG->prog(CFG, block, 0, buffer_, BLOCK_SIZE) => 0;
|
|
}
|
|
}
|
|
free(seen);
|
|
|
|
// and the tree should still work
|
|
|
|
// assert that our entry is still in the mtree
|
|
assert(lfs.mroot.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 dstart 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.r.rbyd.eoff = BLOCK_SIZE;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
|
|
// assert mdir was unininlined correctly
|
|
assert(lfsr_mtree_weight(&lfs) == 1);
|
|
// 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, LFSR_MID(0, -1), &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
|
|
lfsr_mdir_get(&lfs, &mdir, 0, 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_mtree_traversal_t traversal = LFSR_MTREE_TRAVERSAL(
|
|
VALIDATE ? LFSR_MTREE_TRAVERSAL_VALIDATE : 0);
|
|
|
|
for (lfs_block_t i = 0;; i++) {
|
|
// a bit hacky, but this catches infinite loops
|
|
assert(i < 2*2);
|
|
|
|
lfsr_mid_t mid_;
|
|
lfsr_tag_t tag_;
|
|
lfsr_data_t data_;
|
|
int err = lfsr_mtree_traversal_next(&lfs, &traversal,
|
|
&mid_, &tag_, &data_);
|
|
assert(!err || err == LFS_ERR_NOENT);
|
|
if (err == LFS_ERR_NOENT) {
|
|
break;
|
|
}
|
|
|
|
if (tag_ == LFSR_TAG_BTREE) {
|
|
lfsr_rbyd_t *branch = (lfsr_rbyd_t *)data_.u.b.buffer;
|
|
printf("traversal: %d.%d 0x%x btree 0x%x.%x\n",
|
|
mid_.bid,
|
|
mid_.rid,
|
|
tag_,
|
|
branch->block, branch->trunk);
|
|
|
|
// keep track of seen blocks
|
|
seen[branch->block / 8] |= 1 << (branch->block % 8);
|
|
} else if (tag_ == LFSR_TAG_MDIR) {
|
|
lfsr_mdir_t *mdir = (lfsr_mdir_t*)data_.u.b.buffer;
|
|
printf("traversal: %d.%d 0x%x mdir 0x{%x,%x}\n",
|
|
mid_.bid,
|
|
mid_.rid,
|
|
tag_,
|
|
mdir->u.m.blocks[0], mdir->u.m.blocks[1]);
|
|
|
|
// keep track of seen blocks
|
|
seen[mdir->u.m.blocks[1] / 8] |= 1 << (mdir->u.m.blocks[1] % 8);
|
|
seen[mdir->u.m.blocks[0] / 8] |= 1 << (mdir->u.m.blocks[0] % 8);
|
|
} else {
|
|
// this shouldn't happen
|
|
printf("traversal: %d.%d 0x%x %d\n",
|
|
mid_.bid,
|
|
mid_.rid,
|
|
tag_,
|
|
lfsr_data_size(&data_));
|
|
assert(false);
|
|
}
|
|
}
|
|
|
|
// if traversal worked, we should be able to clobber all other blocks
|
|
uint8_t buffer_[BLOCK_SIZE];
|
|
memset(buffer_, 0xcc, BLOCK_SIZE);
|
|
for (lfs_block_t block = 0; block < BLOCK_COUNT; block++) {
|
|
if (!(seen[block / 8] & (1 << (block % 8)))) {
|
|
CFG->erase(CFG, block) => 0;
|
|
CFG->prog(CFG, block, 0, buffer_, BLOCK_SIZE) => 0;
|
|
}
|
|
}
|
|
free(seen);
|
|
|
|
// and the tree should still work
|
|
|
|
// assert mdir was unininlined correctly
|
|
assert(lfsr_mtree_weight(&lfs) == 1);
|
|
// assert mroot now has no entries
|
|
assert(lfs.mroot.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, LFSR_MID(0, -1), &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
|
|
lfsr_mdir_get(&lfs, &mdir, 0, 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 dstart 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.r.rbyd.eoff = BLOCK_SIZE;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
|
|
// assert mdirs were unininlined and split
|
|
assert(lfsr_mtree_weight(&lfs) == 2);
|
|
// 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, LFSR_MID(0, -1), &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_REG,
|
|
buffer, SIZE) => SIZE;
|
|
assert(memcmp(buffer, "a", 1) == 0);
|
|
|
|
lfsr_mdir_t msibling;
|
|
lfsr_mtree_lookup(&lfs, LFSR_MID(1, -1), &msibling) => 0;
|
|
assert(msibling.u.m.weight == 1);
|
|
lfsr_mdir_get(&lfs, &msibling, 0, 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_mtree_traversal_t traversal = LFSR_MTREE_TRAVERSAL(
|
|
VALIDATE ? LFSR_MTREE_TRAVERSAL_VALIDATE : 0);
|
|
|
|
for (lfs_block_t i = 0;; i++) {
|
|
// a bit hacky, but this catches infinite loops
|
|
assert(i < 2*3);
|
|
|
|
lfsr_mid_t mid_;
|
|
lfsr_tag_t tag_;
|
|
lfsr_data_t data_;
|
|
int err = lfsr_mtree_traversal_next(&lfs, &traversal,
|
|
&mid_, &tag_, &data_);
|
|
assert(!err || err == LFS_ERR_NOENT);
|
|
if (err == LFS_ERR_NOENT) {
|
|
break;
|
|
}
|
|
|
|
if (tag_ == LFSR_TAG_BTREE) {
|
|
lfsr_rbyd_t *branch = (lfsr_rbyd_t *)data_.u.b.buffer;
|
|
printf("traversal: %d.%d 0x%x btree 0x%x.%x\n",
|
|
mid_.bid,
|
|
mid_.rid,
|
|
tag_,
|
|
branch->block, branch->trunk);
|
|
|
|
// keep track of seen blocks
|
|
seen[branch->block / 8] |= 1 << (branch->block % 8);
|
|
} else if (tag_ == LFSR_TAG_MDIR) {
|
|
lfsr_mdir_t *mdir = (lfsr_mdir_t*)data_.u.b.buffer;
|
|
printf("traversal: %d.%d 0x%x mdir 0x{%x,%x}\n",
|
|
mid_.bid,
|
|
mid_.rid,
|
|
tag_,
|
|
mdir->u.m.blocks[0], mdir->u.m.blocks[1]);
|
|
|
|
// keep track of seen blocks
|
|
seen[mdir->u.m.blocks[1] / 8] |= 1 << (mdir->u.m.blocks[1] % 8);
|
|
seen[mdir->u.m.blocks[0] / 8] |= 1 << (mdir->u.m.blocks[0] % 8);
|
|
} else {
|
|
// this shouldn't happen
|
|
printf("traversal: %d.%d 0x%x %d\n",
|
|
mid_.bid,
|
|
mid_.rid,
|
|
tag_,
|
|
lfsr_data_size(&data_));
|
|
assert(false);
|
|
}
|
|
}
|
|
|
|
// if traversal worked, we should be able to clobber all other blocks
|
|
uint8_t buffer_[BLOCK_SIZE];
|
|
memset(buffer_, 0xcc, BLOCK_SIZE);
|
|
for (lfs_block_t block = 0; block < BLOCK_COUNT; block++) {
|
|
if (!(seen[block / 8] & (1 << (block % 8)))) {
|
|
CFG->erase(CFG, block) => 0;
|
|
CFG->prog(CFG, block, 0, buffer_, BLOCK_SIZE) => 0;
|
|
}
|
|
}
|
|
free(seen);
|
|
|
|
// and the tree should still work
|
|
|
|
// assert mdirs were unininlined and split
|
|
assert(lfsr_mtree_weight(&lfs) == 2);
|
|
// assert mroot now has no entries
|
|
assert(lfs.mroot.u.m.weight == 0);
|
|
|
|
// assert that our entries are still in the mtree
|
|
lfsr_mtree_lookup(&lfs, LFSR_MID(0, -1), &mdir) => 0;
|
|
assert(mdir.u.m.weight == 1);
|
|
lfsr_mdir_get(&lfs, &mdir, 0, LFSR_TAG_REG,
|
|
buffer, SIZE) => SIZE;
|
|
assert(memcmp(buffer, "a", 1) == 0);
|
|
|
|
lfsr_mtree_lookup(&lfs, LFSR_MID(1, -1), &msibling) => 0;
|
|
assert(msibling.u.m.weight == 1);
|
|
lfsr_mdir_get(&lfs, &msibling, 0, 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 dstart 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.r.rbyd.eoff = BLOCK_SIZE;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
lfs.mroot.u.r.rbyd.eoff = BLOCK_SIZE;
|
|
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.u.m.blocks, lfs.mroot.u.m.blocks) != 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_mtree_traversal_t traversal = LFSR_MTREE_TRAVERSAL(
|
|
VALIDATE ? LFSR_MTREE_TRAVERSAL_VALIDATE : 0);
|
|
|
|
for (lfs_block_t i = 0;; i++) {
|
|
// a bit hacky, but this catches infinite loops
|
|
assert(i < 2*3);
|
|
|
|
lfsr_mid_t mid_;
|
|
lfsr_tag_t tag_;
|
|
lfsr_data_t data_;
|
|
int err = lfsr_mtree_traversal_next(&lfs, &traversal,
|
|
&mid_, &tag_, &data_);
|
|
assert(!err || err == LFS_ERR_NOENT);
|
|
if (err == LFS_ERR_NOENT) {
|
|
break;
|
|
}
|
|
|
|
if (tag_ == LFSR_TAG_BTREE) {
|
|
lfsr_rbyd_t *branch = (lfsr_rbyd_t *)data_.u.b.buffer;
|
|
printf("traversal: %d.%d 0x%x btree 0x%x.%x\n",
|
|
mid_.bid,
|
|
mid_.rid,
|
|
tag_,
|
|
branch->block, branch->trunk);
|
|
|
|
// keep track of seen blocks
|
|
seen[branch->block / 8] |= 1 << (branch->block % 8);
|
|
} else if (tag_ == LFSR_TAG_MDIR) {
|
|
lfsr_mdir_t *mdir = (lfsr_mdir_t*)data_.u.b.buffer;
|
|
printf("traversal: %d.%d 0x%x mdir 0x{%x,%x}\n",
|
|
mid_.bid,
|
|
mid_.rid,
|
|
tag_,
|
|
mdir->u.m.blocks[0], mdir->u.m.blocks[1]);
|
|
|
|
// keep track of seen blocks
|
|
seen[mdir->u.m.blocks[1] / 8] |= 1 << (mdir->u.m.blocks[1] % 8);
|
|
seen[mdir->u.m.blocks[0] / 8] |= 1 << (mdir->u.m.blocks[0] % 8);
|
|
} else {
|
|
// this shouldn't happen
|
|
printf("traversal: %d.%d 0x%x %d\n",
|
|
mid_.bid,
|
|
mid_.rid,
|
|
tag_,
|
|
lfsr_data_size(&data_));
|
|
assert(false);
|
|
}
|
|
}
|
|
|
|
// if traversal worked, we should be able to clobber all other blocks
|
|
uint8_t buffer_[BLOCK_SIZE];
|
|
memset(buffer_, 0xcc, BLOCK_SIZE);
|
|
for (lfs_block_t block = 0; block < BLOCK_COUNT; block++) {
|
|
if (!(seen[block / 8] & (1 << (block % 8)))) {
|
|
CFG->erase(CFG, block) => 0;
|
|
CFG->prog(CFG, block, 0, buffer_, BLOCK_SIZE) => 0;
|
|
}
|
|
}
|
|
free(seen);
|
|
|
|
// and the tree should still work
|
|
|
|
// assert we relocated
|
|
assert(lfsr_mdir_cmp(old_mroot.u.m.blocks, lfs.mroot.u.m.blocks) != 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 dstart 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, LFSR_MID(lfs_smax32(lfsr_mtree_weight(&lfs)-1, 0), -1),
|
|
&mdir) => 0;
|
|
|
|
mdir.mid.rid = 0;
|
|
for (lfs_size_t i = 0; i < N; i++) {
|
|
// force a compaction?
|
|
if (FORCE_COMPACTION) {
|
|
mdir.u.r.rbyd.eoff = -1;
|
|
lfs.mroot.u.r.rbyd.eoff = -1;
|
|
}
|
|
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(mdir.mid.rid, REG, +1,
|
|
BUF(&alphas[i % 26], 1)))) => 0;
|
|
|
|
uint8_t buffer[4];
|
|
lfsr_mdir_get(&lfs, &mdir, mdir.mid.rid, LFSR_TAG_REG,
|
|
buffer, 4) => 1;
|
|
assert(memcmp(buffer, &alphas[i % 26], 1) == 0);
|
|
|
|
mdir.mid.rid += 1;
|
|
}
|
|
|
|
// try looking up each entry
|
|
lfs_size_t i = 0;
|
|
for (lfs_ssize_t mid = 0;
|
|
mid < lfs_smax32(lfsr_mtree_weight(&lfs), 1);
|
|
mid++) {
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_lookup(&lfs, LFSR_MID(mid, -1), &mdir) => 0;
|
|
for (mdir.mid.rid = 0;
|
|
mdir.mid.rid < (lfs_ssize_t)mdir.u.m.weight;
|
|
mdir.mid.rid++) {
|
|
uint8_t buffer[4];
|
|
lfsr_mdir_get(&lfs, &mdir, mdir.mid.rid, 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_mtree_traversal_t traversal = LFSR_MTREE_TRAVERSAL(
|
|
VALIDATE ? LFSR_MTREE_TRAVERSAL_VALIDATE : 0);
|
|
|
|
for (lfs_block_t i = 0;; i++) {
|
|
// a bit hacky, but this catches infinite loops
|
|
assert(i < 2*(1+N));
|
|
|
|
lfsr_mid_t mid_;
|
|
lfsr_tag_t tag_;
|
|
lfsr_data_t data_;
|
|
int err = lfsr_mtree_traversal_next(&lfs, &traversal,
|
|
&mid_, &tag_, &data_);
|
|
assert(!err || err == LFS_ERR_NOENT);
|
|
if (err == LFS_ERR_NOENT) {
|
|
break;
|
|
}
|
|
|
|
if (tag_ == LFSR_TAG_BTREE) {
|
|
lfsr_rbyd_t *branch = (lfsr_rbyd_t *)data_.u.b.buffer;
|
|
printf("traversal: %d.%d 0x%x btree 0x%x.%x\n",
|
|
mid_.bid,
|
|
mid_.rid,
|
|
tag_,
|
|
branch->block, branch->trunk);
|
|
|
|
// keep track of seen blocks
|
|
seen[branch->block / 8] |= 1 << (branch->block % 8);
|
|
} else if (tag_ == LFSR_TAG_MDIR) {
|
|
lfsr_mdir_t *mdir = (lfsr_mdir_t*)data_.u.b.buffer;
|
|
printf("traversal: %d.%d 0x%x mdir 0x{%x,%x}\n",
|
|
mid_.bid,
|
|
mid_.rid,
|
|
tag_,
|
|
mdir->u.m.blocks[0], mdir->u.m.blocks[1]);
|
|
|
|
// keep track of seen blocks
|
|
seen[mdir->u.m.blocks[1] / 8] |= 1 << (mdir->u.m.blocks[1] % 8);
|
|
seen[mdir->u.m.blocks[0] / 8] |= 1 << (mdir->u.m.blocks[0] % 8);
|
|
} else {
|
|
// this shouldn't happen
|
|
printf("traversal: %d.%d 0x%x %d\n",
|
|
mid_.bid,
|
|
mid_.rid,
|
|
tag_,
|
|
lfsr_data_size(&data_));
|
|
assert(false);
|
|
}
|
|
}
|
|
|
|
// if traversal worked, we should be able to clobber all other blocks
|
|
uint8_t buffer_[BLOCK_SIZE];
|
|
memset(buffer_, 0xcc, BLOCK_SIZE);
|
|
for (lfs_block_t block = 0; block < BLOCK_COUNT; block++) {
|
|
if (!(seen[block / 8] & (1 << (block % 8)))) {
|
|
CFG->erase(CFG, block) => 0;
|
|
CFG->prog(CFG, block, 0, buffer_, BLOCK_SIZE) => 0;
|
|
}
|
|
}
|
|
free(seen);
|
|
|
|
// and the tree should still work
|
|
|
|
// try looking up each entry
|
|
i = 0;
|
|
for (lfs_ssize_t mid = 0;
|
|
mid < lfs_smax32(lfsr_mtree_weight(&lfs), 1);
|
|
mid++) {
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_lookup(&lfs, LFSR_MID(mid, -1), &mdir) => 0;
|
|
for (mdir.mid.rid = 0;
|
|
mdir.mid.rid < (lfs_ssize_t)mdir.u.m.weight;
|
|
mdir.mid.rid++) {
|
|
uint8_t buffer[4];
|
|
lfsr_mdir_get(&lfs, &mdir, mdir.mid.rid, 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 dstart 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), 1));
|
|
// fetch mdir
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_lookup(&lfs, LFSR_MID(mid, -1), &mdir) => 0;
|
|
// choose a pseudo-random rid
|
|
mdir.mid.rid = TEST_PRNG(&prng) % (mdir.u.m.weight+1);
|
|
|
|
// force a compaction?
|
|
if (FORCE_COMPACTION) {
|
|
mdir.u.r.rbyd.eoff = -1;
|
|
lfs.mroot.u.r.rbyd.eoff = -1;
|
|
}
|
|
|
|
// add to rbyd, potentially splitting the mdir
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(mdir.mid.rid, 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.rid, 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), 1);
|
|
mid++) {
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_lookup(&lfs, LFSR_MID(mid, -1), &mdir) => 0;
|
|
for (mdir.mid.rid = 0;
|
|
mdir.mid.rid < (lfs_ssize_t)mdir.u.m.weight;
|
|
mdir.mid.rid++) {
|
|
uint8_t buffer[4];
|
|
lfsr_mdir_get(&lfs, &mdir, mdir.mid.rid, 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_mtree_traversal_t traversal = LFSR_MTREE_TRAVERSAL(
|
|
VALIDATE ? LFSR_MTREE_TRAVERSAL_VALIDATE : 0);
|
|
|
|
for (lfs_block_t i = 0;; i++) {
|
|
// a bit hacky, but this catches infinite loops
|
|
assert(i < 2*(1+N));
|
|
|
|
lfsr_mid_t mid_;
|
|
lfsr_tag_t tag_;
|
|
lfsr_data_t data_;
|
|
int err = lfsr_mtree_traversal_next(&lfs, &traversal,
|
|
&mid_, &tag_, &data_);
|
|
assert(!err || err == LFS_ERR_NOENT);
|
|
if (err == LFS_ERR_NOENT) {
|
|
break;
|
|
}
|
|
|
|
if (tag_ == LFSR_TAG_BTREE) {
|
|
lfsr_rbyd_t *branch = (lfsr_rbyd_t *)data_.u.b.buffer;
|
|
printf("traversal: %d.%d 0x%x btree 0x%x.%x\n",
|
|
mid_.bid,
|
|
mid_.rid,
|
|
tag_,
|
|
branch->block, branch->trunk);
|
|
|
|
// keep track of seen blocks
|
|
seen[branch->block / 8] |= 1 << (branch->block % 8);
|
|
} else if (tag_ == LFSR_TAG_MDIR) {
|
|
lfsr_mdir_t *mdir = (lfsr_mdir_t*)data_.u.b.buffer;
|
|
printf("traversal: %d.%d 0x%x mdir 0x{%x,%x}\n",
|
|
mid_.bid,
|
|
mid_.rid,
|
|
tag_,
|
|
mdir->u.m.blocks[0], mdir->u.m.blocks[1]);
|
|
|
|
// keep track of seen blocks
|
|
seen[mdir->u.m.blocks[1] / 8] |= 1 << (mdir->u.m.blocks[1] % 8);
|
|
seen[mdir->u.m.blocks[0] / 8] |= 1 << (mdir->u.m.blocks[0] % 8);
|
|
} else {
|
|
// this shouldn't happen
|
|
printf("traversal: %d.%d 0x%x %d\n",
|
|
mid_.bid,
|
|
mid_.rid,
|
|
tag_,
|
|
lfsr_data_size(&data_));
|
|
assert(false);
|
|
}
|
|
}
|
|
|
|
// if traversal worked, we should be able to clobber all other blocks
|
|
uint8_t buffer_[BLOCK_SIZE];
|
|
memset(buffer_, 0xcc, BLOCK_SIZE);
|
|
for (lfs_block_t block = 0; block < BLOCK_COUNT; block++) {
|
|
if (!(seen[block / 8] & (1 << (block % 8)))) {
|
|
CFG->erase(CFG, block) => 0;
|
|
CFG->prog(CFG, block, 0, buffer_, BLOCK_SIZE) => 0;
|
|
}
|
|
}
|
|
free(seen);
|
|
|
|
// and the tree should still work
|
|
|
|
// try looking up each entry
|
|
count_ = 0;
|
|
|
|
for (lfs_ssize_t mid = 0;
|
|
mid < lfs_smax32(lfsr_mtree_weight(&lfs), 1);
|
|
mid++) {
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_lookup(&lfs, LFSR_MID(mid, -1), &mdir) => 0;
|
|
for (mdir.mid.rid = 0;
|
|
mdir.mid.rid < (lfs_ssize_t)mdir.u.m.weight;
|
|
mdir.mid.rid++) {
|
|
uint8_t buffer[4];
|
|
lfsr_mdir_get(&lfs, &mdir, mdir.mid.rid, 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 buffer[LFSR_MDIR_DSIZE];
|
|
lfs_ssize_t d = lfsr_mdir_todisk(&lfs, LFSR_MDIR_MROOTANCHOR, buffer);
|
|
assert(d >= 0);
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(-1, MROOT, 0, BUF(buffer, d)))) => 0;
|
|
|
|
// technically, cycle detection only needs to work when we're validating
|
|
lfsr_mtree_traversal_t traversal = LFSR_MTREE_TRAVERSAL(
|
|
LFSR_MTREE_TRAVERSAL_VALIDATE);
|
|
|
|
for (lfs_block_t i = 0;; i++) {
|
|
// assert that we detect the cycle in a reasonable number of iterations
|
|
assert(i < 1024);
|
|
|
|
lfsr_mid_t mid_;
|
|
lfsr_tag_t tag_;
|
|
lfsr_data_t data_;
|
|
int err = lfsr_mtree_traversal_next(&lfs, &traversal,
|
|
&mid_, &tag_, &data_);
|
|
assert(!err || err == LFS_ERR_CORRUPT);
|
|
if (err == LFS_ERR_CORRUPT) {
|
|
break;
|
|
}
|
|
|
|
if (tag_ == LFSR_TAG_BTREE) {
|
|
lfsr_rbyd_t *branch = (lfsr_rbyd_t *)data_.u.b.buffer;
|
|
printf("traversal: %d.%d 0x%x btree 0x%x.%x\n",
|
|
mid_.bid,
|
|
mid_.rid,
|
|
tag_,
|
|
branch->block, branch->trunk);
|
|
} else if (tag_ == LFSR_TAG_MDIR) {
|
|
lfsr_mdir_t *mdir = (lfsr_mdir_t*)data_.u.b.buffer;
|
|
printf("traversal: %d.%d 0x%x mdir 0x{%x,%x}\n",
|
|
mid_.bid,
|
|
mid_.rid,
|
|
tag_,
|
|
mdir->u.m.blocks[0], mdir->u.m.blocks[1]);
|
|
} else {
|
|
// this shouldn't happen
|
|
printf("traversal: %d.%d 0x%x %d\n",
|
|
mid_.bid,
|
|
mid_.rid,
|
|
tag_,
|
|
lfsr_data_size(&data_));
|
|
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.r.rbyd.eoff = BLOCK_SIZE;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
lfs.mroot.u.r.rbyd.eoff = BLOCK_SIZE;
|
|
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.u.m.blocks, lfs.mroot.u.m.blocks) != 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.r.rbyd.eoff = BLOCK_SIZE;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
}
|
|
lfs.mroot.u.r.rbyd.eoff = BLOCK_SIZE;
|
|
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.u.m.blocks, lfs.mroot.u.m.blocks) != 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);
|
|
'''
|
|
|