cd22c0d68b
This turned into a sort of system-wide refactor based on learned
knowledge of what we can do with lfsr_attr_t.
The big changes:
- Reverted LFSR_ATTR to mainly take lfsr_data_t again, keeping
lfsr_data_t as the default data representation in the codebase.
Now that we know
LFSR_ATTR_CAT_ still provides concatenation mechanics, and LFSR_ATTR_
provides a way to edit in-flight lfsr_attr_ts.
- Dropped lfsr_cat_t, replaced with explicit const void* + uint16_t,
tried to limit to low-level operations and prefer passing aroud
lfsr_attr_t and lfsr_data_t at a high-level.
Note this cat + cat_count pair is quite similar to the common attrs +
attr_count and buffer + size arguments.
- Adopted lfsr_attr_t more in mid-level functions, lfsr_rbyd_appendattr,
lfsr_rbyd_appendcompactattr, lfsr_file_carve, etc. This is a bit more
ergonomical, allows for use of LFSR_ATTR* macros, and in theory might
even save a bit of stack.
Unfortunately this seems to have resulted in a net hit to code cost,
though I still think it's worth it for the internal ergonomics:
code stack
before: 33652 2624
after: 33780 (+0.4%) 2640 (+0.4%)
Investigating further suggests this may just be the result of compiler
noise and changes to argument placement. lfsr_attr_t does touch a lot of
code...
It's interesting to note the adoption of lfsr_attr_t in
lfsr_rbyd_appendattr* and friends prevents their transformation into
.isra functions, though this doesn't seem to impact code cost too much:
function (5 added, 5 removed) osize nsize dsize
lfsr_cat_size - 48 +48 (+100.0%)
lfsr_file_carve - 1600 +1600 (+100.0%)
lfsr_rbyd_appendattr - 2120 +2120 (+100.0%)
lfsr_rbyd_appendattr_ - 244 +244 (+100.0%)
lfsr_rbyd_appendcompactattr - 68 +68 (+100.0%)
lfsr_rbyd_appendcompactrbyd 144 152 +8 (+5.6%)
lfsr_file_truncate 298 314 +16 (+5.4%)
lfsr_mdir_commit__ 1056 1112 +56 (+5.3%)
lfsr_mdir_compact__ 502 526 +24 (+4.8%)
lfsr_rbyd_appendattrs 132 138 +6 (+4.5%)
lfsr_file_fruncate 386 402 +16 (+4.1%)
lfsr_data_frombtree 84 86 +2 (+2.4%)
lfsr_rbyd_appendcksum 512 520 +8 (+1.6%)
lfsr_file_opencfg 572 580 +8 (+1.4%)
lfsr_rename 608 616 +8 (+1.3%)
lfsr_mkdir 500 504 +4 (+0.8%)
lfsr_bd_prog 278 280 +2 (+0.7%)
lfsr_mdir_commit 2364 2360 -4 (-0.2%)
lfsr_bshrub_commit 716 712 -4 (-0.6%)
lfsr_file_sync 526 514 -12 (-2.3%)
lfsr_file_flush_ 1868 1820 -48 (-2.6%)
lfsr_remove 456 436 -20 (-4.4%)
lfsr_fs_fixgrm 168 160 -8 (-4.8%)
lfsr_cat_size.isra.0 42 - -42 (-100.0%)
lfsr_file_carve.isra.0 1596 - -1596 (-100.0%)
lfsr_rbyd_appendattr.isra.0 2088 - -2088 (-100.0%)
lfsr_rbyd_appendattr_.isra.0 232 - -232 (-100.0%)
lfsr_rbyd_appendcompactattr.isra.0 56 - -56 (-100.0%)
TOTAL 33652 33780 +128 (+0.4%)
4126 lines
147 KiB
TOML
4126 lines
147 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'
|
|
|
|
# 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 = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, CFG) => 0;
|
|
lfsr_mount(&lfs, CFG) => 0;
|
|
lfs_alloc_ckpoint(&lfs);
|
|
|
|
for (lfs_size_t i = 0; i < N; i++) {
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(
|
|
LFSR_TAG_UATTR(i), 0,
|
|
LFSR_DATA_BUF(&(uint8_t){'a'+(i % 26)}, 1)))) => 0;
|
|
}
|
|
|
|
for (lfs_size_t i = 0; i < N; i++) {
|
|
lfsr_data_t data;
|
|
uint8_t buffer[1];
|
|
lfsr_mdir_lookup(&lfs, &lfs.mroot, LFSR_TAG_UATTR(i), &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, 1) => 1;
|
|
assert(memcmp(buffer, &(uint8_t){'a'+(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++) {
|
|
lfsr_data_t data;
|
|
uint8_t buffer[1];
|
|
lfsr_mdir_lookup(&lfs, &lfs.mroot, LFSR_TAG_UATTR(i), &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, 1) => 1;
|
|
assert(memcmp(buffer, &(uint8_t){'a'+(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 = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, CFG) => 0;
|
|
lfsr_mount(&lfs, CFG) => 0;
|
|
lfs_alloc_ckpoint(&lfs);
|
|
|
|
for (lfs_size_t i = 0; i < N; i++) {
|
|
// force mroot to compact
|
|
lfs.mroot.rbyd.eoff = -1;
|
|
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(
|
|
LFSR_TAG_UATTR(i), 0,
|
|
LFSR_DATA_BUF(&(uint8_t){'a'+(i % 26)}, 1)))) => 0;
|
|
}
|
|
|
|
for (lfs_size_t i = 0; i < N; i++) {
|
|
lfsr_data_t data;
|
|
uint8_t buffer[1];
|
|
lfsr_mdir_lookup(&lfs, &lfs.mroot, LFSR_TAG_UATTR(i), &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, 1) => 1;
|
|
assert(memcmp(buffer, &(uint8_t){'a'+(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++) {
|
|
lfsr_data_t data;
|
|
uint8_t buffer[1];
|
|
lfsr_mdir_lookup(&lfs, &lfs.mroot, LFSR_TAG_UATTR(i), &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, 1) => 1;
|
|
assert(memcmp(buffer, &(uint8_t){'a'+(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 = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, CFG) => 0;
|
|
lfsr_mount(&lfs, CFG) => 0;
|
|
lfs_alloc_ckpoint(&lfs);
|
|
|
|
for (lfs_size_t i = 0; i < N; i++) {
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(
|
|
LFSR_TAG_UATTR(1), 0,
|
|
LFSR_DATA_BUF(&(uint8_t){'a'+(i % 26)}, 1)))) => 0;
|
|
lfsr_data_t data;
|
|
|
|
uint8_t buffer[4];
|
|
lfsr_mdir_lookup(&lfs, &lfs.mroot, LFSR_TAG_UATTR(1), &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, 4) => 1;
|
|
assert(memcmp(buffer, &(uint8_t){'a'+(i % 26)}, 1) == 0);
|
|
}
|
|
lfsr_data_t data;
|
|
|
|
uint8_t buffer[4];
|
|
lfsr_mdir_lookup(&lfs, &lfs.mroot, LFSR_TAG_UATTR(1), &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, 4) => 1;
|
|
assert(memcmp(buffer, &(uint8_t){'a'+((N-1) % 26)}, 1) == 0);
|
|
|
|
lfsr_unmount(&lfs) => 0;
|
|
|
|
|
|
// check things stay sane after remount
|
|
lfsr_mount(&lfs, CFG) => 0;
|
|
|
|
lfsr_mdir_lookup(&lfs, &lfs.mroot, LFSR_TAG_UATTR(1), &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, 4) => 1;
|
|
assert(memcmp(buffer, &(uint8_t){'a'+((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_ckpoint(&lfs);
|
|
lfsr_data_t data;
|
|
|
|
// create a 2 large attrs that needs to be uninlined
|
|
uint8_t buffer[SIZE];
|
|
memset(buffer, 'a', SIZE);
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_UATTR(1), 0, LFSR_DATA_BUF(buffer, SIZE)))) => 0;
|
|
|
|
memset(buffer, 'b', SIZE);
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0, &mdir) => 0;
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_UATTR(1), 0, LFSR_DATA_BUF(buffer, SIZE)))) => 0;
|
|
|
|
// force mroot to compact
|
|
lfs.mroot.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
|
|
// assert mdirs were unininlined
|
|
assert(lfsr_mtree_weight(&lfs.mtree) == 1*lfsr_mleafweight(&lfs));
|
|
// assert mroot now has no entries
|
|
assert(lfs.mroot.rbyd.weight == 0);
|
|
|
|
// assert that our attrs are still in the mroot/mtree
|
|
lfsr_mdir_lookup(&lfs, &lfs.mroot, LFSR_TAG_UATTR(1), &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[0] == 'a');
|
|
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0, &mdir) => 0;
|
|
assert(mdir.rbyd.weight == 1);
|
|
lfsr_mdir_lookup(&lfs, &mdir, LFSR_TAG_UATTR(1), &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[0] == 'b');
|
|
|
|
// check things stay sane after remount
|
|
lfsr_unmount(&lfs) => 0;
|
|
lfsr_mount(&lfs, CFG) => 0;
|
|
|
|
// assert mdirs were unininlined
|
|
assert(lfsr_mtree_weight(&lfs.mtree) == 1*lfsr_mleafweight(&lfs));
|
|
// assert mroot now has no entries
|
|
assert(lfs.mroot.rbyd.weight == 0);
|
|
|
|
// assert that our attrs are still in the mroot/mtree
|
|
lfsr_mdir_lookup(&lfs, &lfs.mroot, LFSR_TAG_UATTR(1), &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[0] == 'a');
|
|
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0, &mdir) => 0;
|
|
assert(mdir.rbyd.weight == 1);
|
|
lfsr_mdir_lookup(&lfs, &mdir, LFSR_TAG_UATTR(1), &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[0] == 'b');
|
|
|
|
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_ckpoint(&lfs);
|
|
lfsr_data_t data;
|
|
|
|
// create 2 large entries that needs to be uninlined and split
|
|
uint8_t buffer[SIZE];
|
|
buffer[0] = '\0';
|
|
memset(buffer+1, 'a', SIZE-1);
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL, NULL) => LFS_ERR_NOENT;
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_REG, +1, LFSR_DATA_BUF(buffer, SIZE)))) => 0;
|
|
assert(mdir.rbyd.weight == 2);
|
|
|
|
memset(buffer+1, 'b', SIZE-1);
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL, NULL) => LFS_ERR_NOENT;
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_REG, +1, LFSR_DATA_BUF(buffer, SIZE)))) => 0;
|
|
assert(mdir.rbyd.weight == 3);
|
|
|
|
// force mroot to compact
|
|
lfs.mroot.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
|
|
// assert mdirs were unininlined and split
|
|
assert(lfsr_mtree_weight(&lfs.mtree) == 2*lfsr_mleafweight(&lfs));
|
|
// assert mroot now has no entries
|
|
assert(lfs.mroot.rbyd.weight == 0);
|
|
|
|
// assert that our entries are still in the mtree
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0*lfsr_mleafweight(&lfs)+1, &mdir) => 0;
|
|
assert(mdir.rbyd.weight == 2);
|
|
lfsr_mdir_lookup(&lfs, &mdir, LFSR_TAG_REG, &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'a');
|
|
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 1*lfsr_mleafweight(&lfs)+0, &mdir) => 0;
|
|
assert(mdir.rbyd.weight == 1);
|
|
lfsr_mdir_lookup(&lfs, &mdir, LFSR_TAG_REG, &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'b');
|
|
|
|
// check things stay sane after remount
|
|
lfsr_unmount(&lfs) => 0;
|
|
lfsr_mount(&lfs, CFG) => 0;
|
|
|
|
// assert mdirs were unininlined and split
|
|
assert(lfsr_mtree_weight(&lfs.mtree) == 2*lfsr_mleafweight(&lfs));
|
|
// assert mroot now has no entries
|
|
assert(lfs.mroot.rbyd.weight == 0);
|
|
|
|
// assert that our entries are still in the mtree
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0*lfsr_mleafweight(&lfs)+1, &mdir) => 0;
|
|
assert(mdir.rbyd.weight == 2);
|
|
lfsr_mdir_lookup(&lfs, &mdir, LFSR_TAG_REG, &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'a');
|
|
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 1*lfsr_mleafweight(&lfs)+0, &mdir) => 0;
|
|
assert(mdir.rbyd.weight == 1);
|
|
lfsr_mdir_lookup(&lfs, &mdir, LFSR_TAG_REG, &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'b');
|
|
|
|
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_ckpoint(&lfs);
|
|
lfsr_data_t data;
|
|
|
|
// create 2 large entries that needs to be uninlined and split
|
|
uint8_t buffer[SIZE];
|
|
buffer[0] = '\0';
|
|
memset(buffer+1, 'a', SIZE-1);
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL, NULL) => LFS_ERR_NOENT;
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_REG, +1, LFSR_DATA_BUF(buffer, SIZE)))) => 0;
|
|
assert(mdir.rbyd.weight == 2);
|
|
|
|
memset(buffer+1, 'b', SIZE-1);
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL, NULL) => LFS_ERR_NOENT;
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_REG, +1, LFSR_DATA_BUF(buffer, SIZE)))) => 0;
|
|
assert(mdir.rbyd.weight == 3);
|
|
|
|
// force mroot to compact
|
|
lfs.mroot.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
|
|
// assert mdirs were unininlined and split
|
|
assert(lfsr_mtree_weight(&lfs.mtree) == 2*lfsr_mleafweight(&lfs));
|
|
// assert mroot now has no entries
|
|
assert(lfs.mroot.rbyd.weight == 0);
|
|
|
|
// now add another large entry to an mdir, forcing a split
|
|
memset(buffer+1, 'c', SIZE-1);
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL, NULL) => LFS_ERR_NOENT;
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_REG, +1, LFSR_DATA_BUF(buffer, SIZE)))) => 0;
|
|
assert(mdir.rbyd.weight == 2);
|
|
|
|
// force mdir to compact
|
|
mdir.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &mdir, NULL, 0) => 0;
|
|
|
|
// assert mdir was split correctly
|
|
assert(lfsr_mtree_weight(&lfs.mtree) == 3*lfsr_mleafweight(&lfs));
|
|
// assert mroot still has no entries
|
|
assert(lfs.mroot.rbyd.weight == 0);
|
|
|
|
// assert that our entries are still in the mtree
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0*lfsr_mleafweight(&lfs)+1, &mdir) => 0;
|
|
assert(mdir.rbyd.weight == 2);
|
|
lfsr_mdir_lookup(&lfs, &mdir, LFSR_TAG_REG, &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'a');
|
|
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 1*lfsr_mleafweight(&lfs)+0, &mdir) => 0;
|
|
assert(mdir.rbyd.weight == 1);
|
|
lfsr_mdir_lookup(&lfs, &mdir, LFSR_TAG_REG, &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'b');
|
|
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 2*lfsr_mleafweight(&lfs)+0, &mdir) => 0;
|
|
assert(mdir.rbyd.weight == 1);
|
|
lfsr_mdir_lookup(&lfs, &mdir, LFSR_TAG_REG, &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'c');
|
|
|
|
// check things stay sane after remount
|
|
lfsr_unmount(&lfs) => 0;
|
|
lfsr_mount(&lfs, CFG) => 0;
|
|
|
|
// assert mdir was split correctly
|
|
assert(lfsr_mtree_weight(&lfs.mtree) == 3*lfsr_mleafweight(&lfs));
|
|
// assert mroot still has no entries
|
|
assert(lfs.mroot.rbyd.weight == 0);
|
|
|
|
// assert that our entries are still in the mtree
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0*lfsr_mleafweight(&lfs)+1, &mdir) => 0;
|
|
assert(mdir.rbyd.weight == 2);
|
|
lfsr_mdir_lookup(&lfs, &mdir, LFSR_TAG_REG, &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'a');
|
|
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 1*lfsr_mleafweight(&lfs)+0, &mdir) => 0;
|
|
assert(mdir.rbyd.weight == 1);
|
|
lfsr_mdir_lookup(&lfs, &mdir, LFSR_TAG_REG, &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'b');
|
|
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 2*lfsr_mleafweight(&lfs)+0, &mdir) => 0;
|
|
assert(mdir.rbyd.weight == 1);
|
|
lfsr_mdir_lookup(&lfs, &mdir, LFSR_TAG_REG, &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'c');
|
|
|
|
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 = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, CFG) => 0;
|
|
lfsr_mount(&lfs, CFG) => 0;
|
|
lfs_alloc_ckpoint(&lfs);
|
|
|
|
// create entries
|
|
for (lfs_size_t i = 0; i < N; i++) {
|
|
char name[256];
|
|
name[0] = '\0';
|
|
sprintf(name+1, "%03x", i);
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, (const char*)name+1, 3,
|
|
&mdir, NULL, NULL) => LFS_ERR_NOENT;
|
|
// force a compaction?
|
|
if (FORCE_COMPACTION) {
|
|
lfs.mroot.rbyd.eoff = -1;
|
|
mdir.rbyd.eoff = -1;
|
|
}
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_REG, +1, LFSR_DATA_BUF(name, 4)))) => 0;
|
|
lfsr_data_t data;
|
|
|
|
uint8_t buffer[256];
|
|
lfsr_mdir_lookup(&lfs, &mdir, LFSR_TAG_REG, &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, sizeof(buffer)) => 4;
|
|
assert(memcmp(buffer, name, 4) == 0);
|
|
}
|
|
|
|
// try looking up each entry
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0, &mdir) => 0;
|
|
lfsr_data_t data;
|
|
uint8_t buffer[256];
|
|
lfsr_mdir_lookup(&lfs, &mdir, LFSR_TAG_BOOKMARK, &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, sizeof(buffer)) => 1;
|
|
for (lfs_size_t i = 0; i < N; i++) {
|
|
char name[256];
|
|
name[0] = '\0';
|
|
sprintf(name+1, "%03x", i);
|
|
lfsr_mtree_seek(&lfs, &lfs.mtree, &mdir, +1) => 0;
|
|
lfsr_mdir_lookup(&lfs, &mdir, LFSR_TAG_REG, &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, sizeof(buffer)) => 4;
|
|
assert(memcmp(buffer, name, 4) == 0);
|
|
}
|
|
|
|
// check things stay sane after remount
|
|
lfsr_unmount(&lfs) => 0;
|
|
lfsr_mount(&lfs, CFG) => 0;
|
|
|
|
// try looking up each entry
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0, &mdir) => 0;
|
|
lfsr_mdir_lookup(&lfs, &mdir, LFSR_TAG_BOOKMARK, &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, sizeof(buffer)) => 1;
|
|
for (lfs_size_t i = 0; i < N; i++) {
|
|
char name[256];
|
|
name[0] = '\0';
|
|
sprintf(name+1, "%03x", i);
|
|
lfsr_mtree_seek(&lfs, &lfs.mtree, &mdir, +1) => 0;
|
|
lfsr_mdir_lookup(&lfs, &mdir, LFSR_TAG_REG, &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, sizeof(buffer)) => 4;
|
|
assert(memcmp(buffer, name, 4) == 0);
|
|
}
|
|
|
|
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 = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, CFG) => 0;
|
|
lfsr_mount(&lfs, CFG) => 0;
|
|
lfs_alloc_ckpoint(&lfs);
|
|
|
|
bool sim[N];
|
|
for (lfs_size_t i = 0; i < N; i++) {
|
|
sim[i] = false;
|
|
}
|
|
|
|
uint32_t prng = SEED;
|
|
for (lfs_size_t i = 0; i < N; i++) {
|
|
// choose a pseudo-random name
|
|
lfs_size_t x = TEST_PRNG(&prng) % N;
|
|
|
|
// update sim
|
|
sim[x] = true;
|
|
|
|
// update mtree
|
|
char name[256];
|
|
name[0] = '\0';
|
|
sprintf(name+1, "%03x", x);
|
|
lfsr_mdir_t mdir;
|
|
int err = lfsr_mtree_namelookup(&lfs, &lfs.mtree,
|
|
0, (const char*)name+1, 3,
|
|
&mdir, NULL, NULL);
|
|
assert(!err || err == LFS_ERR_NOENT);
|
|
if (!err) {
|
|
continue;
|
|
}
|
|
// force a compaction?
|
|
if (FORCE_COMPACTION) {
|
|
lfs.mroot.rbyd.eoff = -1;
|
|
mdir.rbyd.eoff = -1;
|
|
}
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_REG, +1, LFSR_DATA_BUF(name, 4)))) => 0;
|
|
lfsr_data_t data;
|
|
|
|
// double check
|
|
uint8_t buffer[256];
|
|
lfsr_mdir_lookup(&lfs, &mdir, LFSR_TAG_REG, &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, sizeof(buffer)) => 4;
|
|
assert(memcmp(buffer, name, 4) == 0);
|
|
}
|
|
|
|
// try looking up each entry
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0, &mdir) => 0;
|
|
lfsr_data_t data;
|
|
uint8_t buffer[256];
|
|
lfsr_mdir_lookup(&lfs, &mdir, LFSR_TAG_BOOKMARK, &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, sizeof(buffer)) => 1;
|
|
for (lfs_size_t i = 0; i < N; i++) {
|
|
char name[256];
|
|
name[0] = '\0';
|
|
sprintf(name+1, "%03x", i);
|
|
if (sim[i]) {
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, (const char*)name+1, 3,
|
|
&mdir, NULL, NULL) => 0;
|
|
lfsr_mdir_lookup(&lfs, &mdir, LFSR_TAG_REG, &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, sizeof(buffer)) => 4;
|
|
assert(memcmp(buffer, name, 4) == 0);
|
|
} else {
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, (const char*)name+1, 3,
|
|
&mdir, NULL, NULL) => LFS_ERR_NOENT;
|
|
}
|
|
}
|
|
|
|
// check things stay sane after remount
|
|
lfsr_unmount(&lfs) => 0;
|
|
lfsr_mount(&lfs, CFG) => 0;
|
|
|
|
// try looking up each entry
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0, &mdir) => 0;
|
|
lfsr_mdir_lookup(&lfs, &mdir, LFSR_TAG_BOOKMARK, &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, sizeof(buffer)) => 1;
|
|
for (lfs_size_t i = 0; i < N; i++) {
|
|
char name[256];
|
|
name[0] = '\0';
|
|
sprintf(name+1, "%03x", i);
|
|
if (sim[i]) {
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, (const char*)name+1, 3,
|
|
&mdir, NULL, NULL) => 0;
|
|
lfsr_mdir_lookup(&lfs, &mdir, LFSR_TAG_REG, &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, sizeof(buffer)) => 4;
|
|
assert(memcmp(buffer, name, 4) == 0);
|
|
} else {
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, (const char*)name+1, 3,
|
|
&mdir, NULL, NULL) => LFS_ERR_NOENT;
|
|
}
|
|
}
|
|
|
|
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_ckpoint(&lfs);
|
|
lfsr_data_t data;
|
|
|
|
// create 2 large entries that needs to be uninlined and split
|
|
uint8_t buffer[SIZE];
|
|
buffer[0] = '\0';
|
|
memset(buffer+1, 'a', SIZE-1);
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL, NULL) => LFS_ERR_NOENT;
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_REG, +1, LFSR_DATA_BUF(buffer, SIZE)))) => 0;
|
|
assert(mdir.rbyd.weight == 2);
|
|
|
|
memset(buffer+1, 'b', SIZE-1);
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL, NULL) => LFS_ERR_NOENT;
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_REG, +1, LFSR_DATA_BUF(buffer, SIZE)))) => 0;
|
|
assert(mdir.rbyd.weight == 3);
|
|
|
|
// force mroot to compact
|
|
lfs.mroot.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
|
|
// assert mdirs were unininlined and split
|
|
assert(lfsr_mtree_weight(&lfs.mtree) == 2*lfsr_mleafweight(&lfs));
|
|
// assert mroot now has no entries
|
|
assert(lfs.mroot.rbyd.weight == 0);
|
|
|
|
// remove an entry, forcing the mdir to be dropped
|
|
memset(buffer+1, 'b', SIZE-1);
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL, NULL) => 0;
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_RM, -1, LFSR_DATA_NULL()))) => 0;
|
|
assert(mdir.rbyd.weight == 0);
|
|
|
|
// assert mdir was dropped
|
|
assert(lfsr_mtree_weight(&lfs.mtree) == 1*lfsr_mleafweight(&lfs));
|
|
// assert mroot still has no entries
|
|
assert(lfs.mroot.rbyd.weight == 0);
|
|
|
|
// assert that our entries are still in the mtree
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0*lfsr_mleafweight(&lfs)+1, &mdir) => 0;
|
|
assert(mdir.rbyd.weight == 2);
|
|
lfsr_mdir_lookup(&lfs, &mdir, LFSR_TAG_REG, &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'a');
|
|
|
|
// check things stay sane after remount
|
|
lfsr_unmount(&lfs) => 0;
|
|
lfsr_mount(&lfs, CFG) => 0;
|
|
|
|
// assert mdir was dropped
|
|
assert(lfsr_mtree_weight(&lfs.mtree) == 1*lfsr_mleafweight(&lfs));
|
|
// assert mroot still has no entries
|
|
assert(lfs.mroot.rbyd.weight == 0);
|
|
|
|
// assert that our entries are still in the mtree
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0*lfsr_mleafweight(&lfs)+1, &mdir) => 0;
|
|
assert(mdir.rbyd.weight == 2);
|
|
lfsr_mdir_lookup(&lfs, &mdir, LFSR_TAG_REG, &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'a');
|
|
|
|
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_ckpoint(&lfs);
|
|
lfsr_data_t data;
|
|
|
|
// create 2 large entries that needs to be uninlined and split
|
|
uint8_t buffer[SIZE];
|
|
buffer[0] = '\0';
|
|
memset(buffer+1, 'a', SIZE-1);
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL, NULL) => LFS_ERR_NOENT;
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_REG, +1, LFSR_DATA_BUF(buffer, SIZE)))) => 0;
|
|
assert(mdir.rbyd.weight == 2);
|
|
|
|
memset(buffer+1, 'b', SIZE-1);
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL, NULL) => LFS_ERR_NOENT;
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_REG, +1, LFSR_DATA_BUF(buffer, SIZE)))) => 0;
|
|
assert(mdir.rbyd.weight == 3);
|
|
|
|
// force mroot to compact
|
|
lfs.mroot.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
|
|
// assert mdirs were unininlined and split
|
|
assert(lfsr_mtree_weight(&lfs.mtree) == 2*lfsr_mleafweight(&lfs));
|
|
// assert mroot now has no entries
|
|
assert(lfs.mroot.rbyd.weight == 0);
|
|
|
|
// remove an entry, forcing the mdir to be dropped
|
|
memset(buffer+1, 'b', SIZE-1);
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL, NULL) => 0;
|
|
// force mdir to compact while we're removing
|
|
mdir.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_RM, -1, LFSR_DATA_NULL()))) => 0;
|
|
assert(mdir.rbyd.weight == 0);
|
|
|
|
// assert mdir was dropped
|
|
assert(lfsr_mtree_weight(&lfs.mtree) == 1*lfsr_mleafweight(&lfs));
|
|
// assert mroot still has no entries
|
|
assert(lfs.mroot.rbyd.weight == 0);
|
|
|
|
// assert that our entries are still in the mtree
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0*lfsr_mleafweight(&lfs)+1, &mdir) => 0;
|
|
assert(mdir.rbyd.weight == 2);
|
|
lfsr_mdir_lookup(&lfs, &mdir, LFSR_TAG_REG, &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'a');
|
|
|
|
// check things stay sane after remount
|
|
lfsr_unmount(&lfs) => 0;
|
|
lfsr_mount(&lfs, CFG) => 0;
|
|
|
|
// assert mdir was dropped
|
|
assert(lfsr_mtree_weight(&lfs.mtree) == 1*lfsr_mleafweight(&lfs));
|
|
// assert mroot still has no entries
|
|
assert(lfs.mroot.rbyd.weight == 0);
|
|
|
|
// assert that our entries are still in the mtree
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0*lfsr_mleafweight(&lfs)+1, &mdir) => 0;
|
|
assert(mdir.rbyd.weight == 2);
|
|
lfsr_mdir_lookup(&lfs, &mdir, LFSR_TAG_REG, &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'a');
|
|
|
|
lfsr_unmount(&lfs) => 0;
|
|
'''
|
|
|
|
[cases.test_mtree_drop_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_ckpoint(&lfs);
|
|
lfsr_data_t data;
|
|
|
|
// create 2 large entries that needs to be uninlined and split
|
|
uint8_t buffer[SIZE];
|
|
buffer[0] = '\0';
|
|
memset(buffer+1, 'a', SIZE-1);
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL, NULL) => LFS_ERR_NOENT;
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_REG, +1, LFSR_DATA_BUF(buffer, SIZE)))) => 0;
|
|
assert(mdir.rbyd.weight == 2);
|
|
|
|
memset(buffer+1, 'b', SIZE-1);
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL, NULL) => LFS_ERR_NOENT;
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_REG, +1, LFSR_DATA_BUF(buffer, SIZE)))) => 0;
|
|
assert(mdir.rbyd.weight == 3);
|
|
|
|
// remove an entry, forcing the mdir to be dropped
|
|
memset(buffer+1, 'b', SIZE-1);
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL, NULL) => 0;
|
|
// force mdir to compact while we're removing
|
|
lfs.mroot.rbyd.eoff = -1;
|
|
mdir.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_RM, -1, LFSR_DATA_NULL()))) => 0;
|
|
assert(mdir.rbyd.weight == 2);
|
|
|
|
// assert split/drop worked out
|
|
assert(lfsr_mtree_weight(&lfs.mtree) == 1*lfsr_mleafweight(&lfs));
|
|
// assert mroot still has no entries
|
|
assert(lfs.mroot.rbyd.weight == 0);
|
|
|
|
// assert that our entries are still in the mtree
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0*lfsr_mleafweight(&lfs)+1, &mdir) => 0;
|
|
assert(mdir.rbyd.weight == 2);
|
|
lfsr_mdir_lookup(&lfs, &mdir, LFSR_TAG_REG, &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'a');
|
|
|
|
// check things stay sane after remount
|
|
lfsr_unmount(&lfs) => 0;
|
|
lfsr_mount(&lfs, CFG) => 0;
|
|
|
|
// assert mdir was dropped
|
|
assert(lfsr_mtree_weight(&lfs.mtree) == 1*lfsr_mleafweight(&lfs));
|
|
// assert mroot still has no entries
|
|
assert(lfs.mroot.rbyd.weight == 0);
|
|
|
|
// assert that our entries are still in the mtree
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0*lfsr_mleafweight(&lfs)+1, &mdir) => 0;
|
|
assert(mdir.rbyd.weight == 2);
|
|
lfsr_mdir_lookup(&lfs, &mdir, LFSR_TAG_REG, &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'a');
|
|
|
|
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_ckpoint(&lfs);
|
|
lfsr_data_t data;
|
|
|
|
// create 2 large entries that needs to be uninlined and split
|
|
uint8_t buffer[SIZE];
|
|
buffer[0] = '\0';
|
|
memset(buffer+1, 'a', SIZE-1);
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL, NULL) => LFS_ERR_NOENT;
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_REG, +1, LFSR_DATA_BUF(buffer, SIZE)))) => 0;
|
|
assert(mdir.rbyd.weight == 2);
|
|
|
|
memset(buffer+1, 'b', SIZE-1);
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL, NULL) => LFS_ERR_NOENT;
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_REG, +1, LFSR_DATA_BUF(buffer, SIZE)))) => 0;
|
|
assert(mdir.rbyd.weight == 3);
|
|
|
|
// force mroot to compact
|
|
lfs.mroot.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
|
|
// assert mdirs were unininlined and split
|
|
assert(lfsr_mtree_weight(&lfs.mtree) == 2*lfsr_mleafweight(&lfs));
|
|
// assert mroot now has no entries
|
|
assert(lfs.mroot.rbyd.weight == 0);
|
|
|
|
// now add another large entry to an mdir, forcing a split
|
|
memset(buffer+1, 'c', SIZE-1);
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL, NULL) => LFS_ERR_NOENT;
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_REG, +1, LFSR_DATA_BUF(buffer, SIZE)))) => 0;
|
|
assert(mdir.rbyd.weight == 2);
|
|
|
|
// remove an entry, forcing the mdir to be dropped
|
|
memset(buffer+1, 'b', SIZE-1);
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL, NULL) => 0;
|
|
// force mdir to compact while we're removing
|
|
mdir.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_RM, -1, LFSR_DATA_NULL()))) => 0;
|
|
assert(mdir.rbyd.weight == 1);
|
|
|
|
// assert split/drop worked out
|
|
assert(lfsr_mtree_weight(&lfs.mtree) == 2*lfsr_mleafweight(&lfs));
|
|
// assert mroot still has no entries
|
|
assert(lfs.mroot.rbyd.weight == 0);
|
|
|
|
// assert that our entries are still in the mtree
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0*lfsr_mleafweight(&lfs)+1, &mdir) => 0;
|
|
assert(mdir.rbyd.weight == 2);
|
|
lfsr_mdir_lookup(&lfs, &mdir, LFSR_TAG_REG, &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'a');
|
|
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 1*lfsr_mleafweight(&lfs)+0, &mdir) => 0;
|
|
assert(mdir.rbyd.weight == 1);
|
|
lfsr_mdir_lookup(&lfs, &mdir, LFSR_TAG_REG, &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'c');
|
|
|
|
// check things stay sane after remount
|
|
lfsr_unmount(&lfs) => 0;
|
|
lfsr_mount(&lfs, CFG) => 0;
|
|
|
|
// assert split/drop worked out
|
|
assert(lfsr_mtree_weight(&lfs.mtree) == 2*lfsr_mleafweight(&lfs));
|
|
// assert mroot still has no entries
|
|
assert(lfs.mroot.rbyd.weight == 0);
|
|
|
|
// assert that our entries are still in the mtree
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0*lfsr_mleafweight(&lfs)+1, &mdir) => 0;
|
|
assert(mdir.rbyd.weight == 2);
|
|
lfsr_mdir_lookup(&lfs, &mdir, LFSR_TAG_REG, &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'a');
|
|
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 1*lfsr_mleafweight(&lfs)+0, &mdir) => 0;
|
|
assert(mdir.rbyd.weight == 1);
|
|
lfsr_mdir_lookup(&lfs, &mdir, LFSR_TAG_REG, &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'c');
|
|
|
|
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_ckpoint(&lfs);
|
|
lfsr_data_t data;
|
|
|
|
// create 2 large entries that needs to be uninlined and split
|
|
uint8_t buffer[SIZE];
|
|
buffer[0] = '\0';
|
|
memset(buffer+1, 'a', SIZE-1);
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL, NULL) => LFS_ERR_NOENT;
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_REG, +1, LFSR_DATA_BUF(buffer, SIZE)))) => 0;
|
|
assert(mdir.rbyd.weight == 2);
|
|
|
|
memset(buffer+1, 'b', SIZE-1);
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL, NULL) => LFS_ERR_NOENT;
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_REG, +1, LFSR_DATA_BUF(buffer, SIZE)))) => 0;
|
|
assert(mdir.rbyd.weight == 3);
|
|
|
|
// force mroot to compact
|
|
lfs.mroot.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
|
|
// assert mdirs were unininlined and split
|
|
assert(lfsr_mtree_weight(&lfs.mtree) == 2*lfsr_mleafweight(&lfs));
|
|
// assert mroot now has no entries
|
|
assert(lfs.mroot.rbyd.weight == 0);
|
|
|
|
// now add another large entry to an mdir, forcing a split
|
|
memset(buffer+1, 'c', SIZE-1);
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL, NULL) => LFS_ERR_NOENT;
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_REG, +1, LFSR_DATA_BUF(buffer, SIZE)))) => 0;
|
|
assert(mdir.rbyd.weight == 2);
|
|
|
|
// remove an entry, forcing the mdir to be dropped
|
|
memset(buffer+1, 'c', SIZE-1);
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL, NULL) => 0;
|
|
// force mdir to compact while we're removing
|
|
mdir.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_RM, -1, LFSR_DATA_NULL()))) => 0;
|
|
assert(mdir.rbyd.weight == 1);
|
|
|
|
// assert split/drop worked out
|
|
assert(lfsr_mtree_weight(&lfs.mtree) == 2*lfsr_mleafweight(&lfs));
|
|
// assert mroot still has no entries
|
|
assert(lfs.mroot.rbyd.weight == 0);
|
|
|
|
// assert that our entries are still in the mtree
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0*lfsr_mleafweight(&lfs)+1, &mdir) => 0;
|
|
assert(mdir.rbyd.weight == 2);
|
|
lfsr_mdir_lookup(&lfs, &mdir, LFSR_TAG_REG, &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'a');
|
|
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 1*lfsr_mleafweight(&lfs)+0, &mdir) => 0;
|
|
assert(mdir.rbyd.weight == 1);
|
|
lfsr_mdir_lookup(&lfs, &mdir, LFSR_TAG_REG, &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'b');
|
|
|
|
// check things stay sane after remount
|
|
lfsr_unmount(&lfs) => 0;
|
|
lfsr_mount(&lfs, CFG) => 0;
|
|
|
|
// assert split/drop worked out
|
|
assert(lfsr_mtree_weight(&lfs.mtree) == 2*lfsr_mleafweight(&lfs));
|
|
// assert mroot still has no entries
|
|
assert(lfs.mroot.rbyd.weight == 0);
|
|
|
|
// assert that our entries are still in the mtree
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0*lfsr_mleafweight(&lfs)+1, &mdir) => 0;
|
|
assert(mdir.rbyd.weight == 2);
|
|
lfsr_mdir_lookup(&lfs, &mdir, LFSR_TAG_REG, &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'a');
|
|
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 1*lfsr_mleafweight(&lfs)+0, &mdir) => 0;
|
|
assert(mdir.rbyd.weight == 1);
|
|
lfsr_mdir_lookup(&lfs, &mdir, LFSR_TAG_REG, &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'b');
|
|
|
|
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 = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, CFG) => 0;
|
|
lfsr_mount(&lfs, CFG) => 0;
|
|
lfs_alloc_ckpoint(&lfs);
|
|
|
|
bool sim[N];
|
|
for (lfs_size_t i = 0; i < N; i++) {
|
|
sim[i] = false;
|
|
}
|
|
|
|
uint32_t prng = SEED;
|
|
for (lfs_size_t i = 0; i < N; i++) {
|
|
// choose a pseudo-random name
|
|
lfs_size_t x = TEST_PRNG(&prng) % N;
|
|
char name[256];
|
|
name[0] = '\0';
|
|
sprintf(name+1, "%03x", x);
|
|
// choose to create or delete
|
|
uint8_t op = TEST_PRNG(&prng) % 2;
|
|
|
|
// create
|
|
if (op == 0) {
|
|
// update sim
|
|
sim[x] = true;
|
|
|
|
// update mtree
|
|
lfsr_mdir_t mdir;
|
|
int err = lfsr_mtree_namelookup(&lfs, &lfs.mtree,
|
|
0, (const char*)name+1, 3,
|
|
&mdir, NULL, NULL);
|
|
assert(!err || err == LFS_ERR_NOENT);
|
|
if (!err) {
|
|
continue;
|
|
}
|
|
// force a compaction?
|
|
if (FORCE_COMPACTION) {
|
|
lfs.mroot.rbyd.eoff = -1;
|
|
mdir.rbyd.eoff = -1;
|
|
}
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_REG, +1, LFSR_DATA_BUF(name, 4)))) => 0;
|
|
lfsr_data_t data;
|
|
|
|
// double check
|
|
uint8_t buffer[256];
|
|
lfsr_mdir_lookup(&lfs, &mdir, LFSR_TAG_REG, &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, sizeof(buffer)) => 4;
|
|
assert(memcmp(buffer, name, 4) == 0);
|
|
|
|
// delete
|
|
} else {
|
|
// update sim
|
|
sim[x] = false;
|
|
|
|
// update mtree
|
|
lfsr_mdir_t mdir;
|
|
int err = lfsr_mtree_namelookup(&lfs, &lfs.mtree,
|
|
0, (const char*)name+1, 3,
|
|
&mdir, NULL, NULL);
|
|
assert(!err || err == LFS_ERR_NOENT);
|
|
if (err == LFS_ERR_NOENT) {
|
|
continue;
|
|
}
|
|
// force a compaction?
|
|
if (FORCE_COMPACTION) {
|
|
lfs.mroot.rbyd.eoff = -1;
|
|
mdir.rbyd.eoff = -1;
|
|
}
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_RM, -1, LFSR_DATA_NULL()))) => 0;
|
|
}
|
|
}
|
|
|
|
// try looking up each entry
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0, &mdir) => 0;
|
|
lfsr_data_t data;
|
|
uint8_t buffer[256];
|
|
lfsr_mdir_lookup(&lfs, &mdir, LFSR_TAG_BOOKMARK, &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, sizeof(buffer)) => 1;
|
|
for (lfs_size_t i = 0; i < N; i++) {
|
|
char name[256];
|
|
name[0] = '\0';
|
|
sprintf(name+1, "%03x", i);
|
|
if (sim[i]) {
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, (const char*)name+1, 3,
|
|
&mdir, NULL, NULL) => 0;
|
|
lfsr_mdir_lookup(&lfs, &mdir, LFSR_TAG_REG, &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, sizeof(buffer)) => 4;
|
|
assert(memcmp(buffer, name, 4) == 0);
|
|
} else {
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, (const char*)name+1, 3,
|
|
&mdir, NULL, NULL) => LFS_ERR_NOENT;
|
|
}
|
|
}
|
|
|
|
// check things stay sane after remount
|
|
lfsr_unmount(&lfs) => 0;
|
|
lfsr_mount(&lfs, CFG) => 0;
|
|
|
|
// try looking up each entry
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0, &mdir) => 0;
|
|
lfsr_mdir_lookup(&lfs, &mdir, LFSR_TAG_BOOKMARK, &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, sizeof(buffer)) => 1;
|
|
for (lfs_size_t i = 0; i < N; i++) {
|
|
char name[256];
|
|
name[0] = '\0';
|
|
sprintf(name+1, "%03x", i);
|
|
if (sim[i]) {
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, (const char*)name+1, 3,
|
|
&mdir, NULL, NULL) => 0;
|
|
lfsr_mdir_lookup(&lfs, &mdir, LFSR_TAG_REG, &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, sizeof(buffer)) => 4;
|
|
assert(memcmp(buffer, name, 4) == 0);
|
|
} else {
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, (const char*)name+1, 3,
|
|
&mdir, NULL, NULL) => LFS_ERR_NOENT;
|
|
}
|
|
}
|
|
|
|
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_ckpoint(&lfs);
|
|
lfsr_data_t data;
|
|
|
|
// create a 2 large attrs that needs to be uninlined
|
|
uint8_t buffer[SIZE];
|
|
memset(buffer, 'a', SIZE);
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_UATTR(1), 0, LFSR_DATA_BUF(buffer, SIZE)))) => 0;
|
|
|
|
memset(buffer, 'b', SIZE);
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0, &mdir) => 0;
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_UATTR(1), 0, LFSR_DATA_BUF(buffer, SIZE)))) => 0;
|
|
|
|
// force mroot to compact
|
|
lfs.mroot.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
|
|
// assert mdirs were unininlined
|
|
assert(lfsr_mtree_weight(&lfs.mtree) == 1*lfsr_mleafweight(&lfs));
|
|
// assert mroot now has no entries
|
|
assert(lfs.mroot.rbyd.weight == 0);
|
|
|
|
// force mdir to compact twice, this should relocate
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0, &mdir) => 0;
|
|
lfsr_mdir_t old_mdir = mdir;
|
|
mdir.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_REG, 0, LFSR_DATA_BUF(buffer, SIZE)))) => 0;
|
|
mdir.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_REG, 0, LFSR_DATA_BUF(buffer, SIZE)))) => 0;
|
|
// assert we relocated
|
|
assert(lfsr_mdir_cmp(&old_mdir, &mdir) != 0);
|
|
|
|
// assert mdirs were unininlined
|
|
assert(lfsr_mtree_weight(&lfs.mtree) == 1*lfsr_mleafweight(&lfs));
|
|
// assert mroot now has no entries
|
|
assert(lfs.mroot.rbyd.weight == 0);
|
|
|
|
// assert that our attrs are still in the mroot/mtree
|
|
lfsr_mdir_lookup(&lfs, &lfs.mroot, LFSR_TAG_UATTR(1), &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[0] == 'a');
|
|
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0, &mdir) => 0;
|
|
assert(mdir.rbyd.weight == 1);
|
|
lfsr_mdir_lookup(&lfs, &mdir, LFSR_TAG_UATTR(1), &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[0] == 'b');
|
|
|
|
// check things stay sane after remount
|
|
lfsr_unmount(&lfs) => 0;
|
|
lfsr_mount(&lfs, CFG) => 0;
|
|
|
|
// assert mdirs were unininlined
|
|
assert(lfsr_mtree_weight(&lfs.mtree) == 1*lfsr_mleafweight(&lfs));
|
|
// assert mroot now has no entries
|
|
assert(lfs.mroot.rbyd.weight == 0);
|
|
|
|
// assert that our attrs are still in the mroot/mtree
|
|
lfsr_mdir_lookup(&lfs, &lfs.mroot, LFSR_TAG_UATTR(1), &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[0] == 'a');
|
|
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0, &mdir) => 0;
|
|
assert(mdir.rbyd.weight == 1);
|
|
lfsr_mdir_lookup(&lfs, &mdir, LFSR_TAG_UATTR(1), &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[0] == 'b');
|
|
|
|
lfsr_unmount(&lfs) => 0;
|
|
'''
|
|
|
|
[cases.test_mtree_relocate_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_ckpoint(&lfs);
|
|
lfsr_data_t data;
|
|
|
|
// create 2 large entries that needs to be uninlined and split
|
|
uint8_t buffer[SIZE];
|
|
buffer[0] = '\0';
|
|
memset(buffer+1, 'a', SIZE-1);
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL, NULL) => LFS_ERR_NOENT;
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_REG, +1, LFSR_DATA_BUF(buffer, SIZE)))) => 0;
|
|
assert(mdir.rbyd.weight == 2);
|
|
|
|
memset(buffer+1, 'b', SIZE-1);
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL, NULL) => LFS_ERR_NOENT;
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_REG, +1, LFSR_DATA_BUF(buffer, SIZE)))) => 0;
|
|
assert(mdir.rbyd.weight == 3);
|
|
|
|
// force mroot to compact
|
|
lfs.mroot.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
|
|
// assert mdirs were unininlined and split
|
|
assert(lfsr_mtree_weight(&lfs.mtree) == 2*lfsr_mleafweight(&lfs));
|
|
// assert mroot now has no entries
|
|
assert(lfs.mroot.rbyd.weight == 0);
|
|
|
|
// force mdir to compact twice, this should relocate
|
|
memset(buffer+1, 'a', SIZE-1);
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL, NULL) => 0;
|
|
lfsr_mdir_t old_mdir = mdir;
|
|
mdir.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_REG, 0, LFSR_DATA_BUF(buffer, SIZE)))) => 0;
|
|
mdir.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_REG, 0, LFSR_DATA_BUF(buffer, SIZE)))) => 0;
|
|
// assert we relocated
|
|
assert(lfsr_mdir_cmp(&old_mdir, &mdir) != 0);
|
|
|
|
// assert mdirs were unininlined and split
|
|
assert(lfsr_mtree_weight(&lfs.mtree) == 2*lfsr_mleafweight(&lfs));
|
|
// assert mroot now has no entries
|
|
assert(lfs.mroot.rbyd.weight == 0);
|
|
|
|
// assert that our entries are still in the mtree
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0*lfsr_mleafweight(&lfs)+1, &mdir) => 0;
|
|
assert(mdir.rbyd.weight == 2);
|
|
lfsr_mdir_lookup(&lfs, &mdir, LFSR_TAG_REG, &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'a');
|
|
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 1*lfsr_mleafweight(&lfs)+0, &mdir) => 0;
|
|
assert(mdir.rbyd.weight == 1);
|
|
lfsr_mdir_lookup(&lfs, &mdir, LFSR_TAG_REG, &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'b');
|
|
|
|
// check things stay sane after remount
|
|
lfsr_unmount(&lfs) => 0;
|
|
lfsr_mount(&lfs, CFG) => 0;
|
|
|
|
// assert mdirs were unininlined and split
|
|
assert(lfsr_mtree_weight(&lfs.mtree) == 2*lfsr_mleafweight(&lfs));
|
|
// assert mroot now has no entries
|
|
assert(lfs.mroot.rbyd.weight == 0);
|
|
|
|
// assert that our entries are still in the mtree
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0*lfsr_mleafweight(&lfs)+1, &mdir) => 0;
|
|
assert(mdir.rbyd.weight == 2);
|
|
lfsr_mdir_lookup(&lfs, &mdir, LFSR_TAG_REG, &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'a');
|
|
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 1*lfsr_mleafweight(&lfs)+0, &mdir) => 0;
|
|
assert(mdir.rbyd.weight == 1);
|
|
lfsr_mdir_lookup(&lfs, &mdir, LFSR_TAG_REG, &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'b');
|
|
|
|
lfsr_unmount(&lfs) => 0;
|
|
'''
|
|
|
|
[cases.test_mtree_relocate_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_ckpoint(&lfs);
|
|
lfsr_data_t data;
|
|
|
|
// create 2 large entries that needs to be uninlined and split
|
|
uint8_t buffer[SIZE];
|
|
buffer[0] = '\0';
|
|
memset(buffer+1, 'a', SIZE-1);
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL, NULL) => LFS_ERR_NOENT;
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_REG, +1, LFSR_DATA_BUF(buffer, SIZE)))) => 0;
|
|
assert(mdir.rbyd.weight == 2);
|
|
|
|
memset(buffer+1, 'b', SIZE-1);
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL, NULL) => LFS_ERR_NOENT;
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_REG, +1, LFSR_DATA_BUF(buffer, SIZE)))) => 0;
|
|
assert(mdir.rbyd.weight == 3);
|
|
|
|
// force mroot to compact
|
|
lfs.mroot.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
|
|
// assert mdirs were unininlined and split
|
|
assert(lfsr_mtree_weight(&lfs.mtree) == 2*lfsr_mleafweight(&lfs));
|
|
// assert mroot now has no entries
|
|
assert(lfs.mroot.rbyd.weight == 0);
|
|
|
|
// force mdir to compact twice, this should relocate
|
|
memset(buffer+1, 'b', SIZE-1);
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL, NULL) => 0;
|
|
lfsr_mdir_t old_mdir = mdir;
|
|
mdir.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_REG, 0, LFSR_DATA_BUF(buffer, SIZE)))) => 0;
|
|
mdir.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_REG, 0, LFSR_DATA_BUF(buffer, SIZE)))) => 0;
|
|
// assert we relocated
|
|
assert(lfsr_mdir_cmp(&old_mdir, &mdir) != 0);
|
|
|
|
// assert mdirs were unininlined and split
|
|
assert(lfsr_mtree_weight(&lfs.mtree) == 2*lfsr_mleafweight(&lfs));
|
|
// assert mroot now has no entries
|
|
assert(lfs.mroot.rbyd.weight == 0);
|
|
|
|
// assert that our entries are still in the mtree
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0*lfsr_mleafweight(&lfs)+1, &mdir) => 0;
|
|
assert(mdir.rbyd.weight == 2);
|
|
lfsr_mdir_lookup(&lfs, &mdir, LFSR_TAG_REG, &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'a');
|
|
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 1*lfsr_mleafweight(&lfs)+0, &mdir) => 0;
|
|
assert(mdir.rbyd.weight == 1);
|
|
lfsr_mdir_lookup(&lfs, &mdir, LFSR_TAG_REG, &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'b');
|
|
|
|
// check things stay sane after remount
|
|
lfsr_unmount(&lfs) => 0;
|
|
lfsr_mount(&lfs, CFG) => 0;
|
|
|
|
// assert mdirs were unininlined and split
|
|
assert(lfsr_mtree_weight(&lfs.mtree) == 2*lfsr_mleafweight(&lfs));
|
|
// assert mroot now has no entries
|
|
assert(lfs.mroot.rbyd.weight == 0);
|
|
|
|
// assert that our entries are still in the mtree
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0*lfsr_mleafweight(&lfs)+1, &mdir) => 0;
|
|
assert(mdir.rbyd.weight == 2);
|
|
lfsr_mdir_lookup(&lfs, &mdir, LFSR_TAG_REG, &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'a');
|
|
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 1*lfsr_mleafweight(&lfs)+0, &mdir) => 0;
|
|
assert(mdir.rbyd.weight == 1);
|
|
lfsr_mdir_lookup(&lfs, &mdir, LFSR_TAG_REG, &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'b');
|
|
|
|
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_ckpoint(&lfs);
|
|
lfsr_data_t data;
|
|
|
|
// prepare mroot with an entry
|
|
uint8_t buffer[SIZE];
|
|
buffer[0] = '\0';
|
|
memset(buffer+1, 'a', SIZE-1);
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL, NULL) => LFS_ERR_NOENT;
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_REG, +1, LFSR_DATA_BUF(buffer, SIZE)))) => 0;
|
|
assert(mdir.rbyd.weight == 2);
|
|
|
|
// force mroot to compact twice, this should extend the mroot
|
|
lfsr_mdir_t old_mroot = lfs.mroot;
|
|
lfs.mroot.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
lfs.mroot.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
// assert we relocated
|
|
assert(lfsr_mdir_cmp(&old_mroot, &lfs.mroot) != 0);
|
|
|
|
// assert that our entry is still in the mroot
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0*lfsr_mleafweight(&lfs)+1, &mdir) => 0;
|
|
assert(mdir.rbyd.weight == 2);
|
|
lfsr_mdir_lookup(&lfs, &mdir, LFSR_TAG_REG, &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'a');
|
|
|
|
// check things stay sane after remount
|
|
lfsr_unmount(&lfs) => 0;
|
|
lfsr_mount(&lfs, CFG) => 0;
|
|
|
|
// assert that our attr is still in the mroot
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0*lfsr_mleafweight(&lfs)+1, &mdir) => 0;
|
|
assert(mdir.rbyd.weight == 2);
|
|
lfsr_mdir_lookup(&lfs, &mdir, LFSR_TAG_REG, &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'a');
|
|
|
|
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
|
|
# an easy way to 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_ckpoint(&lfs);
|
|
lfsr_data_t data;
|
|
|
|
// prepare mroot with an entry
|
|
uint8_t buffer[SIZE];
|
|
buffer[0] = '\0';
|
|
memset(buffer+1, 'a', SIZE-1);
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL, NULL) => LFS_ERR_NOENT;
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_REG, +1, LFSR_DATA_BUF(buffer, SIZE)))) => 0;
|
|
assert(mdir.rbyd.weight == 2);
|
|
|
|
// force mroot to compact twice, this should extend the mroot
|
|
lfsr_mdir_t old_mroot = lfs.mroot;
|
|
lfs.mroot.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
lfs.mroot.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
// assert we relocated
|
|
assert(lfsr_mdir_cmp(&old_mroot, &lfs.mroot) != 0);
|
|
|
|
// force mroot to compact four times, this should relocate the mroot
|
|
// twice, forcing a second mroot extension
|
|
old_mroot = lfs.mroot;
|
|
lfs.mroot.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
lfs.mroot.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
lfs.mroot.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
lfs.mroot.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
// assert we relocated
|
|
assert(lfsr_mdir_cmp(&old_mroot, &lfs.mroot) != 0);
|
|
|
|
// assert that our attr is still in the mroot
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0*lfsr_mleafweight(&lfs)+1, &mdir) => 0;
|
|
assert(mdir.rbyd.weight == 2);
|
|
lfsr_mdir_lookup(&lfs, &mdir, LFSR_TAG_REG, &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'a');
|
|
|
|
// check things stay sane after remount
|
|
lfsr_unmount(&lfs) => 0;
|
|
lfsr_mount(&lfs, CFG) => 0;
|
|
|
|
// assert that our attr is still in the mroot
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0*lfsr_mleafweight(&lfs)+1, &mdir) => 0;
|
|
assert(mdir.rbyd.weight == 2);
|
|
lfsr_mdir_lookup(&lfs, &mdir, LFSR_TAG_REG, &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'a');
|
|
|
|
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_ckpoint(&lfs);
|
|
lfsr_data_t data;
|
|
|
|
// prepare mroot with an entry
|
|
uint8_t buffer[SIZE];
|
|
buffer[0] = '\0';
|
|
memset(buffer+1, 'a', SIZE-1);
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL, NULL) => LFS_ERR_NOENT;
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_REG, +1, LFSR_DATA_BUF(buffer, SIZE)))) => 0;
|
|
assert(mdir.rbyd.weight == 2);
|
|
|
|
// force mroot to compact twice, this should extend the mroot
|
|
lfsr_mdir_t old_mroot = lfs.mroot;
|
|
lfs.mroot.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
lfs.mroot.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
// assert we relocated
|
|
assert(lfsr_mdir_cmp(&old_mroot, &lfs.mroot) != 0);
|
|
|
|
// force mroot to compact twice again, this should relocate the mroot
|
|
old_mroot = lfs.mroot;
|
|
lfs.mroot.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
lfs.mroot.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
// assert we relocated
|
|
assert(lfsr_mdir_cmp(&old_mroot, &lfs.mroot) != 0);
|
|
|
|
// assert that our entry is still in the mroot
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0*lfsr_mleafweight(&lfs)+1, &mdir) => 0;
|
|
assert(mdir.rbyd.weight == 2);
|
|
lfsr_mdir_lookup(&lfs, &mdir, LFSR_TAG_REG, &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'a');
|
|
|
|
// check things stay sane after remount
|
|
lfsr_unmount(&lfs) => 0;
|
|
lfsr_mount(&lfs, CFG) => 0;
|
|
|
|
// assert that our attr is still in the mroot
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0*lfsr_mleafweight(&lfs)+1, &mdir) => 0;
|
|
assert(mdir.rbyd.weight == 2);
|
|
lfsr_mdir_lookup(&lfs, &mdir, LFSR_TAG_REG, &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'a');
|
|
|
|
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_ckpoint(&lfs);
|
|
lfsr_data_t data;
|
|
|
|
// create 2 large entries that needs to be uninlined and split
|
|
uint8_t buffer[SIZE];
|
|
buffer[0] = '\0';
|
|
memset(buffer+1, 'a', SIZE-1);
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL, NULL) => LFS_ERR_NOENT;
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_REG, +1, LFSR_DATA_BUF(buffer, SIZE)))) => 0;
|
|
assert(mdir.rbyd.weight == 2);
|
|
|
|
memset(buffer+1, 'b', SIZE-1);
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL, NULL) => LFS_ERR_NOENT;
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_REG, +1, LFSR_DATA_BUF(buffer, SIZE)))) => 0;
|
|
assert(mdir.rbyd.weight == 3);
|
|
|
|
// force mroot to compact
|
|
lfs.mroot.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
|
|
// assert mdirs were unininlined and split
|
|
assert(lfsr_mtree_weight(&lfs.mtree) == 2*lfsr_mleafweight(&lfs));
|
|
// assert mroot now has no entries
|
|
assert(lfs.mroot.rbyd.weight == 0);
|
|
|
|
// setup mroot to compact and relocate on next commit
|
|
lfs.mroot.rbyd.eoff = -1;
|
|
lfsr_mdir_t old_mroot = lfs.mroot;
|
|
// force mdir to compact twice, this should relocate
|
|
memset(buffer+1, 'b', SIZE-1);
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL, NULL) => 0;
|
|
lfsr_mdir_t old_mdir = mdir;
|
|
mdir.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_REG, 0, LFSR_DATA_BUF(buffer, SIZE)))) => 0;
|
|
mdir.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_REG, 0, LFSR_DATA_BUF(buffer, SIZE)))) => 0;
|
|
// assert we relocated
|
|
assert(lfsr_mdir_cmp(&old_mdir, &mdir) != 0);
|
|
// assert mroot relocated
|
|
assert(lfsr_mdir_cmp(&old_mroot, &lfs.mroot) != 0);
|
|
|
|
// assert mdirs were unininlined and split
|
|
assert(lfsr_mtree_weight(&lfs.mtree) == 2*lfsr_mleafweight(&lfs));
|
|
// assert mroot now has no entries
|
|
assert(lfs.mroot.rbyd.weight == 0);
|
|
|
|
// assert that our entries are still in the mtree
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0*lfsr_mleafweight(&lfs)+1, &mdir) => 0;
|
|
assert(mdir.rbyd.weight == 2);
|
|
lfsr_mdir_lookup(&lfs, &mdir, LFSR_TAG_REG, &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'a');
|
|
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 1*lfsr_mleafweight(&lfs)+0, &mdir) => 0;
|
|
assert(mdir.rbyd.weight == 1);
|
|
lfsr_mdir_lookup(&lfs, &mdir, LFSR_TAG_REG, &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'b');
|
|
|
|
// check things stay sane after remount
|
|
lfsr_unmount(&lfs) => 0;
|
|
lfsr_mount(&lfs, CFG) => 0;
|
|
|
|
// assert mdirs were unininlined and split
|
|
assert(lfsr_mtree_weight(&lfs.mtree) == 2*lfsr_mleafweight(&lfs));
|
|
// assert mroot now has no entries
|
|
assert(lfs.mroot.rbyd.weight == 0);
|
|
|
|
// assert that our entries are still in the mtree
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0*lfsr_mleafweight(&lfs)+1, &mdir) => 0;
|
|
assert(mdir.rbyd.weight == 2);
|
|
lfsr_mdir_lookup(&lfs, &mdir, LFSR_TAG_REG, &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'a');
|
|
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 1*lfsr_mleafweight(&lfs)+0, &mdir) => 0;
|
|
assert(mdir.rbyd.weight == 1);
|
|
lfsr_mdir_lookup(&lfs, &mdir, LFSR_TAG_REG, &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'b');
|
|
|
|
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_ckpoint(&lfs);
|
|
lfsr_data_t data;
|
|
|
|
// create 2 large entries that needs to be uninlined and split
|
|
uint8_t buffer[SIZE];
|
|
buffer[0] = '\0';
|
|
memset(buffer+1, 'a', SIZE-1);
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL, NULL) => LFS_ERR_NOENT;
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_REG, +1, LFSR_DATA_BUF(buffer, SIZE)))) => 0;
|
|
assert(mdir.rbyd.weight == 2);
|
|
|
|
memset(buffer+1, 'b', SIZE-1);
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL, NULL) => LFS_ERR_NOENT;
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_REG, +1, LFSR_DATA_BUF(buffer, SIZE)))) => 0;
|
|
assert(mdir.rbyd.weight == 3);
|
|
|
|
// force mroot to compact
|
|
lfs.mroot.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
|
|
// assert mdirs were unininlined and split
|
|
assert(lfsr_mtree_weight(&lfs.mtree) == 2*lfsr_mleafweight(&lfs));
|
|
// assert mroot now has no entries
|
|
assert(lfs.mroot.rbyd.weight == 0);
|
|
|
|
// now add another large entry to an mdir, forcing a split
|
|
memset(buffer+1, 'c', SIZE-1);
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL, NULL) => LFS_ERR_NOENT;
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_REG, +1, LFSR_DATA_BUF(buffer, SIZE)))) => 0;
|
|
assert(mdir.rbyd.weight == 2);
|
|
|
|
// setup mroot to compact and relocate on next commit
|
|
lfs.mroot.rbyd.eoff = -1;
|
|
lfsr_mdir_t old_mroot = lfs.mroot;
|
|
// force mdir to compact twice, this should relocate
|
|
lfsr_mdir_t old_mdir = mdir;
|
|
mdir.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_REG, 0, LFSR_DATA_BUF(buffer, SIZE)))) => 0;
|
|
mdir.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_REG, 0, LFSR_DATA_BUF(buffer, SIZE)))) => 0;
|
|
// assert we relocated
|
|
assert(lfsr_mdir_cmp(&old_mdir, &mdir) != 0);
|
|
// assert mroot relocated
|
|
assert(lfsr_mdir_cmp(&old_mroot, &lfs.mroot) != 0);
|
|
|
|
// assert mdir was split correctly
|
|
assert(lfsr_mtree_weight(&lfs.mtree) == 3*lfsr_mleafweight(&lfs));
|
|
// assert mroot still has no entries
|
|
assert(lfs.mroot.rbyd.weight == 0);
|
|
|
|
// assert that our entries are still in the mtree
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0*lfsr_mleafweight(&lfs)+1, &mdir) => 0;
|
|
assert(mdir.rbyd.weight == 2);
|
|
lfsr_mdir_lookup(&lfs, &mdir, LFSR_TAG_REG, &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'a');
|
|
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 1*lfsr_mleafweight(&lfs)+0, &mdir) => 0;
|
|
assert(mdir.rbyd.weight == 1);
|
|
lfsr_mdir_lookup(&lfs, &mdir, LFSR_TAG_REG, &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'b');
|
|
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 2*lfsr_mleafweight(&lfs)+0, &mdir) => 0;
|
|
assert(mdir.rbyd.weight == 1);
|
|
lfsr_mdir_lookup(&lfs, &mdir, LFSR_TAG_REG, &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'c');
|
|
|
|
// check things stay sane after remount
|
|
lfsr_unmount(&lfs) => 0;
|
|
lfsr_mount(&lfs, CFG) => 0;
|
|
|
|
// assert mdir was split correctly
|
|
assert(lfsr_mtree_weight(&lfs.mtree) == 3*lfsr_mleafweight(&lfs));
|
|
// assert mroot still has no entries
|
|
assert(lfs.mroot.rbyd.weight == 0);
|
|
|
|
// assert that our entries are still in the mtree
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0*lfsr_mleafweight(&lfs)+1, &mdir) => 0;
|
|
assert(mdir.rbyd.weight == 2);
|
|
lfsr_mdir_lookup(&lfs, &mdir, LFSR_TAG_REG, &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'a');
|
|
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 1*lfsr_mleafweight(&lfs)+0, &mdir) => 0;
|
|
assert(mdir.rbyd.weight == 1);
|
|
lfsr_mdir_lookup(&lfs, &mdir, LFSR_TAG_REG, &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'b');
|
|
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 2*lfsr_mleafweight(&lfs)+0, &mdir) => 0;
|
|
assert(mdir.rbyd.weight == 1);
|
|
lfsr_mdir_lookup(&lfs, &mdir, LFSR_TAG_REG, &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'c');
|
|
|
|
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_ckpoint(&lfs);
|
|
lfsr_data_t data;
|
|
|
|
// create 2 large entries that needs to be uninlined and split
|
|
uint8_t buffer[SIZE];
|
|
buffer[0] = '\0';
|
|
memset(buffer+1, 'a', SIZE-1);
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL, NULL) => LFS_ERR_NOENT;
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_REG, +1, LFSR_DATA_BUF(buffer, SIZE)))) => 0;
|
|
assert(mdir.rbyd.weight == 2);
|
|
|
|
memset(buffer+1, 'b', SIZE-1);
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL, NULL) => LFS_ERR_NOENT;
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_REG, +1, LFSR_DATA_BUF(buffer, SIZE)))) => 0;
|
|
assert(mdir.rbyd.weight == 3);
|
|
|
|
// force mroot to compact
|
|
lfs.mroot.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
|
|
// assert mdirs were unininlined and split
|
|
assert(lfsr_mtree_weight(&lfs.mtree) == 2*lfsr_mleafweight(&lfs));
|
|
// assert mroot now has no entries
|
|
assert(lfs.mroot.rbyd.weight == 0);
|
|
|
|
// setup mroot to compact and relocate on next commit
|
|
lfs.mroot.rbyd.eoff = -1;
|
|
lfsr_mdir_t old_mroot = lfs.mroot;
|
|
// remove an entry, forcing the mdir to be dropped
|
|
memset(buffer+1, 'b', SIZE-1);
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL, NULL) => 0;
|
|
// force mdir to compact twice, this should relocate
|
|
mdir.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_REG, 0, LFSR_DATA_BUF(buffer, SIZE)))) => 0;
|
|
// force mdir to compact while we're removing
|
|
mdir.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_RM, -1, LFSR_DATA_NULL()))) => 0;
|
|
assert(mdir.rbyd.weight == 0);
|
|
// assert mroot relocated
|
|
assert(lfsr_mdir_cmp(&old_mroot, &lfs.mroot) != 0);
|
|
|
|
// assert mdir was dropped
|
|
assert(lfsr_mtree_weight(&lfs.mtree) == 1*lfsr_mleafweight(&lfs));
|
|
// assert mroot still has no entries
|
|
assert(lfs.mroot.rbyd.weight == 0);
|
|
|
|
// assert that our entries are still in the mtree
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0*lfsr_mleafweight(&lfs)+1, &mdir) => 0;
|
|
assert(mdir.rbyd.weight == 2);
|
|
lfsr_mdir_lookup(&lfs, &mdir, LFSR_TAG_REG, &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'a');
|
|
|
|
// check things stay sane after remount
|
|
lfsr_unmount(&lfs) => 0;
|
|
lfsr_mount(&lfs, CFG) => 0;
|
|
|
|
// assert mdir was dropped
|
|
assert(lfsr_mtree_weight(&lfs.mtree) == 1*lfsr_mleafweight(&lfs));
|
|
// assert mroot still has no entries
|
|
assert(lfs.mroot.rbyd.weight == 0);
|
|
|
|
// assert that our entries are still in the mtree
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0*lfsr_mleafweight(&lfs)+1, &mdir) => 0;
|
|
assert(mdir.rbyd.weight == 2);
|
|
lfsr_mdir_lookup(&lfs, &mdir, LFSR_TAG_REG, &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'a');
|
|
|
|
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_ckpoint(&lfs);
|
|
|
|
// force mroot to compact once, so the second compact below will
|
|
// trigger a relocation
|
|
lfs.mroot.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
lfsr_data_t data;
|
|
|
|
// create a 2 large attrs that needs to be uninlined
|
|
uint8_t buffer[SIZE];
|
|
memset(buffer, 'a', SIZE);
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_UATTR(1), 0, LFSR_DATA_BUF(buffer, SIZE)))) => 0;
|
|
|
|
memset(buffer, 'b', SIZE);
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0, &mdir) => 0;
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_UATTR(1), 0, LFSR_DATA_BUF(buffer, SIZE)))) => 0;
|
|
|
|
// force mroot to compact, this should both uninline and relocate
|
|
lfsr_mdir_t old_mroot = lfs.mroot;
|
|
lfs.mroot.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
// assert mroot relocated
|
|
assert(lfsr_mdir_cmp(&old_mroot, &lfs.mroot) != 0);
|
|
|
|
// assert mdirs were unininlined
|
|
assert(lfsr_mtree_weight(&lfs.mtree) == 1*lfsr_mleafweight(&lfs));
|
|
// assert mroot now has no entries
|
|
assert(lfs.mroot.rbyd.weight == 0);
|
|
|
|
// assert that our attrs are still in the mroot/mtree
|
|
lfsr_mdir_lookup(&lfs, &lfs.mroot, LFSR_TAG_UATTR(1), &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[0] == 'a');
|
|
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0, &mdir) => 0;
|
|
assert(mdir.rbyd.weight == 1);
|
|
lfsr_mdir_lookup(&lfs, &mdir, LFSR_TAG_UATTR(1), &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[0] == 'b');
|
|
|
|
// check things stay sane after remount
|
|
lfsr_unmount(&lfs) => 0;
|
|
lfsr_mount(&lfs, CFG) => 0;
|
|
|
|
// assert mdirs were unininlined
|
|
assert(lfsr_mtree_weight(&lfs.mtree) == 1*lfsr_mleafweight(&lfs));
|
|
// assert mroot now has no entries
|
|
assert(lfs.mroot.rbyd.weight == 0);
|
|
|
|
// assert that our attrs are still in the mroot/mtree
|
|
lfsr_mdir_lookup(&lfs, &lfs.mroot, LFSR_TAG_UATTR(1), &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[0] == 'a');
|
|
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0, &mdir) => 0;
|
|
assert(mdir.rbyd.weight == 1);
|
|
lfsr_mdir_lookup(&lfs, &mdir, LFSR_TAG_UATTR(1), &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[0] == 'b');
|
|
|
|
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_ckpoint(&lfs);
|
|
|
|
// force mroot to compact once, so the second compact below will
|
|
// trigger a relocation
|
|
lfs.mroot.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
lfsr_data_t data;
|
|
|
|
// create 2 large entries that needs to be uninlined and split
|
|
uint8_t buffer[SIZE];
|
|
buffer[0] = '\0';
|
|
memset(buffer+1, 'a', SIZE-1);
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL, NULL) => LFS_ERR_NOENT;
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_REG, +1, LFSR_DATA_BUF(buffer, SIZE)))) => 0;
|
|
assert(mdir.rbyd.weight == 2);
|
|
|
|
memset(buffer+1, 'b', SIZE-1);
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL, NULL) => LFS_ERR_NOENT;
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_REG, +1, LFSR_DATA_BUF(buffer, SIZE)))) => 0;
|
|
assert(mdir.rbyd.weight == 3);
|
|
|
|
// force mroot to compact, this should both split and relocate
|
|
lfsr_mdir_t old_mroot = lfs.mroot;
|
|
lfs.mroot.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
// assert mroot relocated
|
|
assert(lfsr_mdir_cmp(&old_mroot, &lfs.mroot) != 0);
|
|
|
|
// assert mdirs were unininlined and split
|
|
assert(lfsr_mtree_weight(&lfs.mtree) == 2*lfsr_mleafweight(&lfs));
|
|
// assert mroot now has no entries
|
|
assert(lfs.mroot.rbyd.weight == 0);
|
|
|
|
// assert that our entries are still in the mtree
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0*lfsr_mleafweight(&lfs)+1, &mdir) => 0;
|
|
assert(mdir.rbyd.weight == 2);
|
|
lfsr_mdir_lookup(&lfs, &mdir, LFSR_TAG_REG, &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'a');
|
|
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 1*lfsr_mleafweight(&lfs)+0, &mdir) => 0;
|
|
assert(mdir.rbyd.weight == 1);
|
|
lfsr_mdir_lookup(&lfs, &mdir, LFSR_TAG_REG, &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'b');
|
|
|
|
// check things stay sane after remount
|
|
lfsr_unmount(&lfs) => 0;
|
|
lfsr_mount(&lfs, CFG) => 0;
|
|
|
|
// assert mdirs were unininlined and split
|
|
assert(lfsr_mtree_weight(&lfs.mtree) == 2*lfsr_mleafweight(&lfs));
|
|
// assert mroot now has no entries
|
|
assert(lfs.mroot.rbyd.weight == 0);
|
|
|
|
// assert that our entries are still in the mtree
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0*lfsr_mleafweight(&lfs)+1, &mdir) => 0;
|
|
assert(mdir.rbyd.weight == 2);
|
|
lfsr_mdir_lookup(&lfs, &mdir, LFSR_TAG_REG, &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'a');
|
|
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 1*lfsr_mleafweight(&lfs)+0, &mdir) => 0;
|
|
assert(mdir.rbyd.weight == 1);
|
|
lfsr_mdir_lookup(&lfs, &mdir, LFSR_TAG_REG, &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'b');
|
|
|
|
lfsr_unmount(&lfs) => 0;
|
|
'''
|
|
|
|
# this fuzz covers a lot of configurations
|
|
[cases.test_mtree_relocate_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 = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, CFG) => 0;
|
|
lfsr_mount(&lfs, CFG) => 0;
|
|
lfs_alloc_ckpoint(&lfs);
|
|
|
|
bool sim[N];
|
|
for (lfs_size_t i = 0; i < N; i++) {
|
|
sim[i] = false;
|
|
}
|
|
|
|
uint32_t prng = SEED;
|
|
for (lfs_size_t i = 0; i < N; i++) {
|
|
// choose a pseudo-random name
|
|
lfs_size_t x = TEST_PRNG(&prng) % N;
|
|
char name[256];
|
|
name[0] = '\0';
|
|
sprintf(name+1, "%03x", x);
|
|
// choose to create or delete
|
|
uint8_t op = TEST_PRNG(&prng) % 2;
|
|
|
|
// create
|
|
if (op == 0) {
|
|
// update sim
|
|
sim[x] = true;
|
|
|
|
// update mtree
|
|
lfsr_mdir_t mdir;
|
|
int err = lfsr_mtree_namelookup(&lfs, &lfs.mtree,
|
|
0, (const char*)name+1, 3,
|
|
&mdir, NULL, NULL);
|
|
assert(!err || err == LFS_ERR_NOENT);
|
|
if (!err) {
|
|
continue;
|
|
}
|
|
// force a compaction?
|
|
if (FORCE_COMPACTION) {
|
|
lfs.mroot.rbyd.eoff = -1;
|
|
mdir.rbyd.eoff = -1;
|
|
}
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_REG, +1, LFSR_DATA_BUF(name, 4)))) => 0;
|
|
lfsr_data_t data;
|
|
|
|
// double check
|
|
uint8_t buffer[256];
|
|
lfsr_mdir_lookup(&lfs, &mdir, LFSR_TAG_REG, &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, sizeof(buffer)) => 4;
|
|
assert(memcmp(buffer, name, 4) == 0);
|
|
|
|
// update
|
|
} else if (op == 1) {
|
|
// sim update is a noop
|
|
|
|
// update mtree
|
|
lfsr_mdir_t mdir;
|
|
int err = lfsr_mtree_namelookup(&lfs, &lfs.mtree,
|
|
0, (const char*)name+1, 3,
|
|
&mdir, NULL, NULL);
|
|
assert(!err || err == LFS_ERR_NOENT);
|
|
if (err == LFS_ERR_NOENT) {
|
|
continue;
|
|
}
|
|
// force a compaction?
|
|
if (FORCE_COMPACTION) {
|
|
lfs.mroot.rbyd.eoff = -1;
|
|
mdir.rbyd.eoff = -1;
|
|
}
|
|
// we can't really change metadata names, but commits still
|
|
// trigger writes to the mdir
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_REG, 0, LFSR_DATA_BUF(name, 4)))) => 0;
|
|
lfsr_data_t data;
|
|
|
|
// double check
|
|
uint8_t buffer[256];
|
|
lfsr_mdir_lookup(&lfs, &mdir, LFSR_TAG_REG, &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, sizeof(buffer)) => 4;
|
|
assert(memcmp(buffer, name, 4) == 0);
|
|
|
|
// delete
|
|
} else {
|
|
// update sim
|
|
sim[x] = false;
|
|
|
|
// update mtree
|
|
lfsr_mdir_t mdir;
|
|
int err = lfsr_mtree_namelookup(&lfs, &lfs.mtree,
|
|
0, (const char*)name+1, 3,
|
|
&mdir, NULL, NULL);
|
|
assert(!err || err == LFS_ERR_NOENT);
|
|
if (err == LFS_ERR_NOENT) {
|
|
continue;
|
|
}
|
|
// force a compaction?
|
|
if (FORCE_COMPACTION) {
|
|
lfs.mroot.rbyd.eoff = -1;
|
|
mdir.rbyd.eoff = -1;
|
|
}
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_RM, -1, LFSR_DATA_NULL()))) => 0;
|
|
}
|
|
}
|
|
|
|
// try looking up each entry
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0, &mdir) => 0;
|
|
lfsr_data_t data;
|
|
uint8_t buffer[256];
|
|
lfsr_mdir_lookup(&lfs, &mdir, LFSR_TAG_BOOKMARK, &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, sizeof(buffer)) => 1;
|
|
for (lfs_size_t i = 0; i < N; i++) {
|
|
char name[256];
|
|
name[0] = '\0';
|
|
sprintf(name+1, "%03x", i);
|
|
if (sim[i]) {
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, (const char*)name+1, 3,
|
|
&mdir, NULL, NULL) => 0;
|
|
lfsr_mdir_lookup(&lfs, &mdir, LFSR_TAG_REG, &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, sizeof(buffer)) => 4;
|
|
assert(memcmp(buffer, name, 4) == 0);
|
|
} else {
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, (const char*)name+1, 3,
|
|
&mdir, NULL, NULL) => LFS_ERR_NOENT;
|
|
}
|
|
}
|
|
|
|
// check things stay sane after remount
|
|
lfsr_unmount(&lfs) => 0;
|
|
lfsr_mount(&lfs, CFG) => 0;
|
|
|
|
// try looking up each entry
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0, &mdir) => 0;
|
|
lfsr_mdir_lookup(&lfs, &mdir, LFSR_TAG_BOOKMARK, &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, sizeof(buffer)) => 1;
|
|
for (lfs_size_t i = 0; i < N; i++) {
|
|
char name[256];
|
|
name[0] = '\0';
|
|
sprintf(name+1, "%03x", i);
|
|
if (sim[i]) {
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, (const char*)name+1, 3,
|
|
&mdir, NULL, NULL) => 0;
|
|
lfsr_mdir_lookup(&lfs, &mdir, LFSR_TAG_REG, &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, sizeof(buffer)) => 4;
|
|
assert(memcmp(buffer, name, 4) == 0);
|
|
} else {
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, (const char*)name+1, 3,
|
|
&mdir, NULL, NULL) => LFS_ERR_NOENT;
|
|
}
|
|
}
|
|
|
|
lfsr_unmount(&lfs) => 0;
|
|
'''
|
|
|
|
|
|
## Opened mdir tracking ##
|
|
|
|
[cases.test_mtree_opened]
|
|
in = 'lfs.c'
|
|
code = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, CFG) => 0;
|
|
lfsr_mount(&lfs, CFG) => 0;
|
|
lfs_alloc_ckpoint(&lfs);
|
|
|
|
// setup our neighbors
|
|
lfsr_opened_t left = {.type=0};
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, "a", 1,
|
|
&left.mdir, NULL, NULL) => LFS_ERR_NOENT;
|
|
lfsr_mdir_commit(&lfs, &left.mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_REG, +1, LFSR_DATA_BUF("\0a", 2)))) => 0;
|
|
assert(left.mdir.rbyd.weight == 2);
|
|
lfsr_opened_add(&lfs, &left);
|
|
|
|
lfsr_opened_t right = {.type=0};
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, "c", 1,
|
|
&right.mdir, NULL, NULL) => LFS_ERR_NOENT;
|
|
lfsr_mdir_commit(&lfs, &right.mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_REG, +1, LFSR_DATA_BUF("\0c", 2)))) => 0;
|
|
assert(right.mdir.rbyd.weight == 3);
|
|
lfsr_opened_add(&lfs, &right);
|
|
|
|
// insert a new entry, this should update our neighbors
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, "b", 1,
|
|
&mdir, NULL, NULL) => LFS_ERR_NOENT;
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_REG, +1, LFSR_DATA_BUF("\0b", 2)))) => 0;
|
|
assert(mdir.rbyd.weight == 4);
|
|
lfsr_data_t data;
|
|
|
|
// assert our entry was created
|
|
uint8_t buffer[2];
|
|
lfsr_mdir_lookup(&lfs, &mdir, LFSR_TAG_REG, &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, 2) => 2;
|
|
assert(buffer[1] == 'b');
|
|
|
|
// assert that our neighbors were updated correctly
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, "a", 1,
|
|
&mdir, NULL, NULL) => 0;
|
|
assert(left.mdir.mid == mdir.mid);
|
|
assert(lfsr_mdir_cmp(&left.mdir, &mdir) == 0);
|
|
assert(left.mdir.rbyd.trunk == mdir.rbyd.trunk);
|
|
assert(left.mdir.rbyd.cksum == mdir.rbyd.cksum);
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, "c", 1,
|
|
&mdir, NULL, NULL) => 0;
|
|
assert(right.mdir.mid == mdir.mid);
|
|
assert(lfsr_mdir_cmp(&right.mdir, &mdir) == 0);
|
|
assert(right.mdir.rbyd.trunk == mdir.rbyd.trunk);
|
|
assert(right.mdir.rbyd.cksum == mdir.rbyd.cksum);
|
|
|
|
lfsr_opened_remove(&lfs, &left);
|
|
lfsr_opened_remove(&lfs, &right);
|
|
lfsr_unmount(&lfs) => 0;
|
|
'''
|
|
|
|
[cases.test_mtree_opened_remove_l]
|
|
in = 'lfs.c'
|
|
code = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, CFG) => 0;
|
|
lfsr_mount(&lfs, CFG) => 0;
|
|
lfs_alloc_ckpoint(&lfs);
|
|
|
|
// setup our neighbors
|
|
lfsr_opened_t left = {.type=0};
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, "a", 1,
|
|
&left.mdir, NULL, NULL) => LFS_ERR_NOENT;
|
|
lfsr_mdir_commit(&lfs, &left.mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_REG, +1, LFSR_DATA_BUF("\0a", 2)))) => 0;
|
|
assert(left.mdir.rbyd.weight == 2);
|
|
lfsr_opened_add(&lfs, &left);
|
|
|
|
lfsr_opened_t right = {.type=0};
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, "b", 1,
|
|
&right.mdir, NULL, NULL) => LFS_ERR_NOENT;
|
|
lfsr_mdir_commit(&lfs, &right.mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_REG, +1, LFSR_DATA_BUF("\0b", 2)))) => 0;
|
|
assert(right.mdir.rbyd.weight == 3);
|
|
lfsr_opened_add(&lfs, &right);
|
|
|
|
// try removing left neighbor
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, "a", 1,
|
|
&mdir, NULL, NULL) => 0;
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_RM, -1, LFSR_DATA_NULL()))) => 0;
|
|
assert(mdir.rbyd.weight == 2);
|
|
|
|
// assert neighbor was removed
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, "a", 1,
|
|
&mdir, NULL, NULL) => LFS_ERR_NOENT;
|
|
|
|
// assert that our neighbors were updated correctly
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, "b", 1,
|
|
&mdir, NULL, NULL) => 0;
|
|
assert(right.mdir.mid == mdir.mid);
|
|
assert(lfsr_mdir_cmp(&right.mdir, &mdir) == 0);
|
|
assert(right.mdir.rbyd.trunk == mdir.rbyd.trunk);
|
|
assert(right.mdir.rbyd.cksum == mdir.rbyd.cksum);
|
|
|
|
lfsr_opened_remove(&lfs, &left);
|
|
lfsr_opened_remove(&lfs, &right);
|
|
lfsr_unmount(&lfs) => 0;
|
|
'''
|
|
|
|
[cases.test_mtree_opened_remove_r]
|
|
in = 'lfs.c'
|
|
code = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, CFG) => 0;
|
|
lfsr_mount(&lfs, CFG) => 0;
|
|
lfs_alloc_ckpoint(&lfs);
|
|
|
|
// setup our neighbors
|
|
lfsr_opened_t left = {.type=0};
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, "a", 1,
|
|
&left.mdir, NULL, NULL) => LFS_ERR_NOENT;
|
|
lfsr_mdir_commit(&lfs, &left.mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_REG, +1, LFSR_DATA_BUF("\0a", 2)))) => 0;
|
|
assert(left.mdir.rbyd.weight == 2);
|
|
lfsr_opened_add(&lfs, &left);
|
|
|
|
lfsr_opened_t right = {.type=0};
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, "b", 1,
|
|
&right.mdir, NULL, NULL) => LFS_ERR_NOENT;
|
|
lfsr_mdir_commit(&lfs, &right.mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_REG, +1, LFSR_DATA_BUF("\0b", 2)))) => 0;
|
|
assert(right.mdir.rbyd.weight == 3);
|
|
lfsr_opened_add(&lfs, &right);
|
|
|
|
// try removing right neighbor
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, "b", 1,
|
|
&mdir, NULL, NULL) => 0;
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_RM, -1, LFSR_DATA_NULL()))) => 0;
|
|
assert(mdir.rbyd.weight == 2);
|
|
|
|
// assert neighbor was removed
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, "b", 1,
|
|
&mdir, NULL, NULL) => LFS_ERR_NOENT;
|
|
|
|
// assert that our neighbors were updated correctly
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, "a", 1,
|
|
&mdir, NULL, NULL) => 0;
|
|
assert(left.mdir.mid == mdir.mid);
|
|
assert(lfsr_mdir_cmp(&left.mdir, &mdir) == 0);
|
|
assert(left.mdir.rbyd.trunk == mdir.rbyd.trunk);
|
|
assert(left.mdir.rbyd.cksum == mdir.rbyd.cksum);
|
|
|
|
lfsr_opened_remove(&lfs, &left);
|
|
lfsr_opened_remove(&lfs, &right);
|
|
lfsr_unmount(&lfs) => 0;
|
|
'''
|
|
|
|
[cases.test_mtree_opened_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_ckpoint(&lfs);
|
|
|
|
// setup our neighbors
|
|
lfsr_opened_t left = {.type=0};
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, "a", 1,
|
|
&left.mdir, NULL, NULL) => LFS_ERR_NOENT;
|
|
lfsr_mdir_commit(&lfs, &left.mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_REG, +1, LFSR_DATA_BUF("\0a", 2)))) => 0;
|
|
assert(left.mdir.rbyd.weight == 2);
|
|
lfsr_opened_add(&lfs, &left);
|
|
|
|
lfsr_opened_t right = {.type=0};
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, "d", 1,
|
|
&right.mdir, NULL, NULL) => LFS_ERR_NOENT;
|
|
lfsr_mdir_commit(&lfs, &right.mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_REG, +1, LFSR_DATA_BUF("\0d", 2)))) => 0;
|
|
assert(right.mdir.rbyd.weight == 3);
|
|
lfsr_opened_add(&lfs, &right);
|
|
|
|
// create 2 large entries that needs to be uninlined and split
|
|
uint8_t buffer[SIZE];
|
|
buffer[0] = '\0';
|
|
memset(buffer+1, 'b', SIZE-1);
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL, NULL) => LFS_ERR_NOENT;
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_REG, +1, LFSR_DATA_BUF(buffer, SIZE)))) => 0;
|
|
assert(mdir.rbyd.weight == 4);
|
|
|
|
memset(buffer+1, 'c', SIZE-1);
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL, NULL) => LFS_ERR_NOENT;
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_REG, +1, LFSR_DATA_BUF(buffer, SIZE)))) => 0;
|
|
assert(mdir.rbyd.weight == 5);
|
|
|
|
// force mroot to compact
|
|
lfs.mroot.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
|
|
// assert mdirs were unininlined and split
|
|
assert(lfsr_mtree_weight(&lfs.mtree) == 2*lfsr_mleafweight(&lfs));
|
|
// assert mroot now has no entries
|
|
assert(lfs.mroot.rbyd.weight == 0);
|
|
|
|
// assert that our neighbors were updated correctly
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, "a", 1,
|
|
&mdir, NULL, NULL) => 0;
|
|
assert(left.mdir.mid == mdir.mid);
|
|
assert(lfsr_mdir_cmp(&left.mdir, &mdir) == 0);
|
|
assert(left.mdir.rbyd.trunk == mdir.rbyd.trunk);
|
|
assert(left.mdir.rbyd.cksum == mdir.rbyd.cksum);
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, "d", 1,
|
|
&mdir, NULL, NULL) => 0;
|
|
assert(right.mdir.mid == mdir.mid);
|
|
assert(lfsr_mdir_cmp(&right.mdir, &mdir) == 0);
|
|
assert(right.mdir.rbyd.trunk == mdir.rbyd.trunk);
|
|
assert(right.mdir.rbyd.cksum == mdir.rbyd.cksum);
|
|
|
|
lfsr_opened_remove(&lfs, &left);
|
|
lfsr_opened_remove(&lfs, &right);
|
|
lfsr_unmount(&lfs) => 0;
|
|
'''
|
|
|
|
[cases.test_mtree_opened_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_ckpoint(&lfs);
|
|
|
|
// setup our neighbors
|
|
lfsr_opened_t left = {.type=0};
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, "a", 1,
|
|
&left.mdir, NULL, NULL) => LFS_ERR_NOENT;
|
|
lfsr_mdir_commit(&lfs, &left.mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_REG, +1, LFSR_DATA_BUF("\0a", 2)))) => 0;
|
|
assert(left.mdir.rbyd.weight == 2);
|
|
lfsr_opened_add(&lfs, &left);
|
|
|
|
lfsr_opened_t right = {.type=0};
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, "e", 1,
|
|
&right.mdir, NULL, NULL) => LFS_ERR_NOENT;
|
|
lfsr_mdir_commit(&lfs, &right.mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_REG, +1, LFSR_DATA_BUF("\0e", 2)))) => 0;
|
|
assert(right.mdir.rbyd.weight == 3);
|
|
lfsr_opened_add(&lfs, &right);
|
|
|
|
// create 2 large entries that needs to be uninlined and split
|
|
uint8_t buffer[SIZE];
|
|
buffer[0] = '\0';
|
|
memset(buffer+1, 'b', SIZE-1);
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL, NULL) => LFS_ERR_NOENT;
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_REG, +1, LFSR_DATA_BUF(buffer, SIZE)))) => 0;
|
|
assert(mdir.rbyd.weight == 4);
|
|
|
|
memset(buffer+1, 'd', SIZE-1);
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL, NULL) => LFS_ERR_NOENT;
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_REG, +1, LFSR_DATA_BUF(buffer, SIZE)))) => 0;
|
|
assert(mdir.rbyd.weight == 5);
|
|
|
|
// force mroot to compact
|
|
lfs.mroot.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
|
|
// now add another large entry to an mdir, forcing a split
|
|
memset(buffer+1, 'c', SIZE-1);
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL, NULL) => LFS_ERR_NOENT;
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_REG, +1, LFSR_DATA_BUF(buffer, SIZE)))) => 0;
|
|
assert(mdir.rbyd.weight == 4);
|
|
|
|
// force mdir to compact
|
|
mdir.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &mdir, NULL, 0) => 0;
|
|
|
|
// assert mdir was split correctly
|
|
assert(lfsr_mtree_weight(&lfs.mtree) == 3*lfsr_mleafweight(&lfs));
|
|
// assert mroot still has no entries
|
|
assert(lfs.mroot.rbyd.weight == 0);
|
|
|
|
// assert that our neighbors were updated correctly
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, "a", 1,
|
|
&mdir, NULL, NULL) => 0;
|
|
assert(left.mdir.mid == mdir.mid);
|
|
assert(lfsr_mdir_cmp(&left.mdir, &mdir) == 0);
|
|
assert(left.mdir.rbyd.trunk == mdir.rbyd.trunk);
|
|
assert(left.mdir.rbyd.cksum == mdir.rbyd.cksum);
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, "e", 1,
|
|
&mdir, NULL, NULL) => 0;
|
|
assert(right.mdir.mid == mdir.mid);
|
|
assert(lfsr_mdir_cmp(&right.mdir, &mdir) == 0);
|
|
assert(right.mdir.rbyd.trunk == mdir.rbyd.trunk);
|
|
assert(right.mdir.rbyd.cksum == mdir.rbyd.cksum);
|
|
|
|
lfsr_opened_remove(&lfs, &left);
|
|
lfsr_opened_remove(&lfs, &right);
|
|
lfsr_unmount(&lfs) => 0;
|
|
'''
|
|
|
|
[cases.test_mtree_opened_extend]
|
|
# 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_ckpoint(&lfs);
|
|
|
|
// setup our neighbors
|
|
lfsr_opened_t left = {.type=0};
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, "a", 1,
|
|
&left.mdir, NULL, NULL) => LFS_ERR_NOENT;
|
|
lfsr_mdir_commit(&lfs, &left.mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_REG, +1, LFSR_DATA_BUF("\0a", 2)))) => 0;
|
|
assert(left.mdir.rbyd.weight == 2);
|
|
lfsr_opened_add(&lfs, &left);
|
|
|
|
lfsr_opened_t right = {.type=0};
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, "b", 1,
|
|
&right.mdir, NULL, NULL) => LFS_ERR_NOENT;
|
|
lfsr_mdir_commit(&lfs, &right.mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_REG, +1, LFSR_DATA_BUF("\0b", 2)))) => 0;
|
|
assert(right.mdir.rbyd.weight == 3);
|
|
lfsr_opened_add(&lfs, &right);
|
|
|
|
// force mroot to compact twice, this should extend the mroot
|
|
lfsr_mdir_t old_mroot = lfs.mroot;
|
|
lfs.mroot.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
lfs.mroot.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
// assert we relocated
|
|
assert(lfsr_mdir_cmp(&old_mroot, &lfs.mroot) != 0);
|
|
|
|
// assert that our neighbors were updated correctly
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, "a", 1,
|
|
&mdir, NULL, NULL) => 0;
|
|
assert(left.mdir.mid == mdir.mid);
|
|
assert(lfsr_mdir_cmp(&left.mdir, &mdir) == 0);
|
|
assert(left.mdir.rbyd.trunk == mdir.rbyd.trunk);
|
|
assert(left.mdir.rbyd.cksum == mdir.rbyd.cksum);
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, "b", 1,
|
|
&mdir, NULL, NULL) => 0;
|
|
assert(right.mdir.mid == mdir.mid);
|
|
assert(lfsr_mdir_cmp(&right.mdir, &mdir) == 0);
|
|
assert(right.mdir.rbyd.trunk == mdir.rbyd.trunk);
|
|
assert(right.mdir.rbyd.cksum == mdir.rbyd.cksum);
|
|
|
|
lfsr_opened_remove(&lfs, &left);
|
|
lfsr_opened_remove(&lfs, &right);
|
|
lfsr_unmount(&lfs) => 0;
|
|
'''
|
|
|
|
[cases.test_mtree_opened_relocate_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_ckpoint(&lfs);
|
|
|
|
// setup our neighbors
|
|
lfsr_opened_t left = {.type=0};
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, "a", 1,
|
|
&left.mdir, NULL, NULL) => LFS_ERR_NOENT;
|
|
lfsr_mdir_commit(&lfs, &left.mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_REG, +1, LFSR_DATA_BUF("\0a", 2)))) => 0;
|
|
assert(left.mdir.rbyd.weight == 2);
|
|
lfsr_opened_add(&lfs, &left);
|
|
|
|
lfsr_opened_t right = {.type=0};
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, "d", 1,
|
|
&right.mdir, NULL, NULL) => LFS_ERR_NOENT;
|
|
lfsr_mdir_commit(&lfs, &right.mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_REG, +1, LFSR_DATA_BUF("\0d", 2)))) => 0;
|
|
assert(right.mdir.rbyd.weight == 3);
|
|
lfsr_opened_add(&lfs, &right);
|
|
|
|
// create 2 large entries that needs to be uninlined and split
|
|
uint8_t buffer[SIZE];
|
|
buffer[0] = '\0';
|
|
memset(buffer+1, 'b', SIZE-1);
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL, NULL) => LFS_ERR_NOENT;
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_REG, +1, LFSR_DATA_BUF(buffer, SIZE)))) => 0;
|
|
assert(mdir.rbyd.weight == 4);
|
|
|
|
memset(buffer+1, 'c', SIZE-1);
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL, NULL) => LFS_ERR_NOENT;
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_REG, +1, LFSR_DATA_BUF(buffer, SIZE)))) => 0;
|
|
assert(mdir.rbyd.weight == 5);
|
|
|
|
// force mroot to compact
|
|
lfs.mroot.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
|
|
// assert mdirs were unininlined and split
|
|
assert(lfsr_mtree_weight(&lfs.mtree) == 2*lfsr_mleafweight(&lfs));
|
|
// assert mroot now has no entries
|
|
assert(lfs.mroot.rbyd.weight == 0);
|
|
|
|
// force mdir to compact twice, this should relocate
|
|
memset(buffer+1, 'b', SIZE-1);
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL, NULL) => 0;
|
|
lfsr_mdir_t old_mdir = mdir;
|
|
mdir.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_REG, 0, LFSR_DATA_BUF(buffer, SIZE)))) => 0;
|
|
mdir.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_REG, 0, LFSR_DATA_BUF(buffer, SIZE)))) => 0;
|
|
// assert we relocated
|
|
assert(lfsr_mdir_cmp(&old_mdir, &mdir) != 0);
|
|
|
|
// assert that our neighbors were updated correctly
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, "a", 1,
|
|
&mdir, NULL, NULL) => 0;
|
|
assert(left.mdir.mid == mdir.mid);
|
|
assert(lfsr_mdir_cmp(&left.mdir, &mdir) == 0);
|
|
assert(left.mdir.rbyd.trunk == mdir.rbyd.trunk);
|
|
assert(left.mdir.rbyd.cksum == mdir.rbyd.cksum);
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, "d", 1,
|
|
&mdir, NULL, NULL) => 0;
|
|
assert(right.mdir.mid == mdir.mid);
|
|
assert(lfsr_mdir_cmp(&right.mdir, &mdir) == 0);
|
|
assert(right.mdir.rbyd.trunk == mdir.rbyd.trunk);
|
|
assert(right.mdir.rbyd.cksum == mdir.rbyd.cksum);
|
|
|
|
lfsr_opened_remove(&lfs, &left);
|
|
lfsr_opened_remove(&lfs, &right);
|
|
lfsr_unmount(&lfs) => 0;
|
|
'''
|
|
|
|
[cases.test_mtree_opened_relocate_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_ckpoint(&lfs);
|
|
|
|
// setup our neighbors
|
|
lfsr_opened_t left = {.type=0};
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, "a", 1,
|
|
&left.mdir, NULL, NULL) => LFS_ERR_NOENT;
|
|
lfsr_mdir_commit(&lfs, &left.mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_REG, +1, LFSR_DATA_BUF("\0a", 2)))) => 0;
|
|
assert(left.mdir.rbyd.weight == 2);
|
|
lfsr_opened_add(&lfs, &left);
|
|
|
|
lfsr_opened_t right = {.type=0};
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, "d", 1,
|
|
&right.mdir, NULL, NULL) => LFS_ERR_NOENT;
|
|
lfsr_mdir_commit(&lfs, &right.mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_REG, +1, LFSR_DATA_BUF("\0d", 2)))) => 0;
|
|
assert(right.mdir.rbyd.weight == 3);
|
|
lfsr_opened_add(&lfs, &right);
|
|
|
|
// create 2 large entries that needs to be uninlined and split
|
|
uint8_t buffer[SIZE];
|
|
buffer[0] = '\0';
|
|
memset(buffer+1, 'b', SIZE-1);
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL, NULL) => LFS_ERR_NOENT;
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_REG, +1, LFSR_DATA_BUF(buffer, SIZE)))) => 0;
|
|
assert(mdir.rbyd.weight == 4);
|
|
|
|
memset(buffer+1, 'c', SIZE-1);
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL, NULL) => LFS_ERR_NOENT;
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_REG, +1, LFSR_DATA_BUF(buffer, SIZE)))) => 0;
|
|
assert(mdir.rbyd.weight == 5);
|
|
|
|
// force mroot to compact
|
|
lfs.mroot.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
|
|
// assert mdirs were unininlined and split
|
|
assert(lfsr_mtree_weight(&lfs.mtree) == 2*lfsr_mleafweight(&lfs));
|
|
// assert mroot now has no entries
|
|
assert(lfs.mroot.rbyd.weight == 0);
|
|
|
|
// force mdir to compact twice, this should relocate
|
|
memset(buffer+1, 'c', SIZE-1);
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL, NULL) => 0;
|
|
lfsr_mdir_t old_mdir = mdir;
|
|
mdir.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_REG, 0, LFSR_DATA_BUF(buffer, SIZE)))) => 0;
|
|
mdir.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_REG, 0, LFSR_DATA_BUF(buffer, SIZE)))) => 0;
|
|
// assert we relocated
|
|
assert(lfsr_mdir_cmp(&old_mdir, &mdir) != 0);
|
|
|
|
// assert that our neighbors were updated correctly
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, "a", 1,
|
|
&mdir, NULL, NULL) => 0;
|
|
assert(left.mdir.mid == mdir.mid);
|
|
assert(lfsr_mdir_cmp(&left.mdir, &mdir) == 0);
|
|
assert(left.mdir.rbyd.trunk == mdir.rbyd.trunk);
|
|
assert(left.mdir.rbyd.cksum == mdir.rbyd.cksum);
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, "d", 1,
|
|
&mdir, NULL, NULL) => 0;
|
|
assert(right.mdir.mid == mdir.mid);
|
|
assert(lfsr_mdir_cmp(&right.mdir, &mdir) == 0);
|
|
assert(right.mdir.rbyd.trunk == mdir.rbyd.trunk);
|
|
assert(right.mdir.rbyd.cksum == mdir.rbyd.cksum);
|
|
|
|
lfsr_opened_remove(&lfs, &left);
|
|
lfsr_opened_remove(&lfs, &right);
|
|
lfsr_unmount(&lfs) => 0;
|
|
'''
|
|
|
|
[cases.test_mtree_opened_middle_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_ckpoint(&lfs);
|
|
|
|
// setup our neighbors
|
|
lfsr_opened_t left = {.type=0};
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, "a", 1,
|
|
&left.mdir, NULL, NULL) => LFS_ERR_NOENT;
|
|
lfsr_mdir_commit(&lfs, &left.mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_REG, +1, LFSR_DATA_BUF("\0a", 2)))) => 0;
|
|
assert(left.mdir.rbyd.weight == 2);
|
|
lfsr_opened_add(&lfs, &left);
|
|
|
|
lfsr_opened_t right = {.type=0};
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, "f", 1,
|
|
&right.mdir, NULL, NULL) => LFS_ERR_NOENT;
|
|
lfsr_mdir_commit(&lfs, &right.mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_REG, +1, LFSR_DATA_BUF("\0f", 2)))) => 0;
|
|
assert(right.mdir.rbyd.weight == 3);
|
|
lfsr_opened_add(&lfs, &right);
|
|
|
|
// create 2 large entries that needs to be uninlined and split
|
|
uint8_t buffer[SIZE];
|
|
buffer[0] = '\0';
|
|
memset(buffer+1, 'b', SIZE-1);
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL, NULL) => LFS_ERR_NOENT;
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_REG, +1, LFSR_DATA_BUF(buffer, SIZE)))) => 0;
|
|
assert(mdir.rbyd.weight == 4);
|
|
|
|
memset(buffer+1, 'e', SIZE-1);
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL, NULL) => LFS_ERR_NOENT;
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_REG, +1, LFSR_DATA_BUF(buffer, SIZE)))) => 0;
|
|
assert(mdir.rbyd.weight == 5);
|
|
|
|
// force mroot to compact
|
|
lfs.mroot.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
|
|
// now add another large entry to an mdir, forcing a split
|
|
memset(buffer+1, 'c', SIZE-1);
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL, NULL) => LFS_ERR_NOENT;
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_REG, +1, LFSR_DATA_BUF(buffer, SIZE)))) => 0;
|
|
assert(mdir.rbyd.weight == 4);
|
|
|
|
// force mdir to compact
|
|
mdir.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &mdir, NULL, 0) => 0;
|
|
|
|
// assert mdir was split correctly
|
|
assert(lfsr_mtree_weight(&lfs.mtree) == 3*lfsr_mleafweight(&lfs));
|
|
// assert mroot still has no entries
|
|
assert(lfs.mroot.rbyd.weight == 0);
|
|
|
|
// now add _another_ large entry to the middle mdir, forcing another split
|
|
memset(buffer+1, 'd', SIZE-1);
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL, NULL) => LFS_ERR_NOENT;
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_REG, +1, LFSR_DATA_BUF(buffer, SIZE)))) => 0;
|
|
assert(mdir.rbyd.weight == 2);
|
|
|
|
// force mdir to compact
|
|
mdir.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &mdir, NULL, 0) => 0;
|
|
|
|
// assert mdir was split correctly
|
|
assert(lfsr_mtree_weight(&lfs.mtree) == 4*lfsr_mleafweight(&lfs));
|
|
// assert mroot still has no entries
|
|
assert(lfs.mroot.rbyd.weight == 0);
|
|
|
|
// assert that our neighbors were updated correctly
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, "a", 1,
|
|
&mdir, NULL, NULL) => 0;
|
|
assert(left.mdir.mid == mdir.mid);
|
|
assert(lfsr_mdir_cmp(&left.mdir, &mdir) == 0);
|
|
assert(left.mdir.rbyd.trunk == mdir.rbyd.trunk);
|
|
assert(left.mdir.rbyd.cksum == mdir.rbyd.cksum);
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, "f", 1,
|
|
&mdir, NULL, NULL) => 0;
|
|
assert(right.mdir.mid == mdir.mid);
|
|
assert(lfsr_mdir_cmp(&right.mdir, &mdir) == 0);
|
|
assert(right.mdir.rbyd.trunk == mdir.rbyd.trunk);
|
|
assert(right.mdir.rbyd.cksum == mdir.rbyd.cksum);
|
|
|
|
lfsr_opened_remove(&lfs, &left);
|
|
lfsr_opened_remove(&lfs, &right);
|
|
lfsr_unmount(&lfs) => 0;
|
|
'''
|
|
|
|
[cases.test_mtree_opened_middle_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_ckpoint(&lfs);
|
|
|
|
// setup our neighbors
|
|
lfsr_opened_t left = {.type=0};
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, "a", 1,
|
|
&left.mdir, NULL, NULL) => LFS_ERR_NOENT;
|
|
lfsr_mdir_commit(&lfs, &left.mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_REG, +1, LFSR_DATA_BUF("\0a", 2)))) => 0;
|
|
assert(left.mdir.rbyd.weight == 2);
|
|
lfsr_opened_add(&lfs, &left);
|
|
|
|
lfsr_opened_t right = {.type=0};
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, "e", 1,
|
|
&right.mdir, NULL, NULL) => LFS_ERR_NOENT;
|
|
lfsr_mdir_commit(&lfs, &right.mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_REG, +1, LFSR_DATA_BUF("\0e", 2)))) => 0;
|
|
assert(right.mdir.rbyd.weight == 3);
|
|
lfsr_opened_add(&lfs, &right);
|
|
|
|
// create 2 large entries that needs to be uninlined and split
|
|
uint8_t buffer[SIZE];
|
|
buffer[0] = '\0';
|
|
memset(buffer+1, 'b', SIZE-1);
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL, NULL) => LFS_ERR_NOENT;
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_REG, +1, LFSR_DATA_BUF(buffer, SIZE)))) => 0;
|
|
assert(mdir.rbyd.weight == 4);
|
|
|
|
memset(buffer+1, 'd', SIZE-1);
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL, NULL) => LFS_ERR_NOENT;
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_REG, +1, LFSR_DATA_BUF(buffer, SIZE)))) => 0;
|
|
assert(mdir.rbyd.weight == 5);
|
|
|
|
// force mroot to compact
|
|
lfs.mroot.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
|
|
// now add another large entry to an mdir, forcing a split
|
|
memset(buffer+1, 'c', SIZE-1);
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL, NULL) => LFS_ERR_NOENT;
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_REG, +1, LFSR_DATA_BUF(buffer, SIZE)))) => 0;
|
|
assert(mdir.rbyd.weight == 4);
|
|
|
|
// force mdir to compact
|
|
mdir.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &mdir, NULL, 0) => 0;
|
|
|
|
// assert mdir was split correctly
|
|
assert(lfsr_mtree_weight(&lfs.mtree) == 3*lfsr_mleafweight(&lfs));
|
|
// assert mroot still has no entries
|
|
assert(lfs.mroot.rbyd.weight == 0);
|
|
|
|
// now remove the middle entry, forcing a drop
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_RM, -1, LFSR_DATA_NULL()))) => 0;
|
|
assert(mdir.rbyd.weight == 0);
|
|
|
|
// assert mdir was dropped correctly
|
|
assert(lfsr_mtree_weight(&lfs.mtree) == 2*lfsr_mleafweight(&lfs));
|
|
// assert mroot still has no entries
|
|
assert(lfs.mroot.rbyd.weight == 0);
|
|
|
|
// assert that our neighbors were updated correctly
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, "a", 1,
|
|
&mdir, NULL, NULL) => 0;
|
|
assert(left.mdir.mid == mdir.mid);
|
|
assert(lfsr_mdir_cmp(&left.mdir, &mdir) == 0);
|
|
assert(left.mdir.rbyd.trunk == mdir.rbyd.trunk);
|
|
assert(left.mdir.rbyd.cksum == mdir.rbyd.cksum);
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, "e", 1,
|
|
&mdir, NULL, NULL) => 0;
|
|
assert(right.mdir.mid == mdir.mid);
|
|
assert(lfsr_mdir_cmp(&right.mdir, &mdir) == 0);
|
|
assert(right.mdir.rbyd.trunk == mdir.rbyd.trunk);
|
|
assert(right.mdir.rbyd.cksum == mdir.rbyd.cksum);
|
|
|
|
lfsr_opened_remove(&lfs, &left);
|
|
lfsr_opened_remove(&lfs, &right);
|
|
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_ckpoint(&lfs);
|
|
|
|
// insert at least one entry
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, "a", 1,
|
|
&mdir, NULL, NULL) => LFS_ERR_NOENT;
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_REG, +1, LFSR_DATA_BUF("\0a", 2)))) => 0;
|
|
assert(mdir.rbyd.weight == 2);
|
|
|
|
// test that we can traverse the tree, keeping track of all blocks we see
|
|
uint8_t *seen = malloc((BLOCK_COUNT+7)/8);
|
|
memset(seen, 0, (BLOCK_COUNT+7)/8);
|
|
|
|
lfsr_traversal_t traversal = LFSR_TRAVERSAL(
|
|
(VALIDATE) ? LFSR_TRAVERSAL_VALIDATE : 0);
|
|
for (lfs_block_t i = 0;; i++) {
|
|
// a bit hacky, but this catches infinite loops
|
|
assert(i < 2*BLOCK_COUNT);
|
|
|
|
lfsr_tinfo_t tinfo;
|
|
int err = lfsr_traversal_read(&lfs, &traversal, &tinfo);
|
|
assert(!err || err == LFS_ERR_NOENT);
|
|
if (err == LFS_ERR_NOENT) {
|
|
break;
|
|
}
|
|
|
|
if (tinfo.tag == LFSR_TAG_MDIR) {
|
|
printf("traversal: 0x%x mdir 0x{%x,%x}\n",
|
|
tinfo.tag,
|
|
tinfo.u.mdir.rbyd.blocks[0],
|
|
tinfo.u.mdir.rbyd.blocks[1]);
|
|
|
|
// keep track of seen blocks
|
|
seen[tinfo.u.mdir.rbyd.blocks[1] / 8]
|
|
|= 1 << (tinfo.u.mdir.rbyd.blocks[1] % 8);
|
|
seen[tinfo.u.mdir.rbyd.blocks[0] / 8]
|
|
|= 1 << (tinfo.u.mdir.rbyd.blocks[0] % 8);
|
|
|
|
} else if (tinfo.tag == LFSR_TAG_BRANCH) {
|
|
printf("traversal: 0x%x btree 0x%x.%x\n",
|
|
tinfo.tag,
|
|
tinfo.u.rbyd.blocks[0], tinfo.u.rbyd.trunk);
|
|
|
|
// keep track of seen blocks
|
|
seen[tinfo.u.rbyd.blocks[0] / 8]
|
|
|= 1 << (tinfo.u.rbyd.blocks[0] % 8);
|
|
|
|
} else {
|
|
// this shouldn't happen
|
|
printf("traversal: 0x%x\n", tinfo.tag);
|
|
assert(false);
|
|
}
|
|
}
|
|
|
|
// if traversal worked, we should be able to clobber all other blocks
|
|
uint8_t clobber_buf[BLOCK_SIZE];
|
|
memset(clobber_buf, 0xcc, BLOCK_SIZE);
|
|
for (lfs_block_t block = 0; block < BLOCK_COUNT; block++) {
|
|
if (!(seen[block / 8] & (1 << (block % 8)))) {
|
|
CFG->erase(CFG, block) => 0;
|
|
CFG->prog(CFG, block, 0, clobber_buf, BLOCK_SIZE) => 0;
|
|
}
|
|
}
|
|
free(seen);
|
|
lfsr_data_t data;
|
|
|
|
// and the tree should still work
|
|
|
|
// assert that our entry is still in the mtree
|
|
uint8_t buffer[256];
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, "a", 1,
|
|
&mdir, NULL, NULL) => 0;
|
|
lfsr_mdir_lookup(&lfs, &mdir, LFSR_TAG_REG, &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, sizeof(buffer)) => 2;
|
|
assert(memcmp(buffer, "\0a", 2) == 0);
|
|
|
|
// check things stay sane after remount
|
|
lfsr_unmount(&lfs) => 0;
|
|
lfsr_mount(&lfs, CFG) => 0;
|
|
|
|
// assert that our entry is still in the mtree
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, "a", 1,
|
|
&mdir, NULL, NULL) => 0;
|
|
lfsr_mdir_lookup(&lfs, &mdir, LFSR_TAG_REG, &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, sizeof(buffer)) => 2;
|
|
assert(memcmp(buffer, "\0a", 2) == 0);
|
|
|
|
lfsr_unmount(&lfs) => 0;
|
|
'''
|
|
|
|
[cases.test_mtree_traversal_uninline]
|
|
defines.VALIDATE = [false, true]
|
|
# 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_ckpoint(&lfs);
|
|
lfsr_data_t data;
|
|
|
|
// create a 2 large attrs that needs to be uninlined
|
|
uint8_t buffer[SIZE];
|
|
memset(buffer, 'a', SIZE);
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_UATTR(1), 0, LFSR_DATA_BUF(buffer, SIZE)))) => 0;
|
|
|
|
memset(buffer, 'b', SIZE);
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0, &mdir) => 0;
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_UATTR(1), 0, LFSR_DATA_BUF(buffer, SIZE)))) => 0;
|
|
|
|
// force mroot to compact
|
|
lfs.mroot.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
|
|
// assert mdirs were unininlined
|
|
assert(lfsr_mtree_weight(&lfs.mtree) == 1*lfsr_mleafweight(&lfs));
|
|
// assert mroot now has no entries
|
|
assert(lfs.mroot.rbyd.weight == 0);
|
|
|
|
// test that we can traverse the tree, keeping track of all blocks we see
|
|
uint8_t *seen = malloc((BLOCK_COUNT+7)/8);
|
|
memset(seen, 0, (BLOCK_COUNT+7)/8);
|
|
|
|
lfsr_traversal_t traversal = LFSR_TRAVERSAL(
|
|
(VALIDATE) ? LFSR_TRAVERSAL_VALIDATE : 0);
|
|
for (lfs_block_t i = 0;; i++) {
|
|
// a bit hacky, but this catches infinite loops
|
|
assert(i < 2*BLOCK_COUNT);
|
|
|
|
lfsr_tinfo_t tinfo;
|
|
int err = lfsr_traversal_read(&lfs, &traversal, &tinfo);
|
|
assert(!err || err == LFS_ERR_NOENT);
|
|
if (err == LFS_ERR_NOENT) {
|
|
break;
|
|
}
|
|
|
|
if (tinfo.tag == LFSR_TAG_MDIR) {
|
|
printf("traversal: 0x%x mdir 0x{%x,%x}\n",
|
|
tinfo.tag,
|
|
tinfo.u.mdir.rbyd.blocks[0],
|
|
tinfo.u.mdir.rbyd.blocks[1]);
|
|
|
|
// keep track of seen blocks
|
|
seen[tinfo.u.mdir.rbyd.blocks[1] / 8]
|
|
|= 1 << (tinfo.u.mdir.rbyd.blocks[1] % 8);
|
|
seen[tinfo.u.mdir.rbyd.blocks[0] / 8]
|
|
|= 1 << (tinfo.u.mdir.rbyd.blocks[0] % 8);
|
|
|
|
} else if (tinfo.tag == LFSR_TAG_BRANCH) {
|
|
printf("traversal: 0x%x btree 0x%x.%x\n",
|
|
tinfo.tag,
|
|
tinfo.u.rbyd.blocks[0], tinfo.u.rbyd.trunk);
|
|
|
|
// keep track of seen blocks
|
|
seen[tinfo.u.rbyd.blocks[0] / 8]
|
|
|= 1 << (tinfo.u.rbyd.blocks[0] % 8);
|
|
|
|
} else {
|
|
// this shouldn't happen
|
|
printf("traversal: 0x%x\n", tinfo.tag);
|
|
assert(false);
|
|
}
|
|
}
|
|
|
|
// if traversal worked, we should be able to clobber all other blocks
|
|
uint8_t clobber_buf[BLOCK_SIZE];
|
|
memset(clobber_buf, 0xcc, BLOCK_SIZE);
|
|
for (lfs_block_t block = 0; block < BLOCK_COUNT; block++) {
|
|
if (!(seen[block / 8] & (1 << (block % 8)))) {
|
|
CFG->erase(CFG, block) => 0;
|
|
CFG->prog(CFG, block, 0, clobber_buf, BLOCK_SIZE) => 0;
|
|
}
|
|
}
|
|
free(seen);
|
|
|
|
// and the tree should still work
|
|
|
|
// assert mdirs were unininlined
|
|
assert(lfsr_mtree_weight(&lfs.mtree) == 1*lfsr_mleafweight(&lfs));
|
|
// assert mroot now has no entries
|
|
assert(lfs.mroot.rbyd.weight == 0);
|
|
|
|
// assert that our attrs are still in the mroot/mtree
|
|
lfsr_mdir_lookup(&lfs, &lfs.mroot, LFSR_TAG_UATTR(1), &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[0] == 'a');
|
|
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0, &mdir) => 0;
|
|
assert(mdir.rbyd.weight == 1);
|
|
lfsr_mdir_lookup(&lfs, &mdir, LFSR_TAG_UATTR(1), &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[0] == 'b');
|
|
|
|
// check things stay sane after remount
|
|
lfsr_unmount(&lfs) => 0;
|
|
lfsr_mount(&lfs, CFG) => 0;
|
|
|
|
// assert mdirs were unininlined
|
|
assert(lfsr_mtree_weight(&lfs.mtree) == 1*lfsr_mleafweight(&lfs));
|
|
// assert mroot now has no entries
|
|
assert(lfs.mroot.rbyd.weight == 0);
|
|
|
|
// assert that our attrs are still in the mroot/mtree
|
|
lfsr_mdir_lookup(&lfs, &lfs.mroot, LFSR_TAG_UATTR(1), &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[0] == 'a');
|
|
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0, &mdir) => 0;
|
|
assert(mdir.rbyd.weight == 1);
|
|
lfsr_mdir_lookup(&lfs, &mdir, LFSR_TAG_UATTR(1), &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[0] == 'b');
|
|
|
|
lfsr_unmount(&lfs) => 0;
|
|
'''
|
|
|
|
[cases.test_mtree_traversal_uninline_split]
|
|
defines.VALIDATE = [false, true]
|
|
# 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_ckpoint(&lfs);
|
|
lfsr_data_t data;
|
|
|
|
// create 2 large entries that needs to be uninlined and split
|
|
uint8_t buffer[SIZE];
|
|
buffer[0] = '\0';
|
|
memset(buffer+1, 'a', SIZE-1);
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL, NULL) => LFS_ERR_NOENT;
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_REG, +1, LFSR_DATA_BUF(buffer, SIZE)))) => 0;
|
|
assert(mdir.rbyd.weight == 2);
|
|
|
|
memset(buffer+1, 'b', SIZE-1);
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL, NULL) => LFS_ERR_NOENT;
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_REG, +1, LFSR_DATA_BUF(buffer, SIZE)))) => 0;
|
|
assert(mdir.rbyd.weight == 3);
|
|
|
|
// force mroot to compact
|
|
lfs.mroot.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
|
|
// assert mdirs were unininlined and split
|
|
assert(lfsr_mtree_weight(&lfs.mtree) == 2*lfsr_mleafweight(&lfs));
|
|
// assert mroot now has no entries
|
|
assert(lfs.mroot.rbyd.weight == 0);
|
|
|
|
// test that we can traverse the tree, keeping track of all blocks we see
|
|
uint8_t *seen = malloc((BLOCK_COUNT+7)/8);
|
|
memset(seen, 0, (BLOCK_COUNT+7)/8);
|
|
|
|
lfsr_traversal_t traversal = LFSR_TRAVERSAL(
|
|
(VALIDATE) ? LFSR_TRAVERSAL_VALIDATE : 0);
|
|
for (lfs_block_t i = 0;; i++) {
|
|
// a bit hacky, but this catches infinite loops
|
|
assert(i < 2*BLOCK_COUNT);
|
|
|
|
lfsr_tinfo_t tinfo;
|
|
int err = lfsr_traversal_read(&lfs, &traversal, &tinfo);
|
|
assert(!err || err == LFS_ERR_NOENT);
|
|
if (err == LFS_ERR_NOENT) {
|
|
break;
|
|
}
|
|
|
|
if (tinfo.tag == LFSR_TAG_MDIR) {
|
|
printf("traversal: 0x%x mdir 0x{%x,%x}\n",
|
|
tinfo.tag,
|
|
tinfo.u.mdir.rbyd.blocks[0],
|
|
tinfo.u.mdir.rbyd.blocks[1]);
|
|
|
|
// keep track of seen blocks
|
|
seen[tinfo.u.mdir.rbyd.blocks[1] / 8]
|
|
|= 1 << (tinfo.u.mdir.rbyd.blocks[1] % 8);
|
|
seen[tinfo.u.mdir.rbyd.blocks[0] / 8]
|
|
|= 1 << (tinfo.u.mdir.rbyd.blocks[0] % 8);
|
|
|
|
} else if (tinfo.tag == LFSR_TAG_BRANCH) {
|
|
printf("traversal: 0x%x btree 0x%x.%x\n",
|
|
tinfo.tag,
|
|
tinfo.u.rbyd.blocks[0], tinfo.u.rbyd.trunk);
|
|
|
|
// keep track of seen blocks
|
|
seen[tinfo.u.rbyd.blocks[0] / 8]
|
|
|= 1 << (tinfo.u.rbyd.blocks[0] % 8);
|
|
|
|
} else {
|
|
// this shouldn't happen
|
|
printf("traversal: 0x%x\n", tinfo.tag);
|
|
assert(false);
|
|
}
|
|
}
|
|
|
|
// if traversal worked, we should be able to clobber all other blocks
|
|
uint8_t clobber_buf[BLOCK_SIZE];
|
|
memset(clobber_buf, 0xcc, BLOCK_SIZE);
|
|
for (lfs_block_t block = 0; block < BLOCK_COUNT; block++) {
|
|
if (!(seen[block / 8] & (1 << (block % 8)))) {
|
|
CFG->erase(CFG, block) => 0;
|
|
CFG->prog(CFG, block, 0, clobber_buf, BLOCK_SIZE) => 0;
|
|
}
|
|
}
|
|
free(seen);
|
|
|
|
// and the tree should still work
|
|
|
|
// assert mdirs were unininlined and split
|
|
assert(lfsr_mtree_weight(&lfs.mtree) == 2*lfsr_mleafweight(&lfs));
|
|
// assert mroot now has no entries
|
|
assert(lfs.mroot.rbyd.weight == 0);
|
|
|
|
// assert that our entries are still in the mtree
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0*lfsr_mleafweight(&lfs)+1, &mdir) => 0;
|
|
assert(mdir.rbyd.weight == 2);
|
|
lfsr_mdir_lookup(&lfs, &mdir, LFSR_TAG_REG, &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'a');
|
|
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 1*lfsr_mleafweight(&lfs)+0, &mdir) => 0;
|
|
assert(mdir.rbyd.weight == 1);
|
|
lfsr_mdir_lookup(&lfs, &mdir, LFSR_TAG_REG, &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'b');
|
|
|
|
// check things stay sane after remount
|
|
lfsr_unmount(&lfs) => 0;
|
|
lfsr_mount(&lfs, CFG) => 0;
|
|
|
|
// assert mdirs were unininlined and split
|
|
assert(lfsr_mtree_weight(&lfs.mtree) == 2*lfsr_mleafweight(&lfs));
|
|
// assert mroot now has no entries
|
|
assert(lfs.mroot.rbyd.weight == 0);
|
|
|
|
// assert that our entries are still in the mtree
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0*lfsr_mleafweight(&lfs)+1, &mdir) => 0;
|
|
assert(mdir.rbyd.weight == 2);
|
|
lfsr_mdir_lookup(&lfs, &mdir, LFSR_TAG_REG, &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'a');
|
|
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 1*lfsr_mleafweight(&lfs)+0, &mdir) => 0;
|
|
assert(mdir.rbyd.weight == 1);
|
|
lfsr_mdir_lookup(&lfs, &mdir, LFSR_TAG_REG, &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'b');
|
|
|
|
lfsr_unmount(&lfs) => 0;
|
|
'''
|
|
|
|
[cases.test_mtree_traversal_split]
|
|
defines.VALIDATE = [false, true]
|
|
# 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_ckpoint(&lfs);
|
|
lfsr_data_t data;
|
|
|
|
// create 2 large entries that needs to be uninlined and split
|
|
uint8_t buffer[SIZE];
|
|
buffer[0] = '\0';
|
|
memset(buffer+1, 'a', SIZE-1);
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL, NULL) => LFS_ERR_NOENT;
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_REG, +1, LFSR_DATA_BUF(buffer, SIZE)))) => 0;
|
|
assert(mdir.rbyd.weight == 2);
|
|
|
|
memset(buffer+1, 'b', SIZE-1);
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL, NULL) => LFS_ERR_NOENT;
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_REG, +1, LFSR_DATA_BUF(buffer, SIZE)))) => 0;
|
|
assert(mdir.rbyd.weight == 3);
|
|
|
|
// force mroot to compact
|
|
lfs.mroot.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
|
|
// assert mdirs were unininlined and split
|
|
assert(lfsr_mtree_weight(&lfs.mtree) == 2*lfsr_mleafweight(&lfs));
|
|
// assert mroot now has no entries
|
|
assert(lfs.mroot.rbyd.weight == 0);
|
|
|
|
// now add another large entry to an mdir, forcing a split
|
|
memset(buffer+1, 'c', SIZE-1);
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL, NULL) => LFS_ERR_NOENT;
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_REG, +1, LFSR_DATA_BUF(buffer, SIZE)))) => 0;
|
|
assert(mdir.rbyd.weight == 2);
|
|
|
|
// force mdir to compact
|
|
mdir.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &mdir, NULL, 0) => 0;
|
|
|
|
// assert mdir was split correctly
|
|
assert(lfsr_mtree_weight(&lfs.mtree) == 3*lfsr_mleafweight(&lfs));
|
|
// assert mroot still has no entries
|
|
assert(lfs.mroot.rbyd.weight == 0);
|
|
|
|
// test that we can traverse the tree, keeping track of all blocks we see
|
|
uint8_t *seen = malloc((BLOCK_COUNT+7)/8);
|
|
memset(seen, 0, (BLOCK_COUNT+7)/8);
|
|
|
|
lfsr_traversal_t traversal = LFSR_TRAVERSAL(
|
|
(VALIDATE) ? LFSR_TRAVERSAL_VALIDATE : 0);
|
|
for (lfs_block_t i = 0;; i++) {
|
|
// a bit hacky, but this catches infinite loops
|
|
assert(i < 2*BLOCK_COUNT);
|
|
|
|
lfsr_tinfo_t tinfo;
|
|
int err = lfsr_traversal_read(&lfs, &traversal, &tinfo);
|
|
assert(!err || err == LFS_ERR_NOENT);
|
|
if (err == LFS_ERR_NOENT) {
|
|
break;
|
|
}
|
|
|
|
if (tinfo.tag == LFSR_TAG_MDIR) {
|
|
printf("traversal: 0x%x mdir 0x{%x,%x}\n",
|
|
tinfo.tag,
|
|
tinfo.u.mdir.rbyd.blocks[0],
|
|
tinfo.u.mdir.rbyd.blocks[1]);
|
|
|
|
// keep track of seen blocks
|
|
seen[tinfo.u.mdir.rbyd.blocks[1] / 8]
|
|
|= 1 << (tinfo.u.mdir.rbyd.blocks[1] % 8);
|
|
seen[tinfo.u.mdir.rbyd.blocks[0] / 8]
|
|
|= 1 << (tinfo.u.mdir.rbyd.blocks[0] % 8);
|
|
|
|
} else if (tinfo.tag == LFSR_TAG_BRANCH) {
|
|
printf("traversal: 0x%x btree 0x%x.%x\n",
|
|
tinfo.tag,
|
|
tinfo.u.rbyd.blocks[0], tinfo.u.rbyd.trunk);
|
|
|
|
// keep track of seen blocks
|
|
seen[tinfo.u.rbyd.blocks[0] / 8]
|
|
|= 1 << (tinfo.u.rbyd.blocks[0] % 8);
|
|
|
|
} else {
|
|
// this shouldn't happen
|
|
printf("traversal: 0x%x\n", tinfo.tag);
|
|
assert(false);
|
|
}
|
|
}
|
|
|
|
// if traversal worked, we should be able to clobber all other blocks
|
|
uint8_t clobber_buf[BLOCK_SIZE];
|
|
memset(clobber_buf, 0xcc, BLOCK_SIZE);
|
|
for (lfs_block_t block = 0; block < BLOCK_COUNT; block++) {
|
|
if (!(seen[block / 8] & (1 << (block % 8)))) {
|
|
CFG->erase(CFG, block) => 0;
|
|
CFG->prog(CFG, block, 0, clobber_buf, BLOCK_SIZE) => 0;
|
|
}
|
|
}
|
|
free(seen);
|
|
|
|
// and the tree should still work
|
|
|
|
// assert mdir was split correctly
|
|
assert(lfsr_mtree_weight(&lfs.mtree) == 3*lfsr_mleafweight(&lfs));
|
|
// assert mroot still has no entries
|
|
assert(lfs.mroot.rbyd.weight == 0);
|
|
|
|
// assert that our entries are still in the mtree
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0*lfsr_mleafweight(&lfs)+1, &mdir) => 0;
|
|
assert(mdir.rbyd.weight == 2);
|
|
lfsr_mdir_lookup(&lfs, &mdir, LFSR_TAG_REG, &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'a');
|
|
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 1*lfsr_mleafweight(&lfs)+0, &mdir) => 0;
|
|
assert(mdir.rbyd.weight == 1);
|
|
lfsr_mdir_lookup(&lfs, &mdir, LFSR_TAG_REG, &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'b');
|
|
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 2*lfsr_mleafweight(&lfs)+0, &mdir) => 0;
|
|
assert(mdir.rbyd.weight == 1);
|
|
lfsr_mdir_lookup(&lfs, &mdir, LFSR_TAG_REG, &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'c');
|
|
|
|
// check things stay sane after remount
|
|
lfsr_unmount(&lfs) => 0;
|
|
lfsr_mount(&lfs, CFG) => 0;
|
|
|
|
// assert mdir was split correctly
|
|
assert(lfsr_mtree_weight(&lfs.mtree) == 3*lfsr_mleafweight(&lfs));
|
|
// assert mroot still has no entries
|
|
assert(lfs.mroot.rbyd.weight == 0);
|
|
|
|
// assert that our entries are still in the mtree
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0*lfsr_mleafweight(&lfs)+1, &mdir) => 0;
|
|
assert(mdir.rbyd.weight == 2);
|
|
lfsr_mdir_lookup(&lfs, &mdir, LFSR_TAG_REG, &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'a');
|
|
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 1*lfsr_mleafweight(&lfs)+0, &mdir) => 0;
|
|
assert(mdir.rbyd.weight == 1);
|
|
lfsr_mdir_lookup(&lfs, &mdir, LFSR_TAG_REG, &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'b');
|
|
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 2*lfsr_mleafweight(&lfs)+0, &mdir) => 0;
|
|
assert(mdir.rbyd.weight == 1);
|
|
lfsr_mdir_lookup(&lfs, &mdir, LFSR_TAG_REG, &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'c');
|
|
|
|
lfsr_unmount(&lfs) => 0;
|
|
'''
|
|
|
|
[cases.test_mtree_traversal_extend]
|
|
defines.VALIDATE = [false, true]
|
|
# 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_ckpoint(&lfs);
|
|
|
|
// insert at least one entry
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, "a", 1,
|
|
&mdir, NULL, NULL) => LFS_ERR_NOENT;
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_REG, +1, LFSR_DATA_BUF("\0a", 2)))) => 0;
|
|
assert(mdir.rbyd.weight == 2);
|
|
|
|
// force mroot to compact twice, this should extend the mroot
|
|
lfsr_mdir_t old_mroot = lfs.mroot;
|
|
lfs.mroot.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
lfs.mroot.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
// assert we relocated
|
|
assert(lfsr_mdir_cmp(&old_mroot, &lfs.mroot) != 0);
|
|
|
|
// test that we can traverse the tree, keeping track of all blocks we see
|
|
uint8_t *seen = malloc((BLOCK_COUNT+7)/8);
|
|
memset(seen, 0, (BLOCK_COUNT+7)/8);
|
|
|
|
lfsr_traversal_t traversal = LFSR_TRAVERSAL(
|
|
(VALIDATE) ? LFSR_TRAVERSAL_VALIDATE : 0);
|
|
for (lfs_block_t i = 0;; i++) {
|
|
// a bit hacky, but this catches infinite loops
|
|
assert(i < 2*BLOCK_COUNT);
|
|
|
|
lfsr_tinfo_t tinfo;
|
|
int err = lfsr_traversal_read(&lfs, &traversal, &tinfo);
|
|
assert(!err || err == LFS_ERR_NOENT);
|
|
if (err == LFS_ERR_NOENT) {
|
|
break;
|
|
}
|
|
|
|
if (tinfo.tag == LFSR_TAG_MDIR) {
|
|
printf("traversal: 0x%x mdir 0x{%x,%x}\n",
|
|
tinfo.tag,
|
|
tinfo.u.mdir.rbyd.blocks[0],
|
|
tinfo.u.mdir.rbyd.blocks[1]);
|
|
|
|
// keep track of seen blocks
|
|
seen[tinfo.u.mdir.rbyd.blocks[1] / 8]
|
|
|= 1 << (tinfo.u.mdir.rbyd.blocks[1] % 8);
|
|
seen[tinfo.u.mdir.rbyd.blocks[0] / 8]
|
|
|= 1 << (tinfo.u.mdir.rbyd.blocks[0] % 8);
|
|
|
|
} else if (tinfo.tag == LFSR_TAG_BRANCH) {
|
|
printf("traversal: 0x%x btree 0x%x.%x\n",
|
|
tinfo.tag,
|
|
tinfo.u.rbyd.blocks[0], tinfo.u.rbyd.trunk);
|
|
|
|
// keep track of seen blocks
|
|
seen[tinfo.u.rbyd.blocks[0] / 8]
|
|
|= 1 << (tinfo.u.rbyd.blocks[0] % 8);
|
|
|
|
} else {
|
|
// this shouldn't happen
|
|
printf("traversal: 0x%x\n", tinfo.tag);
|
|
assert(false);
|
|
}
|
|
}
|
|
|
|
// if traversal worked, we should be able to clobber all other blocks
|
|
uint8_t clobber_buf[BLOCK_SIZE];
|
|
memset(clobber_buf, 0xcc, BLOCK_SIZE);
|
|
for (lfs_block_t block = 0; block < BLOCK_COUNT; block++) {
|
|
if (!(seen[block / 8] & (1 << (block % 8)))) {
|
|
CFG->erase(CFG, block) => 0;
|
|
CFG->prog(CFG, block, 0, clobber_buf, BLOCK_SIZE) => 0;
|
|
}
|
|
}
|
|
free(seen);
|
|
lfsr_data_t data;
|
|
|
|
// and the tree should still work
|
|
|
|
// assert that our entry is still in the mtree
|
|
uint8_t buffer[256];
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, "a", 1,
|
|
&mdir, NULL, NULL) => 0;
|
|
lfsr_mdir_lookup(&lfs, &mdir, LFSR_TAG_REG, &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, sizeof(buffer)) => 2;
|
|
assert(memcmp(buffer, "\0a", 2) == 0);
|
|
|
|
// check things stay sane after remount
|
|
lfsr_unmount(&lfs) => 0;
|
|
lfsr_mount(&lfs, CFG) => 0;
|
|
|
|
// assert that our entry is still in the mtree
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, "a", 1,
|
|
&mdir, NULL, NULL) => 0;
|
|
lfsr_mdir_lookup(&lfs, &mdir, LFSR_TAG_REG, &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, sizeof(buffer)) => 2;
|
|
assert(memcmp(buffer, "\0a", 2) == 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 = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, CFG) => 0;
|
|
lfsr_mount(&lfs, CFG) => 0;
|
|
lfs_alloc_ckpoint(&lfs);
|
|
|
|
// create entries
|
|
for (lfs_size_t i = 0; i < N; i++) {
|
|
char name[256];
|
|
name[0] = '\0';
|
|
sprintf(name+1, "%03x", i);
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, (const char*)name+1, 3,
|
|
&mdir, NULL, NULL) => LFS_ERR_NOENT;
|
|
// force a compaction?
|
|
if (FORCE_COMPACTION) {
|
|
lfs.mroot.rbyd.eoff = -1;
|
|
mdir.rbyd.eoff = -1;
|
|
}
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_REG, +1, LFSR_DATA_BUF(name, 4)))) => 0;
|
|
lfsr_data_t data;
|
|
|
|
uint8_t buffer[256];
|
|
lfsr_mdir_lookup(&lfs, &mdir, LFSR_TAG_REG, &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, sizeof(buffer)) => 4;
|
|
assert(memcmp(buffer, name, 4) == 0);
|
|
}
|
|
|
|
// test that we can traverse the tree, keeping track of all blocks we see
|
|
uint8_t *seen = malloc((BLOCK_COUNT+7)/8);
|
|
memset(seen, 0, (BLOCK_COUNT+7)/8);
|
|
|
|
lfsr_traversal_t traversal = LFSR_TRAVERSAL(
|
|
(VALIDATE) ? LFSR_TRAVERSAL_VALIDATE : 0);
|
|
for (lfs_block_t i = 0;; i++) {
|
|
// a bit hacky, but this catches infinite loops
|
|
assert(i < 2*BLOCK_COUNT);
|
|
|
|
lfsr_tinfo_t tinfo;
|
|
int err = lfsr_traversal_read(&lfs, &traversal, &tinfo);
|
|
assert(!err || err == LFS_ERR_NOENT);
|
|
if (err == LFS_ERR_NOENT) {
|
|
break;
|
|
}
|
|
|
|
if (tinfo.tag == LFSR_TAG_MDIR) {
|
|
printf("traversal: 0x%x mdir 0x{%x,%x}\n",
|
|
tinfo.tag,
|
|
tinfo.u.mdir.rbyd.blocks[0],
|
|
tinfo.u.mdir.rbyd.blocks[1]);
|
|
|
|
// keep track of seen blocks
|
|
seen[tinfo.u.mdir.rbyd.blocks[1] / 8]
|
|
|= 1 << (tinfo.u.mdir.rbyd.blocks[1] % 8);
|
|
seen[tinfo.u.mdir.rbyd.blocks[0] / 8]
|
|
|= 1 << (tinfo.u.mdir.rbyd.blocks[0] % 8);
|
|
|
|
} else if (tinfo.tag == LFSR_TAG_BRANCH) {
|
|
printf("traversal: 0x%x btree 0x%x.%x\n",
|
|
tinfo.tag,
|
|
tinfo.u.rbyd.blocks[0], tinfo.u.rbyd.trunk);
|
|
|
|
// keep track of seen blocks
|
|
seen[tinfo.u.rbyd.blocks[0] / 8]
|
|
|= 1 << (tinfo.u.rbyd.blocks[0] % 8);
|
|
|
|
} else {
|
|
// this shouldn't happen
|
|
printf("traversal: 0x%x\n", tinfo.tag);
|
|
assert(false);
|
|
}
|
|
}
|
|
|
|
// if traversal worked, we should be able to clobber all other blocks
|
|
uint8_t clobber_buf[BLOCK_SIZE];
|
|
memset(clobber_buf, 0xcc, BLOCK_SIZE);
|
|
for (lfs_block_t block = 0; block < BLOCK_COUNT; block++) {
|
|
if (!(seen[block / 8] & (1 << (block % 8)))) {
|
|
CFG->erase(CFG, block) => 0;
|
|
CFG->prog(CFG, block, 0, clobber_buf, BLOCK_SIZE) => 0;
|
|
}
|
|
}
|
|
free(seen);
|
|
|
|
// and the tree should still work
|
|
|
|
// try looking up each entry
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0, &mdir) => 0;
|
|
lfsr_data_t data;
|
|
uint8_t buffer[256];
|
|
lfsr_mdir_lookup(&lfs, &mdir, LFSR_TAG_BOOKMARK, &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, sizeof(buffer)) => 1;
|
|
for (lfs_size_t i = 0; i < N; i++) {
|
|
char name[256];
|
|
name[0] = '\0';
|
|
sprintf(name+1, "%03x", i);
|
|
lfsr_mtree_seek(&lfs, &lfs.mtree, &mdir, +1) => 0;
|
|
lfsr_mdir_lookup(&lfs, &mdir, LFSR_TAG_REG, &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, sizeof(buffer)) => 4;
|
|
assert(memcmp(buffer, name, 4) == 0);
|
|
}
|
|
|
|
// check things stay sane after remount
|
|
lfsr_unmount(&lfs) => 0;
|
|
lfsr_mount(&lfs, CFG) => 0;
|
|
|
|
// try looking up each entry
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0, &mdir) => 0;
|
|
lfsr_mdir_lookup(&lfs, &mdir, LFSR_TAG_BOOKMARK, &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, sizeof(buffer)) => 1;
|
|
for (lfs_size_t i = 0; i < N; i++) {
|
|
char name[256];
|
|
name[0] = '\0';
|
|
sprintf(name+1, "%03x", i);
|
|
lfsr_mtree_seek(&lfs, &lfs.mtree, &mdir, +1) => 0;
|
|
lfsr_mdir_lookup(&lfs, &mdir, LFSR_TAG_REG, &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, sizeof(buffer)) => 4;
|
|
assert(memcmp(buffer, name, 4) == 0);
|
|
}
|
|
|
|
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 = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, CFG) => 0;
|
|
lfsr_mount(&lfs, CFG) => 0;
|
|
lfs_alloc_ckpoint(&lfs);
|
|
|
|
bool sim[N];
|
|
for (lfs_size_t i = 0; i < N; i++) {
|
|
sim[i] = false;
|
|
}
|
|
|
|
uint32_t prng = SEED;
|
|
for (lfs_size_t i = 0; i < N; i++) {
|
|
// choose a pseudo-random name
|
|
lfs_size_t x = TEST_PRNG(&prng) % N;
|
|
|
|
// update sim
|
|
sim[x] = true;
|
|
|
|
// update mtree
|
|
char name[256];
|
|
name[0] = '\0';
|
|
sprintf(name+1, "%03x", x);
|
|
lfsr_mdir_t mdir;
|
|
int err = lfsr_mtree_namelookup(&lfs, &lfs.mtree,
|
|
0, (const char*)name+1, 3,
|
|
&mdir, NULL, NULL);
|
|
assert(!err || err == LFS_ERR_NOENT);
|
|
if (!err) {
|
|
continue;
|
|
}
|
|
// force a compaction?
|
|
if (FORCE_COMPACTION) {
|
|
lfs.mroot.rbyd.eoff = -1;
|
|
mdir.rbyd.eoff = -1;
|
|
}
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_REG, +1, LFSR_DATA_BUF(name, 4)))) => 0;
|
|
lfsr_data_t data;
|
|
|
|
// double check
|
|
uint8_t buffer[256];
|
|
lfsr_mdir_lookup(&lfs, &mdir, LFSR_TAG_REG, &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, sizeof(buffer)) => 4;
|
|
assert(memcmp(buffer, name, 4) == 0);
|
|
}
|
|
|
|
// test that we can traverse the tree, keeping track of all blocks
|
|
// we see
|
|
uint8_t *seen = malloc((BLOCK_COUNT+7)/8);
|
|
memset(seen, 0, (BLOCK_COUNT+7)/8);
|
|
|
|
lfsr_traversal_t traversal = LFSR_TRAVERSAL(
|
|
(VALIDATE) ? LFSR_TRAVERSAL_VALIDATE : 0);
|
|
for (lfs_block_t i = 0;; i++) {
|
|
// a bit hacky, but this catches infinite loops
|
|
assert(i < 2*BLOCK_COUNT);
|
|
|
|
lfsr_tinfo_t tinfo;
|
|
int err = lfsr_traversal_read(&lfs, &traversal, &tinfo);
|
|
assert(!err || err == LFS_ERR_NOENT);
|
|
if (err == LFS_ERR_NOENT) {
|
|
break;
|
|
}
|
|
|
|
if (tinfo.tag == LFSR_TAG_MDIR) {
|
|
printf("traversal: 0x%x mdir 0x{%x,%x}\n",
|
|
tinfo.tag,
|
|
tinfo.u.mdir.rbyd.blocks[0],
|
|
tinfo.u.mdir.rbyd.blocks[1]);
|
|
|
|
// keep track of seen blocks
|
|
seen[tinfo.u.mdir.rbyd.blocks[1] / 8]
|
|
|= 1 << (tinfo.u.mdir.rbyd.blocks[1] % 8);
|
|
seen[tinfo.u.mdir.rbyd.blocks[0] / 8]
|
|
|= 1 << (tinfo.u.mdir.rbyd.blocks[0] % 8);
|
|
|
|
} else if (tinfo.tag == LFSR_TAG_BRANCH) {
|
|
printf("traversal: 0x%x btree 0x%x.%x\n",
|
|
tinfo.tag,
|
|
tinfo.u.rbyd.blocks[0], tinfo.u.rbyd.trunk);
|
|
|
|
// keep track of seen blocks
|
|
seen[tinfo.u.rbyd.blocks[0] / 8]
|
|
|= 1 << (tinfo.u.rbyd.blocks[0] % 8);
|
|
|
|
} else {
|
|
// this shouldn't happen
|
|
printf("traversal: 0x%x\n", tinfo.tag);
|
|
assert(false);
|
|
}
|
|
}
|
|
|
|
// if traversal worked, we should be able to clobber all other blocks
|
|
uint8_t clobber_buf[BLOCK_SIZE];
|
|
memset(clobber_buf, 0xcc, BLOCK_SIZE);
|
|
for (lfs_block_t block = 0; block < BLOCK_COUNT; block++) {
|
|
if (!(seen[block / 8] & (1 << (block % 8)))) {
|
|
CFG->erase(CFG, block) => 0;
|
|
CFG->prog(CFG, block, 0, clobber_buf, BLOCK_SIZE) => 0;
|
|
}
|
|
}
|
|
free(seen);
|
|
|
|
// and the tree should still work
|
|
|
|
// try looking up each entry
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0, &mdir) => 0;
|
|
lfsr_data_t data;
|
|
uint8_t buffer[256];
|
|
lfsr_mdir_lookup(&lfs, &mdir, LFSR_TAG_BOOKMARK, &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, sizeof(buffer)) => 1;
|
|
for (lfs_size_t i = 0; i < N; i++) {
|
|
char name[256];
|
|
name[0] = '\0';
|
|
sprintf(name+1, "%03x", i);
|
|
if (sim[i]) {
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, (const char*)name+1, 3,
|
|
&mdir, NULL, NULL) => 0;
|
|
lfsr_mdir_lookup(&lfs, &mdir, LFSR_TAG_REG, &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, sizeof(buffer)) => 4;
|
|
assert(memcmp(buffer, name, 4) == 0);
|
|
} else {
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, (const char*)name+1, 3,
|
|
&mdir, NULL, NULL) => LFS_ERR_NOENT;
|
|
}
|
|
}
|
|
|
|
// check things stay sane after remount
|
|
lfsr_unmount(&lfs) => 0;
|
|
lfsr_mount(&lfs, CFG) => 0;
|
|
|
|
// try looking up each entry
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0, &mdir) => 0;
|
|
lfsr_mdir_lookup(&lfs, &mdir, LFSR_TAG_BOOKMARK, &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, sizeof(buffer)) => 1;
|
|
for (lfs_size_t i = 0; i < N; i++) {
|
|
char name[256];
|
|
name[0] = '\0';
|
|
sprintf(name+1, "%03x", i);
|
|
if (sim[i]) {
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, (const char*)name+1, 3,
|
|
&mdir, NULL, NULL) => 0;
|
|
lfsr_mdir_lookup(&lfs, &mdir, LFSR_TAG_REG, &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, sizeof(buffer)) => 4;
|
|
assert(memcmp(buffer, name, 4) == 0);
|
|
} else {
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, (const char*)name+1, 3,
|
|
&mdir, NULL, NULL) => LFS_ERR_NOENT;
|
|
}
|
|
}
|
|
|
|
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_ckpoint(&lfs);
|
|
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(
|
|
LFSR_TAG_MROOT, 0,
|
|
LFSR_DATA_MPTR(&LFSR_MPTR_MROOTANCHOR())))) => 0;
|
|
|
|
// technically, cycle detection only needs to work when we're validating
|
|
lfsr_traversal_t traversal = LFSR_TRAVERSAL(LFSR_TRAVERSAL_VALIDATE);
|
|
for (lfs_block_t i = 0;; i++) {
|
|
// assert that we detect the cycle in a reasonable number of iterations
|
|
assert(i < 2*BLOCK_COUNT);
|
|
|
|
lfsr_tinfo_t tinfo;
|
|
int err = lfsr_traversal_read(&lfs, &traversal, &tinfo);
|
|
assert(!err || err == LFS_ERR_CORRUPT);
|
|
if (err == LFS_ERR_CORRUPT) {
|
|
break;
|
|
}
|
|
|
|
if (tinfo.tag == LFSR_TAG_MDIR) {
|
|
printf("traversal: 0x%x mdir 0x{%x,%x}\n",
|
|
tinfo.tag,
|
|
tinfo.u.mdir.rbyd.blocks[0],
|
|
tinfo.u.mdir.rbyd.blocks[1]);
|
|
|
|
} else if (tinfo.tag == LFSR_TAG_BRANCH) {
|
|
printf("traversal: 0x%x btree 0x%x.%x\n",
|
|
tinfo.tag,
|
|
tinfo.u.rbyd.blocks[0], tinfo.u.rbyd.trunk);
|
|
|
|
} else {
|
|
// this shouldn't happen
|
|
printf("traversal: 0x%x\n", tinfo.tag);
|
|
assert(false);
|
|
}
|
|
}
|
|
|
|
lfsr_unmount(&lfs) => 0;
|
|
'''
|
|
|
|
|
|
## Magic consistency ##
|
|
|
|
# make sure our magic string ("littlefs") shows up in the same place (off=8)
|
|
[cases.test_mtree_magic]
|
|
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_ckpoint(&lfs);
|
|
lfsr_data_t data;
|
|
|
|
// prepare mroot with an entry
|
|
uint8_t buffer[SIZE];
|
|
buffer[0] = '\0';
|
|
memset(buffer+1, 'a', SIZE-1);
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL, NULL) => LFS_ERR_NOENT;
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_REG, +1, LFSR_DATA_BUF(buffer, SIZE)))) => 0;
|
|
assert(mdir.rbyd.weight == 2);
|
|
|
|
// force mroot to compact twice, this should extend the mroot
|
|
lfsr_mdir_t old_mroot = lfs.mroot;
|
|
lfs.mroot.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
lfs.mroot.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
// assert we relocated
|
|
assert(lfsr_mdir_cmp(&old_mroot, &lfs.mroot) != 0);
|
|
|
|
// assert that our entry is still in the mroot
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0*lfsr_mleafweight(&lfs)+1, &mdir) => 0;
|
|
assert(mdir.rbyd.weight == 2);
|
|
lfsr_mdir_lookup(&lfs, &mdir, LFSR_TAG_REG, &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'a');
|
|
|
|
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_ckpoint(&lfs);
|
|
lfsr_data_t data;
|
|
|
|
// prepare mroot with an entry
|
|
uint8_t buffer[SIZE];
|
|
buffer[0] = '\0';
|
|
memset(buffer+1, 'a', SIZE-1);
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_namelookup(&lfs, &lfs.mtree, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL, NULL) => LFS_ERR_NOENT;
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(LFSR_TAG_REG, +1, LFSR_DATA_BUF(buffer, SIZE)))) => 0;
|
|
assert(mdir.rbyd.weight == 2);
|
|
|
|
// force mroot to compact twice, this should extend the mroot
|
|
lfsr_mdir_t old_mroot = lfs.mroot;
|
|
lfs.mroot.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
lfs.mroot.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
// assert we relocated
|
|
assert(lfsr_mdir_cmp(&old_mroot, &lfs.mroot) != 0);
|
|
|
|
// force mroot to compact four times, this should relocate the mroot
|
|
// twice, forcing a second mroot extension
|
|
old_mroot = lfs.mroot;
|
|
lfs.mroot.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
lfs.mroot.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
lfs.mroot.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
lfs.mroot.rbyd.eoff = -1;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, NULL, 0) => 0;
|
|
// assert we relocated
|
|
assert(lfsr_mdir_cmp(&old_mroot, &lfs.mroot) != 0);
|
|
|
|
// assert that our attr is still in the mroot
|
|
lfsr_mtree_lookup(&lfs, &lfs.mtree, 0*lfsr_mleafweight(&lfs)+1, &mdir) => 0;
|
|
assert(mdir.rbyd.weight == 2);
|
|
lfsr_mdir_lookup(&lfs, &mdir, LFSR_TAG_REG, &data) => 0;
|
|
lfsr_data_read(&lfs, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'a');
|
|
|
|
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);
|
|
'''
|