cd9f93d859
This is an indulgence to simplify the upcoming auxiliary btree work.
Brings back the previously-reverted per-btree leaf caches, where each
lfs3_btree_t keeps track of two rbyds: The root and the most recently
accessed leaf.
At the surface level, this optimizes repeated access to the same btree
leaf. A common pattern for a number of littlefs's operations that has
proven tricky to manually optimize:
- Btree iteration
- Pokes for our crystalization heuristic
- Checksum collision resolution for dids and (FUTURE) ddkeys
- Related rattrs attached to a single bid
But the real motivation is to drop lfs3_btree_*lookupleaf and simplify
the internal APIs. If repeated lfs3_btree_lookup*s are already
efficient, there's no reason for extra leaf-level APIs, and in theory
any logic that interacts with btrees will be simpler.
---
This comes at a cost (humorously about the same amount as the
tag-returning refactor, if you ignore the extra 28 bytes of ctx).
Unsurprisingly, increasing the size of lfs3_btree_t has the biggest
impact on stack and ctx:
code stack ctx
before: 36084 2336 656
after: 36784 (+1.9%) 2400 (+2.7%) 684 (+4.3%)
Also note from the previous commit messages: Btree leaf caching has
resulted in surprisingly little performance improvement for our current
benchmarks + implementation. It turns out if you're dominated by write
cost, optimizing btree lookups -- which already skip rbyd fetches, has
barely noticeable impact.
---
A note on reverting!
Eventually (after the auxiliary btree work) it will probably make sense
to revert this -- or at least provide a non-leaf-caching build for
code/RAM sensitive users.
I don't think this should be reverted as-is. Instead, I think we should
allow the option to just disable the leaf cache, while keeping the
simpler internal API. This would give us the best of all three worlds:
- A small code/RAM option
- Optimal btree iteration/nearby-lookup performance
- Simpler internal APIs
The only reason this isn't already implemented is because I want to
avoid fragmenting the codebase further while we're still in development
mode.
5121 lines
176 KiB
TOML
5121 lines
176 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-1) / 8'
|
|
|
|
# test with normal revision counts, debug revision counts, and noisy
|
|
# revision counts
|
|
defines.REVDBG = [false, true]
|
|
defines.REVNOISE = [false, true]
|
|
defines.F_FLAGS = '''
|
|
((REVDBG) ? LFS3_IFDEF_REVDBG(LFS3_F_REVDBG, -1) : 0)
|
|
| ((REVNOISE) ? LFS3_IFDEF_REVNOISE(LFS3_F_REVNOISE, -1) : 0)
|
|
'''
|
|
defines.M_FLAGS = '''
|
|
((REVDBG) ? LFS3_IFDEF_REVDBG(LFS3_M_REVDBG, -1) : 0)
|
|
| ((REVNOISE) ? LFS3_IFDEF_REVNOISE(LFS3_M_REVNOISE, -1) : 0)
|
|
'''
|
|
if = [
|
|
'LFS3_IFDEF_REVDBG(true, !REVDBG)',
|
|
'LFS3_IFDEF_REVNOISE(true, !REVNOISE)',
|
|
'!REVDBG || !REVNOISE',
|
|
]
|
|
|
|
|
|
|
|
# test a single mroot
|
|
[cases.test_mtree_mroot]
|
|
code = '''
|
|
lfs3_t lfs3;
|
|
lfs3_format(&lfs3, LFS3_F_RDWR | F_FLAGS, CFG) => 0;
|
|
lfs3_mount(&lfs3, LFS3_M_RDWR | M_FLAGS, CFG) => 0;
|
|
lfs3_unmount(&lfs3) => 0;
|
|
'''
|
|
|
|
# test a single mroot with attributes
|
|
[cases.test_mtree_mroot_rattrs]
|
|
defines.N = [1, 3]
|
|
in = 'lfs3.c'
|
|
code = '''
|
|
lfs3_t lfs3;
|
|
lfs3_format(&lfs3, LFS3_F_RDWR | F_FLAGS, CFG) => 0;
|
|
lfs3_mount(&lfs3, LFS3_M_RDWR | M_FLAGS, CFG) => 0;
|
|
lfs3_alloc_ckpoint(&lfs3);
|
|
|
|
for (lfs3_size_t i = 0; i < N; i++) {
|
|
lfs3_mdir_commit(&lfs3, &lfs3.mroot, LFS3_RATTRS(
|
|
LFS3_RATTR_BUF(
|
|
LFS3_TAG_ATTR(i), 0,
|
|
&(uint8_t){'a'+(i % 26)}, 1))) => 0;
|
|
}
|
|
|
|
for (lfs3_size_t i = 0; i < N; i++) {
|
|
lfs3_data_t data;
|
|
uint8_t buffer[1];
|
|
lfs3_mdir_lookup(&lfs3, &lfs3.mroot, LFS3_TAG_ATTR(i),
|
|
&data) => LFS3_TAG_ATTR(i);
|
|
lfs3_data_read(&lfs3, &data, buffer, 1) => 1;
|
|
assert(memcmp(buffer, &(uint8_t){'a'+(i % 26)}, 1) == 0);
|
|
}
|
|
|
|
lfs3_unmount(&lfs3) => 0;
|
|
|
|
|
|
// check things stay sane after remount
|
|
lfs3_mount(&lfs3, LFS3_M_RDWR | M_FLAGS, CFG) => 0;
|
|
|
|
for (lfs3_size_t i = 0; i < N; i++) {
|
|
lfs3_data_t data;
|
|
uint8_t buffer[1];
|
|
lfs3_mdir_lookup(&lfs3, &lfs3.mroot, LFS3_TAG_ATTR(i),
|
|
&data) => LFS3_TAG_ATTR(i);
|
|
lfs3_data_read(&lfs3, &data, buffer, 1) => 1;
|
|
assert(memcmp(buffer, &(uint8_t){'a'+(i % 26)}, 1) == 0);
|
|
}
|
|
|
|
lfs3_unmount(&lfs3) => 0;
|
|
'''
|
|
|
|
# test a single mroot with forced compaction
|
|
[cases.test_mtree_mroot_compact]
|
|
defines.N = [1, 3]
|
|
in = 'lfs3.c'
|
|
code = '''
|
|
lfs3_t lfs3;
|
|
lfs3_format(&lfs3, LFS3_F_RDWR | F_FLAGS, CFG) => 0;
|
|
lfs3_mount(&lfs3, LFS3_M_RDWR | M_FLAGS, CFG) => 0;
|
|
lfs3_alloc_ckpoint(&lfs3);
|
|
|
|
for (lfs3_size_t i = 0; i < N; i++) {
|
|
// force mroot to compact
|
|
lfs3.mroot.r.eoff = -1;
|
|
|
|
lfs3_mdir_commit(&lfs3, &lfs3.mroot, LFS3_RATTRS(
|
|
LFS3_RATTR_BUF(
|
|
LFS3_TAG_ATTR(i), 0,
|
|
&(uint8_t){'a'+(i % 26)}, 1))) => 0;
|
|
}
|
|
|
|
for (lfs3_size_t i = 0; i < N; i++) {
|
|
lfs3_data_t data;
|
|
uint8_t buffer[1];
|
|
lfs3_mdir_lookup(&lfs3, &lfs3.mroot, LFS3_TAG_ATTR(i),
|
|
&data) => LFS3_TAG_ATTR(i);
|
|
lfs3_data_read(&lfs3, &data, buffer, 1) => 1;
|
|
assert(memcmp(buffer, &(uint8_t){'a'+(i % 26)}, 1) == 0);
|
|
}
|
|
|
|
lfs3_unmount(&lfs3) => 0;
|
|
|
|
|
|
// check things stay sane after remount
|
|
lfs3_mount(&lfs3, LFS3_M_RDWR | M_FLAGS, CFG) => 0;
|
|
|
|
for (lfs3_size_t i = 0; i < N; i++) {
|
|
lfs3_data_t data;
|
|
uint8_t buffer[1];
|
|
lfs3_mdir_lookup(&lfs3, &lfs3.mroot, LFS3_TAG_ATTR(i),
|
|
&data) => LFS3_TAG_ATTR(i);
|
|
lfs3_data_read(&lfs3, &data, buffer, 1) => 1;
|
|
assert(memcmp(buffer, &(uint8_t){'a'+(i % 26)}, 1) == 0);
|
|
}
|
|
|
|
lfs3_unmount(&lfs3) => 0;
|
|
'''
|
|
|
|
# test a single mroot with many commits
|
|
[cases.test_mtree_mroot_many_commits]
|
|
defines.N = [5, 5000]
|
|
in = 'lfs3.c'
|
|
code = '''
|
|
lfs3_t lfs3;
|
|
lfs3_format(&lfs3, LFS3_F_RDWR | F_FLAGS, CFG) => 0;
|
|
lfs3_mount(&lfs3, LFS3_M_RDWR | M_FLAGS, CFG) => 0;
|
|
lfs3_alloc_ckpoint(&lfs3);
|
|
|
|
for (lfs3_size_t i = 0; i < N; i++) {
|
|
lfs3_mdir_commit(&lfs3, &lfs3.mroot, LFS3_RATTRS(
|
|
LFS3_RATTR_BUF(
|
|
LFS3_TAG_ATTR(1), 0,
|
|
&(uint8_t){'a'+(i % 26)}, 1))) => 0;
|
|
lfs3_data_t data;
|
|
|
|
uint8_t buffer[4];
|
|
lfs3_mdir_lookup(&lfs3, &lfs3.mroot, LFS3_TAG_ATTR(1),
|
|
&data) => LFS3_TAG_ATTR(1);
|
|
lfs3_data_read(&lfs3, &data, buffer, 4) => 1;
|
|
assert(memcmp(buffer, &(uint8_t){'a'+(i % 26)}, 1) == 0);
|
|
}
|
|
lfs3_data_t data;
|
|
|
|
uint8_t buffer[4];
|
|
lfs3_mdir_lookup(&lfs3, &lfs3.mroot, LFS3_TAG_ATTR(1),
|
|
&data) => LFS3_TAG_ATTR(1);
|
|
lfs3_data_read(&lfs3, &data, buffer, 4) => 1;
|
|
assert(memcmp(buffer, &(uint8_t){'a'+((N-1) % 26)}, 1) == 0);
|
|
|
|
lfs3_unmount(&lfs3) => 0;
|
|
|
|
|
|
// check things stay sane after remount
|
|
lfs3_mount(&lfs3, LFS3_M_RDWR | M_FLAGS, CFG) => 0;
|
|
|
|
lfs3_mdir_lookup(&lfs3, &lfs3.mroot, LFS3_TAG_ATTR(1),
|
|
&data) => LFS3_TAG_ATTR(1);
|
|
lfs3_data_read(&lfs3, &data, buffer, 4) => 1;
|
|
assert(memcmp(buffer, &(uint8_t){'a'+((N-1) % 26)}, 1) == 0);
|
|
|
|
lfs3_unmount(&lfs3) => 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 = 'lfs3.c'
|
|
code = '''
|
|
lfs3_t lfs3;
|
|
lfs3_format(&lfs3, LFS3_F_RDWR | F_FLAGS, CFG) => 0;
|
|
lfs3_mount(&lfs3, LFS3_M_RDWR | M_FLAGS, CFG) => 0;
|
|
lfs3_alloc_ckpoint(&lfs3);
|
|
lfs3_data_t data;
|
|
|
|
// create a 2 large rattrs that needs to be uninlined
|
|
uint8_t buffer[SIZE];
|
|
memset(buffer, 'a', SIZE);
|
|
lfs3_mdir_commit(&lfs3, &lfs3.mroot, LFS3_RATTRS(
|
|
LFS3_RATTR_DATA(LFS3_TAG_ATTR(1), 0,
|
|
&LFS3_DATA_BUF(buffer, SIZE)))) => 0;
|
|
|
|
memset(buffer, 'b', SIZE);
|
|
lfs3_mdir_t mdir;
|
|
lfs3_mtree_lookup(&lfs3, 0, &mdir) => 0;
|
|
lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS(
|
|
LFS3_RATTR_DATA(LFS3_TAG_ATTR(2), 0,
|
|
&LFS3_DATA_BUF(buffer, SIZE)))) => 0;
|
|
|
|
// force mroot to compact
|
|
lfs3.mroot.r.eoff = -1;
|
|
lfs3_mdir_commit(&lfs3, &lfs3.mroot, LFS3_RATTRS(
|
|
LFS3_RATTR_BUF(LFS3_TAG_ATTR(3), 0, "c", 1))) => 0;
|
|
|
|
// assert mdirs were unininlined
|
|
assert(lfs3.mtree.r.weight == (1 << lfs3.mbits));
|
|
// assert mroot now has no entries
|
|
assert(lfs3.mroot.r.weight == 0);
|
|
|
|
// assert that our rattrs are still in the mroot/mtree
|
|
lfs3_mdir_lookup(&lfs3, &lfs3.mroot, LFS3_TAG_ATTR(1),
|
|
&data) => LFS3_TAG_ATTR(1);
|
|
lfs3_data_read(&lfs3, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[0] == 'a');
|
|
|
|
lfs3_mtree_lookup(&lfs3, 0, &mdir) => 0;
|
|
assert(mdir.r.weight == 1);
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_ATTR(2),
|
|
&data) => LFS3_TAG_ATTR(2);
|
|
lfs3_data_read(&lfs3, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[0] == 'b');
|
|
|
|
lfs3_mdir_lookup(&lfs3, &lfs3.mroot, LFS3_TAG_ATTR(3),
|
|
&data) => LFS3_TAG_ATTR(3);
|
|
lfs3_data_read(&lfs3, &data, buffer, 1) => 1;
|
|
assert(buffer[0] == 'c');
|
|
|
|
// check things stay sane after remount
|
|
lfs3_unmount(&lfs3) => 0;
|
|
lfs3_mount(&lfs3, LFS3_M_RDWR | M_FLAGS, CFG) => 0;
|
|
|
|
// assert mdirs were unininlined
|
|
assert(lfs3.mtree.r.weight == (1 << lfs3.mbits));
|
|
// assert mroot now has no entries
|
|
assert(lfs3.mroot.r.weight == 0);
|
|
|
|
// assert that our rattrs are still in the mroot/mtree
|
|
lfs3_mdir_lookup(&lfs3, &lfs3.mroot, LFS3_TAG_ATTR(1),
|
|
&data) => LFS3_TAG_ATTR(1);
|
|
lfs3_data_read(&lfs3, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[0] == 'a');
|
|
|
|
lfs3_mtree_lookup(&lfs3, 0, &mdir) => 0;
|
|
assert(mdir.r.weight == 1);
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_ATTR(2),
|
|
&data) => LFS3_TAG_ATTR(2);
|
|
lfs3_data_read(&lfs3, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[0] == 'b');
|
|
|
|
lfs3_mdir_lookup(&lfs3, &lfs3.mroot, LFS3_TAG_ATTR(3),
|
|
&data) => LFS3_TAG_ATTR(3);
|
|
lfs3_data_read(&lfs3, &data, buffer, 1) => 1;
|
|
assert(buffer[0] == 'c');
|
|
|
|
lfs3_unmount(&lfs3) => 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 = 'lfs3.c'
|
|
code = '''
|
|
lfs3_t lfs3;
|
|
lfs3_format(&lfs3, LFS3_F_RDWR | F_FLAGS, CFG) => 0;
|
|
lfs3_mount(&lfs3, LFS3_M_RDWR | M_FLAGS, CFG) => 0;
|
|
lfs3_alloc_ckpoint(&lfs3);
|
|
lfs3_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);
|
|
lfs3_mdir_t mdir;
|
|
lfs3_mtree_namelookup(&lfs3, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL) => LFS3_ERR_NOENT;
|
|
lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS(
|
|
LFS3_RATTR_NAME(
|
|
LFS3_TAG_REG, +1,
|
|
0, (const char*)buffer+1, SIZE-1))) => 0;
|
|
assert(mdir.r.weight == 2);
|
|
|
|
memset(buffer+1, 'b', SIZE-1);
|
|
lfs3_mtree_namelookup(&lfs3, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL) => LFS3_ERR_NOENT;
|
|
lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS(
|
|
LFS3_RATTR_NAME(
|
|
LFS3_TAG_REG, +1,
|
|
0, (const char*)buffer+1, SIZE-1))) => 0;
|
|
assert(mdir.r.weight == 3);
|
|
|
|
// force mroot to compact
|
|
lfs3.mroot.r.eoff = -1;
|
|
lfs3_mdir_commit(&lfs3, &lfs3.mroot, LFS3_RATTRS(
|
|
LFS3_RATTR_BUF(LFS3_TAG_ATTR(1), 0, "c", 1))) => 0;
|
|
|
|
// assert mdirs were unininlined and split
|
|
assert(lfs3.mtree.r.weight == (2 << lfs3.mbits));
|
|
// assert mroot now has no entries
|
|
assert(lfs3.mroot.r.weight == 0);
|
|
|
|
// assert that our entries are still in the mtree
|
|
lfs3_mtree_lookup(&lfs3, (0 << lfs3.mbits)+1, &mdir) => 0;
|
|
assert(mdir.r.weight == 2);
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_REG,
|
|
&data) => LFS3_TAG_REG;
|
|
lfs3_data_read(&lfs3, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'a');
|
|
|
|
lfs3_mtree_lookup(&lfs3, (1 << lfs3.mbits)+0, &mdir) => 0;
|
|
assert(mdir.r.weight == 1);
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_REG,
|
|
&data) => LFS3_TAG_REG;
|
|
lfs3_data_read(&lfs3, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'b');
|
|
|
|
lfs3_mdir_lookup(&lfs3, &lfs3.mroot, LFS3_TAG_ATTR(1),
|
|
&data) => LFS3_TAG_ATTR(1);
|
|
lfs3_data_read(&lfs3, &data, buffer, 1) => 1;
|
|
assert(buffer[0] == 'c');
|
|
|
|
// check things stay sane after remount
|
|
lfs3_unmount(&lfs3) => 0;
|
|
lfs3_mount(&lfs3, LFS3_M_RDWR | M_FLAGS, CFG) => 0;
|
|
|
|
// assert mdirs were unininlined and split
|
|
assert(lfs3.mtree.r.weight == (2 << lfs3.mbits));
|
|
// assert mroot now has no entries
|
|
assert(lfs3.mroot.r.weight == 0);
|
|
|
|
// assert that our entries are still in the mtree
|
|
lfs3_mtree_lookup(&lfs3, (0 << lfs3.mbits)+1, &mdir) => 0;
|
|
assert(mdir.r.weight == 2);
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_REG,
|
|
&data) => LFS3_TAG_REG;
|
|
lfs3_data_read(&lfs3, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'a');
|
|
|
|
lfs3_mtree_lookup(&lfs3, (1 << lfs3.mbits)+0, &mdir) => 0;
|
|
assert(mdir.r.weight == 1);
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_REG,
|
|
&data) => LFS3_TAG_REG;
|
|
lfs3_data_read(&lfs3, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'b');
|
|
|
|
lfs3_mdir_lookup(&lfs3, &lfs3.mroot, LFS3_TAG_ATTR(1),
|
|
&data) => LFS3_TAG_ATTR(1);
|
|
lfs3_data_read(&lfs3, &data, buffer, 1) => 1;
|
|
assert(buffer[0] == 'c');
|
|
|
|
lfs3_unmount(&lfs3) => 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 = 'lfs3.c'
|
|
code = '''
|
|
lfs3_t lfs3;
|
|
lfs3_format(&lfs3, LFS3_F_RDWR | F_FLAGS, CFG) => 0;
|
|
lfs3_mount(&lfs3, LFS3_M_RDWR | M_FLAGS, CFG) => 0;
|
|
lfs3_alloc_ckpoint(&lfs3);
|
|
lfs3_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);
|
|
lfs3_mdir_t mdir;
|
|
lfs3_mtree_namelookup(&lfs3, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL) => LFS3_ERR_NOENT;
|
|
lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS(
|
|
LFS3_RATTR_NAME(
|
|
LFS3_TAG_REG, +1,
|
|
0, (const char*)buffer+1, SIZE-1))) => 0;
|
|
assert(mdir.r.weight == 2);
|
|
|
|
memset(buffer+1, 'b', SIZE-1);
|
|
lfs3_mtree_namelookup(&lfs3, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL) => LFS3_ERR_NOENT;
|
|
lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS(
|
|
LFS3_RATTR_NAME(
|
|
LFS3_TAG_REG, +1,
|
|
0, (const char*)buffer+1, SIZE-1))) => 0;
|
|
assert(mdir.r.weight == 3);
|
|
|
|
// force mroot to compact
|
|
lfs3.mroot.r.eoff = -1;
|
|
lfs3_mdir_commit(&lfs3, &lfs3.mroot, LFS3_RATTRS(
|
|
LFS3_RATTR_BUF(LFS3_TAG_ATTR(1), 0, "c", 1))) => 0;
|
|
|
|
// assert mdirs were unininlined and split
|
|
assert(lfs3.mtree.r.weight == (2 << lfs3.mbits));
|
|
// assert mroot now has no entries
|
|
assert(lfs3.mroot.r.weight == 0);
|
|
|
|
// now add another large entry to an mdir, forcing a split
|
|
memset(buffer+1, 'd', SIZE-1);
|
|
lfs3_mtree_namelookup(&lfs3, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL) => LFS3_ERR_NOENT;
|
|
lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS(
|
|
LFS3_RATTR_NAME(
|
|
LFS3_TAG_REG, +1,
|
|
0, (const char*)buffer+1, SIZE-1))) => 0;
|
|
assert(mdir.r.weight == 2);
|
|
|
|
// force mdir to compact
|
|
mdir.r.eoff = -1;
|
|
lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS(
|
|
LFS3_RATTR_BUF(LFS3_TAG_ATTR(2), 0, "e", 1))) => 0;
|
|
|
|
// assert mdir was split correctly
|
|
assert(lfs3.mtree.r.weight == (3 << lfs3.mbits));
|
|
// assert mroot still has no entries
|
|
assert(lfs3.mroot.r.weight == 0);
|
|
|
|
// assert that our entries are still in the mtree
|
|
lfs3_mtree_lookup(&lfs3, (0 << lfs3.mbits)+1, &mdir) => 0;
|
|
assert(mdir.r.weight == 2);
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_REG,
|
|
&data) => LFS3_TAG_REG;
|
|
lfs3_data_read(&lfs3, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'a');
|
|
|
|
lfs3_mtree_lookup(&lfs3, (1 << lfs3.mbits)+0, &mdir) => 0;
|
|
assert(mdir.r.weight == 1);
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_REG,
|
|
&data) => LFS3_TAG_REG;
|
|
lfs3_data_read(&lfs3, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'b');
|
|
|
|
lfs3_mdir_lookup(&lfs3, &lfs3.mroot, LFS3_TAG_ATTR(1),
|
|
&data) => LFS3_TAG_ATTR(1);
|
|
lfs3_data_read(&lfs3, &data, buffer, 1) => 1;
|
|
assert(buffer[0] == 'c');
|
|
|
|
lfs3_mtree_lookup(&lfs3, (2 << lfs3.mbits)+0, &mdir) => 0;
|
|
assert(mdir.r.weight == 1);
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_REG,
|
|
&data) => LFS3_TAG_REG;
|
|
lfs3_data_read(&lfs3, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'd');
|
|
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_ATTR(2),
|
|
&data) => LFS3_TAG_ATTR(2);
|
|
lfs3_data_read(&lfs3, &data, buffer, 1) => 1;
|
|
assert(buffer[0] == 'e');
|
|
|
|
// check things stay sane after remount
|
|
lfs3_unmount(&lfs3) => 0;
|
|
lfs3_mount(&lfs3, LFS3_M_RDWR | M_FLAGS, CFG) => 0;
|
|
|
|
// assert mdir was split correctly
|
|
assert(lfs3.mtree.r.weight == (3 << lfs3.mbits));
|
|
// assert mroot still has no entries
|
|
assert(lfs3.mroot.r.weight == 0);
|
|
|
|
// assert that our entries are still in the mtree
|
|
lfs3_mtree_lookup(&lfs3, (0 << lfs3.mbits)+1, &mdir) => 0;
|
|
assert(mdir.r.weight == 2);
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_REG,
|
|
&data) => LFS3_TAG_REG;
|
|
lfs3_data_read(&lfs3, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'a');
|
|
|
|
lfs3_mtree_lookup(&lfs3, (1 << lfs3.mbits)+0, &mdir) => 0;
|
|
assert(mdir.r.weight == 1);
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_REG,
|
|
&data) => LFS3_TAG_REG;
|
|
lfs3_data_read(&lfs3, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'b');
|
|
|
|
lfs3_mdir_lookup(&lfs3, &lfs3.mroot, LFS3_TAG_ATTR(1),
|
|
&data) => LFS3_TAG_ATTR(1);
|
|
lfs3_data_read(&lfs3, &data, buffer, 1) => 1;
|
|
assert(buffer[0] == 'c');
|
|
|
|
lfs3_mtree_lookup(&lfs3, (2 << lfs3.mbits)+0, &mdir) => 0;
|
|
assert(mdir.r.weight == 1);
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_REG,
|
|
&data) => LFS3_TAG_REG;
|
|
lfs3_data_read(&lfs3, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'd');
|
|
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_ATTR(2),
|
|
&data) => LFS3_TAG_ATTR(2);
|
|
lfs3_data_read(&lfs3, &data, buffer, 1) => 1;
|
|
assert(buffer[0] == 'e');
|
|
|
|
lfs3_unmount(&lfs3) => 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 = 'lfs3.c'
|
|
code = '''
|
|
lfs3_t lfs3;
|
|
lfs3_format(&lfs3, LFS3_F_RDWR | F_FLAGS, CFG) => 0;
|
|
lfs3_mount(&lfs3, LFS3_M_RDWR | M_FLAGS, CFG) => 0;
|
|
lfs3_alloc_ckpoint(&lfs3);
|
|
|
|
// create entries
|
|
for (lfs3_size_t i = 0; i < N; i++) {
|
|
char name[256];
|
|
name[0] = '\0';
|
|
sprintf(name+1, "%03x", i);
|
|
lfs3_mdir_t mdir;
|
|
lfs3_mtree_namelookup(&lfs3, 0, name+1, 3,
|
|
&mdir, NULL) => LFS3_ERR_NOENT;
|
|
// force a compaction?
|
|
if (FORCE_COMPACTION) {
|
|
lfs3.mroot.r.eoff = -1;
|
|
mdir.r.eoff = -1;
|
|
}
|
|
lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS(
|
|
LFS3_RATTR_NAME(LFS3_TAG_REG, +1, 0, name+1, 3))) => 0;
|
|
lfs3_data_t data;
|
|
|
|
uint8_t buffer[256];
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_REG,
|
|
&data) => LFS3_TAG_REG;
|
|
lfs3_data_read(&lfs3, &data, buffer, sizeof(buffer)) => 4;
|
|
assert(memcmp(buffer, name, 4) == 0);
|
|
}
|
|
|
|
// try looking up each entry
|
|
lfs3_mdir_t mdir;
|
|
lfs3_mtree_lookup(&lfs3, 0, &mdir) => 0;
|
|
lfs3_data_t data;
|
|
uint8_t buffer[256];
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_BOOKMARK,
|
|
&data) => LFS3_TAG_BOOKMARK;
|
|
lfs3_data_read(&lfs3, &data, buffer, sizeof(buffer)) => 1;
|
|
for (lfs3_size_t i = 0; i < N; i++) {
|
|
char name[256];
|
|
name[0] = '\0';
|
|
sprintf(name+1, "%03x", i);
|
|
mdir.mid += 1;
|
|
if (lfs3_mrid(&lfs3, mdir.mid) >= (lfs3_srid_t)mdir.r.weight) {
|
|
lfs3_mtree_lookup(&lfs3, lfs3_mbid(&lfs3, mdir.mid) + 1,
|
|
&mdir) => 0;
|
|
}
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_REG,
|
|
&data) => LFS3_TAG_REG;
|
|
lfs3_data_read(&lfs3, &data, buffer, sizeof(buffer)) => 4;
|
|
assert(memcmp(buffer, name, 4) == 0);
|
|
}
|
|
|
|
// check things stay sane after remount
|
|
lfs3_unmount(&lfs3) => 0;
|
|
lfs3_mount(&lfs3, LFS3_M_RDWR | M_FLAGS, CFG) => 0;
|
|
|
|
// try looking up each entry
|
|
lfs3_mtree_lookup(&lfs3, 0, &mdir) => 0;
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_BOOKMARK,
|
|
&data) => LFS3_TAG_BOOKMARK;
|
|
lfs3_data_read(&lfs3, &data, buffer, sizeof(buffer)) => 1;
|
|
for (lfs3_size_t i = 0; i < N; i++) {
|
|
char name[256];
|
|
name[0] = '\0';
|
|
sprintf(name+1, "%03x", i);
|
|
mdir.mid += 1;
|
|
if (lfs3_mrid(&lfs3, mdir.mid) >= (lfs3_srid_t)mdir.r.weight) {
|
|
lfs3_mtree_lookup(&lfs3, lfs3_mbid(&lfs3, mdir.mid) + 1,
|
|
&mdir) => 0;
|
|
}
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_REG,
|
|
&data) => LFS3_TAG_REG;
|
|
lfs3_data_read(&lfs3, &data, buffer, sizeof(buffer)) => 4;
|
|
assert(memcmp(buffer, name, 4) == 0);
|
|
}
|
|
|
|
lfs3_unmount(&lfs3) => 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)'
|
|
fuzz = 'SEED'
|
|
in = 'lfs3.c'
|
|
code = '''
|
|
lfs3_t lfs3;
|
|
lfs3_format(&lfs3, LFS3_F_RDWR | F_FLAGS, CFG) => 0;
|
|
lfs3_mount(&lfs3, LFS3_M_RDWR | M_FLAGS, CFG) => 0;
|
|
lfs3_alloc_ckpoint(&lfs3);
|
|
|
|
bool sim[N];
|
|
for (lfs3_size_t i = 0; i < N; i++) {
|
|
sim[i] = false;
|
|
}
|
|
|
|
uint32_t prng = SEED;
|
|
for (lfs3_size_t i = 0; i < N; i++) {
|
|
// choose a pseudo-random name
|
|
lfs3_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);
|
|
lfs3_mdir_t mdir;
|
|
lfs3_stag_t tag = lfs3_mtree_namelookup(&lfs3, 0, name+1, 3,
|
|
&mdir, NULL);
|
|
assert(tag >= 0 || tag == LFS3_ERR_NOENT);
|
|
if (tag != LFS3_ERR_NOENT) {
|
|
continue;
|
|
}
|
|
// force a compaction?
|
|
if (FORCE_COMPACTION) {
|
|
lfs3.mroot.r.eoff = -1;
|
|
mdir.r.eoff = -1;
|
|
}
|
|
lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS(
|
|
LFS3_RATTR_NAME(LFS3_TAG_REG, +1, 0, name+1, 3))) => 0;
|
|
lfs3_data_t data;
|
|
|
|
// double check
|
|
uint8_t buffer[256];
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_REG,
|
|
&data) => LFS3_TAG_REG;
|
|
lfs3_data_read(&lfs3, &data, buffer, sizeof(buffer)) => 4;
|
|
assert(memcmp(buffer, name, 4) == 0);
|
|
}
|
|
|
|
// try looking up each entry
|
|
lfs3_mdir_t mdir;
|
|
lfs3_mtree_lookup(&lfs3, 0, &mdir) => 0;
|
|
lfs3_data_t data;
|
|
uint8_t buffer[256];
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_BOOKMARK,
|
|
&data) => LFS3_TAG_BOOKMARK;
|
|
lfs3_data_read(&lfs3, &data, buffer, sizeof(buffer)) => 1;
|
|
for (lfs3_size_t i = 0; i < N; i++) {
|
|
char name[256];
|
|
name[0] = '\0';
|
|
sprintf(name+1, "%03x", i);
|
|
if (sim[i]) {
|
|
lfs3_mtree_namelookup(&lfs3, 0, name+1, 3,
|
|
&mdir, NULL) => LFS3_TAG_REG;
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_REG,
|
|
&data) => LFS3_TAG_REG;
|
|
lfs3_data_read(&lfs3, &data, buffer, sizeof(buffer)) => 4;
|
|
assert(memcmp(buffer, name, 4) == 0);
|
|
} else {
|
|
lfs3_mtree_namelookup(&lfs3, 0, name+1, 3,
|
|
&mdir, NULL) => LFS3_ERR_NOENT;
|
|
}
|
|
}
|
|
|
|
// check things stay sane after remount
|
|
lfs3_unmount(&lfs3) => 0;
|
|
lfs3_mount(&lfs3, LFS3_M_RDWR | M_FLAGS, CFG) => 0;
|
|
|
|
// try looking up each entry
|
|
lfs3_mtree_lookup(&lfs3, 0, &mdir) => 0;
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_BOOKMARK,
|
|
&data) => LFS3_TAG_BOOKMARK;
|
|
lfs3_data_read(&lfs3, &data, buffer, sizeof(buffer)) => 1;
|
|
for (lfs3_size_t i = 0; i < N; i++) {
|
|
char name[256];
|
|
name[0] = '\0';
|
|
sprintf(name+1, "%03x", i);
|
|
if (sim[i]) {
|
|
lfs3_mtree_namelookup(&lfs3, 0, name+1, 3,
|
|
&mdir, NULL) => LFS3_TAG_REG;
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_REG,
|
|
&data) => LFS3_TAG_REG;
|
|
lfs3_data_read(&lfs3, &data, buffer, sizeof(buffer)) => 4;
|
|
assert(memcmp(buffer, name, 4) == 0);
|
|
} else {
|
|
lfs3_mtree_namelookup(&lfs3, 0, name+1, 3,
|
|
&mdir, NULL) => LFS3_ERR_NOENT;
|
|
}
|
|
}
|
|
|
|
lfs3_unmount(&lfs3) => 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 = 'lfs3.c'
|
|
code = '''
|
|
lfs3_t lfs3;
|
|
lfs3_format(&lfs3, LFS3_F_RDWR | F_FLAGS, CFG) => 0;
|
|
lfs3_mount(&lfs3, LFS3_M_RDWR | M_FLAGS, CFG) => 0;
|
|
lfs3_alloc_ckpoint(&lfs3);
|
|
lfs3_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);
|
|
lfs3_mdir_t mdir;
|
|
lfs3_mtree_namelookup(&lfs3, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL) => LFS3_ERR_NOENT;
|
|
lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS(
|
|
LFS3_RATTR_NAME(
|
|
LFS3_TAG_REG, +1,
|
|
0, (const char*)buffer+1, SIZE-1))) => 0;
|
|
assert(mdir.r.weight == 2);
|
|
|
|
memset(buffer+1, 'b', SIZE-1);
|
|
lfs3_mtree_namelookup(&lfs3, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL) => LFS3_ERR_NOENT;
|
|
lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS(
|
|
LFS3_RATTR_NAME(
|
|
LFS3_TAG_REG, +1,
|
|
0, (const char*)buffer+1, SIZE-1))) => 0;
|
|
assert(mdir.r.weight == 3);
|
|
|
|
// force mroot to compact
|
|
lfs3.mroot.r.eoff = -1;
|
|
lfs3_mdir_commit(&lfs3, &lfs3.mroot, LFS3_RATTRS(
|
|
LFS3_RATTR_BUF(LFS3_TAG_ATTR(1), 0, "c", 1))) => 0;
|
|
|
|
// assert mdirs were unininlined and split
|
|
assert(lfs3.mtree.r.weight == (2 << lfs3.mbits));
|
|
// assert mroot now has no entries
|
|
assert(lfs3.mroot.r.weight == 0);
|
|
|
|
// remove an entry, forcing the mdir to be dropped
|
|
memset(buffer+1, 'b', SIZE-1);
|
|
lfs3_mtree_namelookup(&lfs3, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL) => LFS3_TAG_REG;
|
|
lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS(
|
|
LFS3_RATTR(LFS3_TAG_RM, -1))) => 0;
|
|
assert(mdir.r.weight == 0);
|
|
|
|
// assert mdir was dropped
|
|
assert(lfs3.mtree.r.weight == (1 << lfs3.mbits));
|
|
// assert mroot still has no entries
|
|
assert(lfs3.mroot.r.weight == 0);
|
|
|
|
// assert that our entries are still in the mtree
|
|
lfs3_mtree_lookup(&lfs3, (0 << lfs3.mbits)+1, &mdir) => 0;
|
|
assert(mdir.r.weight == 2);
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_REG,
|
|
&data) => LFS3_TAG_REG;
|
|
lfs3_data_read(&lfs3, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'a');
|
|
|
|
lfs3_mdir_lookup(&lfs3, &lfs3.mroot, LFS3_TAG_ATTR(1),
|
|
&data) => LFS3_TAG_ATTR(1);
|
|
lfs3_data_read(&lfs3, &data, buffer, 1) => 1;
|
|
assert(buffer[0] == 'c');
|
|
|
|
// check things stay sane after remount
|
|
lfs3_unmount(&lfs3) => 0;
|
|
lfs3_mount(&lfs3, LFS3_M_RDWR | M_FLAGS, CFG) => 0;
|
|
|
|
// assert mdir was dropped
|
|
assert(lfs3.mtree.r.weight == (1 << lfs3.mbits));
|
|
// assert mroot still has no entries
|
|
assert(lfs3.mroot.r.weight == 0);
|
|
|
|
// assert that our entries are still in the mtree
|
|
lfs3_mtree_lookup(&lfs3, (0 << lfs3.mbits)+1, &mdir) => 0;
|
|
assert(mdir.r.weight == 2);
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_REG,
|
|
&data) => LFS3_TAG_REG;
|
|
lfs3_data_read(&lfs3, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'a');
|
|
|
|
lfs3_mdir_lookup(&lfs3, &lfs3.mroot, LFS3_TAG_ATTR(1),
|
|
&data) => LFS3_TAG_ATTR(1);
|
|
lfs3_data_read(&lfs3, &data, buffer, 1) => 1;
|
|
assert(buffer[0] == 'c');
|
|
|
|
lfs3_unmount(&lfs3) => 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 = 'lfs3.c'
|
|
code = '''
|
|
lfs3_t lfs3;
|
|
lfs3_format(&lfs3, LFS3_F_RDWR | F_FLAGS, CFG) => 0;
|
|
lfs3_mount(&lfs3, LFS3_M_RDWR | M_FLAGS, CFG) => 0;
|
|
lfs3_alloc_ckpoint(&lfs3);
|
|
lfs3_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);
|
|
lfs3_mdir_t mdir;
|
|
lfs3_mtree_namelookup(&lfs3, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL) => LFS3_ERR_NOENT;
|
|
lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS(
|
|
LFS3_RATTR_NAME(
|
|
LFS3_TAG_REG, +1,
|
|
0, (const char*)buffer+1, SIZE-1))) => 0;
|
|
assert(mdir.r.weight == 2);
|
|
|
|
memset(buffer+1, 'b', SIZE-1);
|
|
lfs3_mtree_namelookup(&lfs3, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL) => LFS3_ERR_NOENT;
|
|
lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS(
|
|
LFS3_RATTR_NAME(
|
|
LFS3_TAG_REG, +1,
|
|
0, (const char*)buffer+1, SIZE-1))) => 0;
|
|
assert(mdir.r.weight == 3);
|
|
|
|
// force mroot to compact
|
|
lfs3.mroot.r.eoff = -1;
|
|
lfs3_mdir_commit(&lfs3, &lfs3.mroot, LFS3_RATTRS(
|
|
LFS3_RATTR_BUF(LFS3_TAG_ATTR(1), 0, "c", 1))) => 0;
|
|
|
|
// assert mdirs were unininlined and split
|
|
assert(lfs3.mtree.r.weight == (2 << lfs3.mbits));
|
|
// assert mroot now has no entries
|
|
assert(lfs3.mroot.r.weight == 0);
|
|
|
|
// remove an entry, forcing the mdir to be dropped
|
|
memset(buffer+1, 'b', SIZE-1);
|
|
lfs3_mtree_namelookup(&lfs3, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL) => LFS3_TAG_REG;
|
|
// force mdir to compact while we're removing
|
|
mdir.r.eoff = -1;
|
|
lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS(
|
|
LFS3_RATTR(LFS3_TAG_RM, -1))) => 0;
|
|
assert(mdir.r.weight == 0);
|
|
|
|
// assert mdir was dropped
|
|
assert(lfs3.mtree.r.weight == (1 << lfs3.mbits));
|
|
// assert mroot still has no entries
|
|
assert(lfs3.mroot.r.weight == 0);
|
|
|
|
// assert that our entries are still in the mtree
|
|
lfs3_mtree_lookup(&lfs3, (0 << lfs3.mbits)+1, &mdir) => 0;
|
|
assert(mdir.r.weight == 2);
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_REG,
|
|
&data) => LFS3_TAG_REG;
|
|
lfs3_data_read(&lfs3, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'a');
|
|
|
|
lfs3_mdir_lookup(&lfs3, &lfs3.mroot, LFS3_TAG_ATTR(1),
|
|
&data) => LFS3_TAG_ATTR(1);
|
|
lfs3_data_read(&lfs3, &data, buffer, 1) => 1;
|
|
assert(buffer[0] == 'c');
|
|
|
|
// check things stay sane after remount
|
|
lfs3_unmount(&lfs3) => 0;
|
|
lfs3_mount(&lfs3, LFS3_M_RDWR | M_FLAGS, CFG) => 0;
|
|
|
|
// assert mdir was dropped
|
|
assert(lfs3.mtree.r.weight == (1 << lfs3.mbits));
|
|
// assert mroot still has no entries
|
|
assert(lfs3.mroot.r.weight == 0);
|
|
|
|
// assert that our entries are still in the mtree
|
|
lfs3_mtree_lookup(&lfs3, (0 << lfs3.mbits)+1, &mdir) => 0;
|
|
assert(mdir.r.weight == 2);
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_REG,
|
|
&data) => LFS3_TAG_REG;
|
|
lfs3_data_read(&lfs3, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'a');
|
|
|
|
lfs3_mdir_lookup(&lfs3, &lfs3.mroot, LFS3_TAG_ATTR(1),
|
|
&data) => LFS3_TAG_ATTR(1);
|
|
lfs3_data_read(&lfs3, &data, buffer, 1) => 1;
|
|
assert(buffer[0] == 'c');
|
|
|
|
lfs3_unmount(&lfs3) => 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 = 'lfs3.c'
|
|
code = '''
|
|
lfs3_t lfs3;
|
|
lfs3_format(&lfs3, LFS3_F_RDWR | F_FLAGS, CFG) => 0;
|
|
lfs3_mount(&lfs3, LFS3_M_RDWR | M_FLAGS, CFG) => 0;
|
|
lfs3_alloc_ckpoint(&lfs3);
|
|
lfs3_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);
|
|
lfs3_mdir_t mdir;
|
|
lfs3_mtree_namelookup(&lfs3, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL) => LFS3_ERR_NOENT;
|
|
lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS(
|
|
LFS3_RATTR_NAME(
|
|
LFS3_TAG_REG, +1,
|
|
0, (const char*)buffer+1, SIZE-1))) => 0;
|
|
assert(mdir.r.weight == 2);
|
|
|
|
memset(buffer+1, 'b', SIZE-1);
|
|
lfs3_mtree_namelookup(&lfs3, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL) => LFS3_ERR_NOENT;
|
|
lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS(
|
|
LFS3_RATTR_NAME(
|
|
LFS3_TAG_REG, +1,
|
|
0, (const char*)buffer+1, SIZE-1))) => 0;
|
|
assert(mdir.r.weight == 3);
|
|
|
|
// remove an entry, forcing the mdir to be dropped
|
|
memset(buffer+1, 'b', SIZE-1);
|
|
lfs3_mtree_namelookup(&lfs3, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL) => LFS3_TAG_REG;
|
|
// force mdir to compact while we're removing
|
|
lfs3.mroot.r.eoff = -1;
|
|
mdir.r.eoff = -1;
|
|
lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS(
|
|
LFS3_RATTR(LFS3_TAG_RM, -1))) => 0;
|
|
assert(mdir.r.weight == 2);
|
|
|
|
// assert split/drop worked out
|
|
assert(lfs3.mtree.r.weight == (1 << lfs3.mbits));
|
|
// assert mroot still has no entries
|
|
assert(lfs3.mroot.r.weight == 0);
|
|
|
|
// assert that our entries are still in the mtree
|
|
lfs3_mtree_lookup(&lfs3, (0 << lfs3.mbits)+1, &mdir) => 0;
|
|
assert(mdir.r.weight == 2);
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_REG,
|
|
&data) => LFS3_TAG_REG;
|
|
lfs3_data_read(&lfs3, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'a');
|
|
|
|
// check things stay sane after remount
|
|
lfs3_unmount(&lfs3) => 0;
|
|
lfs3_mount(&lfs3, LFS3_M_RDWR | M_FLAGS, CFG) => 0;
|
|
|
|
// assert mdir was dropped
|
|
assert(lfs3.mtree.r.weight == (1 << lfs3.mbits));
|
|
// assert mroot still has no entries
|
|
assert(lfs3.mroot.r.weight == 0);
|
|
|
|
// assert that our entries are still in the mtree
|
|
lfs3_mtree_lookup(&lfs3, (0 << lfs3.mbits)+1, &mdir) => 0;
|
|
assert(mdir.r.weight == 2);
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_REG,
|
|
&data) => LFS3_TAG_REG;
|
|
lfs3_data_read(&lfs3, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'a');
|
|
|
|
lfs3_unmount(&lfs3) => 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 = 'lfs3.c'
|
|
code = '''
|
|
lfs3_t lfs3;
|
|
lfs3_format(&lfs3, LFS3_F_RDWR | F_FLAGS, CFG) => 0;
|
|
lfs3_mount(&lfs3, LFS3_M_RDWR | M_FLAGS, CFG) => 0;
|
|
lfs3_alloc_ckpoint(&lfs3);
|
|
lfs3_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);
|
|
lfs3_mdir_t mdir;
|
|
lfs3_mtree_namelookup(&lfs3, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL) => LFS3_ERR_NOENT;
|
|
lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS(
|
|
LFS3_RATTR_NAME(
|
|
LFS3_TAG_REG, +1,
|
|
0, (const char*)buffer+1, SIZE-1))) => 0;
|
|
assert(mdir.r.weight == 2);
|
|
|
|
memset(buffer+1, 'b', SIZE-1);
|
|
lfs3_mtree_namelookup(&lfs3, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL) => LFS3_ERR_NOENT;
|
|
lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS(
|
|
LFS3_RATTR_NAME(
|
|
LFS3_TAG_REG, +1,
|
|
0, (const char*)buffer+1, SIZE-1))) => 0;
|
|
assert(mdir.r.weight == 3);
|
|
|
|
// force mroot to compact
|
|
lfs3.mroot.r.eoff = -1;
|
|
lfs3_mdir_commit(&lfs3, &lfs3.mroot, LFS3_RATTRS(
|
|
LFS3_RATTR_BUF(LFS3_TAG_ATTR(1), 0, "c", 1))) => 0;
|
|
|
|
// assert mdirs were unininlined and split
|
|
assert(lfs3.mtree.r.weight == (2 << lfs3.mbits));
|
|
// assert mroot now has no entries
|
|
assert(lfs3.mroot.r.weight == 0);
|
|
|
|
// now add another large entry to an mdir, forcing a split
|
|
memset(buffer+1, 'd', SIZE-1);
|
|
lfs3_mtree_namelookup(&lfs3, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL) => LFS3_ERR_NOENT;
|
|
lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS(
|
|
LFS3_RATTR_NAME(
|
|
LFS3_TAG_REG, +1,
|
|
0, (const char*)buffer+1, SIZE-1))) => 0;
|
|
assert(mdir.r.weight == 2);
|
|
|
|
// remove an entry, forcing the mdir to be dropped
|
|
memset(buffer+1, 'b', SIZE-1);
|
|
lfs3_mtree_namelookup(&lfs3, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL) => LFS3_TAG_REG;
|
|
// force mdir to compact while we're removing
|
|
mdir.r.eoff = -1;
|
|
lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS(
|
|
LFS3_RATTR(LFS3_TAG_RM, -1))) => 0;
|
|
assert(mdir.r.weight == 1);
|
|
|
|
// assert split/drop worked out
|
|
assert(lfs3.mtree.r.weight == (2 << lfs3.mbits));
|
|
// assert mroot still has no entries
|
|
assert(lfs3.mroot.r.weight == 0);
|
|
|
|
// assert that our entries are still in the mtree
|
|
lfs3_mtree_lookup(&lfs3, (0 << lfs3.mbits)+1, &mdir) => 0;
|
|
assert(mdir.r.weight == 2);
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_REG,
|
|
&data) => LFS3_TAG_REG;
|
|
lfs3_data_read(&lfs3, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'a');
|
|
|
|
lfs3_mdir_lookup(&lfs3, &lfs3.mroot, LFS3_TAG_ATTR(1),
|
|
&data) => LFS3_TAG_ATTR(1);
|
|
lfs3_data_read(&lfs3, &data, buffer, 1) => 1;
|
|
assert(buffer[0] == 'c');
|
|
|
|
lfs3_mtree_lookup(&lfs3, (1 << lfs3.mbits)+0, &mdir) => 0;
|
|
assert(mdir.r.weight == 1);
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_REG,
|
|
&data) => LFS3_TAG_REG;
|
|
lfs3_data_read(&lfs3, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'd');
|
|
|
|
// check things stay sane after remount
|
|
lfs3_unmount(&lfs3) => 0;
|
|
lfs3_mount(&lfs3, LFS3_M_RDWR | M_FLAGS, CFG) => 0;
|
|
|
|
// assert split/drop worked out
|
|
assert(lfs3.mtree.r.weight == (2 << lfs3.mbits));
|
|
// assert mroot still has no entries
|
|
assert(lfs3.mroot.r.weight == 0);
|
|
|
|
// assert that our entries are still in the mtree
|
|
lfs3_mtree_lookup(&lfs3, (0 << lfs3.mbits)+1, &mdir) => 0;
|
|
assert(mdir.r.weight == 2);
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_REG,
|
|
&data) => LFS3_TAG_REG;
|
|
lfs3_data_read(&lfs3, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'a');
|
|
|
|
lfs3_mdir_lookup(&lfs3, &lfs3.mroot, LFS3_TAG_ATTR(1),
|
|
&data) => LFS3_TAG_ATTR(1);
|
|
lfs3_data_read(&lfs3, &data, buffer, 1) => 1;
|
|
assert(buffer[0] == 'c');
|
|
|
|
lfs3_mtree_lookup(&lfs3, (1 << lfs3.mbits)+0, &mdir) => 0;
|
|
assert(mdir.r.weight == 1);
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_REG,
|
|
&data) => LFS3_TAG_REG;
|
|
lfs3_data_read(&lfs3, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'd');
|
|
|
|
lfs3_unmount(&lfs3) => 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 = 'lfs3.c'
|
|
code = '''
|
|
lfs3_t lfs3;
|
|
lfs3_format(&lfs3, LFS3_F_RDWR | F_FLAGS, CFG) => 0;
|
|
lfs3_mount(&lfs3, LFS3_M_RDWR | M_FLAGS, CFG) => 0;
|
|
lfs3_alloc_ckpoint(&lfs3);
|
|
lfs3_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);
|
|
lfs3_mdir_t mdir;
|
|
lfs3_mtree_namelookup(&lfs3, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL) => LFS3_ERR_NOENT;
|
|
lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS(
|
|
LFS3_RATTR_NAME(
|
|
LFS3_TAG_REG, +1,
|
|
0, (const char*)buffer+1, SIZE-1))) => 0;
|
|
assert(mdir.r.weight == 2);
|
|
|
|
memset(buffer+1, 'b', SIZE-1);
|
|
lfs3_mtree_namelookup(&lfs3, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL) => LFS3_ERR_NOENT;
|
|
lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS(
|
|
LFS3_RATTR_NAME(
|
|
LFS3_TAG_REG, +1,
|
|
0, (const char*)buffer+1, SIZE-1))) => 0;
|
|
assert(mdir.r.weight == 3);
|
|
|
|
// force mroot to compact
|
|
lfs3.mroot.r.eoff = -1;
|
|
lfs3_mdir_commit(&lfs3, &lfs3.mroot, LFS3_RATTRS(
|
|
LFS3_RATTR_BUF(LFS3_TAG_ATTR(1), 0, "c", 1))) => 0;
|
|
|
|
// assert mdirs were unininlined and split
|
|
assert(lfs3.mtree.r.weight == (2 << lfs3.mbits));
|
|
// assert mroot now has no entries
|
|
assert(lfs3.mroot.r.weight == 0);
|
|
|
|
// now add another large entry to an mdir, forcing a split
|
|
memset(buffer+1, 'd', SIZE-1);
|
|
lfs3_mtree_namelookup(&lfs3, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL) => LFS3_ERR_NOENT;
|
|
lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS(
|
|
LFS3_RATTR_NAME(
|
|
LFS3_TAG_REG, +1,
|
|
0, (const char*)buffer+1, SIZE-1))) => 0;
|
|
assert(mdir.r.weight == 2);
|
|
|
|
// remove an entry, forcing the mdir to be dropped
|
|
memset(buffer+1, 'd', SIZE-1);
|
|
lfs3_mtree_namelookup(&lfs3, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL) => LFS3_TAG_REG;
|
|
// force mdir to compact while we're removing
|
|
mdir.r.eoff = -1;
|
|
lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS(
|
|
LFS3_RATTR(LFS3_TAG_RM, -1))) => 0;
|
|
assert(mdir.r.weight == 1);
|
|
|
|
// assert split/drop worked out
|
|
assert(lfs3.mtree.r.weight == (2 << lfs3.mbits));
|
|
// assert mroot still has no entries
|
|
assert(lfs3.mroot.r.weight == 0);
|
|
|
|
// assert that our entries are still in the mtree
|
|
lfs3_mtree_lookup(&lfs3, (0 << lfs3.mbits)+1, &mdir) => 0;
|
|
assert(mdir.r.weight == 2);
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_REG,
|
|
&data) => LFS3_TAG_REG;
|
|
lfs3_data_read(&lfs3, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'a');
|
|
|
|
lfs3_mtree_lookup(&lfs3, (1 << lfs3.mbits)+0, &mdir) => 0;
|
|
assert(mdir.r.weight == 1);
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_REG,
|
|
&data) => LFS3_TAG_REG;
|
|
lfs3_data_read(&lfs3, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'b');
|
|
|
|
lfs3_mdir_lookup(&lfs3, &lfs3.mroot, LFS3_TAG_ATTR(1),
|
|
&data) => LFS3_TAG_ATTR(1);
|
|
lfs3_data_read(&lfs3, &data, buffer, 1) => 1;
|
|
assert(buffer[0] == 'c');
|
|
|
|
// check things stay sane after remount
|
|
lfs3_unmount(&lfs3) => 0;
|
|
lfs3_mount(&lfs3, LFS3_M_RDWR | M_FLAGS, CFG) => 0;
|
|
|
|
// assert split/drop worked out
|
|
assert(lfs3.mtree.r.weight == (2 << lfs3.mbits));
|
|
// assert mroot still has no entries
|
|
assert(lfs3.mroot.r.weight == 0);
|
|
|
|
// assert that our entries are still in the mtree
|
|
lfs3_mtree_lookup(&lfs3, (0 << lfs3.mbits)+1, &mdir) => 0;
|
|
assert(mdir.r.weight == 2);
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_REG,
|
|
&data) => LFS3_TAG_REG;
|
|
lfs3_data_read(&lfs3, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'a');
|
|
|
|
lfs3_mtree_lookup(&lfs3, (1 << lfs3.mbits)+0, &mdir) => 0;
|
|
assert(mdir.r.weight == 1);
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_REG,
|
|
&data) => LFS3_TAG_REG;
|
|
lfs3_data_read(&lfs3, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'b');
|
|
|
|
lfs3_mdir_lookup(&lfs3, &lfs3.mroot, LFS3_TAG_ATTR(1),
|
|
&data) => LFS3_TAG_ATTR(1);
|
|
lfs3_data_read(&lfs3, &data, buffer, 1) => 1;
|
|
assert(buffer[0] == 'c');
|
|
|
|
lfs3_unmount(&lfs3) => 0;
|
|
'''
|
|
|
|
[cases.test_mtree_drop_fuzz]
|
|
defines.N = [5, 10, 20, 40, 80, 160]
|
|
defines.FORCE_COMPACTION = [false, true]
|
|
defines.SEED = 'range(100)'
|
|
fuzz = 'SEED'
|
|
in = 'lfs3.c'
|
|
code = '''
|
|
lfs3_t lfs3;
|
|
lfs3_format(&lfs3, LFS3_F_RDWR | F_FLAGS, CFG) => 0;
|
|
lfs3_mount(&lfs3, LFS3_M_RDWR | M_FLAGS, CFG) => 0;
|
|
lfs3_alloc_ckpoint(&lfs3);
|
|
|
|
bool sim[N];
|
|
for (lfs3_size_t i = 0; i < N; i++) {
|
|
sim[i] = false;
|
|
}
|
|
|
|
uint32_t prng = SEED;
|
|
for (lfs3_size_t i = 0; i < N; i++) {
|
|
// choose a pseudo-random name
|
|
lfs3_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
|
|
lfs3_mdir_t mdir;
|
|
lfs3_stag_t tag = lfs3_mtree_namelookup(&lfs3, 0, name+1, 3,
|
|
&mdir, NULL);
|
|
assert(tag >= 0 || tag == LFS3_ERR_NOENT);
|
|
if (tag != LFS3_ERR_NOENT) {
|
|
continue;
|
|
}
|
|
// force a compaction?
|
|
if (FORCE_COMPACTION) {
|
|
lfs3.mroot.r.eoff = -1;
|
|
mdir.r.eoff = -1;
|
|
}
|
|
lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS(
|
|
LFS3_RATTR_NAME(LFS3_TAG_REG, +1, 0, name+1, 3))) => 0;
|
|
lfs3_data_t data;
|
|
|
|
// double check
|
|
uint8_t buffer[256];
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_REG,
|
|
&data) => LFS3_TAG_REG;
|
|
lfs3_data_read(&lfs3, &data, buffer, sizeof(buffer)) => 4;
|
|
assert(memcmp(buffer, name, 4) == 0);
|
|
|
|
// delete
|
|
} else {
|
|
// update sim
|
|
sim[x] = false;
|
|
|
|
// update mtree
|
|
lfs3_mdir_t mdir;
|
|
lfs3_stag_t tag = lfs3_mtree_namelookup(&lfs3, 0, name+1, 3,
|
|
&mdir, NULL);
|
|
assert(tag >= 0 || tag == LFS3_ERR_NOENT);
|
|
if (tag == LFS3_ERR_NOENT) {
|
|
continue;
|
|
}
|
|
// force a compaction?
|
|
if (FORCE_COMPACTION) {
|
|
lfs3.mroot.r.eoff = -1;
|
|
mdir.r.eoff = -1;
|
|
}
|
|
lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS(
|
|
LFS3_RATTR(LFS3_TAG_RM, -1))) => 0;
|
|
}
|
|
}
|
|
|
|
// try looking up each entry
|
|
lfs3_mdir_t mdir;
|
|
lfs3_mtree_lookup(&lfs3, 0, &mdir) => 0;
|
|
lfs3_data_t data;
|
|
uint8_t buffer[256];
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_BOOKMARK,
|
|
&data) => LFS3_TAG_BOOKMARK;
|
|
lfs3_data_read(&lfs3, &data, buffer, sizeof(buffer)) => 1;
|
|
for (lfs3_size_t i = 0; i < N; i++) {
|
|
char name[256];
|
|
name[0] = '\0';
|
|
sprintf(name+1, "%03x", i);
|
|
if (sim[i]) {
|
|
lfs3_mtree_namelookup(&lfs3, 0, name+1, 3,
|
|
&mdir, NULL) => LFS3_TAG_REG;
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_REG,
|
|
&data) => LFS3_TAG_REG;
|
|
lfs3_data_read(&lfs3, &data, buffer, sizeof(buffer)) => 4;
|
|
assert(memcmp(buffer, name, 4) == 0);
|
|
} else {
|
|
lfs3_mtree_namelookup(&lfs3, 0, name+1, 3,
|
|
&mdir, NULL) => LFS3_ERR_NOENT;
|
|
}
|
|
}
|
|
|
|
// check things stay sane after remount
|
|
lfs3_unmount(&lfs3) => 0;
|
|
lfs3_mount(&lfs3, LFS3_M_RDWR | M_FLAGS, CFG) => 0;
|
|
|
|
// try looking up each entry
|
|
lfs3_mtree_lookup(&lfs3, 0, &mdir) => 0;
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_BOOKMARK,
|
|
&data) => LFS3_TAG_BOOKMARK;
|
|
lfs3_data_read(&lfs3, &data, buffer, sizeof(buffer)) => 1;
|
|
for (lfs3_size_t i = 0; i < N; i++) {
|
|
char name[256];
|
|
name[0] = '\0';
|
|
sprintf(name+1, "%03x", i);
|
|
if (sim[i]) {
|
|
lfs3_mtree_namelookup(&lfs3, 0, name+1, 3,
|
|
&mdir, NULL) => LFS3_TAG_REG;
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_REG,
|
|
&data) => LFS3_TAG_REG;
|
|
lfs3_data_read(&lfs3, &data, buffer, sizeof(buffer)) => 4;
|
|
assert(memcmp(buffer, name, 4) == 0);
|
|
} else {
|
|
lfs3_mtree_namelookup(&lfs3, 0, name+1, 3,
|
|
&mdir, NULL) => LFS3_ERR_NOENT;
|
|
}
|
|
}
|
|
|
|
lfs3_unmount(&lfs3) => 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_RECYCLES = 0
|
|
in = 'lfs3.c'
|
|
code = '''
|
|
lfs3_t lfs3;
|
|
lfs3_format(&lfs3, LFS3_F_RDWR | F_FLAGS, CFG) => 0;
|
|
lfs3_mount(&lfs3, LFS3_M_RDWR | M_FLAGS, CFG) => 0;
|
|
lfs3_alloc_ckpoint(&lfs3);
|
|
lfs3_data_t data;
|
|
|
|
// create a 2 large rattrs that needs to be uninlined
|
|
uint8_t buffer[SIZE];
|
|
memset(buffer, 'a', SIZE);
|
|
lfs3_mdir_commit(&lfs3, &lfs3.mroot, LFS3_RATTRS(
|
|
LFS3_RATTR_DATA(LFS3_TAG_ATTR(1), 0,
|
|
&LFS3_DATA_BUF(buffer, SIZE)))) => 0;
|
|
|
|
memset(buffer, 'b', SIZE);
|
|
lfs3_mdir_t mdir;
|
|
lfs3_mtree_lookup(&lfs3, 0, &mdir) => 0;
|
|
lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS(
|
|
LFS3_RATTR_DATA(LFS3_TAG_ATTR(2), 0,
|
|
&LFS3_DATA_BUF(buffer, SIZE)))) => 0;
|
|
|
|
// force mroot to compact
|
|
lfs3.mroot.r.eoff = -1;
|
|
lfs3_mdir_commit(&lfs3, &lfs3.mroot, LFS3_RATTRS(
|
|
LFS3_RATTR_BUF(LFS3_TAG_ATTR(3), 0, "c", 1))) => 0;
|
|
|
|
// assert mdirs were unininlined
|
|
assert(lfs3.mtree.r.weight == (1 << lfs3.mbits));
|
|
// assert mroot now has no entries
|
|
assert(lfs3.mroot.r.weight == 0);
|
|
|
|
// force mdir to compact twice, this should relocate
|
|
lfs3_mtree_lookup(&lfs3, 0, &mdir) => 0;
|
|
lfs3_mdir_t old_mdir = mdir;
|
|
mdir.r.eoff = -1;
|
|
lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS(
|
|
LFS3_RATTR_BUF(LFS3_TAG_ATTR(4), 0, "d", 1))) => 0;
|
|
mdir.r.eoff = -1;
|
|
lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS(
|
|
LFS3_RATTR_BUF(LFS3_TAG_ATTR(5), 0, "e", 1))) => 0;
|
|
// assert we relocated
|
|
assert(lfs3_mdir_cmp(&old_mdir, &mdir) != 0);
|
|
|
|
// assert mdirs were unininlined
|
|
assert(lfs3.mtree.r.weight == (1 << lfs3.mbits));
|
|
// assert mroot now has no entries
|
|
assert(lfs3.mroot.r.weight == 0);
|
|
|
|
// assert that our rattrs are still in the mroot/mtree
|
|
lfs3_mdir_lookup(&lfs3, &lfs3.mroot, LFS3_TAG_ATTR(1),
|
|
&data) => LFS3_TAG_ATTR(1);
|
|
lfs3_data_read(&lfs3, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[0] == 'a');
|
|
|
|
lfs3_mtree_lookup(&lfs3, 0, &mdir) => 0;
|
|
assert(mdir.r.weight == 1);
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_ATTR(2),
|
|
&data) => LFS3_TAG_ATTR(2);
|
|
lfs3_data_read(&lfs3, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[0] == 'b');
|
|
|
|
lfs3_mdir_lookup(&lfs3, &lfs3.mroot, LFS3_TAG_ATTR(3),
|
|
&data) => LFS3_TAG_ATTR(3);
|
|
lfs3_data_read(&lfs3, &data, buffer, 1) => 1;
|
|
assert(buffer[0] == 'c');
|
|
|
|
lfs3_mtree_lookup(&lfs3, 0, &mdir) => 0;
|
|
assert(mdir.r.weight == 1);
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_ATTR(4),
|
|
&data) => LFS3_TAG_ATTR(4);
|
|
lfs3_data_read(&lfs3, &data, buffer, 1) => 1;
|
|
assert(buffer[0] == 'd');
|
|
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_ATTR(5),
|
|
&data) => LFS3_TAG_ATTR(5);
|
|
lfs3_data_read(&lfs3, &data, buffer, 1) => 1;
|
|
assert(buffer[0] == 'e');
|
|
|
|
// check things stay sane after remount
|
|
lfs3_unmount(&lfs3) => 0;
|
|
lfs3_mount(&lfs3, LFS3_M_RDWR | M_FLAGS, CFG) => 0;
|
|
|
|
// assert mdirs were unininlined
|
|
assert(lfs3.mtree.r.weight == (1 << lfs3.mbits));
|
|
// assert mroot now has no entries
|
|
assert(lfs3.mroot.r.weight == 0);
|
|
|
|
// assert that our rattrs are still in the mroot/mtree
|
|
lfs3_mdir_lookup(&lfs3, &lfs3.mroot, LFS3_TAG_ATTR(1),
|
|
&data) => LFS3_TAG_ATTR(1);
|
|
lfs3_data_read(&lfs3, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[0] == 'a');
|
|
|
|
lfs3_mtree_lookup(&lfs3, 0, &mdir) => 0;
|
|
assert(mdir.r.weight == 1);
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_ATTR(2),
|
|
&data) => LFS3_TAG_ATTR(2);
|
|
lfs3_data_read(&lfs3, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[0] == 'b');
|
|
|
|
lfs3_mdir_lookup(&lfs3, &lfs3.mroot, LFS3_TAG_ATTR(3),
|
|
&data) => LFS3_TAG_ATTR(3);
|
|
lfs3_data_read(&lfs3, &data, buffer, 1) => 1;
|
|
assert(buffer[0] == 'c');
|
|
|
|
lfs3_mtree_lookup(&lfs3, 0, &mdir) => 0;
|
|
assert(mdir.r.weight == 1);
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_ATTR(4),
|
|
&data) => LFS3_TAG_ATTR(4);
|
|
lfs3_data_read(&lfs3, &data, buffer, 1) => 1;
|
|
assert(buffer[0] == 'd');
|
|
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_ATTR(5),
|
|
&data) => LFS3_TAG_ATTR(5);
|
|
lfs3_data_read(&lfs3, &data, buffer, 1) => 1;
|
|
assert(buffer[0] == 'e');
|
|
|
|
lfs3_unmount(&lfs3) => 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_RECYCLES = 0
|
|
in = 'lfs3.c'
|
|
code = '''
|
|
lfs3_t lfs3;
|
|
lfs3_format(&lfs3, LFS3_F_RDWR | F_FLAGS, CFG) => 0;
|
|
lfs3_mount(&lfs3, LFS3_M_RDWR | M_FLAGS, CFG) => 0;
|
|
lfs3_alloc_ckpoint(&lfs3);
|
|
lfs3_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);
|
|
lfs3_mdir_t mdir;
|
|
lfs3_mtree_namelookup(&lfs3, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL) => LFS3_ERR_NOENT;
|
|
lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS(
|
|
LFS3_RATTR_NAME(
|
|
LFS3_TAG_REG, +1,
|
|
0, (const char*)buffer+1, SIZE-1))) => 0;
|
|
assert(mdir.r.weight == 2);
|
|
|
|
memset(buffer+1, 'b', SIZE-1);
|
|
lfs3_mtree_namelookup(&lfs3, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL) => LFS3_ERR_NOENT;
|
|
lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS(
|
|
LFS3_RATTR_NAME(
|
|
LFS3_TAG_REG, +1,
|
|
0, (const char*)buffer+1, SIZE-1))) => 0;
|
|
assert(mdir.r.weight == 3);
|
|
|
|
// force mroot to compact
|
|
lfs3.mroot.r.eoff = -1;
|
|
lfs3_mdir_commit(&lfs3, &lfs3.mroot, LFS3_RATTRS(
|
|
LFS3_RATTR_BUF(LFS3_TAG_ATTR(1), 0, "c", 1))) => 0;
|
|
|
|
// assert mdirs were unininlined and split
|
|
assert(lfs3.mtree.r.weight == (2 << lfs3.mbits));
|
|
// assert mroot now has no entries
|
|
assert(lfs3.mroot.r.weight == 0);
|
|
|
|
// force mdir to compact twice, this should relocate
|
|
memset(buffer+1, 'a', SIZE-1);
|
|
lfs3_mtree_namelookup(&lfs3, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL) => LFS3_TAG_REG;
|
|
lfs3_mdir_t old_mdir = mdir;
|
|
mdir.r.eoff = -1;
|
|
lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS(
|
|
LFS3_RATTR_BUF(LFS3_TAG_ATTR(2), 0, "d", 1))) => 0;
|
|
mdir.r.eoff = -1;
|
|
lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS(
|
|
LFS3_RATTR_BUF(LFS3_TAG_ATTR(3), 0, "e", 1))) => 0;
|
|
// assert we relocated
|
|
assert(lfs3_mdir_cmp(&old_mdir, &mdir) != 0);
|
|
|
|
// assert mdirs were unininlined and split
|
|
assert(lfs3.mtree.r.weight == (2 << lfs3.mbits));
|
|
// assert mroot now has no entries
|
|
assert(lfs3.mroot.r.weight == 0);
|
|
|
|
// assert that our entries are still in the mtree
|
|
lfs3_mtree_lookup(&lfs3, (0 << lfs3.mbits)+1, &mdir) => 0;
|
|
assert(mdir.r.weight == 2);
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_REG,
|
|
&data) => LFS3_TAG_REG;
|
|
lfs3_data_read(&lfs3, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'a');
|
|
|
|
lfs3_mtree_lookup(&lfs3, (1 << lfs3.mbits)+0, &mdir) => 0;
|
|
assert(mdir.r.weight == 1);
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_REG,
|
|
&data) => LFS3_TAG_REG;
|
|
lfs3_data_read(&lfs3, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'b');
|
|
|
|
lfs3_mdir_lookup(&lfs3, &lfs3.mroot, LFS3_TAG_ATTR(1),
|
|
&data) => LFS3_TAG_ATTR(1);
|
|
lfs3_data_read(&lfs3, &data, buffer, 1) => 1;
|
|
assert(buffer[0] == 'c');
|
|
|
|
lfs3_mtree_lookup(&lfs3, (0 << lfs3.mbits)+1, &mdir) => 0;
|
|
assert(mdir.r.weight == 2);
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_ATTR(2),
|
|
&data) => LFS3_TAG_ATTR(2);
|
|
lfs3_data_read(&lfs3, &data, buffer, 1) => 1;
|
|
assert(buffer[0] == 'd');
|
|
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_ATTR(3),
|
|
&data) => LFS3_TAG_ATTR(3);
|
|
lfs3_data_read(&lfs3, &data, buffer, 1) => 1;
|
|
assert(buffer[0] == 'e');
|
|
|
|
// check things stay sane after remount
|
|
lfs3_unmount(&lfs3) => 0;
|
|
lfs3_mount(&lfs3, LFS3_M_RDWR | M_FLAGS, CFG) => 0;
|
|
|
|
// assert mdirs were unininlined and split
|
|
assert(lfs3.mtree.r.weight == (2 << lfs3.mbits));
|
|
// assert mroot now has no entries
|
|
assert(lfs3.mroot.r.weight == 0);
|
|
|
|
// assert that our entries are still in the mtree
|
|
lfs3_mtree_lookup(&lfs3, (0 << lfs3.mbits)+1, &mdir) => 0;
|
|
assert(mdir.r.weight == 2);
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_REG,
|
|
&data) => LFS3_TAG_REG;
|
|
lfs3_data_read(&lfs3, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'a');
|
|
|
|
lfs3_mtree_lookup(&lfs3, (1 << lfs3.mbits)+0, &mdir) => 0;
|
|
assert(mdir.r.weight == 1);
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_REG,
|
|
&data) => LFS3_TAG_REG;
|
|
lfs3_data_read(&lfs3, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'b');
|
|
|
|
lfs3_mdir_lookup(&lfs3, &lfs3.mroot, LFS3_TAG_ATTR(1),
|
|
&data) => LFS3_TAG_ATTR(1);
|
|
lfs3_data_read(&lfs3, &data, buffer, 1) => 1;
|
|
assert(buffer[0] == 'c');
|
|
|
|
lfs3_mtree_lookup(&lfs3, (0 << lfs3.mbits)+1, &mdir) => 0;
|
|
assert(mdir.r.weight == 2);
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_ATTR(2),
|
|
&data) => LFS3_TAG_ATTR(2);
|
|
lfs3_data_read(&lfs3, &data, buffer, 1) => 1;
|
|
assert(buffer[0] == 'd');
|
|
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_ATTR(3),
|
|
&data) => LFS3_TAG_ATTR(3);
|
|
lfs3_data_read(&lfs3, &data, buffer, 1) => 1;
|
|
assert(buffer[0] == 'e');
|
|
|
|
lfs3_unmount(&lfs3) => 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_RECYCLES = 0
|
|
in = 'lfs3.c'
|
|
code = '''
|
|
lfs3_t lfs3;
|
|
lfs3_format(&lfs3, LFS3_F_RDWR | F_FLAGS, CFG) => 0;
|
|
lfs3_mount(&lfs3, LFS3_M_RDWR | M_FLAGS, CFG) => 0;
|
|
lfs3_alloc_ckpoint(&lfs3);
|
|
lfs3_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);
|
|
lfs3_mdir_t mdir;
|
|
lfs3_mtree_namelookup(&lfs3, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL) => LFS3_ERR_NOENT;
|
|
lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS(
|
|
LFS3_RATTR_NAME(
|
|
LFS3_TAG_REG, +1,
|
|
0, (const char*)buffer+1, SIZE-1))) => 0;
|
|
assert(mdir.r.weight == 2);
|
|
|
|
memset(buffer+1, 'b', SIZE-1);
|
|
lfs3_mtree_namelookup(&lfs3, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL) => LFS3_ERR_NOENT;
|
|
lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS(
|
|
LFS3_RATTR_NAME(
|
|
LFS3_TAG_REG, +1,
|
|
0, (const char*)buffer+1, SIZE-1))) => 0;
|
|
assert(mdir.r.weight == 3);
|
|
|
|
// force mroot to compact
|
|
lfs3.mroot.r.eoff = -1;
|
|
lfs3_mdir_commit(&lfs3, &lfs3.mroot, LFS3_RATTRS(
|
|
LFS3_RATTR_BUF(LFS3_TAG_ATTR(1), 0, "c", 1))) => 0;
|
|
|
|
// assert mdirs were unininlined and split
|
|
assert(lfs3.mtree.r.weight == (2 << lfs3.mbits));
|
|
// assert mroot now has no entries
|
|
assert(lfs3.mroot.r.weight == 0);
|
|
|
|
// force mdir to compact twice, this should relocate
|
|
memset(buffer+1, 'b', SIZE-1);
|
|
lfs3_mtree_namelookup(&lfs3, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL) => LFS3_TAG_REG;
|
|
lfs3_mdir_t old_mdir = mdir;
|
|
mdir.r.eoff = -1;
|
|
lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS(
|
|
LFS3_RATTR_BUF(LFS3_TAG_ATTR(2), 0, "d", 1))) => 0;
|
|
mdir.r.eoff = -1;
|
|
lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS(
|
|
LFS3_RATTR_BUF(LFS3_TAG_ATTR(3), 0, "e", 1))) => 0;
|
|
// assert we relocated
|
|
assert(lfs3_mdir_cmp(&old_mdir, &mdir) != 0);
|
|
|
|
// assert mdirs were unininlined and split
|
|
assert(lfs3.mtree.r.weight == (2 << lfs3.mbits));
|
|
// assert mroot now has no entries
|
|
assert(lfs3.mroot.r.weight == 0);
|
|
|
|
// assert that our entries are still in the mtree
|
|
lfs3_mtree_lookup(&lfs3, (0 << lfs3.mbits)+1, &mdir) => 0;
|
|
assert(mdir.r.weight == 2);
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_REG,
|
|
&data) => LFS3_TAG_REG;
|
|
lfs3_data_read(&lfs3, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'a');
|
|
|
|
lfs3_mtree_lookup(&lfs3, (1 << lfs3.mbits)+0, &mdir) => 0;
|
|
assert(mdir.r.weight == 1);
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_REG,
|
|
&data) => LFS3_TAG_REG;
|
|
lfs3_data_read(&lfs3, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'b');
|
|
|
|
lfs3_mdir_lookup(&lfs3, &lfs3.mroot, LFS3_TAG_ATTR(1),
|
|
&data) => LFS3_TAG_ATTR(1);
|
|
lfs3_data_read(&lfs3, &data, buffer, 1) => 1;
|
|
assert(buffer[0] == 'c');
|
|
|
|
lfs3_mtree_lookup(&lfs3, (1 << lfs3.mbits)+0, &mdir) => 0;
|
|
assert(mdir.r.weight == 1);
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_ATTR(2),
|
|
&data) => LFS3_TAG_ATTR(2);
|
|
lfs3_data_read(&lfs3, &data, buffer, 1) => 1;
|
|
assert(buffer[0] == 'd');
|
|
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_ATTR(3),
|
|
&data) => LFS3_TAG_ATTR(3);
|
|
lfs3_data_read(&lfs3, &data, buffer, 1) => 1;
|
|
assert(buffer[0] == 'e');
|
|
|
|
// check things stay sane after remount
|
|
lfs3_unmount(&lfs3) => 0;
|
|
lfs3_mount(&lfs3, LFS3_M_RDWR | M_FLAGS, CFG) => 0;
|
|
|
|
// assert mdirs were unininlined and split
|
|
assert(lfs3.mtree.r.weight == (2 << lfs3.mbits));
|
|
// assert mroot now has no entries
|
|
assert(lfs3.mroot.r.weight == 0);
|
|
|
|
// assert that our entries are still in the mtree
|
|
lfs3_mtree_lookup(&lfs3, (0 << lfs3.mbits)+1, &mdir) => 0;
|
|
assert(mdir.r.weight == 2);
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_REG,
|
|
&data) => LFS3_TAG_REG;
|
|
lfs3_data_read(&lfs3, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'a');
|
|
|
|
lfs3_mtree_lookup(&lfs3, (1 << lfs3.mbits)+0, &mdir) => 0;
|
|
assert(mdir.r.weight == 1);
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_REG,
|
|
&data) => LFS3_TAG_REG;
|
|
lfs3_data_read(&lfs3, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'b');
|
|
|
|
lfs3_mdir_lookup(&lfs3, &lfs3.mroot, LFS3_TAG_ATTR(1),
|
|
&data) => LFS3_TAG_ATTR(1);
|
|
lfs3_data_read(&lfs3, &data, buffer, 1) => 1;
|
|
assert(buffer[0] == 'c');
|
|
|
|
lfs3_mtree_lookup(&lfs3, (1 << lfs3.mbits)+0, &mdir) => 0;
|
|
assert(mdir.r.weight == 1);
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_ATTR(2),
|
|
&data) => LFS3_TAG_ATTR(2);
|
|
lfs3_data_read(&lfs3, &data, buffer, 1) => 1;
|
|
assert(buffer[0] == 'd');
|
|
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_ATTR(3),
|
|
&data) => LFS3_TAG_ATTR(3);
|
|
lfs3_data_read(&lfs3, &data, buffer, 1) => 1;
|
|
assert(buffer[0] == 'e');
|
|
|
|
lfs3_unmount(&lfs3) => 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_RECYCLES = 0
|
|
in = 'lfs3.c'
|
|
code = '''
|
|
lfs3_t lfs3;
|
|
lfs3_format(&lfs3, LFS3_F_RDWR | F_FLAGS, CFG) => 0;
|
|
lfs3_mount(&lfs3, LFS3_M_RDWR | M_FLAGS, CFG) => 0;
|
|
lfs3_alloc_ckpoint(&lfs3);
|
|
lfs3_data_t data;
|
|
|
|
// prepare mroot with an entry
|
|
uint8_t buffer[SIZE];
|
|
buffer[0] = '\0';
|
|
memset(buffer+1, 'a', SIZE-1);
|
|
lfs3_mdir_t mdir;
|
|
lfs3_mtree_namelookup(&lfs3, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL) => LFS3_ERR_NOENT;
|
|
lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS(
|
|
LFS3_RATTR_NAME(
|
|
LFS3_TAG_REG, +1,
|
|
0, (const char*)buffer+1, SIZE-1))) => 0;
|
|
assert(mdir.r.weight == 2);
|
|
|
|
// force mroot to compact twice, this should extend the mroot
|
|
lfs3_mdir_t old_mroot = lfs3.mroot;
|
|
lfs3.mroot.r.eoff = -1;
|
|
lfs3_mdir_commit(&lfs3, &lfs3.mroot, LFS3_RATTRS(
|
|
LFS3_RATTR_BUF(LFS3_TAG_ATTR(1), 0, "b", 1))) => 0;
|
|
lfs3.mroot.r.eoff = -1;
|
|
lfs3_mdir_commit(&lfs3, &lfs3.mroot, LFS3_RATTRS(
|
|
LFS3_RATTR_BUF(LFS3_TAG_ATTR(2), 0, "c", 1))) => 0;
|
|
// assert we relocated
|
|
assert(lfs3_mdir_cmp(&old_mroot, &lfs3.mroot) != 0);
|
|
|
|
// assert that our entry is still in the mroot
|
|
lfs3_mtree_lookup(&lfs3, (0 << lfs3.mbits)+1, &mdir) => 0;
|
|
assert(mdir.r.weight == 2);
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_REG,
|
|
&data) => LFS3_TAG_REG;
|
|
lfs3_data_read(&lfs3, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'a');
|
|
|
|
lfs3_mdir_lookup(&lfs3, &lfs3.mroot, LFS3_TAG_ATTR(1),
|
|
&data) => LFS3_TAG_ATTR(1);
|
|
lfs3_data_read(&lfs3, &data, buffer, 1) => 1;
|
|
assert(buffer[0] == 'b');
|
|
|
|
lfs3_mdir_lookup(&lfs3, &lfs3.mroot, LFS3_TAG_ATTR(2),
|
|
&data) => LFS3_TAG_ATTR(2);
|
|
lfs3_data_read(&lfs3, &data, buffer, 1) => 1;
|
|
assert(buffer[0] == 'c');
|
|
|
|
// check things stay sane after remount
|
|
lfs3_unmount(&lfs3) => 0;
|
|
lfs3_mount(&lfs3, LFS3_M_RDWR | M_FLAGS, CFG) => 0;
|
|
|
|
// assert that our rattr is still in the mroot
|
|
lfs3_mtree_lookup(&lfs3, (0 << lfs3.mbits)+1, &mdir) => 0;
|
|
assert(mdir.r.weight == 2);
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_REG,
|
|
&data) => LFS3_TAG_REG;
|
|
lfs3_data_read(&lfs3, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'a');
|
|
|
|
lfs3_mdir_lookup(&lfs3, &lfs3.mroot, LFS3_TAG_ATTR(1),
|
|
&data) => LFS3_TAG_ATTR(1);
|
|
lfs3_data_read(&lfs3, &data, buffer, 1) => 1;
|
|
assert(buffer[0] == 'b');
|
|
|
|
lfs3_mdir_lookup(&lfs3, &lfs3.mroot, LFS3_TAG_ATTR(2),
|
|
&data) => LFS3_TAG_ATTR(2);
|
|
lfs3_data_read(&lfs3, &data, buffer, 1) => 1;
|
|
assert(buffer[0] == 'c');
|
|
|
|
lfs3_unmount(&lfs3) => 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_RECYCLES = 0
|
|
# 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 = 'lfs3.c'
|
|
code = '''
|
|
lfs3_t lfs3;
|
|
lfs3_format(&lfs3, LFS3_F_RDWR | F_FLAGS, CFG) => 0;
|
|
lfs3_mount(&lfs3, LFS3_M_RDWR | M_FLAGS, CFG) => 0;
|
|
lfs3_alloc_ckpoint(&lfs3);
|
|
lfs3_data_t data;
|
|
|
|
// prepare mroot with an entry
|
|
uint8_t buffer[SIZE];
|
|
buffer[0] = '\0';
|
|
memset(buffer+1, 'a', SIZE-1);
|
|
lfs3_mdir_t mdir;
|
|
lfs3_mtree_namelookup(&lfs3, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL) => LFS3_ERR_NOENT;
|
|
lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS(
|
|
LFS3_RATTR_NAME(
|
|
LFS3_TAG_REG, +1,
|
|
0, (const char*)buffer+1, SIZE-1))) => 0;
|
|
assert(mdir.r.weight == 2);
|
|
|
|
// force mroot to compact twice, this should extend the mroot
|
|
lfs3_mdir_t old_mroot = lfs3.mroot;
|
|
lfs3.mroot.r.eoff = -1;
|
|
lfs3_mdir_commit(&lfs3, &lfs3.mroot, LFS3_RATTRS(
|
|
LFS3_RATTR_BUF(LFS3_TAG_ATTR(1), 0, "b", 1))) => 0;
|
|
lfs3.mroot.r.eoff = -1;
|
|
lfs3_mdir_commit(&lfs3, &lfs3.mroot, LFS3_RATTRS(
|
|
LFS3_RATTR_BUF(LFS3_TAG_ATTR(2), 0, "c", 1))) => 0;
|
|
// assert we relocated
|
|
assert(lfs3_mdir_cmp(&old_mroot, &lfs3.mroot) != 0);
|
|
|
|
// force mroot to compact four times, this should relocate the mroot
|
|
// twice, forcing a second mroot extension
|
|
old_mroot = lfs3.mroot;
|
|
lfs3.mroot.r.eoff = -1;
|
|
lfs3_mdir_commit(&lfs3, &lfs3.mroot, LFS3_RATTRS(
|
|
LFS3_RATTR_BUF(LFS3_TAG_ATTR(3), 0, "d", 1))) => 0;
|
|
lfs3.mroot.r.eoff = -1;
|
|
lfs3_mdir_commit(&lfs3, &lfs3.mroot, LFS3_RATTRS(
|
|
LFS3_RATTR_BUF(LFS3_TAG_ATTR(4), 0, "e", 1))) => 0;
|
|
lfs3.mroot.r.eoff = -1;
|
|
lfs3_mdir_commit(&lfs3, &lfs3.mroot, LFS3_RATTRS(
|
|
LFS3_RATTR_BUF(LFS3_TAG_ATTR(5), 0, "f", 1))) => 0;
|
|
lfs3.mroot.r.eoff = -1;
|
|
lfs3_mdir_commit(&lfs3, &lfs3.mroot, LFS3_RATTRS(
|
|
LFS3_RATTR_BUF(LFS3_TAG_ATTR(6), 0, "g", 1))) => 0;
|
|
// assert we relocated
|
|
assert(lfs3_mdir_cmp(&old_mroot, &lfs3.mroot) != 0);
|
|
|
|
// assert that our rattr is still in the mroot
|
|
lfs3_mtree_lookup(&lfs3, (0 << lfs3.mbits)+1, &mdir) => 0;
|
|
assert(mdir.r.weight == 2);
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_REG,
|
|
&data) => LFS3_TAG_REG;
|
|
lfs3_data_read(&lfs3, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'a');
|
|
|
|
lfs3_mdir_lookup(&lfs3, &lfs3.mroot, LFS3_TAG_ATTR(1),
|
|
&data) => LFS3_TAG_ATTR(1);
|
|
lfs3_data_read(&lfs3, &data, buffer, 1) => 1;
|
|
assert(buffer[0] == 'b');
|
|
|
|
lfs3_mdir_lookup(&lfs3, &lfs3.mroot, LFS3_TAG_ATTR(2),
|
|
&data) => LFS3_TAG_ATTR(2);
|
|
lfs3_data_read(&lfs3, &data, buffer, 1) => 1;
|
|
assert(buffer[0] == 'c');
|
|
|
|
lfs3_mdir_lookup(&lfs3, &lfs3.mroot, LFS3_TAG_ATTR(3),
|
|
&data) => LFS3_TAG_ATTR(3);
|
|
lfs3_data_read(&lfs3, &data, buffer, 1) => 1;
|
|
assert(buffer[0] == 'd');
|
|
|
|
lfs3_mdir_lookup(&lfs3, &lfs3.mroot, LFS3_TAG_ATTR(4),
|
|
&data) => LFS3_TAG_ATTR(4);
|
|
lfs3_data_read(&lfs3, &data, buffer, 1) => 1;
|
|
assert(buffer[0] == 'e');
|
|
|
|
lfs3_mdir_lookup(&lfs3, &lfs3.mroot, LFS3_TAG_ATTR(5),
|
|
&data) => LFS3_TAG_ATTR(5);
|
|
lfs3_data_read(&lfs3, &data, buffer, 1) => 1;
|
|
assert(buffer[0] == 'f');
|
|
|
|
lfs3_mdir_lookup(&lfs3, &lfs3.mroot, LFS3_TAG_ATTR(6),
|
|
&data) => LFS3_TAG_ATTR(6);
|
|
lfs3_data_read(&lfs3, &data, buffer, 1) => 1;
|
|
assert(buffer[0] == 'g');
|
|
|
|
// check things stay sane after remount
|
|
lfs3_unmount(&lfs3) => 0;
|
|
lfs3_mount(&lfs3, LFS3_M_RDWR | M_FLAGS, CFG) => 0;
|
|
|
|
// assert that our rattr is still in the mroot
|
|
lfs3_mtree_lookup(&lfs3, (0 << lfs3.mbits)+1, &mdir) => 0;
|
|
assert(mdir.r.weight == 2);
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_REG,
|
|
&data) => LFS3_TAG_REG;
|
|
lfs3_data_read(&lfs3, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'a');
|
|
|
|
lfs3_mdir_lookup(&lfs3, &lfs3.mroot, LFS3_TAG_ATTR(1),
|
|
&data) => LFS3_TAG_ATTR(1);
|
|
lfs3_data_read(&lfs3, &data, buffer, 1) => 1;
|
|
assert(buffer[0] == 'b');
|
|
|
|
lfs3_mdir_lookup(&lfs3, &lfs3.mroot, LFS3_TAG_ATTR(2),
|
|
&data) => LFS3_TAG_ATTR(2);
|
|
lfs3_data_read(&lfs3, &data, buffer, 1) => 1;
|
|
assert(buffer[0] == 'c');
|
|
|
|
lfs3_mdir_lookup(&lfs3, &lfs3.mroot, LFS3_TAG_ATTR(3),
|
|
&data) => LFS3_TAG_ATTR(3);
|
|
lfs3_data_read(&lfs3, &data, buffer, 1) => 1;
|
|
assert(buffer[0] == 'd');
|
|
|
|
lfs3_mdir_lookup(&lfs3, &lfs3.mroot, LFS3_TAG_ATTR(4),
|
|
&data) => LFS3_TAG_ATTR(4);
|
|
lfs3_data_read(&lfs3, &data, buffer, 1) => 1;
|
|
assert(buffer[0] == 'e');
|
|
|
|
lfs3_mdir_lookup(&lfs3, &lfs3.mroot, LFS3_TAG_ATTR(5),
|
|
&data) => LFS3_TAG_ATTR(5);
|
|
lfs3_data_read(&lfs3, &data, buffer, 1) => 1;
|
|
assert(buffer[0] == 'f');
|
|
|
|
lfs3_mdir_lookup(&lfs3, &lfs3.mroot, LFS3_TAG_ATTR(6),
|
|
&data) => LFS3_TAG_ATTR(6);
|
|
lfs3_data_read(&lfs3, &data, buffer, 1) => 1;
|
|
assert(buffer[0] == 'g');
|
|
|
|
lfs3_unmount(&lfs3) => 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_RECYCLES = 0
|
|
in = 'lfs3.c'
|
|
code = '''
|
|
lfs3_t lfs3;
|
|
lfs3_format(&lfs3, LFS3_F_RDWR | F_FLAGS, CFG) => 0;
|
|
lfs3_mount(&lfs3, LFS3_M_RDWR | M_FLAGS, CFG) => 0;
|
|
lfs3_alloc_ckpoint(&lfs3);
|
|
lfs3_data_t data;
|
|
|
|
// prepare mroot with an entry
|
|
uint8_t buffer[SIZE];
|
|
buffer[0] = '\0';
|
|
memset(buffer+1, 'a', SIZE-1);
|
|
lfs3_mdir_t mdir;
|
|
lfs3_mtree_namelookup(&lfs3, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL) => LFS3_ERR_NOENT;
|
|
lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS(
|
|
LFS3_RATTR_NAME(
|
|
LFS3_TAG_REG, +1,
|
|
0, (const char*)buffer+1, SIZE-1))) => 0;
|
|
assert(mdir.r.weight == 2);
|
|
|
|
// force mroot to compact twice, this should extend the mroot
|
|
lfs3_mdir_t old_mroot = lfs3.mroot;
|
|
lfs3.mroot.r.eoff = -1;
|
|
lfs3_mdir_commit(&lfs3, &lfs3.mroot, LFS3_RATTRS(
|
|
LFS3_RATTR_BUF(LFS3_TAG_ATTR(1), 0, "b", 1))) => 0;
|
|
lfs3.mroot.r.eoff = -1;
|
|
lfs3_mdir_commit(&lfs3, &lfs3.mroot, LFS3_RATTRS(
|
|
LFS3_RATTR_BUF(LFS3_TAG_ATTR(2), 0, "c", 1))) => 0;
|
|
// assert we relocated
|
|
assert(lfs3_mdir_cmp(&old_mroot, &lfs3.mroot) != 0);
|
|
|
|
// force mroot to compact twice again, this should relocate the mroot
|
|
old_mroot = lfs3.mroot;
|
|
lfs3.mroot.r.eoff = -1;
|
|
lfs3_mdir_commit(&lfs3, &lfs3.mroot, LFS3_RATTRS(
|
|
LFS3_RATTR_BUF(LFS3_TAG_ATTR(3), 0, "d", 1))) => 0;
|
|
lfs3.mroot.r.eoff = -1;
|
|
lfs3_mdir_commit(&lfs3, &lfs3.mroot, LFS3_RATTRS(
|
|
LFS3_RATTR_BUF(LFS3_TAG_ATTR(4), 0, "e", 1))) => 0;
|
|
// assert we relocated
|
|
assert(lfs3_mdir_cmp(&old_mroot, &lfs3.mroot) != 0);
|
|
|
|
// assert that our entry is still in the mroot
|
|
lfs3_mtree_lookup(&lfs3, (0 << lfs3.mbits)+1, &mdir) => 0;
|
|
assert(mdir.r.weight == 2);
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_REG,
|
|
&data) => LFS3_TAG_REG;
|
|
lfs3_data_read(&lfs3, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'a');
|
|
|
|
lfs3_mdir_lookup(&lfs3, &lfs3.mroot, LFS3_TAG_ATTR(1),
|
|
&data) => LFS3_TAG_ATTR(1);
|
|
lfs3_data_read(&lfs3, &data, buffer, 1) => 1;
|
|
assert(buffer[0] == 'b');
|
|
|
|
lfs3_mdir_lookup(&lfs3, &lfs3.mroot, LFS3_TAG_ATTR(2),
|
|
&data) => LFS3_TAG_ATTR(2);
|
|
lfs3_data_read(&lfs3, &data, buffer, 1) => 1;
|
|
assert(buffer[0] == 'c');
|
|
|
|
lfs3_mdir_lookup(&lfs3, &lfs3.mroot, LFS3_TAG_ATTR(3),
|
|
&data) => LFS3_TAG_ATTR(3);
|
|
lfs3_data_read(&lfs3, &data, buffer, 1) => 1;
|
|
assert(buffer[0] == 'd');
|
|
|
|
lfs3_mdir_lookup(&lfs3, &lfs3.mroot, LFS3_TAG_ATTR(4),
|
|
&data) => LFS3_TAG_ATTR(4);
|
|
lfs3_data_read(&lfs3, &data, buffer, 1) => 1;
|
|
assert(buffer[0] == 'e');
|
|
|
|
// check things stay sane after remount
|
|
lfs3_unmount(&lfs3) => 0;
|
|
lfs3_mount(&lfs3, LFS3_M_RDWR | M_FLAGS, CFG) => 0;
|
|
|
|
// assert that our rattr is still in the mroot
|
|
lfs3_mtree_lookup(&lfs3, (0 << lfs3.mbits)+1, &mdir) => 0;
|
|
assert(mdir.r.weight == 2);
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_REG,
|
|
&data) => LFS3_TAG_REG;
|
|
lfs3_data_read(&lfs3, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'a');
|
|
|
|
lfs3_mdir_lookup(&lfs3, &lfs3.mroot, LFS3_TAG_ATTR(1),
|
|
&data) => LFS3_TAG_ATTR(1);
|
|
lfs3_data_read(&lfs3, &data, buffer, 1) => 1;
|
|
assert(buffer[0] == 'b');
|
|
|
|
lfs3_mdir_lookup(&lfs3, &lfs3.mroot, LFS3_TAG_ATTR(2),
|
|
&data) => LFS3_TAG_ATTR(2);
|
|
lfs3_data_read(&lfs3, &data, buffer, 1) => 1;
|
|
assert(buffer[0] == 'c');
|
|
|
|
lfs3_mdir_lookup(&lfs3, &lfs3.mroot, LFS3_TAG_ATTR(3),
|
|
&data) => LFS3_TAG_ATTR(3);
|
|
lfs3_data_read(&lfs3, &data, buffer, 1) => 1;
|
|
assert(buffer[0] == 'd');
|
|
|
|
lfs3_mdir_lookup(&lfs3, &lfs3.mroot, LFS3_TAG_ATTR(4),
|
|
&data) => LFS3_TAG_ATTR(4);
|
|
lfs3_data_read(&lfs3, &data, buffer, 1) => 1;
|
|
assert(buffer[0] == 'e');
|
|
|
|
lfs3_unmount(&lfs3) => 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_RECYCLES = 0
|
|
in = 'lfs3.c'
|
|
code = '''
|
|
lfs3_t lfs3;
|
|
lfs3_format(&lfs3, LFS3_F_RDWR | F_FLAGS, CFG) => 0;
|
|
lfs3_mount(&lfs3, LFS3_M_RDWR | M_FLAGS, CFG) => 0;
|
|
lfs3_alloc_ckpoint(&lfs3);
|
|
lfs3_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);
|
|
lfs3_mdir_t mdir;
|
|
lfs3_mtree_namelookup(&lfs3, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL) => LFS3_ERR_NOENT;
|
|
lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS(
|
|
LFS3_RATTR_NAME(
|
|
LFS3_TAG_REG, +1,
|
|
0, (const char*)buffer+1, SIZE-1))) => 0;
|
|
assert(mdir.r.weight == 2);
|
|
|
|
memset(buffer+1, 'b', SIZE-1);
|
|
lfs3_mtree_namelookup(&lfs3, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL) => LFS3_ERR_NOENT;
|
|
lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS(
|
|
LFS3_RATTR_NAME(
|
|
LFS3_TAG_REG, +1,
|
|
0, (const char*)buffer+1, SIZE-1))) => 0;
|
|
assert(mdir.r.weight == 3);
|
|
|
|
// force mroot to compact
|
|
lfs3.mroot.r.eoff = -1;
|
|
lfs3_mdir_commit(&lfs3, &lfs3.mroot, LFS3_RATTRS(
|
|
LFS3_RATTR_BUF(LFS3_TAG_ATTR(1), 0, "c", 1))) => 0;
|
|
|
|
// assert mdirs were unininlined and split
|
|
assert(lfs3.mtree.r.weight == (2 << lfs3.mbits));
|
|
// assert mroot now has no entries
|
|
assert(lfs3.mroot.r.weight == 0);
|
|
|
|
// setup mroot to compact and relocate on next commit
|
|
lfs3.mroot.r.eoff = -1;
|
|
lfs3_mdir_t old_mroot = lfs3.mroot;
|
|
// force mdir to compact twice, this should relocate
|
|
memset(buffer+1, 'b', SIZE-1);
|
|
lfs3_mtree_namelookup(&lfs3, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL) => LFS3_TAG_REG;
|
|
lfs3_mdir_t old_mdir = mdir;
|
|
mdir.r.eoff = -1;
|
|
lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS(
|
|
LFS3_RATTR_BUF(LFS3_TAG_ATTR(2), 0, "d", 1))) => 0;
|
|
mdir.r.eoff = -1;
|
|
lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS(
|
|
LFS3_RATTR_BUF(LFS3_TAG_ATTR(3), 0, "e", 1))) => 0;
|
|
// assert we relocated
|
|
assert(lfs3_mdir_cmp(&old_mdir, &mdir) != 0);
|
|
// assert mroot relocated
|
|
assert(lfs3_mdir_cmp(&old_mroot, &lfs3.mroot) != 0);
|
|
|
|
// assert mdirs were unininlined and split
|
|
assert(lfs3.mtree.r.weight == (2 << lfs3.mbits));
|
|
// assert mroot now has no entries
|
|
assert(lfs3.mroot.r.weight == 0);
|
|
|
|
// assert that our entries are still in the mtree
|
|
lfs3_mtree_lookup(&lfs3, (0 << lfs3.mbits)+1, &mdir) => 0;
|
|
assert(mdir.r.weight == 2);
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_REG,
|
|
&data) => LFS3_TAG_REG;
|
|
lfs3_data_read(&lfs3, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'a');
|
|
|
|
lfs3_mtree_lookup(&lfs3, (1 << lfs3.mbits)+0, &mdir) => 0;
|
|
assert(mdir.r.weight == 1);
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_REG,
|
|
&data) => LFS3_TAG_REG;
|
|
lfs3_data_read(&lfs3, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'b');
|
|
|
|
lfs3_mdir_lookup(&lfs3, &lfs3.mroot, LFS3_TAG_ATTR(1),
|
|
&data) => LFS3_TAG_ATTR(1);
|
|
lfs3_data_read(&lfs3, &data, buffer, 1) => 1;
|
|
assert(buffer[0] == 'c');
|
|
|
|
lfs3_mtree_lookup(&lfs3, (1 << lfs3.mbits)+0, &mdir) => 0;
|
|
assert(mdir.r.weight == 1);
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_ATTR(2),
|
|
&data) => LFS3_TAG_ATTR(2);
|
|
lfs3_data_read(&lfs3, &data, buffer, 1) => 1;
|
|
assert(buffer[0] == 'd');
|
|
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_ATTR(3),
|
|
&data) => LFS3_TAG_ATTR(3);
|
|
lfs3_data_read(&lfs3, &data, buffer, 1) => 1;
|
|
assert(buffer[0] == 'e');
|
|
|
|
// check things stay sane after remount
|
|
lfs3_unmount(&lfs3) => 0;
|
|
lfs3_mount(&lfs3, LFS3_M_RDWR | M_FLAGS, CFG) => 0;
|
|
|
|
// assert mdirs were unininlined and split
|
|
assert(lfs3.mtree.r.weight == (2 << lfs3.mbits));
|
|
// assert mroot now has no entries
|
|
assert(lfs3.mroot.r.weight == 0);
|
|
|
|
// assert that our entries are still in the mtree
|
|
lfs3_mtree_lookup(&lfs3, (0 << lfs3.mbits)+1, &mdir) => 0;
|
|
assert(mdir.r.weight == 2);
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_REG,
|
|
&data) => LFS3_TAG_REG;
|
|
lfs3_data_read(&lfs3, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'a');
|
|
|
|
lfs3_mtree_lookup(&lfs3, (1 << lfs3.mbits)+0, &mdir) => 0;
|
|
assert(mdir.r.weight == 1);
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_REG,
|
|
&data) => LFS3_TAG_REG;
|
|
lfs3_data_read(&lfs3, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'b');
|
|
|
|
lfs3_mdir_lookup(&lfs3, &lfs3.mroot, LFS3_TAG_ATTR(1),
|
|
&data) => LFS3_TAG_ATTR(1);
|
|
lfs3_data_read(&lfs3, &data, buffer, 1) => 1;
|
|
assert(buffer[0] == 'c');
|
|
|
|
lfs3_mtree_lookup(&lfs3, (1 << lfs3.mbits)+0, &mdir) => 0;
|
|
assert(mdir.r.weight == 1);
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_ATTR(2),
|
|
&data) => LFS3_TAG_ATTR(2);
|
|
lfs3_data_read(&lfs3, &data, buffer, 1) => 1;
|
|
assert(buffer[0] == 'd');
|
|
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_ATTR(3),
|
|
&data) => LFS3_TAG_ATTR(3);
|
|
lfs3_data_read(&lfs3, &data, buffer, 1) => 1;
|
|
assert(buffer[0] == 'e');
|
|
|
|
lfs3_unmount(&lfs3) => 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_RECYCLES = 0
|
|
in = 'lfs3.c'
|
|
code = '''
|
|
lfs3_t lfs3;
|
|
lfs3_format(&lfs3, LFS3_F_RDWR | F_FLAGS, CFG) => 0;
|
|
lfs3_mount(&lfs3, LFS3_M_RDWR | M_FLAGS, CFG) => 0;
|
|
lfs3_alloc_ckpoint(&lfs3);
|
|
lfs3_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);
|
|
lfs3_mdir_t mdir;
|
|
lfs3_mtree_namelookup(&lfs3, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL) => LFS3_ERR_NOENT;
|
|
lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS(
|
|
LFS3_RATTR_NAME(
|
|
LFS3_TAG_REG, +1,
|
|
0, (const char*)buffer+1, SIZE-1))) => 0;
|
|
assert(mdir.r.weight == 2);
|
|
|
|
memset(buffer+1, 'b', SIZE-1);
|
|
lfs3_mtree_namelookup(&lfs3, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL) => LFS3_ERR_NOENT;
|
|
lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS(
|
|
LFS3_RATTR_NAME(
|
|
LFS3_TAG_REG, +1,
|
|
0, (const char*)buffer+1, SIZE-1))) => 0;
|
|
assert(mdir.r.weight == 3);
|
|
|
|
// force mroot to compact
|
|
lfs3.mroot.r.eoff = -1;
|
|
lfs3_mdir_commit(&lfs3, &lfs3.mroot, LFS3_RATTRS(
|
|
LFS3_RATTR_BUF(LFS3_TAG_ATTR(1), 0, "c", 1))) => 0;
|
|
|
|
// assert mdirs were unininlined and split
|
|
assert(lfs3.mtree.r.weight == (2 << lfs3.mbits));
|
|
// assert mroot now has no entries
|
|
assert(lfs3.mroot.r.weight == 0);
|
|
|
|
// now add another large entry to an mdir, forcing a split
|
|
memset(buffer+1, 'd', SIZE-1);
|
|
lfs3_mtree_namelookup(&lfs3, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL) => LFS3_ERR_NOENT;
|
|
lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS(
|
|
LFS3_RATTR_NAME(
|
|
LFS3_TAG_REG, +1,
|
|
0, (const char*)buffer+1, SIZE-1))) => 0;
|
|
assert(mdir.r.weight == 2);
|
|
|
|
// setup mroot to compact and relocate on next commit
|
|
lfs3.mroot.r.eoff = -1;
|
|
lfs3_mdir_t old_mroot = lfs3.mroot;
|
|
// force mdir to compact twice, this should relocate
|
|
lfs3_mdir_t old_mdir = mdir;
|
|
mdir.r.eoff = -1;
|
|
lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS(
|
|
LFS3_RATTR_BUF(LFS3_TAG_ATTR(2), 0, "e", 1))) => 0;
|
|
mdir.r.eoff = -1;
|
|
lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS(
|
|
LFS3_RATTR_BUF(LFS3_TAG_ATTR(3), 0, "f", 1))) => 0;
|
|
// assert we relocated
|
|
assert(lfs3_mdir_cmp(&old_mdir, &mdir) != 0);
|
|
// assert mroot relocated
|
|
assert(lfs3_mdir_cmp(&old_mroot, &lfs3.mroot) != 0);
|
|
|
|
// assert mdir was split correctly
|
|
assert(lfs3.mtree.r.weight == (3 << lfs3.mbits));
|
|
// assert mroot still has no entries
|
|
assert(lfs3.mroot.r.weight == 0);
|
|
|
|
// assert that our entries are still in the mtree
|
|
lfs3_mtree_lookup(&lfs3, (0 << lfs3.mbits)+1, &mdir) => 0;
|
|
assert(mdir.r.weight == 2);
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_REG,
|
|
&data) => LFS3_TAG_REG;
|
|
lfs3_data_read(&lfs3, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'a');
|
|
|
|
lfs3_mtree_lookup(&lfs3, (1 << lfs3.mbits)+0, &mdir) => 0;
|
|
assert(mdir.r.weight == 1);
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_REG,
|
|
&data) => LFS3_TAG_REG;
|
|
lfs3_data_read(&lfs3, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'b');
|
|
|
|
lfs3_mdir_lookup(&lfs3, &lfs3.mroot, LFS3_TAG_ATTR(1),
|
|
&data) => LFS3_TAG_ATTR(1);
|
|
lfs3_data_read(&lfs3, &data, buffer, 1) => 1;
|
|
assert(buffer[0] == 'c');
|
|
|
|
lfs3_mtree_lookup(&lfs3, (2 << lfs3.mbits)+0, &mdir) => 0;
|
|
assert(mdir.r.weight == 1);
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_REG,
|
|
&data) => LFS3_TAG_REG;
|
|
lfs3_data_read(&lfs3, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'd');
|
|
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_ATTR(2),
|
|
&data) => LFS3_TAG_ATTR(2);
|
|
lfs3_data_read(&lfs3, &data, buffer, 1) => 1;
|
|
assert(buffer[0] == 'e');
|
|
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_ATTR(3),
|
|
&data) => LFS3_TAG_ATTR(3);
|
|
lfs3_data_read(&lfs3, &data, buffer, 1) => 1;
|
|
assert(buffer[0] == 'f');
|
|
|
|
// check things stay sane after remount
|
|
lfs3_unmount(&lfs3) => 0;
|
|
lfs3_mount(&lfs3, LFS3_M_RDWR | M_FLAGS, CFG) => 0;
|
|
|
|
// assert mdir was split correctly
|
|
assert(lfs3.mtree.r.weight == (3 << lfs3.mbits));
|
|
// assert mroot still has no entries
|
|
assert(lfs3.mroot.r.weight == 0);
|
|
|
|
// assert that our entries are still in the mtree
|
|
lfs3_mtree_lookup(&lfs3, (0 << lfs3.mbits)+1, &mdir) => 0;
|
|
assert(mdir.r.weight == 2);
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_REG,
|
|
&data) => LFS3_TAG_REG;
|
|
lfs3_data_read(&lfs3, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'a');
|
|
|
|
lfs3_mtree_lookup(&lfs3, (1 << lfs3.mbits)+0, &mdir) => 0;
|
|
assert(mdir.r.weight == 1);
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_REG,
|
|
&data) => LFS3_TAG_REG;
|
|
lfs3_data_read(&lfs3, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'b');
|
|
|
|
lfs3_mdir_lookup(&lfs3, &lfs3.mroot, LFS3_TAG_ATTR(1),
|
|
&data) => LFS3_TAG_ATTR(1);
|
|
lfs3_data_read(&lfs3, &data, buffer, 1) => 1;
|
|
assert(buffer[0] == 'c');
|
|
|
|
lfs3_mtree_lookup(&lfs3, (2 << lfs3.mbits)+0, &mdir) => 0;
|
|
assert(mdir.r.weight == 1);
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_REG,
|
|
&data) => LFS3_TAG_REG;
|
|
lfs3_data_read(&lfs3, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'd');
|
|
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_ATTR(2),
|
|
&data) => LFS3_TAG_ATTR(2);
|
|
lfs3_data_read(&lfs3, &data, buffer, 1) => 1;
|
|
assert(buffer[0] == 'e');
|
|
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_ATTR(3),
|
|
&data) => LFS3_TAG_ATTR(3);
|
|
lfs3_data_read(&lfs3, &data, buffer, 1) => 1;
|
|
assert(buffer[0] == 'f');
|
|
|
|
lfs3_unmount(&lfs3) => 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_RECYCLES = 0
|
|
in = 'lfs3.c'
|
|
code = '''
|
|
lfs3_t lfs3;
|
|
lfs3_format(&lfs3, LFS3_F_RDWR | F_FLAGS, CFG) => 0;
|
|
lfs3_mount(&lfs3, LFS3_M_RDWR | M_FLAGS, CFG) => 0;
|
|
lfs3_alloc_ckpoint(&lfs3);
|
|
lfs3_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);
|
|
lfs3_mdir_t mdir;
|
|
lfs3_mtree_namelookup(&lfs3, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL) => LFS3_ERR_NOENT;
|
|
lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS(
|
|
LFS3_RATTR_NAME(
|
|
LFS3_TAG_REG, +1,
|
|
0, (const char*)buffer+1, SIZE-1))) => 0;
|
|
assert(mdir.r.weight == 2);
|
|
|
|
memset(buffer+1, 'b', SIZE-1);
|
|
lfs3_mtree_namelookup(&lfs3, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL) => LFS3_ERR_NOENT;
|
|
lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS(
|
|
LFS3_RATTR_NAME(
|
|
LFS3_TAG_REG, +1,
|
|
0, (const char*)buffer+1, SIZE-1))) => 0;
|
|
assert(mdir.r.weight == 3);
|
|
|
|
// force mroot to compact
|
|
lfs3.mroot.r.eoff = -1;
|
|
lfs3_mdir_commit(&lfs3, &lfs3.mroot, LFS3_RATTRS(
|
|
LFS3_RATTR_BUF(LFS3_TAG_ATTR(1), 0, "c", 1))) => 0;
|
|
|
|
// assert mdirs were unininlined and split
|
|
assert(lfs3.mtree.r.weight == (2 << lfs3.mbits));
|
|
// assert mroot now has no entries
|
|
assert(lfs3.mroot.r.weight == 0);
|
|
|
|
// setup mroot to compact and relocate on next commit
|
|
lfs3.mroot.r.eoff = -1;
|
|
lfs3_mdir_t old_mroot = lfs3.mroot;
|
|
// remove an entry, forcing the mdir to be dropped
|
|
memset(buffer+1, 'b', SIZE-1);
|
|
lfs3_mtree_namelookup(&lfs3, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL) => LFS3_TAG_REG;
|
|
// force mdir to compact twice, this should relocate
|
|
mdir.r.eoff = -1;
|
|
lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS(
|
|
LFS3_RATTR_BUF(LFS3_TAG_ATTR(2), 0, "d", 1))) => 0;
|
|
// force mdir to compact while we're removing
|
|
mdir.r.eoff = -1;
|
|
lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS(
|
|
LFS3_RATTR(LFS3_TAG_RM, -1))) => 0;
|
|
assert(mdir.r.weight == 0);
|
|
// assert mroot relocated
|
|
assert(lfs3_mdir_cmp(&old_mroot, &lfs3.mroot) != 0);
|
|
|
|
// assert mdir was dropped
|
|
assert(lfs3.mtree.r.weight == (1 << lfs3.mbits));
|
|
// assert mroot still has no entries
|
|
assert(lfs3.mroot.r.weight == 0);
|
|
|
|
// assert that our entries are still in the mtree
|
|
lfs3_mtree_lookup(&lfs3, (0 << lfs3.mbits)+1, &mdir) => 0;
|
|
assert(mdir.r.weight == 2);
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_REG,
|
|
&data) => LFS3_TAG_REG;
|
|
lfs3_data_read(&lfs3, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'a');
|
|
|
|
lfs3_mdir_lookup(&lfs3, &lfs3.mroot, LFS3_TAG_ATTR(1),
|
|
&data) => LFS3_TAG_ATTR(1);
|
|
lfs3_data_read(&lfs3, &data, buffer, 1) => 1;
|
|
assert(buffer[0] == 'c');
|
|
|
|
// check things stay sane after remount
|
|
lfs3_unmount(&lfs3) => 0;
|
|
lfs3_mount(&lfs3, LFS3_M_RDWR | M_FLAGS, CFG) => 0;
|
|
|
|
// assert mdir was dropped
|
|
assert(lfs3.mtree.r.weight == (1 << lfs3.mbits));
|
|
// assert mroot still has no entries
|
|
assert(lfs3.mroot.r.weight == 0);
|
|
|
|
// assert that our entries are still in the mtree
|
|
lfs3_mtree_lookup(&lfs3, (0 << lfs3.mbits)+1, &mdir) => 0;
|
|
assert(mdir.r.weight == 2);
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_REG,
|
|
&data) => LFS3_TAG_REG;
|
|
lfs3_data_read(&lfs3, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'a');
|
|
|
|
lfs3_mdir_lookup(&lfs3, &lfs3.mroot, LFS3_TAG_ATTR(1),
|
|
&data) => LFS3_TAG_ATTR(1);
|
|
lfs3_data_read(&lfs3, &data, buffer, 1) => 1;
|
|
assert(buffer[0] == 'c');
|
|
|
|
lfs3_unmount(&lfs3) => 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_RECYCLES = 0
|
|
in = 'lfs3.c'
|
|
code = '''
|
|
lfs3_t lfs3;
|
|
lfs3_format(&lfs3, LFS3_F_RDWR | F_FLAGS, CFG) => 0;
|
|
lfs3_mount(&lfs3, LFS3_M_RDWR | M_FLAGS, CFG) => 0;
|
|
lfs3_alloc_ckpoint(&lfs3);
|
|
|
|
// force mroot to compact once, so the second compact below will
|
|
// trigger a relocation
|
|
lfs3.mroot.r.eoff = -1;
|
|
lfs3_mdir_commit(&lfs3, &lfs3.mroot, NULL, 0) => 0;
|
|
lfs3_data_t data;
|
|
|
|
// create a 2 large rattrs that needs to be uninlined
|
|
uint8_t buffer[SIZE];
|
|
memset(buffer, 'a', SIZE);
|
|
lfs3_mdir_commit(&lfs3, &lfs3.mroot, LFS3_RATTRS(
|
|
LFS3_RATTR_DATA(LFS3_TAG_ATTR(1), 0,
|
|
&LFS3_DATA_BUF(buffer, SIZE)))) => 0;
|
|
|
|
memset(buffer, 'b', SIZE);
|
|
lfs3_mdir_t mdir;
|
|
lfs3_mtree_lookup(&lfs3, 0, &mdir) => 0;
|
|
lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS(
|
|
LFS3_RATTR_DATA(LFS3_TAG_ATTR(2), 0,
|
|
&LFS3_DATA_BUF(buffer, SIZE)))) => 0;
|
|
|
|
// force mroot to compact, this should both uninline and relocate
|
|
lfs3_mdir_t old_mroot = lfs3.mroot;
|
|
lfs3.mroot.r.eoff = -1;
|
|
lfs3_mdir_commit(&lfs3, &lfs3.mroot, LFS3_RATTRS(
|
|
LFS3_RATTR_BUF(LFS3_TAG_ATTR(3), 0, "c", 1))) => 0;
|
|
// assert mroot relocated
|
|
assert(lfs3_mdir_cmp(&old_mroot, &lfs3.mroot) != 0);
|
|
|
|
// assert mdirs were unininlined
|
|
assert(lfs3.mtree.r.weight == (1 << lfs3.mbits));
|
|
// assert mroot now has no entries
|
|
assert(lfs3.mroot.r.weight == 0);
|
|
|
|
// assert that our rattrs are still in the mroot/mtree
|
|
lfs3_mdir_lookup(&lfs3, &lfs3.mroot, LFS3_TAG_ATTR(1),
|
|
&data) => LFS3_TAG_ATTR(1);
|
|
lfs3_data_read(&lfs3, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[0] == 'a');
|
|
|
|
lfs3_mtree_lookup(&lfs3, 0, &mdir) => 0;
|
|
assert(mdir.r.weight == 1);
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_ATTR(2),
|
|
&data) => LFS3_TAG_ATTR(2);
|
|
lfs3_data_read(&lfs3, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[0] == 'b');
|
|
|
|
lfs3_mdir_lookup(&lfs3, &lfs3.mroot, LFS3_TAG_ATTR(3),
|
|
&data) => LFS3_TAG_ATTR(3);
|
|
lfs3_data_read(&lfs3, &data, buffer, 1) => 1;
|
|
assert(buffer[0] == 'c');
|
|
|
|
// check things stay sane after remount
|
|
lfs3_unmount(&lfs3) => 0;
|
|
lfs3_mount(&lfs3, LFS3_M_RDWR | M_FLAGS, CFG) => 0;
|
|
|
|
// assert mdirs were unininlined
|
|
assert(lfs3.mtree.r.weight == (1 << lfs3.mbits));
|
|
// assert mroot now has no entries
|
|
assert(lfs3.mroot.r.weight == 0);
|
|
|
|
// assert that our rattrs are still in the mroot/mtree
|
|
lfs3_mdir_lookup(&lfs3, &lfs3.mroot, LFS3_TAG_ATTR(1),
|
|
&data) => LFS3_TAG_ATTR(1);
|
|
lfs3_data_read(&lfs3, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[0] == 'a');
|
|
|
|
lfs3_mtree_lookup(&lfs3, 0, &mdir) => 0;
|
|
assert(mdir.r.weight == 1);
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_ATTR(2),
|
|
&data) => LFS3_TAG_ATTR(2);
|
|
lfs3_data_read(&lfs3, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[0] == 'b');
|
|
|
|
lfs3_mdir_lookup(&lfs3, &lfs3.mroot, LFS3_TAG_ATTR(3),
|
|
&data) => LFS3_TAG_ATTR(3);
|
|
lfs3_data_read(&lfs3, &data, buffer, 1) => 1;
|
|
assert(buffer[0] == 'c');
|
|
|
|
lfs3_unmount(&lfs3) => 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_RECYCLES = 0
|
|
in = 'lfs3.c'
|
|
code = '''
|
|
lfs3_t lfs3;
|
|
lfs3_format(&lfs3, LFS3_F_RDWR | F_FLAGS, CFG) => 0;
|
|
lfs3_mount(&lfs3, LFS3_M_RDWR | M_FLAGS, CFG) => 0;
|
|
lfs3_alloc_ckpoint(&lfs3);
|
|
|
|
// force mroot to compact once, so the second compact below will
|
|
// trigger a relocation
|
|
lfs3.mroot.r.eoff = -1;
|
|
lfs3_mdir_commit(&lfs3, &lfs3.mroot, NULL, 0) => 0;
|
|
lfs3_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);
|
|
lfs3_mdir_t mdir;
|
|
lfs3_mtree_namelookup(&lfs3, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL) => LFS3_ERR_NOENT;
|
|
lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS(
|
|
LFS3_RATTR_NAME(
|
|
LFS3_TAG_REG, +1,
|
|
0, (const char*)buffer+1, SIZE-1))) => 0;
|
|
assert(mdir.r.weight == 2);
|
|
|
|
memset(buffer+1, 'b', SIZE-1);
|
|
lfs3_mtree_namelookup(&lfs3, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL) => LFS3_ERR_NOENT;
|
|
lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS(
|
|
LFS3_RATTR_NAME(
|
|
LFS3_TAG_REG, +1,
|
|
0, (const char*)buffer+1, SIZE-1))) => 0;
|
|
assert(mdir.r.weight == 3);
|
|
|
|
// force mroot to compact, this should both split and relocate
|
|
lfs3_mdir_t old_mroot = lfs3.mroot;
|
|
lfs3.mroot.r.eoff = -1;
|
|
lfs3_mdir_commit(&lfs3, &lfs3.mroot, LFS3_RATTRS(
|
|
LFS3_RATTR_BUF(LFS3_TAG_ATTR(1), 0, "c", 1))) => 0;
|
|
// assert mroot relocated
|
|
assert(lfs3_mdir_cmp(&old_mroot, &lfs3.mroot) != 0);
|
|
|
|
// assert mdirs were unininlined and split
|
|
assert(lfs3.mtree.r.weight == (2 << lfs3.mbits));
|
|
// assert mroot now has no entries
|
|
assert(lfs3.mroot.r.weight == 0);
|
|
|
|
// assert that our entries are still in the mtree
|
|
lfs3_mtree_lookup(&lfs3, (0 << lfs3.mbits)+1, &mdir) => 0;
|
|
assert(mdir.r.weight == 2);
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_REG,
|
|
&data) => LFS3_TAG_REG;
|
|
lfs3_data_read(&lfs3, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'a');
|
|
|
|
lfs3_mtree_lookup(&lfs3, (1 << lfs3.mbits)+0, &mdir) => 0;
|
|
assert(mdir.r.weight == 1);
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_REG,
|
|
&data) => LFS3_TAG_REG;
|
|
lfs3_data_read(&lfs3, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'b');
|
|
|
|
lfs3_mdir_lookup(&lfs3, &lfs3.mroot, LFS3_TAG_ATTR(1),
|
|
&data) => LFS3_TAG_ATTR(1);
|
|
lfs3_data_read(&lfs3, &data, buffer, 1) => 1;
|
|
assert(buffer[0] == 'c');
|
|
|
|
// check things stay sane after remount
|
|
lfs3_unmount(&lfs3) => 0;
|
|
lfs3_mount(&lfs3, LFS3_M_RDWR | M_FLAGS, CFG) => 0;
|
|
|
|
// assert mdirs were unininlined and split
|
|
assert(lfs3.mtree.r.weight == (2 << lfs3.mbits));
|
|
// assert mroot now has no entries
|
|
assert(lfs3.mroot.r.weight == 0);
|
|
|
|
// assert that our entries are still in the mtree
|
|
lfs3_mtree_lookup(&lfs3, (0 << lfs3.mbits)+1, &mdir) => 0;
|
|
assert(mdir.r.weight == 2);
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_REG,
|
|
&data) => LFS3_TAG_REG;
|
|
lfs3_data_read(&lfs3, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'a');
|
|
|
|
lfs3_mtree_lookup(&lfs3, (1 << lfs3.mbits)+0, &mdir) => 0;
|
|
assert(mdir.r.weight == 1);
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_REG,
|
|
&data) => LFS3_TAG_REG;
|
|
lfs3_data_read(&lfs3, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'b');
|
|
|
|
lfs3_mdir_lookup(&lfs3, &lfs3.mroot, LFS3_TAG_ATTR(1),
|
|
&data) => LFS3_TAG_ATTR(1);
|
|
lfs3_data_read(&lfs3, &data, buffer, 1) => 1;
|
|
assert(buffer[0] == 'c');
|
|
|
|
lfs3_unmount(&lfs3) => 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_RECYCLES = [4, 1, 0]
|
|
defines.SEED = 'range(500)'
|
|
fuzz = 'SEED'
|
|
in = 'lfs3.c'
|
|
code = '''
|
|
lfs3_t lfs3;
|
|
lfs3_format(&lfs3, LFS3_F_RDWR | F_FLAGS, CFG) => 0;
|
|
lfs3_mount(&lfs3, LFS3_M_RDWR | M_FLAGS, CFG) => 0;
|
|
lfs3_alloc_ckpoint(&lfs3);
|
|
|
|
bool sim[N];
|
|
for (lfs3_size_t i = 0; i < N; i++) {
|
|
sim[i] = false;
|
|
}
|
|
|
|
uint32_t prng = SEED;
|
|
for (lfs3_size_t i = 0; i < N; i++) {
|
|
// choose a pseudo-random name
|
|
lfs3_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
|
|
lfs3_mdir_t mdir;
|
|
lfs3_stag_t tag = lfs3_mtree_namelookup(&lfs3, 0, name+1, 3,
|
|
&mdir, NULL);
|
|
assert(tag >= 0 || tag == LFS3_ERR_NOENT);
|
|
if (tag != LFS3_ERR_NOENT) {
|
|
continue;
|
|
}
|
|
// force a compaction?
|
|
if (FORCE_COMPACTION) {
|
|
lfs3.mroot.r.eoff = -1;
|
|
mdir.r.eoff = -1;
|
|
}
|
|
lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS(
|
|
LFS3_RATTR_NAME(LFS3_TAG_REG, +1, 0, name+1, 3))) => 0;
|
|
lfs3_data_t data;
|
|
|
|
// double check
|
|
uint8_t buffer[256];
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_REG,
|
|
&data) => LFS3_TAG_REG;
|
|
lfs3_data_read(&lfs3, &data, buffer, sizeof(buffer)) => 4;
|
|
assert(memcmp(buffer, name, 4) == 0);
|
|
|
|
// update
|
|
} else if (op == 1) {
|
|
// sim update is a noop
|
|
|
|
// update mtree
|
|
lfs3_mdir_t mdir;
|
|
lfs3_stag_t tag = lfs3_mtree_namelookup(&lfs3, 0, name+1, 3,
|
|
&mdir, NULL);
|
|
assert(tag >= 0 || tag == LFS3_ERR_NOENT);
|
|
if (tag == LFS3_ERR_NOENT) {
|
|
continue;
|
|
}
|
|
// force a compaction?
|
|
if (FORCE_COMPACTION) {
|
|
lfs3.mroot.r.eoff = -1;
|
|
mdir.r.eoff = -1;
|
|
}
|
|
// we can't really change metadata names, but commits still
|
|
// trigger writes to the mdir
|
|
lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS(
|
|
LFS3_RATTR_NAME(LFS3_TAG_REG, 0, 0, name+1, 3))) => 0;
|
|
lfs3_data_t data;
|
|
|
|
// double check
|
|
uint8_t buffer[256];
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_REG,
|
|
&data) => LFS3_TAG_REG;
|
|
lfs3_data_read(&lfs3, &data, buffer, sizeof(buffer)) => 4;
|
|
assert(memcmp(buffer, name, 4) == 0);
|
|
|
|
// delete
|
|
} else {
|
|
// update sim
|
|
sim[x] = false;
|
|
|
|
// update mtree
|
|
lfs3_mdir_t mdir;
|
|
lfs3_stag_t tag = lfs3_mtree_namelookup(&lfs3, 0, name+1, 3,
|
|
&mdir, NULL);
|
|
assert(tag >= 0 || tag == LFS3_ERR_NOENT);
|
|
if (tag == LFS3_ERR_NOENT) {
|
|
continue;
|
|
}
|
|
// force a compaction?
|
|
if (FORCE_COMPACTION) {
|
|
lfs3.mroot.r.eoff = -1;
|
|
mdir.r.eoff = -1;
|
|
}
|
|
lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS(
|
|
LFS3_RATTR(LFS3_TAG_RM, -1))) => 0;
|
|
}
|
|
}
|
|
|
|
// try looking up each entry
|
|
lfs3_mdir_t mdir;
|
|
lfs3_mtree_lookup(&lfs3, 0, &mdir) => 0;
|
|
lfs3_data_t data;
|
|
uint8_t buffer[256];
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_BOOKMARK,
|
|
&data) => LFS3_TAG_BOOKMARK;
|
|
lfs3_data_read(&lfs3, &data, buffer, sizeof(buffer)) => 1;
|
|
for (lfs3_size_t i = 0; i < N; i++) {
|
|
char name[256];
|
|
name[0] = '\0';
|
|
sprintf(name+1, "%03x", i);
|
|
if (sim[i]) {
|
|
lfs3_mtree_namelookup(&lfs3, 0, name+1, 3,
|
|
&mdir, NULL) => LFS3_TAG_REG;
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_REG,
|
|
&data) => LFS3_TAG_REG;
|
|
lfs3_data_read(&lfs3, &data, buffer, sizeof(buffer)) => 4;
|
|
assert(memcmp(buffer, name, 4) == 0);
|
|
} else {
|
|
lfs3_mtree_namelookup(&lfs3, 0, name+1, 3,
|
|
&mdir, NULL) => LFS3_ERR_NOENT;
|
|
}
|
|
}
|
|
|
|
// check things stay sane after remount
|
|
lfs3_unmount(&lfs3) => 0;
|
|
lfs3_mount(&lfs3, LFS3_M_RDWR | M_FLAGS, CFG) => 0;
|
|
|
|
// try looking up each entry
|
|
lfs3_mtree_lookup(&lfs3, 0, &mdir) => 0;
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_BOOKMARK,
|
|
&data) => LFS3_TAG_BOOKMARK;
|
|
lfs3_data_read(&lfs3, &data, buffer, sizeof(buffer)) => 1;
|
|
for (lfs3_size_t i = 0; i < N; i++) {
|
|
char name[256];
|
|
name[0] = '\0';
|
|
sprintf(name+1, "%03x", i);
|
|
if (sim[i]) {
|
|
lfs3_mtree_namelookup(&lfs3, 0, name+1, 3,
|
|
&mdir, NULL) => LFS3_TAG_REG;
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_REG,
|
|
&data) => LFS3_TAG_REG;
|
|
lfs3_data_read(&lfs3, &data, buffer, sizeof(buffer)) => 4;
|
|
assert(memcmp(buffer, name, 4) == 0);
|
|
} else {
|
|
lfs3_mtree_namelookup(&lfs3, 0, name+1, 3,
|
|
&mdir, NULL) => LFS3_ERR_NOENT;
|
|
}
|
|
}
|
|
|
|
lfs3_unmount(&lfs3) => 0;
|
|
'''
|
|
|
|
|
|
## Opened mdir tracking ##
|
|
|
|
[cases.test_mtree_opened]
|
|
in = 'lfs3.c'
|
|
code = '''
|
|
lfs3_t lfs3;
|
|
lfs3_format(&lfs3, LFS3_F_RDWR | F_FLAGS, CFG) => 0;
|
|
lfs3_mount(&lfs3, LFS3_M_RDWR | M_FLAGS, CFG) => 0;
|
|
lfs3_alloc_ckpoint(&lfs3);
|
|
|
|
// setup our neighbors
|
|
lfs3_handle_t left = {.flags=0};
|
|
lfs3_mtree_namelookup(&lfs3, 0, "a", 1,
|
|
&left.mdir, NULL) => LFS3_ERR_NOENT;
|
|
lfs3_mdir_commit(&lfs3, &left.mdir, LFS3_RATTRS(
|
|
LFS3_RATTR_NAME(LFS3_TAG_REG, +1, 0, "a", 1))) => 0;
|
|
assert(left.mdir.r.weight == 2);
|
|
lfs3_handle_open(&lfs3, &left);
|
|
|
|
lfs3_handle_t right = {.flags=0};
|
|
lfs3_mtree_namelookup(&lfs3, 0, "c", 1,
|
|
&right.mdir, NULL) => LFS3_ERR_NOENT;
|
|
lfs3_mdir_commit(&lfs3, &right.mdir, LFS3_RATTRS(
|
|
LFS3_RATTR_NAME(LFS3_TAG_REG, +1, 0, "c", 1))) => 0;
|
|
assert(right.mdir.r.weight == 3);
|
|
lfs3_handle_open(&lfs3, &right);
|
|
|
|
// insert a new entry, this should update our neighbors
|
|
lfs3_mdir_t mdir;
|
|
lfs3_mtree_namelookup(&lfs3, 0, "b", 1,
|
|
&mdir, NULL) => LFS3_ERR_NOENT;
|
|
lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS(
|
|
LFS3_RATTR_NAME(LFS3_TAG_REG, +1, 0, "b", 1))) => 0;
|
|
assert(mdir.r.weight == 4);
|
|
lfs3_data_t data;
|
|
|
|
// assert our entry was created
|
|
uint8_t buffer[2];
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_REG,
|
|
&data) => LFS3_TAG_REG;
|
|
lfs3_data_read(&lfs3, &data, buffer, 2) => 2;
|
|
assert(buffer[1] == 'b');
|
|
|
|
// assert that our neighbors were updated correctly
|
|
lfs3_mtree_namelookup(&lfs3, 0, "a", 1,
|
|
&mdir, NULL) => LFS3_TAG_REG;
|
|
assert(left.mdir.mid == mdir.mid);
|
|
assert(lfs3_mdir_cmp(&left.mdir, &mdir) == 0);
|
|
assert(left.mdir.r.trunk == mdir.r.trunk);
|
|
assert(left.mdir.r.cksum == mdir.r.cksum);
|
|
lfs3_mtree_namelookup(&lfs3, 0, "c", 1,
|
|
&mdir, NULL) => LFS3_TAG_REG;
|
|
assert(right.mdir.mid == mdir.mid);
|
|
assert(lfs3_mdir_cmp(&right.mdir, &mdir) == 0);
|
|
assert(right.mdir.r.trunk == mdir.r.trunk);
|
|
assert(right.mdir.r.cksum == mdir.r.cksum);
|
|
|
|
lfs3_handle_close(&lfs3, &left);
|
|
lfs3_handle_close(&lfs3, &right);
|
|
lfs3_unmount(&lfs3) => 0;
|
|
'''
|
|
|
|
[cases.test_mtree_opened_remove_l]
|
|
in = 'lfs3.c'
|
|
code = '''
|
|
lfs3_t lfs3;
|
|
lfs3_format(&lfs3, LFS3_F_RDWR | F_FLAGS, CFG) => 0;
|
|
lfs3_mount(&lfs3, LFS3_M_RDWR | M_FLAGS, CFG) => 0;
|
|
lfs3_alloc_ckpoint(&lfs3);
|
|
|
|
// setup our neighbors
|
|
lfs3_handle_t left = {.flags=0};
|
|
lfs3_mtree_namelookup(&lfs3, 0, "a", 1,
|
|
&left.mdir, NULL) => LFS3_ERR_NOENT;
|
|
lfs3_mdir_commit(&lfs3, &left.mdir, LFS3_RATTRS(
|
|
LFS3_RATTR_NAME(LFS3_TAG_REG, +1, 0, "a", 1))) => 0;
|
|
assert(left.mdir.r.weight == 2);
|
|
lfs3_handle_open(&lfs3, &left);
|
|
|
|
lfs3_handle_t right = {.flags=0};
|
|
lfs3_mtree_namelookup(&lfs3, 0, "b", 1,
|
|
&right.mdir, NULL) => LFS3_ERR_NOENT;
|
|
lfs3_mdir_commit(&lfs3, &right.mdir, LFS3_RATTRS(
|
|
LFS3_RATTR_NAME(LFS3_TAG_REG, +1, 0, "b", 1))) => 0;
|
|
assert(right.mdir.r.weight == 3);
|
|
lfs3_handle_open(&lfs3, &right);
|
|
|
|
// try removing left neighbor
|
|
lfs3_mdir_t mdir;
|
|
lfs3_mtree_namelookup(&lfs3, 0, "a", 1,
|
|
&mdir, NULL) => LFS3_TAG_REG;
|
|
lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS(
|
|
LFS3_RATTR(LFS3_TAG_RM, -1))) => 0;
|
|
assert(mdir.r.weight == 2);
|
|
|
|
// assert neighbor was removed
|
|
lfs3_mtree_namelookup(&lfs3, 0, "a", 1,
|
|
&mdir, NULL) => LFS3_ERR_NOENT;
|
|
|
|
// assert that our neighbors were updated correctly
|
|
lfs3_mtree_namelookup(&lfs3, 0, "b", 1,
|
|
&mdir, NULL) => LFS3_TAG_REG;
|
|
assert(right.mdir.mid == mdir.mid);
|
|
assert(lfs3_mdir_cmp(&right.mdir, &mdir) == 0);
|
|
assert(right.mdir.r.trunk == mdir.r.trunk);
|
|
assert(right.mdir.r.cksum == mdir.r.cksum);
|
|
|
|
lfs3_handle_close(&lfs3, &left);
|
|
lfs3_handle_close(&lfs3, &right);
|
|
lfs3_unmount(&lfs3) => 0;
|
|
'''
|
|
|
|
[cases.test_mtree_opened_remove_r]
|
|
in = 'lfs3.c'
|
|
code = '''
|
|
lfs3_t lfs3;
|
|
lfs3_format(&lfs3, LFS3_F_RDWR | F_FLAGS, CFG) => 0;
|
|
lfs3_mount(&lfs3, LFS3_M_RDWR | M_FLAGS, CFG) => 0;
|
|
lfs3_alloc_ckpoint(&lfs3);
|
|
|
|
// setup our neighbors
|
|
lfs3_handle_t left = {.flags=0};
|
|
lfs3_mtree_namelookup(&lfs3, 0, "a", 1,
|
|
&left.mdir, NULL) => LFS3_ERR_NOENT;
|
|
lfs3_mdir_commit(&lfs3, &left.mdir, LFS3_RATTRS(
|
|
LFS3_RATTR_NAME(LFS3_TAG_REG, +1, 0, "a", 1))) => 0;
|
|
assert(left.mdir.r.weight == 2);
|
|
lfs3_handle_open(&lfs3, &left);
|
|
|
|
lfs3_handle_t right = {.flags=0};
|
|
lfs3_mtree_namelookup(&lfs3, 0, "b", 1,
|
|
&right.mdir, NULL) => LFS3_ERR_NOENT;
|
|
lfs3_mdir_commit(&lfs3, &right.mdir, LFS3_RATTRS(
|
|
LFS3_RATTR_NAME(LFS3_TAG_REG, +1, 0, "b", 1))) => 0;
|
|
assert(right.mdir.r.weight == 3);
|
|
lfs3_handle_open(&lfs3, &right);
|
|
|
|
// try removing right neighbor
|
|
lfs3_mdir_t mdir;
|
|
lfs3_mtree_namelookup(&lfs3, 0, "b", 1,
|
|
&mdir, NULL) => LFS3_TAG_REG;
|
|
lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS(
|
|
LFS3_RATTR(LFS3_TAG_RM, -1))) => 0;
|
|
assert(mdir.r.weight == 2);
|
|
|
|
// assert neighbor was removed
|
|
lfs3_mtree_namelookup(&lfs3, 0, "b", 1,
|
|
&mdir, NULL) => LFS3_ERR_NOENT;
|
|
|
|
// assert that our neighbors were updated correctly
|
|
lfs3_mtree_namelookup(&lfs3, 0, "a", 1,
|
|
&mdir, NULL) => LFS3_TAG_REG;
|
|
assert(left.mdir.mid == mdir.mid);
|
|
assert(lfs3_mdir_cmp(&left.mdir, &mdir) == 0);
|
|
assert(left.mdir.r.trunk == mdir.r.trunk);
|
|
assert(left.mdir.r.cksum == mdir.r.cksum);
|
|
|
|
lfs3_handle_close(&lfs3, &left);
|
|
lfs3_handle_close(&lfs3, &right);
|
|
lfs3_unmount(&lfs3) => 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 = 'lfs3.c'
|
|
code = '''
|
|
lfs3_t lfs3;
|
|
lfs3_format(&lfs3, LFS3_F_RDWR | F_FLAGS, CFG) => 0;
|
|
lfs3_mount(&lfs3, LFS3_M_RDWR | M_FLAGS, CFG) => 0;
|
|
lfs3_alloc_ckpoint(&lfs3);
|
|
|
|
// setup our neighbors
|
|
lfs3_handle_t left = {.flags=0};
|
|
lfs3_mtree_namelookup(&lfs3, 0, "a", 1,
|
|
&left.mdir, NULL) => LFS3_ERR_NOENT;
|
|
lfs3_mdir_commit(&lfs3, &left.mdir, LFS3_RATTRS(
|
|
LFS3_RATTR_NAME(LFS3_TAG_REG, +1, 0, "a", 1))) => 0;
|
|
assert(left.mdir.r.weight == 2);
|
|
lfs3_handle_open(&lfs3, &left);
|
|
|
|
lfs3_handle_t right = {.flags=0};
|
|
lfs3_mtree_namelookup(&lfs3, 0, "d", 1,
|
|
&right.mdir, NULL) => LFS3_ERR_NOENT;
|
|
lfs3_mdir_commit(&lfs3, &right.mdir, LFS3_RATTRS(
|
|
LFS3_RATTR_NAME(LFS3_TAG_REG, +1, 0, "d", 1))) => 0;
|
|
assert(right.mdir.r.weight == 3);
|
|
lfs3_handle_open(&lfs3, &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);
|
|
lfs3_mdir_t mdir;
|
|
lfs3_mtree_namelookup(&lfs3, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL) => LFS3_ERR_NOENT;
|
|
lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS(
|
|
LFS3_RATTR_NAME(
|
|
LFS3_TAG_REG, +1,
|
|
0, (const char*)buffer+1, SIZE-1))) => 0;
|
|
assert(mdir.r.weight == 4);
|
|
|
|
memset(buffer+1, 'c', SIZE-1);
|
|
lfs3_mtree_namelookup(&lfs3, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL) => LFS3_ERR_NOENT;
|
|
lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS(
|
|
LFS3_RATTR_NAME(
|
|
LFS3_TAG_REG, +1,
|
|
0, (const char*)buffer+1, SIZE-1))) => 0;
|
|
assert(mdir.r.weight == 5);
|
|
|
|
// force mroot to compact
|
|
lfs3.mroot.r.eoff = -1;
|
|
lfs3_mdir_commit(&lfs3, &lfs3.mroot, NULL, 0) => 0;
|
|
|
|
// assert mdirs were unininlined and split
|
|
assert(lfs3.mtree.r.weight == (2 << lfs3.mbits));
|
|
// assert mroot now has no entries
|
|
assert(lfs3.mroot.r.weight == 0);
|
|
|
|
// assert that our neighbors were updated correctly
|
|
lfs3_mtree_namelookup(&lfs3, 0, "a", 1,
|
|
&mdir, NULL) => LFS3_TAG_REG;
|
|
assert(left.mdir.mid == mdir.mid);
|
|
assert(lfs3_mdir_cmp(&left.mdir, &mdir) == 0);
|
|
assert(left.mdir.r.trunk == mdir.r.trunk);
|
|
assert(left.mdir.r.cksum == mdir.r.cksum);
|
|
lfs3_mtree_namelookup(&lfs3, 0, "d", 1,
|
|
&mdir, NULL) => LFS3_TAG_REG;
|
|
assert(right.mdir.mid == mdir.mid);
|
|
assert(lfs3_mdir_cmp(&right.mdir, &mdir) == 0);
|
|
assert(right.mdir.r.trunk == mdir.r.trunk);
|
|
assert(right.mdir.r.cksum == mdir.r.cksum);
|
|
|
|
lfs3_handle_close(&lfs3, &left);
|
|
lfs3_handle_close(&lfs3, &right);
|
|
lfs3_unmount(&lfs3) => 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 = 'lfs3.c'
|
|
code = '''
|
|
lfs3_t lfs3;
|
|
lfs3_format(&lfs3, LFS3_F_RDWR | F_FLAGS, CFG) => 0;
|
|
lfs3_mount(&lfs3, LFS3_M_RDWR | M_FLAGS, CFG) => 0;
|
|
lfs3_alloc_ckpoint(&lfs3);
|
|
|
|
// setup our neighbors
|
|
lfs3_handle_t left = {.flags=0};
|
|
lfs3_mtree_namelookup(&lfs3, 0, "a", 1,
|
|
&left.mdir, NULL) => LFS3_ERR_NOENT;
|
|
lfs3_mdir_commit(&lfs3, &left.mdir, LFS3_RATTRS(
|
|
LFS3_RATTR_NAME(LFS3_TAG_REG, +1, 0, "a", 1))) => 0;
|
|
assert(left.mdir.r.weight == 2);
|
|
lfs3_handle_open(&lfs3, &left);
|
|
|
|
lfs3_handle_t right = {.flags=0};
|
|
lfs3_mtree_namelookup(&lfs3, 0, "e", 1,
|
|
&right.mdir, NULL) => LFS3_ERR_NOENT;
|
|
lfs3_mdir_commit(&lfs3, &right.mdir, LFS3_RATTRS(
|
|
LFS3_RATTR_NAME(LFS3_TAG_REG, +1, 0, "e", 1))) => 0;
|
|
assert(right.mdir.r.weight == 3);
|
|
lfs3_handle_open(&lfs3, &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);
|
|
lfs3_mdir_t mdir;
|
|
lfs3_mtree_namelookup(&lfs3, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL) => LFS3_ERR_NOENT;
|
|
lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS(
|
|
LFS3_RATTR_NAME(
|
|
LFS3_TAG_REG, +1,
|
|
0, (const char*)buffer+1, SIZE-1))) => 0;
|
|
assert(mdir.r.weight == 4);
|
|
|
|
memset(buffer+1, 'd', SIZE-1);
|
|
lfs3_mtree_namelookup(&lfs3, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL) => LFS3_ERR_NOENT;
|
|
lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS(
|
|
LFS3_RATTR_NAME(
|
|
LFS3_TAG_REG, +1,
|
|
0, (const char*)buffer+1, SIZE-1))) => 0;
|
|
assert(mdir.r.weight == 5);
|
|
|
|
// force mroot to compact
|
|
lfs3.mroot.r.eoff = -1;
|
|
lfs3_mdir_commit(&lfs3, &lfs3.mroot, NULL, 0) => 0;
|
|
|
|
// now add another large entry to an mdir, forcing a split
|
|
memset(buffer+1, 'c', SIZE-1);
|
|
lfs3_mtree_namelookup(&lfs3, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL) => LFS3_ERR_NOENT;
|
|
lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS(
|
|
LFS3_RATTR_NAME(
|
|
LFS3_TAG_REG, +1,
|
|
0, (const char*)buffer+1, SIZE-1))) => 0;
|
|
assert(mdir.r.weight == 4);
|
|
|
|
// force mdir to compact
|
|
mdir.r.eoff = -1;
|
|
lfs3_mdir_commit(&lfs3, &mdir, NULL, 0) => 0;
|
|
|
|
// assert mdir was split correctly
|
|
assert(lfs3.mtree.r.weight == (3 << lfs3.mbits));
|
|
// assert mroot still has no entries
|
|
assert(lfs3.mroot.r.weight == 0);
|
|
|
|
// assert that our neighbors were updated correctly
|
|
lfs3_mtree_namelookup(&lfs3, 0, "a", 1,
|
|
&mdir, NULL) => LFS3_TAG_REG;
|
|
assert(left.mdir.mid == mdir.mid);
|
|
assert(lfs3_mdir_cmp(&left.mdir, &mdir) == 0);
|
|
assert(left.mdir.r.trunk == mdir.r.trunk);
|
|
assert(left.mdir.r.cksum == mdir.r.cksum);
|
|
lfs3_mtree_namelookup(&lfs3, 0, "e", 1,
|
|
&mdir, NULL) => LFS3_TAG_REG;
|
|
assert(right.mdir.mid == mdir.mid);
|
|
assert(lfs3_mdir_cmp(&right.mdir, &mdir) == 0);
|
|
assert(right.mdir.r.trunk == mdir.r.trunk);
|
|
assert(right.mdir.r.cksum == mdir.r.cksum);
|
|
|
|
lfs3_handle_close(&lfs3, &left);
|
|
lfs3_handle_close(&lfs3, &right);
|
|
lfs3_unmount(&lfs3) => 0;
|
|
'''
|
|
|
|
[cases.test_mtree_opened_extend]
|
|
# make it so blocks relocate every two compacts
|
|
defines.BLOCK_RECYCLES = 0
|
|
in = 'lfs3.c'
|
|
code = '''
|
|
lfs3_t lfs3;
|
|
lfs3_format(&lfs3, LFS3_F_RDWR | F_FLAGS, CFG) => 0;
|
|
lfs3_mount(&lfs3, LFS3_M_RDWR | M_FLAGS, CFG) => 0;
|
|
lfs3_alloc_ckpoint(&lfs3);
|
|
|
|
// setup our neighbors
|
|
lfs3_handle_t left = {.flags=0};
|
|
lfs3_mtree_namelookup(&lfs3, 0, "a", 1,
|
|
&left.mdir, NULL) => LFS3_ERR_NOENT;
|
|
lfs3_mdir_commit(&lfs3, &left.mdir, LFS3_RATTRS(
|
|
LFS3_RATTR_NAME(LFS3_TAG_REG, +1, 0, "a", 1))) => 0;
|
|
assert(left.mdir.r.weight == 2);
|
|
lfs3_handle_open(&lfs3, &left);
|
|
|
|
lfs3_handle_t right = {.flags=0};
|
|
lfs3_mtree_namelookup(&lfs3, 0, "b", 1,
|
|
&right.mdir, NULL) => LFS3_ERR_NOENT;
|
|
lfs3_mdir_commit(&lfs3, &right.mdir, LFS3_RATTRS(
|
|
LFS3_RATTR_NAME(LFS3_TAG_REG, +1, 0, "b", 1))) => 0;
|
|
assert(right.mdir.r.weight == 3);
|
|
lfs3_handle_open(&lfs3, &right);
|
|
|
|
// force mroot to compact twice, this should extend the mroot
|
|
lfs3_mdir_t old_mroot = lfs3.mroot;
|
|
lfs3.mroot.r.eoff = -1;
|
|
lfs3_mdir_commit(&lfs3, &lfs3.mroot, NULL, 0) => 0;
|
|
lfs3.mroot.r.eoff = -1;
|
|
lfs3_mdir_commit(&lfs3, &lfs3.mroot, NULL, 0) => 0;
|
|
// assert we relocated
|
|
assert(lfs3_mdir_cmp(&old_mroot, &lfs3.mroot) != 0);
|
|
|
|
// assert that our neighbors were updated correctly
|
|
lfs3_mdir_t mdir;
|
|
lfs3_mtree_namelookup(&lfs3, 0, "a", 1,
|
|
&mdir, NULL) => LFS3_TAG_REG;
|
|
assert(left.mdir.mid == mdir.mid);
|
|
assert(lfs3_mdir_cmp(&left.mdir, &mdir) == 0);
|
|
assert(left.mdir.r.trunk == mdir.r.trunk);
|
|
assert(left.mdir.r.cksum == mdir.r.cksum);
|
|
lfs3_mtree_namelookup(&lfs3, 0, "b", 1,
|
|
&mdir, NULL) => LFS3_TAG_REG;
|
|
assert(right.mdir.mid == mdir.mid);
|
|
assert(lfs3_mdir_cmp(&right.mdir, &mdir) == 0);
|
|
assert(right.mdir.r.trunk == mdir.r.trunk);
|
|
assert(right.mdir.r.cksum == mdir.r.cksum);
|
|
|
|
lfs3_handle_close(&lfs3, &left);
|
|
lfs3_handle_close(&lfs3, &right);
|
|
lfs3_unmount(&lfs3) => 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_RECYCLES = 0
|
|
in = 'lfs3.c'
|
|
code = '''
|
|
lfs3_t lfs3;
|
|
lfs3_format(&lfs3, LFS3_F_RDWR | F_FLAGS, CFG) => 0;
|
|
lfs3_mount(&lfs3, LFS3_M_RDWR | M_FLAGS, CFG) => 0;
|
|
lfs3_alloc_ckpoint(&lfs3);
|
|
|
|
// setup our neighbors
|
|
lfs3_handle_t left = {.flags=0};
|
|
lfs3_mtree_namelookup(&lfs3, 0, "a", 1,
|
|
&left.mdir, NULL) => LFS3_ERR_NOENT;
|
|
lfs3_mdir_commit(&lfs3, &left.mdir, LFS3_RATTRS(
|
|
LFS3_RATTR_NAME(LFS3_TAG_REG, +1, 0, "a", 1))) => 0;
|
|
assert(left.mdir.r.weight == 2);
|
|
lfs3_handle_open(&lfs3, &left);
|
|
|
|
lfs3_handle_t right = {.flags=0};
|
|
lfs3_mtree_namelookup(&lfs3, 0, "d", 1,
|
|
&right.mdir, NULL) => LFS3_ERR_NOENT;
|
|
lfs3_mdir_commit(&lfs3, &right.mdir, LFS3_RATTRS(
|
|
LFS3_RATTR_NAME(LFS3_TAG_REG, +1, 0, "d", 1))) => 0;
|
|
assert(right.mdir.r.weight == 3);
|
|
lfs3_handle_open(&lfs3, &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);
|
|
lfs3_mdir_t mdir;
|
|
lfs3_mtree_namelookup(&lfs3, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL) => LFS3_ERR_NOENT;
|
|
lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS(
|
|
LFS3_RATTR_NAME(
|
|
LFS3_TAG_REG, +1,
|
|
0, (const char*)buffer+1, SIZE-1))) => 0;
|
|
assert(mdir.r.weight == 4);
|
|
|
|
memset(buffer+1, 'c', SIZE-1);
|
|
lfs3_mtree_namelookup(&lfs3, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL) => LFS3_ERR_NOENT;
|
|
lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS(
|
|
LFS3_RATTR_NAME(
|
|
LFS3_TAG_REG, +1,
|
|
0, (const char*)buffer+1, SIZE-1))) => 0;
|
|
assert(mdir.r.weight == 5);
|
|
|
|
// force mroot to compact
|
|
lfs3.mroot.r.eoff = -1;
|
|
lfs3_mdir_commit(&lfs3, &lfs3.mroot, NULL, 0) => 0;
|
|
|
|
// assert mdirs were unininlined and split
|
|
assert(lfs3.mtree.r.weight == (2 << lfs3.mbits));
|
|
// assert mroot now has no entries
|
|
assert(lfs3.mroot.r.weight == 0);
|
|
|
|
// force mdir to compact twice, this should relocate
|
|
memset(buffer+1, 'b', SIZE-1);
|
|
lfs3_mtree_namelookup(&lfs3, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL) => LFS3_TAG_REG;
|
|
lfs3_mdir_t old_mdir = mdir;
|
|
mdir.r.eoff = -1;
|
|
lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS(
|
|
LFS3_RATTR_NAME(
|
|
LFS3_TAG_REG, 0,
|
|
0, (const char*)buffer+1, SIZE-1))) => 0;
|
|
mdir.r.eoff = -1;
|
|
lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS(
|
|
LFS3_RATTR_NAME(
|
|
LFS3_TAG_REG, 0,
|
|
0, (const char*)buffer+1, SIZE-1))) => 0;
|
|
// assert we relocated
|
|
assert(lfs3_mdir_cmp(&old_mdir, &mdir) != 0);
|
|
|
|
// assert that our neighbors were updated correctly
|
|
lfs3_mtree_namelookup(&lfs3, 0, "a", 1,
|
|
&mdir, NULL) => LFS3_TAG_REG;
|
|
assert(left.mdir.mid == mdir.mid);
|
|
assert(lfs3_mdir_cmp(&left.mdir, &mdir) == 0);
|
|
assert(left.mdir.r.trunk == mdir.r.trunk);
|
|
assert(left.mdir.r.cksum == mdir.r.cksum);
|
|
lfs3_mtree_namelookup(&lfs3, 0, "d", 1,
|
|
&mdir, NULL) => LFS3_TAG_REG;
|
|
assert(right.mdir.mid == mdir.mid);
|
|
assert(lfs3_mdir_cmp(&right.mdir, &mdir) == 0);
|
|
assert(right.mdir.r.trunk == mdir.r.trunk);
|
|
assert(right.mdir.r.cksum == mdir.r.cksum);
|
|
|
|
lfs3_handle_close(&lfs3, &left);
|
|
lfs3_handle_close(&lfs3, &right);
|
|
lfs3_unmount(&lfs3) => 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_RECYCLES = 0
|
|
in = 'lfs3.c'
|
|
code = '''
|
|
lfs3_t lfs3;
|
|
lfs3_format(&lfs3, LFS3_F_RDWR | F_FLAGS, CFG) => 0;
|
|
lfs3_mount(&lfs3, LFS3_M_RDWR | M_FLAGS, CFG) => 0;
|
|
lfs3_alloc_ckpoint(&lfs3);
|
|
|
|
// setup our neighbors
|
|
lfs3_handle_t left = {.flags=0};
|
|
lfs3_mtree_namelookup(&lfs3, 0, "a", 1,
|
|
&left.mdir, NULL) => LFS3_ERR_NOENT;
|
|
lfs3_mdir_commit(&lfs3, &left.mdir, LFS3_RATTRS(
|
|
LFS3_RATTR_NAME(LFS3_TAG_REG, +1, 0, "a", 1))) => 0;
|
|
assert(left.mdir.r.weight == 2);
|
|
lfs3_handle_open(&lfs3, &left);
|
|
|
|
lfs3_handle_t right = {.flags=0};
|
|
lfs3_mtree_namelookup(&lfs3, 0, "d", 1,
|
|
&right.mdir, NULL) => LFS3_ERR_NOENT;
|
|
lfs3_mdir_commit(&lfs3, &right.mdir, LFS3_RATTRS(
|
|
LFS3_RATTR_NAME(LFS3_TAG_REG, +1, 0, "d", 1))) => 0;
|
|
assert(right.mdir.r.weight == 3);
|
|
lfs3_handle_open(&lfs3, &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);
|
|
lfs3_mdir_t mdir;
|
|
lfs3_mtree_namelookup(&lfs3, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL) => LFS3_ERR_NOENT;
|
|
lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS(
|
|
LFS3_RATTR_NAME(
|
|
LFS3_TAG_REG, +1,
|
|
0, (const char*)buffer+1, SIZE-1))) => 0;
|
|
assert(mdir.r.weight == 4);
|
|
|
|
memset(buffer+1, 'c', SIZE-1);
|
|
lfs3_mtree_namelookup(&lfs3, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL) => LFS3_ERR_NOENT;
|
|
lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS(
|
|
LFS3_RATTR_NAME(
|
|
LFS3_TAG_REG, +1,
|
|
0, (const char*)buffer+1, SIZE-1))) => 0;
|
|
assert(mdir.r.weight == 5);
|
|
|
|
// force mroot to compact
|
|
lfs3.mroot.r.eoff = -1;
|
|
lfs3_mdir_commit(&lfs3, &lfs3.mroot, NULL, 0) => 0;
|
|
|
|
// assert mdirs were unininlined and split
|
|
assert(lfs3.mtree.r.weight == (2 << lfs3.mbits));
|
|
// assert mroot now has no entries
|
|
assert(lfs3.mroot.r.weight == 0);
|
|
|
|
// force mdir to compact twice, this should relocate
|
|
memset(buffer+1, 'c', SIZE-1);
|
|
lfs3_mtree_namelookup(&lfs3, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL) => LFS3_TAG_REG;
|
|
lfs3_mdir_t old_mdir = mdir;
|
|
mdir.r.eoff = -1;
|
|
lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS(
|
|
LFS3_RATTR_NAME(
|
|
LFS3_TAG_REG, 0,
|
|
0, (const char*)buffer+1, SIZE-1))) => 0;
|
|
mdir.r.eoff = -1;
|
|
lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS(
|
|
LFS3_RATTR_NAME(
|
|
LFS3_TAG_REG, 0,
|
|
0, (const char*)buffer+1, SIZE-1))) => 0;
|
|
// assert we relocated
|
|
assert(lfs3_mdir_cmp(&old_mdir, &mdir) != 0);
|
|
|
|
// assert that our neighbors were updated correctly
|
|
lfs3_mtree_namelookup(&lfs3, 0, "a", 1,
|
|
&mdir, NULL) => LFS3_TAG_REG;
|
|
assert(left.mdir.mid == mdir.mid);
|
|
assert(lfs3_mdir_cmp(&left.mdir, &mdir) == 0);
|
|
assert(left.mdir.r.trunk == mdir.r.trunk);
|
|
assert(left.mdir.r.cksum == mdir.r.cksum);
|
|
lfs3_mtree_namelookup(&lfs3, 0, "d", 1,
|
|
&mdir, NULL) => LFS3_TAG_REG;
|
|
assert(right.mdir.mid == mdir.mid);
|
|
assert(lfs3_mdir_cmp(&right.mdir, &mdir) == 0);
|
|
assert(right.mdir.r.trunk == mdir.r.trunk);
|
|
assert(right.mdir.r.cksum == mdir.r.cksum);
|
|
|
|
lfs3_handle_close(&lfs3, &left);
|
|
lfs3_handle_close(&lfs3, &right);
|
|
lfs3_unmount(&lfs3) => 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 = 'lfs3.c'
|
|
code = '''
|
|
lfs3_t lfs3;
|
|
lfs3_format(&lfs3, LFS3_F_RDWR | F_FLAGS, CFG) => 0;
|
|
lfs3_mount(&lfs3, LFS3_M_RDWR | M_FLAGS, CFG) => 0;
|
|
lfs3_alloc_ckpoint(&lfs3);
|
|
|
|
// setup our neighbors
|
|
lfs3_handle_t left = {.flags=0};
|
|
lfs3_mtree_namelookup(&lfs3, 0, "a", 1,
|
|
&left.mdir, NULL) => LFS3_ERR_NOENT;
|
|
lfs3_mdir_commit(&lfs3, &left.mdir, LFS3_RATTRS(
|
|
LFS3_RATTR_NAME(LFS3_TAG_REG, +1, 0, "a", 1))) => 0;
|
|
assert(left.mdir.r.weight == 2);
|
|
lfs3_handle_open(&lfs3, &left);
|
|
|
|
lfs3_handle_t right = {.flags=0};
|
|
lfs3_mtree_namelookup(&lfs3, 0, "f", 1,
|
|
&right.mdir, NULL) => LFS3_ERR_NOENT;
|
|
lfs3_mdir_commit(&lfs3, &right.mdir, LFS3_RATTRS(
|
|
LFS3_RATTR_NAME(LFS3_TAG_REG, +1, 0, "f", 1))) => 0;
|
|
assert(right.mdir.r.weight == 3);
|
|
lfs3_handle_open(&lfs3, &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);
|
|
lfs3_mdir_t mdir;
|
|
lfs3_mtree_namelookup(&lfs3, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL) => LFS3_ERR_NOENT;
|
|
lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS(
|
|
LFS3_RATTR_NAME(
|
|
LFS3_TAG_REG, +1,
|
|
0, (const char*)buffer+1, SIZE-1))) => 0;
|
|
assert(mdir.r.weight == 4);
|
|
|
|
memset(buffer+1, 'e', SIZE-1);
|
|
lfs3_mtree_namelookup(&lfs3, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL) => LFS3_ERR_NOENT;
|
|
lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS(
|
|
LFS3_RATTR_NAME(
|
|
LFS3_TAG_REG, +1,
|
|
0, (const char*)buffer+1, SIZE-1))) => 0;
|
|
assert(mdir.r.weight == 5);
|
|
|
|
// force mroot to compact
|
|
lfs3.mroot.r.eoff = -1;
|
|
lfs3_mdir_commit(&lfs3, &lfs3.mroot, NULL, 0) => 0;
|
|
|
|
// now add another large entry to an mdir, forcing a split
|
|
memset(buffer+1, 'c', SIZE-1);
|
|
lfs3_mtree_namelookup(&lfs3, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL) => LFS3_ERR_NOENT;
|
|
lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS(
|
|
LFS3_RATTR_NAME(
|
|
LFS3_TAG_REG, +1,
|
|
0, (const char*)buffer+1, SIZE-1))) => 0;
|
|
assert(mdir.r.weight == 4);
|
|
|
|
// force mdir to compact
|
|
mdir.r.eoff = -1;
|
|
lfs3_mdir_commit(&lfs3, &mdir, NULL, 0) => 0;
|
|
|
|
// assert mdir was split correctly
|
|
assert(lfs3.mtree.r.weight == (3 << lfs3.mbits));
|
|
// assert mroot still has no entries
|
|
assert(lfs3.mroot.r.weight == 0);
|
|
|
|
// now add _another_ large entry to the middle mdir, forcing another split
|
|
memset(buffer+1, 'd', SIZE-1);
|
|
lfs3_mtree_namelookup(&lfs3, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL) => LFS3_ERR_NOENT;
|
|
lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS(
|
|
LFS3_RATTR_NAME(
|
|
LFS3_TAG_REG, +1,
|
|
0, (const char*)buffer+1, SIZE-1))) => 0;
|
|
assert(mdir.r.weight == 2);
|
|
|
|
// force mdir to compact
|
|
mdir.r.eoff = -1;
|
|
lfs3_mdir_commit(&lfs3, &mdir, NULL, 0) => 0;
|
|
|
|
// assert mdir was split correctly
|
|
assert(lfs3.mtree.r.weight == (4 << lfs3.mbits));
|
|
// assert mroot still has no entries
|
|
assert(lfs3.mroot.r.weight == 0);
|
|
|
|
// assert that our neighbors were updated correctly
|
|
lfs3_mtree_namelookup(&lfs3, 0, "a", 1,
|
|
&mdir, NULL) => LFS3_TAG_REG;
|
|
assert(left.mdir.mid == mdir.mid);
|
|
assert(lfs3_mdir_cmp(&left.mdir, &mdir) == 0);
|
|
assert(left.mdir.r.trunk == mdir.r.trunk);
|
|
assert(left.mdir.r.cksum == mdir.r.cksum);
|
|
lfs3_mtree_namelookup(&lfs3, 0, "f", 1,
|
|
&mdir, NULL) => LFS3_TAG_REG;
|
|
assert(right.mdir.mid == mdir.mid);
|
|
assert(lfs3_mdir_cmp(&right.mdir, &mdir) == 0);
|
|
assert(right.mdir.r.trunk == mdir.r.trunk);
|
|
assert(right.mdir.r.cksum == mdir.r.cksum);
|
|
|
|
lfs3_handle_close(&lfs3, &left);
|
|
lfs3_handle_close(&lfs3, &right);
|
|
lfs3_unmount(&lfs3) => 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 = 'lfs3.c'
|
|
code = '''
|
|
lfs3_t lfs3;
|
|
lfs3_format(&lfs3, LFS3_F_RDWR | F_FLAGS, CFG) => 0;
|
|
lfs3_mount(&lfs3, LFS3_M_RDWR | M_FLAGS, CFG) => 0;
|
|
lfs3_alloc_ckpoint(&lfs3);
|
|
|
|
// setup our neighbors
|
|
lfs3_handle_t left = {.flags=0};
|
|
lfs3_mtree_namelookup(&lfs3, 0, "a", 1,
|
|
&left.mdir, NULL) => LFS3_ERR_NOENT;
|
|
lfs3_mdir_commit(&lfs3, &left.mdir, LFS3_RATTRS(
|
|
LFS3_RATTR_NAME(LFS3_TAG_REG, +1, 0, "a", 1))) => 0;
|
|
assert(left.mdir.r.weight == 2);
|
|
lfs3_handle_open(&lfs3, &left);
|
|
|
|
lfs3_handle_t right = {.flags=0};
|
|
lfs3_mtree_namelookup(&lfs3, 0, "e", 1,
|
|
&right.mdir, NULL) => LFS3_ERR_NOENT;
|
|
lfs3_mdir_commit(&lfs3, &right.mdir, LFS3_RATTRS(
|
|
LFS3_RATTR_NAME(LFS3_TAG_REG, +1, 0, "e", 1))) => 0;
|
|
assert(right.mdir.r.weight == 3);
|
|
lfs3_handle_open(&lfs3, &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);
|
|
lfs3_mdir_t mdir;
|
|
lfs3_mtree_namelookup(&lfs3, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL) => LFS3_ERR_NOENT;
|
|
lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS(
|
|
LFS3_RATTR_NAME(
|
|
LFS3_TAG_REG, +1,
|
|
0, (const char*)buffer+1, SIZE-1))) => 0;
|
|
assert(mdir.r.weight == 4);
|
|
|
|
memset(buffer+1, 'd', SIZE-1);
|
|
lfs3_mtree_namelookup(&lfs3, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL) => LFS3_ERR_NOENT;
|
|
lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS(
|
|
LFS3_RATTR_NAME(
|
|
LFS3_TAG_REG, +1,
|
|
0, (const char*)buffer+1, SIZE-1))) => 0;
|
|
assert(mdir.r.weight == 5);
|
|
|
|
// force mroot to compact
|
|
lfs3.mroot.r.eoff = -1;
|
|
lfs3_mdir_commit(&lfs3, &lfs3.mroot, NULL, 0) => 0;
|
|
|
|
// now add another large entry to an mdir, forcing a split
|
|
memset(buffer+1, 'c', SIZE-1);
|
|
lfs3_mtree_namelookup(&lfs3, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL) => LFS3_ERR_NOENT;
|
|
lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS(
|
|
LFS3_RATTR_NAME(
|
|
LFS3_TAG_REG, +1,
|
|
0, (const char*)buffer+1, SIZE-1))) => 0;
|
|
assert(mdir.r.weight == 4);
|
|
|
|
// force mdir to compact
|
|
mdir.r.eoff = -1;
|
|
lfs3_mdir_commit(&lfs3, &mdir, NULL, 0) => 0;
|
|
|
|
// assert mdir was split correctly
|
|
assert(lfs3.mtree.r.weight == (3 << lfs3.mbits));
|
|
// assert mroot still has no entries
|
|
assert(lfs3.mroot.r.weight == 0);
|
|
|
|
// now remove the middle entry, forcing a drop
|
|
lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS(
|
|
LFS3_RATTR(LFS3_TAG_RM, -1))) => 0;
|
|
assert(mdir.r.weight == 0);
|
|
|
|
// assert mdir was dropped correctly
|
|
assert(lfs3.mtree.r.weight == (2 << lfs3.mbits));
|
|
// assert mroot still has no entries
|
|
assert(lfs3.mroot.r.weight == 0);
|
|
|
|
// assert that our neighbors were updated correctly
|
|
lfs3_mtree_namelookup(&lfs3, 0, "a", 1,
|
|
&mdir, NULL) => LFS3_TAG_REG;
|
|
assert(left.mdir.mid == mdir.mid);
|
|
assert(lfs3_mdir_cmp(&left.mdir, &mdir) == 0);
|
|
assert(left.mdir.r.trunk == mdir.r.trunk);
|
|
assert(left.mdir.r.cksum == mdir.r.cksum);
|
|
lfs3_mtree_namelookup(&lfs3, 0, "e", 1,
|
|
&mdir, NULL) => LFS3_TAG_REG;
|
|
assert(right.mdir.mid == mdir.mid);
|
|
assert(lfs3_mdir_cmp(&right.mdir, &mdir) == 0);
|
|
assert(right.mdir.r.trunk == mdir.r.trunk);
|
|
assert(right.mdir.r.cksum == mdir.r.cksum);
|
|
|
|
lfs3_handle_close(&lfs3, &left);
|
|
lfs3_handle_close(&lfs3, &right);
|
|
lfs3_unmount(&lfs3) => 0;
|
|
'''
|
|
|
|
|
|
## mtree traversal ##
|
|
|
|
# test specific corner cases
|
|
[cases.test_mtree_traversal]
|
|
defines.CKMETA = [false, true]
|
|
in = 'lfs3.c'
|
|
code = '''
|
|
lfs3_t lfs3;
|
|
lfs3_format(&lfs3, LFS3_F_RDWR | F_FLAGS, CFG) => 0;
|
|
lfs3_mount(&lfs3, LFS3_M_RDWR | M_FLAGS, CFG) => 0;
|
|
lfs3_alloc_ckpoint(&lfs3);
|
|
|
|
// insert at least one entry
|
|
lfs3_mdir_t mdir;
|
|
lfs3_mtree_namelookup(&lfs3, 0, "a", 1,
|
|
&mdir, NULL) => LFS3_ERR_NOENT;
|
|
lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS(
|
|
LFS3_RATTR_NAME(LFS3_TAG_REG, +1, 0, "a", 1))) => 0;
|
|
assert(mdir.r.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);
|
|
|
|
lfs3_trv_t trv;
|
|
lfs3_trv_init(&trv,
|
|
LFS3_T_RDONLY
|
|
| LFS3_T_MTREEONLY
|
|
| ((CKMETA) ? LFS3_T_CKMETA : 0));
|
|
for (lfs3_block_t i = 0;; i++) {
|
|
// a bit hacky, but this catches infinite loops
|
|
assert(i < 2*BLOCK_COUNT);
|
|
|
|
lfs3_stag_t tag;
|
|
lfs3_bptr_t bptr;
|
|
tag = lfs3_mtree_traverse(&lfs3, &trv,
|
|
&bptr);
|
|
assert(tag >= 0 || tag == LFS3_ERR_NOENT);
|
|
if (tag == LFS3_ERR_NOENT) {
|
|
break;
|
|
}
|
|
|
|
if (tag == LFS3_TAG_MDIR) {
|
|
lfs3_mdir_t *mdir = (lfs3_mdir_t*)bptr.d.u.buffer;
|
|
printf("traversal: 0x%x mdir 0x{%x,%x}\n",
|
|
tag,
|
|
mdir->r.blocks[0],
|
|
mdir->r.blocks[1]);
|
|
|
|
// keep track of seen blocks
|
|
seen[mdir->r.blocks[1] / 8] |= 1 << (mdir->r.blocks[1] % 8);
|
|
seen[mdir->r.blocks[0] / 8] |= 1 << (mdir->r.blocks[0] % 8);
|
|
|
|
} else if (tag == LFS3_TAG_BRANCH) {
|
|
lfs3_rbyd_t *rbyd = (lfs3_rbyd_t*)bptr.d.u.buffer;
|
|
printf("traversal: 0x%x btree 0x%x.%x\n",
|
|
tag,
|
|
rbyd->blocks[0], rbyd->trunk);
|
|
|
|
// keep track of seen blocks
|
|
seen[rbyd->blocks[0] / 8] |= 1 << (rbyd->blocks[0] % 8);
|
|
|
|
} else {
|
|
// this shouldn't happen
|
|
printf("traversal: 0x%x\n", 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 (lfs3_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);
|
|
lfs3_data_t data;
|
|
|
|
// and the tree should still work
|
|
|
|
// assert that our entry is still in the mtree
|
|
uint8_t buffer[256];
|
|
lfs3_mtree_namelookup(&lfs3, 0, "a", 1,
|
|
&mdir, NULL) => LFS3_TAG_REG;
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_REG,
|
|
&data) => LFS3_TAG_REG;
|
|
lfs3_data_read(&lfs3, &data, buffer, sizeof(buffer)) => 2;
|
|
assert(memcmp(buffer, "\0a", 2) == 0);
|
|
|
|
// check things stay sane after remount
|
|
lfs3_unmount(&lfs3) => 0;
|
|
lfs3_mount(&lfs3, LFS3_M_RDWR | M_FLAGS, CFG) => 0;
|
|
|
|
// assert that our entry is still in the mtree
|
|
lfs3_mtree_namelookup(&lfs3, 0, "a", 1,
|
|
&mdir, NULL) => LFS3_TAG_REG;
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_REG,
|
|
&data) => LFS3_TAG_REG;
|
|
lfs3_data_read(&lfs3, &data, buffer, sizeof(buffer)) => 2;
|
|
assert(memcmp(buffer, "\0a", 2) == 0);
|
|
|
|
lfs3_unmount(&lfs3) => 0;
|
|
'''
|
|
|
|
[cases.test_mtree_traversal_uninline]
|
|
defines.CKMETA = [false, true]
|
|
# this should be set so only one entry can fit in a metadata block
|
|
defines.SIZE = 'BLOCK_SIZE / 4'
|
|
in = 'lfs3.c'
|
|
code = '''
|
|
lfs3_t lfs3;
|
|
lfs3_format(&lfs3, LFS3_F_RDWR | F_FLAGS, CFG) => 0;
|
|
lfs3_mount(&lfs3, LFS3_M_RDWR | M_FLAGS, CFG) => 0;
|
|
lfs3_alloc_ckpoint(&lfs3);
|
|
lfs3_data_t data;
|
|
|
|
// create a 2 large rattrs that needs to be uninlined
|
|
uint8_t buffer[SIZE];
|
|
memset(buffer, 'a', SIZE);
|
|
lfs3_mdir_commit(&lfs3, &lfs3.mroot, LFS3_RATTRS(
|
|
LFS3_RATTR_DATA(LFS3_TAG_ATTR(1), 0,
|
|
&LFS3_DATA_BUF(buffer, SIZE)))) => 0;
|
|
|
|
memset(buffer, 'b', SIZE);
|
|
lfs3_mdir_t mdir;
|
|
lfs3_mtree_lookup(&lfs3, 0, &mdir) => 0;
|
|
lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS(
|
|
LFS3_RATTR_DATA(LFS3_TAG_ATTR(1), 0,
|
|
&LFS3_DATA_BUF(buffer, SIZE)))) => 0;
|
|
|
|
// force mroot to compact
|
|
lfs3.mroot.r.eoff = -1;
|
|
lfs3_mdir_commit(&lfs3, &lfs3.mroot, NULL, 0) => 0;
|
|
|
|
// assert mdirs were unininlined
|
|
assert(lfs3.mtree.r.weight == (1 << lfs3.mbits));
|
|
// assert mroot now has no entries
|
|
assert(lfs3.mroot.r.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);
|
|
|
|
lfs3_trv_t trv;
|
|
lfs3_trv_init(&trv,
|
|
LFS3_T_RDONLY
|
|
| LFS3_T_MTREEONLY
|
|
| ((CKMETA) ? LFS3_T_CKMETA : 0));
|
|
for (lfs3_block_t i = 0;; i++) {
|
|
// a bit hacky, but this catches infinite loops
|
|
assert(i < 2*BLOCK_COUNT);
|
|
|
|
lfs3_stag_t tag;
|
|
lfs3_bptr_t bptr;
|
|
tag = lfs3_mtree_traverse(&lfs3, &trv,
|
|
&bptr);
|
|
assert(tag >= 0 || tag == LFS3_ERR_NOENT);
|
|
if (tag == LFS3_ERR_NOENT) {
|
|
break;
|
|
}
|
|
|
|
if (tag == LFS3_TAG_MDIR) {
|
|
lfs3_mdir_t *mdir = (lfs3_mdir_t*)bptr.d.u.buffer;
|
|
printf("traversal: 0x%x mdir 0x{%x,%x}\n",
|
|
tag,
|
|
mdir->r.blocks[0],
|
|
mdir->r.blocks[1]);
|
|
|
|
// keep track of seen blocks
|
|
seen[mdir->r.blocks[1] / 8] |= 1 << (mdir->r.blocks[1] % 8);
|
|
seen[mdir->r.blocks[0] / 8] |= 1 << (mdir->r.blocks[0] % 8);
|
|
|
|
} else if (tag == LFS3_TAG_BRANCH) {
|
|
lfs3_rbyd_t *rbyd = (lfs3_rbyd_t*)bptr.d.u.buffer;
|
|
printf("traversal: 0x%x btree 0x%x.%x\n",
|
|
tag,
|
|
rbyd->blocks[0], rbyd->trunk);
|
|
|
|
// keep track of seen blocks
|
|
seen[rbyd->blocks[0] / 8] |= 1 << (rbyd->blocks[0] % 8);
|
|
|
|
} else {
|
|
// this shouldn't happen
|
|
printf("traversal: 0x%x\n", 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 (lfs3_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(lfs3.mtree.r.weight == (1 << lfs3.mbits));
|
|
// assert mroot now has no entries
|
|
assert(lfs3.mroot.r.weight == 0);
|
|
|
|
// assert that our rattrs are still in the mroot/mtree
|
|
lfs3_mdir_lookup(&lfs3, &lfs3.mroot, LFS3_TAG_ATTR(1),
|
|
&data) => LFS3_TAG_ATTR(1);
|
|
lfs3_data_read(&lfs3, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[0] == 'a');
|
|
|
|
lfs3_mtree_lookup(&lfs3, 0, &mdir) => 0;
|
|
assert(mdir.r.weight == 1);
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_ATTR(1),
|
|
&data) => LFS3_TAG_ATTR(1);
|
|
lfs3_data_read(&lfs3, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[0] == 'b');
|
|
|
|
// check things stay sane after remount
|
|
lfs3_unmount(&lfs3) => 0;
|
|
lfs3_mount(&lfs3, LFS3_M_RDWR | M_FLAGS, CFG) => 0;
|
|
|
|
// assert mdirs were unininlined
|
|
assert(lfs3.mtree.r.weight == (1 << lfs3.mbits));
|
|
// assert mroot now has no entries
|
|
assert(lfs3.mroot.r.weight == 0);
|
|
|
|
// assert that our rattrs are still in the mroot/mtree
|
|
lfs3_mdir_lookup(&lfs3, &lfs3.mroot, LFS3_TAG_ATTR(1),
|
|
&data) => LFS3_TAG_ATTR(1);
|
|
lfs3_data_read(&lfs3, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[0] == 'a');
|
|
|
|
lfs3_mtree_lookup(&lfs3, 0, &mdir) => 0;
|
|
assert(mdir.r.weight == 1);
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_ATTR(1),
|
|
&data) => LFS3_TAG_ATTR(1);
|
|
lfs3_data_read(&lfs3, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[0] == 'b');
|
|
|
|
lfs3_unmount(&lfs3) => 0;
|
|
'''
|
|
|
|
[cases.test_mtree_traversal_uninline_split]
|
|
defines.CKMETA = [false, true]
|
|
# this should be set so only one entry can fit in a metadata block
|
|
defines.SIZE = 'BLOCK_SIZE / 4'
|
|
in = 'lfs3.c'
|
|
code = '''
|
|
lfs3_t lfs3;
|
|
lfs3_format(&lfs3, LFS3_F_RDWR | F_FLAGS, CFG) => 0;
|
|
lfs3_mount(&lfs3, LFS3_M_RDWR | M_FLAGS, CFG) => 0;
|
|
lfs3_alloc_ckpoint(&lfs3);
|
|
lfs3_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);
|
|
lfs3_mdir_t mdir;
|
|
lfs3_mtree_namelookup(&lfs3, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL) => LFS3_ERR_NOENT;
|
|
lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS(
|
|
LFS3_RATTR_NAME(
|
|
LFS3_TAG_REG, +1,
|
|
0, (const char*)buffer+1, SIZE-1))) => 0;
|
|
assert(mdir.r.weight == 2);
|
|
|
|
memset(buffer+1, 'b', SIZE-1);
|
|
lfs3_mtree_namelookup(&lfs3, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL) => LFS3_ERR_NOENT;
|
|
lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS(
|
|
LFS3_RATTR_NAME(
|
|
LFS3_TAG_REG, +1,
|
|
0, (const char*)buffer+1, SIZE-1))) => 0;
|
|
assert(mdir.r.weight == 3);
|
|
|
|
// force mroot to compact
|
|
lfs3.mroot.r.eoff = -1;
|
|
lfs3_mdir_commit(&lfs3, &lfs3.mroot, NULL, 0) => 0;
|
|
|
|
// assert mdirs were unininlined and split
|
|
assert(lfs3.mtree.r.weight == (2 << lfs3.mbits));
|
|
// assert mroot now has no entries
|
|
assert(lfs3.mroot.r.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);
|
|
|
|
lfs3_trv_t trv;
|
|
lfs3_trv_init(&trv,
|
|
LFS3_T_RDONLY
|
|
| LFS3_T_MTREEONLY
|
|
| ((CKMETA) ? LFS3_T_CKMETA : 0));
|
|
for (lfs3_block_t i = 0;; i++) {
|
|
// a bit hacky, but this catches infinite loops
|
|
assert(i < 2*BLOCK_COUNT);
|
|
|
|
lfs3_stag_t tag;
|
|
lfs3_bptr_t bptr;
|
|
tag = lfs3_mtree_traverse(&lfs3, &trv,
|
|
&bptr);
|
|
assert(tag >= 0 || tag == LFS3_ERR_NOENT);
|
|
if (tag == LFS3_ERR_NOENT) {
|
|
break;
|
|
}
|
|
|
|
if (tag == LFS3_TAG_MDIR) {
|
|
lfs3_mdir_t *mdir = (lfs3_mdir_t*)bptr.d.u.buffer;
|
|
printf("traversal: 0x%x mdir 0x{%x,%x}\n",
|
|
tag,
|
|
mdir->r.blocks[0],
|
|
mdir->r.blocks[1]);
|
|
|
|
// keep track of seen blocks
|
|
seen[mdir->r.blocks[1] / 8] |= 1 << (mdir->r.blocks[1] % 8);
|
|
seen[mdir->r.blocks[0] / 8] |= 1 << (mdir->r.blocks[0] % 8);
|
|
|
|
} else if (tag == LFS3_TAG_BRANCH) {
|
|
lfs3_rbyd_t *rbyd = (lfs3_rbyd_t*)bptr.d.u.buffer;
|
|
printf("traversal: 0x%x btree 0x%x.%x\n",
|
|
tag,
|
|
rbyd->blocks[0], rbyd->trunk);
|
|
|
|
// keep track of seen blocks
|
|
seen[rbyd->blocks[0] / 8] |= 1 << (rbyd->blocks[0] % 8);
|
|
|
|
} else {
|
|
// this shouldn't happen
|
|
printf("traversal: 0x%x\n", 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 (lfs3_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(lfs3.mtree.r.weight == (2 << lfs3.mbits));
|
|
// assert mroot now has no entries
|
|
assert(lfs3.mroot.r.weight == 0);
|
|
|
|
// assert that our entries are still in the mtree
|
|
lfs3_mtree_lookup(&lfs3, (0 << lfs3.mbits)+1, &mdir) => 0;
|
|
assert(mdir.r.weight == 2);
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_REG,
|
|
&data) => LFS3_TAG_REG;
|
|
lfs3_data_read(&lfs3, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'a');
|
|
|
|
lfs3_mtree_lookup(&lfs3, (1 << lfs3.mbits)+0, &mdir) => 0;
|
|
assert(mdir.r.weight == 1);
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_REG,
|
|
&data) => LFS3_TAG_REG;
|
|
lfs3_data_read(&lfs3, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'b');
|
|
|
|
// check things stay sane after remount
|
|
lfs3_unmount(&lfs3) => 0;
|
|
lfs3_mount(&lfs3, LFS3_M_RDWR | M_FLAGS, CFG) => 0;
|
|
|
|
// assert mdirs were unininlined and split
|
|
assert(lfs3.mtree.r.weight == (2 << lfs3.mbits));
|
|
// assert mroot now has no entries
|
|
assert(lfs3.mroot.r.weight == 0);
|
|
|
|
// assert that our entries are still in the mtree
|
|
lfs3_mtree_lookup(&lfs3, (0 << lfs3.mbits)+1, &mdir) => 0;
|
|
assert(mdir.r.weight == 2);
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_REG,
|
|
&data) => LFS3_TAG_REG;
|
|
lfs3_data_read(&lfs3, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'a');
|
|
|
|
lfs3_mtree_lookup(&lfs3, (1 << lfs3.mbits)+0, &mdir) => 0;
|
|
assert(mdir.r.weight == 1);
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_REG,
|
|
&data) => LFS3_TAG_REG;
|
|
lfs3_data_read(&lfs3, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'b');
|
|
|
|
lfs3_unmount(&lfs3) => 0;
|
|
'''
|
|
|
|
[cases.test_mtree_traversal_split]
|
|
defines.CKMETA = [false, true]
|
|
# this should be set so only one entry can fit in a metadata block
|
|
defines.SIZE = 'BLOCK_SIZE / 4'
|
|
in = 'lfs3.c'
|
|
code = '''
|
|
lfs3_t lfs3;
|
|
lfs3_format(&lfs3, LFS3_F_RDWR | F_FLAGS, CFG) => 0;
|
|
lfs3_mount(&lfs3, LFS3_M_RDWR | M_FLAGS, CFG) => 0;
|
|
lfs3_alloc_ckpoint(&lfs3);
|
|
lfs3_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);
|
|
lfs3_mdir_t mdir;
|
|
lfs3_mtree_namelookup(&lfs3, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL) => LFS3_ERR_NOENT;
|
|
lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS(
|
|
LFS3_RATTR_NAME(
|
|
LFS3_TAG_REG, +1,
|
|
0, (const char*)buffer+1, SIZE-1))) => 0;
|
|
assert(mdir.r.weight == 2);
|
|
|
|
memset(buffer+1, 'b', SIZE-1);
|
|
lfs3_mtree_namelookup(&lfs3, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL) => LFS3_ERR_NOENT;
|
|
lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS(
|
|
LFS3_RATTR_NAME(
|
|
LFS3_TAG_REG, +1,
|
|
0, (const char*)buffer+1, SIZE-1))) => 0;
|
|
assert(mdir.r.weight == 3);
|
|
|
|
// force mroot to compact
|
|
lfs3.mroot.r.eoff = -1;
|
|
lfs3_mdir_commit(&lfs3, &lfs3.mroot, NULL, 0) => 0;
|
|
|
|
// assert mdirs were unininlined and split
|
|
assert(lfs3.mtree.r.weight == (2 << lfs3.mbits));
|
|
// assert mroot now has no entries
|
|
assert(lfs3.mroot.r.weight == 0);
|
|
|
|
// now add another large entry to an mdir, forcing a split
|
|
memset(buffer+1, 'c', SIZE-1);
|
|
lfs3_mtree_namelookup(&lfs3, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL) => LFS3_ERR_NOENT;
|
|
lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS(
|
|
LFS3_RATTR_NAME(
|
|
LFS3_TAG_REG, +1,
|
|
0, (const char*)buffer+1, SIZE-1))) => 0;
|
|
assert(mdir.r.weight == 2);
|
|
|
|
// force mdir to compact
|
|
mdir.r.eoff = -1;
|
|
lfs3_mdir_commit(&lfs3, &mdir, NULL, 0) => 0;
|
|
|
|
// assert mdir was split correctly
|
|
assert(lfs3.mtree.r.weight == (3 << lfs3.mbits));
|
|
// assert mroot still has no entries
|
|
assert(lfs3.mroot.r.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);
|
|
|
|
lfs3_trv_t trv;
|
|
lfs3_trv_init(&trv,
|
|
LFS3_T_RDONLY
|
|
| LFS3_T_MTREEONLY
|
|
| ((CKMETA) ? LFS3_T_CKMETA : 0));
|
|
for (lfs3_block_t i = 0;; i++) {
|
|
// a bit hacky, but this catches infinite loops
|
|
assert(i < 2*BLOCK_COUNT);
|
|
|
|
lfs3_stag_t tag;
|
|
lfs3_bptr_t bptr;
|
|
tag = lfs3_mtree_traverse(&lfs3, &trv,
|
|
&bptr);
|
|
assert(tag >= 0 || tag == LFS3_ERR_NOENT);
|
|
if (tag == LFS3_ERR_NOENT) {
|
|
break;
|
|
}
|
|
|
|
if (tag == LFS3_TAG_MDIR) {
|
|
lfs3_mdir_t *mdir = (lfs3_mdir_t*)bptr.d.u.buffer;
|
|
printf("traversal: 0x%x mdir 0x{%x,%x}\n",
|
|
tag,
|
|
mdir->r.blocks[0],
|
|
mdir->r.blocks[1]);
|
|
|
|
// keep track of seen blocks
|
|
seen[mdir->r.blocks[1] / 8] |= 1 << (mdir->r.blocks[1] % 8);
|
|
seen[mdir->r.blocks[0] / 8] |= 1 << (mdir->r.blocks[0] % 8);
|
|
|
|
} else if (tag == LFS3_TAG_BRANCH) {
|
|
lfs3_rbyd_t *rbyd = (lfs3_rbyd_t*)bptr.d.u.buffer;
|
|
printf("traversal: 0x%x btree 0x%x.%x\n",
|
|
tag,
|
|
rbyd->blocks[0], rbyd->trunk);
|
|
|
|
// keep track of seen blocks
|
|
seen[rbyd->blocks[0] / 8] |= 1 << (rbyd->blocks[0] % 8);
|
|
|
|
} else {
|
|
// this shouldn't happen
|
|
printf("traversal: 0x%x\n", 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 (lfs3_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(lfs3.mtree.r.weight == (3 << lfs3.mbits));
|
|
// assert mroot still has no entries
|
|
assert(lfs3.mroot.r.weight == 0);
|
|
|
|
// assert that our entries are still in the mtree
|
|
lfs3_mtree_lookup(&lfs3, (0 << lfs3.mbits)+1, &mdir) => 0;
|
|
assert(mdir.r.weight == 2);
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_REG,
|
|
&data) => LFS3_TAG_REG;
|
|
lfs3_data_read(&lfs3, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'a');
|
|
|
|
lfs3_mtree_lookup(&lfs3, (1 << lfs3.mbits)+0, &mdir) => 0;
|
|
assert(mdir.r.weight == 1);
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_REG,
|
|
&data) => LFS3_TAG_REG;
|
|
lfs3_data_read(&lfs3, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'b');
|
|
|
|
lfs3_mtree_lookup(&lfs3, (2 << lfs3.mbits)+0, &mdir) => 0;
|
|
assert(mdir.r.weight == 1);
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_REG,
|
|
&data) => LFS3_TAG_REG;
|
|
lfs3_data_read(&lfs3, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'c');
|
|
|
|
// check things stay sane after remount
|
|
lfs3_unmount(&lfs3) => 0;
|
|
lfs3_mount(&lfs3, LFS3_M_RDWR | M_FLAGS, CFG) => 0;
|
|
|
|
// assert mdir was split correctly
|
|
assert(lfs3.mtree.r.weight == (3 << lfs3.mbits));
|
|
// assert mroot still has no entries
|
|
assert(lfs3.mroot.r.weight == 0);
|
|
|
|
// assert that our entries are still in the mtree
|
|
lfs3_mtree_lookup(&lfs3, (0 << lfs3.mbits)+1, &mdir) => 0;
|
|
assert(mdir.r.weight == 2);
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_REG,
|
|
&data) => LFS3_TAG_REG;
|
|
lfs3_data_read(&lfs3, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'a');
|
|
|
|
lfs3_mtree_lookup(&lfs3, (1 << lfs3.mbits)+0, &mdir) => 0;
|
|
assert(mdir.r.weight == 1);
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_REG,
|
|
&data) => LFS3_TAG_REG;
|
|
lfs3_data_read(&lfs3, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'b');
|
|
|
|
lfs3_mtree_lookup(&lfs3, (2 << lfs3.mbits)+0, &mdir) => 0;
|
|
assert(mdir.r.weight == 1);
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_REG,
|
|
&data) => LFS3_TAG_REG;
|
|
lfs3_data_read(&lfs3, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'c');
|
|
|
|
lfs3_unmount(&lfs3) => 0;
|
|
'''
|
|
|
|
[cases.test_mtree_traversal_extend]
|
|
defines.CKMETA = [false, true]
|
|
# make it so blocks relocate every two compacts
|
|
defines.BLOCK_RECYCLES = 0
|
|
in = 'lfs3.c'
|
|
code = '''
|
|
lfs3_t lfs3;
|
|
lfs3_format(&lfs3, LFS3_F_RDWR | F_FLAGS, CFG) => 0;
|
|
lfs3_mount(&lfs3, LFS3_M_RDWR | M_FLAGS, CFG) => 0;
|
|
lfs3_alloc_ckpoint(&lfs3);
|
|
|
|
// insert at least one entry
|
|
lfs3_mdir_t mdir;
|
|
lfs3_mtree_namelookup(&lfs3, 0, "a", 1,
|
|
&mdir, NULL) => LFS3_ERR_NOENT;
|
|
lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS(
|
|
LFS3_RATTR_NAME(LFS3_TAG_REG, +1, 0, "a", 1))) => 0;
|
|
assert(mdir.r.weight == 2);
|
|
|
|
// force mroot to compact twice, this should extend the mroot
|
|
lfs3_mdir_t old_mroot = lfs3.mroot;
|
|
lfs3.mroot.r.eoff = -1;
|
|
lfs3_mdir_commit(&lfs3, &lfs3.mroot, NULL, 0) => 0;
|
|
lfs3.mroot.r.eoff = -1;
|
|
lfs3_mdir_commit(&lfs3, &lfs3.mroot, NULL, 0) => 0;
|
|
// assert we relocated
|
|
assert(lfs3_mdir_cmp(&old_mroot, &lfs3.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);
|
|
|
|
lfs3_trv_t trv;
|
|
lfs3_trv_init(&trv,
|
|
LFS3_T_RDONLY
|
|
| LFS3_T_MTREEONLY
|
|
| ((CKMETA) ? LFS3_T_CKMETA : 0));
|
|
for (lfs3_block_t i = 0;; i++) {
|
|
// a bit hacky, but this catches infinite loops
|
|
assert(i < 2*BLOCK_COUNT);
|
|
|
|
lfs3_stag_t tag;
|
|
lfs3_bptr_t bptr;
|
|
tag = lfs3_mtree_traverse(&lfs3, &trv,
|
|
&bptr);
|
|
assert(tag >= 0 || tag == LFS3_ERR_NOENT);
|
|
if (tag == LFS3_ERR_NOENT) {
|
|
break;
|
|
}
|
|
|
|
if (tag == LFS3_TAG_MDIR) {
|
|
lfs3_mdir_t *mdir = (lfs3_mdir_t*)bptr.d.u.buffer;
|
|
printf("traversal: 0x%x mdir 0x{%x,%x}\n",
|
|
tag,
|
|
mdir->r.blocks[0],
|
|
mdir->r.blocks[1]);
|
|
|
|
// keep track of seen blocks
|
|
seen[mdir->r.blocks[1] / 8]
|
|
|= 1 << (mdir->r.blocks[1] % 8);
|
|
seen[mdir->r.blocks[0] / 8]
|
|
|= 1 << (mdir->r.blocks[0] % 8);
|
|
|
|
} else if (tag == LFS3_TAG_BRANCH) {
|
|
lfs3_rbyd_t *rbyd = (lfs3_rbyd_t*)bptr.d.u.buffer;
|
|
printf("traversal: 0x%x btree 0x%x.%x\n",
|
|
tag,
|
|
rbyd->blocks[0], rbyd->trunk);
|
|
|
|
// keep track of seen blocks
|
|
seen[rbyd->blocks[0] / 8]
|
|
|= 1 << (rbyd->blocks[0] % 8);
|
|
|
|
} else {
|
|
// this shouldn't happen
|
|
printf("traversal: 0x%x\n", 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 (lfs3_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);
|
|
lfs3_data_t data;
|
|
|
|
// and the tree should still work
|
|
|
|
// assert that our entry is still in the mtree
|
|
uint8_t buffer[256];
|
|
lfs3_mtree_namelookup(&lfs3, 0, "a", 1,
|
|
&mdir, NULL) => LFS3_TAG_REG;
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_REG,
|
|
&data) => LFS3_TAG_REG;
|
|
lfs3_data_read(&lfs3, &data, buffer, sizeof(buffer)) => 2;
|
|
assert(memcmp(buffer, "\0a", 2) == 0);
|
|
|
|
// check things stay sane after remount
|
|
lfs3_unmount(&lfs3) => 0;
|
|
lfs3_mount(&lfs3, LFS3_M_RDWR | M_FLAGS, CFG) => 0;
|
|
|
|
// assert that our entry is still in the mtree
|
|
lfs3_mtree_namelookup(&lfs3, 0, "a", 1,
|
|
&mdir, NULL) => LFS3_TAG_REG;
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_REG,
|
|
&data) => LFS3_TAG_REG;
|
|
lfs3_data_read(&lfs3, &data, buffer, sizeof(buffer)) => 2;
|
|
assert(memcmp(buffer, "\0a", 2) == 0);
|
|
|
|
lfs3_unmount(&lfs3) => 0;
|
|
'''
|
|
|
|
# larger traversal tests
|
|
[cases.test_mtree_traversal_many]
|
|
defines.N = [5, 10, 20, 40, 80, 160, 320]
|
|
defines.CKMETA = [false, true]
|
|
defines.FORCE_COMPACTION = [false, true]
|
|
in = 'lfs3.c'
|
|
code = '''
|
|
lfs3_t lfs3;
|
|
lfs3_format(&lfs3, LFS3_F_RDWR | F_FLAGS, CFG) => 0;
|
|
lfs3_mount(&lfs3, LFS3_M_RDWR | M_FLAGS, CFG) => 0;
|
|
lfs3_alloc_ckpoint(&lfs3);
|
|
|
|
// create entries
|
|
for (lfs3_size_t i = 0; i < N; i++) {
|
|
char name[256];
|
|
name[0] = '\0';
|
|
sprintf(name+1, "%03x", i);
|
|
lfs3_mdir_t mdir;
|
|
lfs3_mtree_namelookup(&lfs3, 0, name+1, 3,
|
|
&mdir, NULL) => LFS3_ERR_NOENT;
|
|
// force a compaction?
|
|
if (FORCE_COMPACTION) {
|
|
lfs3.mroot.r.eoff = -1;
|
|
mdir.r.eoff = -1;
|
|
}
|
|
lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS(
|
|
LFS3_RATTR_NAME(LFS3_TAG_REG, +1, 0, name+1, 3))) => 0;
|
|
lfs3_data_t data;
|
|
|
|
uint8_t buffer[256];
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_REG,
|
|
&data) => LFS3_TAG_REG;
|
|
lfs3_data_read(&lfs3, &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);
|
|
|
|
lfs3_trv_t trv;
|
|
lfs3_trv_init(&trv,
|
|
LFS3_T_RDONLY
|
|
| LFS3_T_MTREEONLY
|
|
| ((CKMETA) ? LFS3_T_CKMETA : 0));
|
|
for (lfs3_block_t i = 0;; i++) {
|
|
// a bit hacky, but this catches infinite loops
|
|
assert(i < 2*BLOCK_COUNT);
|
|
|
|
lfs3_stag_t tag;
|
|
lfs3_bptr_t bptr;
|
|
tag = lfs3_mtree_traverse(&lfs3, &trv,
|
|
&bptr);
|
|
assert(tag >= 0 || tag == LFS3_ERR_NOENT);
|
|
if (tag == LFS3_ERR_NOENT) {
|
|
break;
|
|
}
|
|
|
|
if (tag == LFS3_TAG_MDIR) {
|
|
lfs3_mdir_t *mdir = (lfs3_mdir_t*)bptr.d.u.buffer;
|
|
printf("traversal: 0x%x mdir 0x{%x,%x}\n",
|
|
tag,
|
|
mdir->r.blocks[0],
|
|
mdir->r.blocks[1]);
|
|
|
|
// keep track of seen blocks
|
|
seen[mdir->r.blocks[1] / 8]
|
|
|= 1 << (mdir->r.blocks[1] % 8);
|
|
seen[mdir->r.blocks[0] / 8]
|
|
|= 1 << (mdir->r.blocks[0] % 8);
|
|
|
|
} else if (tag == LFS3_TAG_BRANCH) {
|
|
lfs3_rbyd_t *rbyd = (lfs3_rbyd_t*)bptr.d.u.buffer;
|
|
printf("traversal: 0x%x btree 0x%x.%x\n",
|
|
tag,
|
|
rbyd->blocks[0], rbyd->trunk);
|
|
|
|
// keep track of seen blocks
|
|
seen[rbyd->blocks[0] / 8]
|
|
|= 1 << (rbyd->blocks[0] % 8);
|
|
|
|
} else {
|
|
// this shouldn't happen
|
|
printf("traversal: 0x%x\n", 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 (lfs3_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
|
|
lfs3_mdir_t mdir;
|
|
lfs3_mtree_lookup(&lfs3, 0, &mdir) => 0;
|
|
lfs3_data_t data;
|
|
uint8_t buffer[256];
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_BOOKMARK,
|
|
&data) => LFS3_TAG_BOOKMARK;
|
|
lfs3_data_read(&lfs3, &data, buffer, sizeof(buffer)) => 1;
|
|
for (lfs3_size_t i = 0; i < N; i++) {
|
|
char name[256];
|
|
name[0] = '\0';
|
|
sprintf(name+1, "%03x", i);
|
|
mdir.mid += 1;
|
|
if (lfs3_mrid(&lfs3, mdir.mid) >= (lfs3_srid_t)mdir.r.weight) {
|
|
lfs3_mtree_lookup(&lfs3, lfs3_mbid(&lfs3, mdir.mid) + 1,
|
|
&mdir) => 0;
|
|
}
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_REG,
|
|
&data) => LFS3_TAG_REG;
|
|
lfs3_data_read(&lfs3, &data, buffer, sizeof(buffer)) => 4;
|
|
assert(memcmp(buffer, name, 4) == 0);
|
|
}
|
|
|
|
// check things stay sane after remount
|
|
lfs3_unmount(&lfs3) => 0;
|
|
lfs3_mount(&lfs3, LFS3_M_RDWR | M_FLAGS, CFG) => 0;
|
|
|
|
// try looking up each entry
|
|
lfs3_mtree_lookup(&lfs3, 0, &mdir) => 0;
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_BOOKMARK,
|
|
&data) => LFS3_TAG_BOOKMARK;
|
|
lfs3_data_read(&lfs3, &data, buffer, sizeof(buffer)) => 1;
|
|
for (lfs3_size_t i = 0; i < N; i++) {
|
|
char name[256];
|
|
name[0] = '\0';
|
|
sprintf(name+1, "%03x", i);
|
|
mdir.mid += 1;
|
|
if (lfs3_mrid(&lfs3, mdir.mid) >= (lfs3_srid_t)mdir.r.weight) {
|
|
lfs3_mtree_lookup(&lfs3, lfs3_mbid(&lfs3, mdir.mid) + 1,
|
|
&mdir) => 0;
|
|
}
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_REG,
|
|
&data) => LFS3_TAG_REG;
|
|
lfs3_data_read(&lfs3, &data, buffer, sizeof(buffer)) => 4;
|
|
assert(memcmp(buffer, name, 4) == 0);
|
|
}
|
|
|
|
lfs3_unmount(&lfs3) => 0;
|
|
'''
|
|
|
|
[cases.test_mtree_traversal_fuzz]
|
|
defines.N = [5, 10, 20, 40, 80, 160]
|
|
defines.CKMETA = [false, true]
|
|
defines.FORCE_COMPACTION = [false, true]
|
|
defines.SEED = 'range(100)'
|
|
fuzz = 'SEED'
|
|
in = 'lfs3.c'
|
|
code = '''
|
|
lfs3_t lfs3;
|
|
lfs3_format(&lfs3, LFS3_F_RDWR | F_FLAGS, CFG) => 0;
|
|
lfs3_mount(&lfs3, LFS3_M_RDWR | M_FLAGS, CFG) => 0;
|
|
lfs3_alloc_ckpoint(&lfs3);
|
|
|
|
bool sim[N];
|
|
for (lfs3_size_t i = 0; i < N; i++) {
|
|
sim[i] = false;
|
|
}
|
|
|
|
uint32_t prng = SEED;
|
|
for (lfs3_size_t i = 0; i < N; i++) {
|
|
// choose a pseudo-random name
|
|
lfs3_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);
|
|
lfs3_mdir_t mdir;
|
|
lfs3_stag_t tag = lfs3_mtree_namelookup(&lfs3, 0, name+1, 3,
|
|
&mdir, NULL);
|
|
assert(tag >= 0 || tag == LFS3_ERR_NOENT);
|
|
if (tag != LFS3_ERR_NOENT) {
|
|
continue;
|
|
}
|
|
// force a compaction?
|
|
if (FORCE_COMPACTION) {
|
|
lfs3.mroot.r.eoff = -1;
|
|
mdir.r.eoff = -1;
|
|
}
|
|
lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS(
|
|
LFS3_RATTR_NAME(LFS3_TAG_REG, +1, 0, name+1, 3))) => 0;
|
|
lfs3_data_t data;
|
|
|
|
// double check
|
|
uint8_t buffer[256];
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_REG,
|
|
&data) => LFS3_TAG_REG;
|
|
lfs3_data_read(&lfs3, &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);
|
|
|
|
lfs3_trv_t trv;
|
|
lfs3_trv_init(&trv,
|
|
LFS3_T_RDONLY
|
|
| LFS3_T_MTREEONLY
|
|
| ((CKMETA) ? LFS3_T_CKMETA : 0));
|
|
for (lfs3_block_t i = 0;; i++) {
|
|
// a bit hacky, but this catches infinite loops
|
|
assert(i < 2*BLOCK_COUNT);
|
|
|
|
lfs3_stag_t tag;
|
|
lfs3_bptr_t bptr;
|
|
tag = lfs3_mtree_traverse(&lfs3, &trv,
|
|
&bptr);
|
|
assert(tag >= 0 || tag == LFS3_ERR_NOENT);
|
|
if (tag == LFS3_ERR_NOENT) {
|
|
break;
|
|
}
|
|
|
|
if (tag == LFS3_TAG_MDIR) {
|
|
lfs3_mdir_t *mdir = (lfs3_mdir_t*)bptr.d.u.buffer;
|
|
printf("traversal: 0x%x mdir 0x{%x,%x}\n",
|
|
tag,
|
|
mdir->r.blocks[0],
|
|
mdir->r.blocks[1]);
|
|
|
|
// keep track of seen blocks
|
|
seen[mdir->r.blocks[1] / 8]
|
|
|= 1 << (mdir->r.blocks[1] % 8);
|
|
seen[mdir->r.blocks[0] / 8]
|
|
|= 1 << (mdir->r.blocks[0] % 8);
|
|
|
|
} else if (tag == LFS3_TAG_BRANCH) {
|
|
lfs3_rbyd_t *rbyd = (lfs3_rbyd_t*)bptr.d.u.buffer;
|
|
printf("traversal: 0x%x btree 0x%x.%x\n",
|
|
tag,
|
|
rbyd->blocks[0], rbyd->trunk);
|
|
|
|
// keep track of seen blocks
|
|
seen[rbyd->blocks[0] / 8]
|
|
|= 1 << (rbyd->blocks[0] % 8);
|
|
|
|
} else {
|
|
// this shouldn't happen
|
|
printf("traversal: 0x%x\n", 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 (lfs3_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
|
|
lfs3_mdir_t mdir;
|
|
lfs3_mtree_lookup(&lfs3, 0, &mdir) => 0;
|
|
lfs3_data_t data;
|
|
uint8_t buffer[256];
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_BOOKMARK,
|
|
&data) => LFS3_TAG_BOOKMARK;
|
|
lfs3_data_read(&lfs3, &data, buffer, sizeof(buffer)) => 1;
|
|
for (lfs3_size_t i = 0; i < N; i++) {
|
|
char name[256];
|
|
name[0] = '\0';
|
|
sprintf(name+1, "%03x", i);
|
|
if (sim[i]) {
|
|
lfs3_mtree_namelookup(&lfs3, 0, name+1, 3,
|
|
&mdir, NULL) => LFS3_TAG_REG;
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_REG,
|
|
&data) => LFS3_TAG_REG;
|
|
lfs3_data_read(&lfs3, &data, buffer, sizeof(buffer)) => 4;
|
|
assert(memcmp(buffer, name, 4) == 0);
|
|
} else {
|
|
lfs3_mtree_namelookup(&lfs3, 0, name+1, 3,
|
|
&mdir, NULL) => LFS3_ERR_NOENT;
|
|
}
|
|
}
|
|
|
|
// check things stay sane after remount
|
|
lfs3_unmount(&lfs3) => 0;
|
|
lfs3_mount(&lfs3, LFS3_M_RDWR | M_FLAGS, CFG) => 0;
|
|
|
|
// try looking up each entry
|
|
lfs3_mtree_lookup(&lfs3, 0, &mdir) => 0;
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_BOOKMARK,
|
|
&data) => LFS3_TAG_BOOKMARK;
|
|
lfs3_data_read(&lfs3, &data, buffer, sizeof(buffer)) => 1;
|
|
for (lfs3_size_t i = 0; i < N; i++) {
|
|
char name[256];
|
|
name[0] = '\0';
|
|
sprintf(name+1, "%03x", i);
|
|
if (sim[i]) {
|
|
lfs3_mtree_namelookup(&lfs3, 0, name+1, 3,
|
|
&mdir, NULL) => LFS3_TAG_REG;
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_REG,
|
|
&data) => LFS3_TAG_REG;
|
|
lfs3_data_read(&lfs3, &data, buffer, sizeof(buffer)) => 4;
|
|
assert(memcmp(buffer, name, 4) == 0);
|
|
} else {
|
|
lfs3_mtree_namelookup(&lfs3, 0, name+1, 3,
|
|
&mdir, NULL) => LFS3_ERR_NOENT;
|
|
}
|
|
}
|
|
|
|
lfs3_unmount(&lfs3) => 0;
|
|
'''
|
|
|
|
|
|
## Cycle detection? ##
|
|
|
|
# test that our cycle detector at least works in common cases
|
|
[cases.test_mtree_traversal_mroot_cycle]
|
|
in = 'lfs3.c'
|
|
code = '''
|
|
lfs3_t lfs3;
|
|
lfs3_format(&lfs3, LFS3_F_RDWR | F_FLAGS, CFG) => 0;
|
|
lfs3_mount(&lfs3, LFS3_M_RDWR | M_FLAGS, CFG) => 0;
|
|
lfs3_alloc_ckpoint(&lfs3);
|
|
|
|
lfs3_mdir_commit(&lfs3, &lfs3.mroot, LFS3_RATTRS(
|
|
LFS3_RATTR_MPTR(
|
|
LFS3_TAG_MROOT, 0,
|
|
LFS3_MPTR_MROOTANCHOR()))) => 0;
|
|
|
|
// technically, cycle detection only needs to work when we're validating
|
|
lfs3_trv_t trv;
|
|
lfs3_trv_init(&trv,
|
|
LFS3_T_RDONLY | LFS3_T_MTREEONLY | LFS3_T_CKMETA);
|
|
for (lfs3_block_t i = 0;; i++) {
|
|
// assert that we detect the cycle in a reasonable number of iterations
|
|
assert(i < 2*BLOCK_COUNT);
|
|
|
|
lfs3_stag_t tag;
|
|
lfs3_bptr_t bptr;
|
|
tag = lfs3_mtree_traverse(&lfs3, &trv,
|
|
&bptr);
|
|
assert(tag >= 0 || tag == LFS3_ERR_CORRUPT);
|
|
if (tag == LFS3_ERR_CORRUPT) {
|
|
break;
|
|
}
|
|
|
|
if (tag == LFS3_TAG_MDIR) {
|
|
lfs3_mdir_t *mdir = (lfs3_mdir_t*)bptr.d.u.buffer;
|
|
printf("traversal: 0x%x mdir 0x{%x,%x}\n",
|
|
tag,
|
|
mdir->r.blocks[0],
|
|
mdir->r.blocks[1]);
|
|
|
|
} else if (tag == LFS3_TAG_BRANCH) {
|
|
lfs3_rbyd_t *rbyd = (lfs3_rbyd_t*)bptr.d.u.buffer;
|
|
printf("traversal: 0x%x btree 0x%x.%x\n",
|
|
tag,
|
|
rbyd->blocks[0], rbyd->trunk);
|
|
|
|
} else {
|
|
// this shouldn't happen
|
|
printf("traversal: 0x%x\n", tag);
|
|
assert(false);
|
|
}
|
|
}
|
|
|
|
lfs3_unmount(&lfs3) => 0;
|
|
'''
|
|
|
|
|
|
## Truncate mroot tests ##
|
|
|
|
# test some that some tricky truncated tags are rejected correctly
|
|
[cases.test_mtree_truncated_tag]
|
|
defines.OVERFLOW = [-3, -2, -1, 0, 1, 2, 3, 4, 5]
|
|
in = 'lfs3.c'
|
|
code = '''
|
|
// create a malformed mroot
|
|
uint8_t buffer[BLOCK_SIZE];
|
|
// fill with zeros to make parity checks easier
|
|
memset(buffer, 0, BLOCK_SIZE);
|
|
memcpy(&buffer[0], "evil", 4);
|
|
uint32_t cksum = lfs3_crc32c(0, &buffer[0], 4);
|
|
|
|
// make sure we're not caught by magic checks
|
|
buffer[4+0] = ((uint8_t)lfs3_parity(cksum) << 7)
|
|
| (uint8_t)(LFS3_TAG_MAGIC >> 8);
|
|
buffer[4+1] = (uint8_t)(LFS3_TAG_MAGIC >> 0);
|
|
buffer[4+2] = 0;
|
|
buffer[4+3] = 8;
|
|
memcpy(&buffer[4+4], "littlefs", 8);
|
|
cksum = lfs3_crc32c(cksum ^ ((uint32_t)lfs3_parity(cksum) << 7),
|
|
&buffer[4], 4+8);
|
|
|
|
// append a tag that overflows our block
|
|
lfs3_size_t size = BLOCK_SIZE - (16+7) + OVERFLOW;
|
|
buffer[16+0] = ((uint8_t)lfs3_parity(cksum) << 7)
|
|
| (uint8_t)(LFS3_TAG_ATTR >> 8);
|
|
buffer[16+1] = (uint8_t)(LFS3_TAG_ATTR >> 0);
|
|
buffer[16+2] = 0;
|
|
buffer[16+3] = 0x80 | (0x7f & (size >> 0));
|
|
buffer[16+4] = 0x80 | (0x7f & (size >> 7));
|
|
buffer[16+5] = 0x80 | (0x7f & (size >> 14));
|
|
buffer[16+6] = 0x00 | (0x7f & (size >> 21));
|
|
cksum = lfs3_crc32c(cksum ^ ((uint32_t)lfs3_parity(cksum) << 7),
|
|
&buffer[16], 7+size);
|
|
|
|
// make next tag look valid to make errors look more likely
|
|
if (OVERFLOW < 0) {
|
|
buffer[BLOCK_SIZE + OVERFLOW] = ((uint8_t)lfs3_parity(cksum) << 7);
|
|
}
|
|
|
|
// write to both mroot blocks
|
|
for (int i = 0; i < 2; i++) {
|
|
CFG->erase(CFG, i) => 0;
|
|
CFG->prog(CFG, i, 0, buffer, BLOCK_SIZE) => 0;
|
|
}
|
|
|
|
// try to mount, this should fail
|
|
lfs3_t lfs3;
|
|
lfs3_mount(&lfs3, LFS3_M_RDWR | M_FLAGS, CFG) => LFS3_ERR_CORRUPT;
|
|
'''
|
|
|
|
[cases.test_mtree_truncated_cksum]
|
|
defines.OVERFLOW = [1, 2, 3, 4]
|
|
defines.TRUNCATED_SIZE = [false, true]
|
|
in = 'lfs3.c'
|
|
code = '''
|
|
// create a malformed mroot
|
|
uint8_t buffer[BLOCK_SIZE];
|
|
// fill with zeros to make parity checks easier
|
|
memset(buffer, 0, BLOCK_SIZE);
|
|
memcpy(&buffer[0], "evil", 4);
|
|
uint32_t cksum = lfs3_crc32c(0, &buffer[0], 4);
|
|
|
|
// make sure we're not caught by magic checks
|
|
buffer[4+0] = ((uint8_t)lfs3_parity(cksum) << 7)
|
|
| (uint8_t)(LFS3_TAG_MAGIC >> 8);
|
|
buffer[4+1] = (uint8_t)(LFS3_TAG_MAGIC >> 0);
|
|
buffer[4+2] = 0;
|
|
buffer[4+3] = 8;
|
|
memcpy(&buffer[4+4], "littlefs", 8);
|
|
cksum = lfs3_crc32c(cksum ^ ((uint32_t)lfs3_parity(cksum) << 7),
|
|
&buffer[4], 4+8);
|
|
|
|
// append a tag for padding
|
|
lfs3_size_t size = BLOCK_SIZE - (16+7) - (7+4) + OVERFLOW;
|
|
buffer[16+0] = ((uint8_t)lfs3_parity(cksum) << 7)
|
|
| (uint8_t)(LFS3_TAG_ATTR >> 8);
|
|
buffer[16+1] = (uint8_t)(LFS3_TAG_ATTR >> 0);
|
|
buffer[16+2] = 0;
|
|
buffer[16+3] = 0x80 | (0x7f & (size >> 0));
|
|
buffer[16+4] = 0x80 | (0x7f & (size >> 7));
|
|
buffer[16+5] = 0x80 | (0x7f & (size >> 14));
|
|
buffer[16+6] = 0x00 | (0x7f & (size >> 21));
|
|
cksum = lfs3_crc32c(cksum ^ ((uint32_t)lfs3_parity(cksum) << 7),
|
|
&buffer[16], 7+size);
|
|
|
|
// append a truncated cksum tag
|
|
lfs3_off_t off = BLOCK_SIZE - (7+4) + OVERFLOW;
|
|
size = (TRUNCATED_SIZE) ? 4-OVERFLOW : 4;
|
|
buffer[off+0] = ((uint8_t)lfs3_parity(cksum) << 7)
|
|
| (uint8_t)(LFS3_TAG_CKSUM >> 8);
|
|
buffer[off+1] = (uint8_t)(LFS3_TAG_CKSUM >> 0);
|
|
buffer[off+2] = 0;
|
|
buffer[off+3] = 0x80 | (0x7f & (size >> 0));
|
|
buffer[off+4] = 0x80 | (0x7f & (size >> 7));
|
|
buffer[off+5] = 0x80 | (0x7f & (size >> 14));
|
|
buffer[off+6] = 0x00 | (0x7f & (size >> 21));
|
|
|
|
// write to both mroot blocks
|
|
for (int i = 0; i < 2; i++) {
|
|
CFG->erase(CFG, i) => 0;
|
|
CFG->prog(CFG, i, 0, buffer, BLOCK_SIZE) => 0;
|
|
}
|
|
|
|
// try to mount, this should fail
|
|
lfs3_t lfs3;
|
|
lfs3_mount(&lfs3, LFS3_M_RDWR | M_FLAGS, CFG) => LFS3_ERR_CORRUPT;
|
|
'''
|
|
|
|
[cases.test_mtree_truncated_ecksum]
|
|
defines.OVERFLOW = [-3, -2, -1, 0, 1, 2, 3, 4, 5]
|
|
defines.TRUNCATED_SIZE = [false, true]
|
|
in = 'lfs3.c'
|
|
code = '''
|
|
// create a malformed mroot
|
|
uint8_t buffer[BLOCK_SIZE];
|
|
// fill with zeros to make parity checks easier
|
|
memset(buffer, 0, BLOCK_SIZE);
|
|
memcpy(&buffer[0], "evil", 4);
|
|
uint32_t cksum = lfs3_crc32c(0, &buffer[0], 4);
|
|
|
|
// make sure we're not caught by magic checks
|
|
buffer[4+0] = ((uint8_t)lfs3_parity(cksum) << 7)
|
|
| (uint8_t)(LFS3_TAG_MAGIC >> 8);
|
|
buffer[4+1] = (uint8_t)(LFS3_TAG_MAGIC >> 0);
|
|
buffer[4+2] = 0;
|
|
buffer[4+3] = 8;
|
|
memcpy(&buffer[4+4], "littlefs", 8);
|
|
cksum = lfs3_crc32c(cksum ^ ((uint32_t)lfs3_parity(cksum) << 7),
|
|
&buffer[4], 4+8);
|
|
|
|
// append a tag for padding
|
|
lfs3_size_t size = BLOCK_SIZE - (16+7) - (7+5) + OVERFLOW;
|
|
buffer[16+0] = ((uint8_t)lfs3_parity(cksum) << 7)
|
|
| (uint8_t)(LFS3_TAG_ATTR >> 8);
|
|
buffer[16+1] = (uint8_t)(LFS3_TAG_ATTR >> 0);
|
|
buffer[16+2] = 0;
|
|
buffer[16+3] = 0x80 | (0x7f & (size >> 0));
|
|
buffer[16+4] = 0x80 | (0x7f & (size >> 7));
|
|
buffer[16+5] = 0x80 | (0x7f & (size >> 14));
|
|
buffer[16+6] = 0x00 | (0x7f & (size >> 21));
|
|
cksum = lfs3_crc32c(cksum ^ ((uint32_t)lfs3_parity(cksum) << 7),
|
|
&buffer[16], 7+size);
|
|
|
|
// append a truncated ecksum tag
|
|
lfs3_off_t off = BLOCK_SIZE - (7+5) + OVERFLOW;
|
|
size = (TRUNCATED_SIZE) ? 5-lfs3_smax(OVERFLOW, 0) : 5;
|
|
buffer[off+0] = ((uint8_t)lfs3_parity(cksum) << 7)
|
|
| (uint8_t)(LFS3_TAG_ECKSUM >> 8);
|
|
buffer[off+1] = (uint8_t)(LFS3_TAG_ECKSUM >> 0);
|
|
buffer[off+2] = 0;
|
|
buffer[off+3] = 0x80 | (0x7f & (size >> 0));
|
|
buffer[off+4] = 0x80 | (0x7f & (size >> 7));
|
|
buffer[off+5] = 0x80 | (0x7f & (size >> 14));
|
|
buffer[off+6] = 0x00 | (0x7f & (size >> 21));
|
|
cksum = lfs3_crc32c(cksum ^ ((uint32_t)lfs3_parity(cksum) << 7),
|
|
&buffer[16], 7+size);
|
|
|
|
// make next tag look valid to make errors look more likely
|
|
if (OVERFLOW < 0) {
|
|
buffer[BLOCK_SIZE + OVERFLOW] = ((uint8_t)lfs3_parity(cksum) << 7);
|
|
}
|
|
|
|
// write to both mroot blocks
|
|
for (int i = 0; i < 2; i++) {
|
|
CFG->erase(CFG, i) => 0;
|
|
CFG->prog(CFG, i, 0, buffer, BLOCK_SIZE) => 0;
|
|
}
|
|
|
|
// try to mount, this should fail
|
|
lfs3_t lfs3;
|
|
lfs3_mount(&lfs3, LFS3_M_RDWR | M_FLAGS, CFG) => LFS3_ERR_CORRUPT;
|
|
'''
|
|
|
|
[cases.test_mtree_truncated_gcksumdelta]
|
|
defines.OVERFLOW = [-3, -2, -1, 0, 1, 2, 3, 4]
|
|
defines.TRUNCATED_SIZE = [false, true]
|
|
in = 'lfs3.c'
|
|
code = '''
|
|
// create a malformed mroot
|
|
uint8_t buffer[BLOCK_SIZE];
|
|
// fill with zeros to make parity checks easier
|
|
memset(buffer, 0, BLOCK_SIZE);
|
|
memcpy(&buffer[0], "evil", 4);
|
|
uint32_t cksum = lfs3_crc32c(0, &buffer[0], 4);
|
|
|
|
// make sure we're not caught by magic checks
|
|
buffer[4+0] = ((uint8_t)lfs3_parity(cksum) << 7)
|
|
| (uint8_t)(LFS3_TAG_MAGIC >> 8);
|
|
buffer[4+1] = (uint8_t)(LFS3_TAG_MAGIC >> 0);
|
|
buffer[4+2] = 0;
|
|
buffer[4+3] = 8;
|
|
memcpy(&buffer[4+4], "littlefs", 8);
|
|
cksum = lfs3_crc32c(cksum ^ ((uint32_t)lfs3_parity(cksum) << 7),
|
|
&buffer[4], 4+8);
|
|
|
|
// append a tag for padding
|
|
lfs3_size_t size = BLOCK_SIZE - (16+7) - (7+4) + OVERFLOW;
|
|
buffer[16+0] = ((uint8_t)lfs3_parity(cksum) << 7)
|
|
| (uint8_t)(LFS3_TAG_ATTR >> 8);
|
|
buffer[16+1] = (uint8_t)(LFS3_TAG_ATTR >> 0);
|
|
buffer[16+2] = 0;
|
|
buffer[16+3] = 0x80 | (0x7f & (size >> 0));
|
|
buffer[16+4] = 0x80 | (0x7f & (size >> 7));
|
|
buffer[16+5] = 0x80 | (0x7f & (size >> 14));
|
|
buffer[16+6] = 0x00 | (0x7f & (size >> 21));
|
|
cksum = lfs3_crc32c(cksum ^ ((uint32_t)lfs3_parity(cksum) << 7),
|
|
&buffer[16], 7+size);
|
|
|
|
// append a truncated gcksumdelta tag
|
|
lfs3_off_t off = BLOCK_SIZE - (7+4) + OVERFLOW;
|
|
size = (TRUNCATED_SIZE) ? 4-lfs3_smax(OVERFLOW, 0) : 4;
|
|
buffer[off+0] = ((uint8_t)lfs3_parity(cksum) << 7)
|
|
| (uint8_t)(LFS3_TAG_GCKSUMDELTA >> 8);
|
|
buffer[off+1] = (uint8_t)(LFS3_TAG_GCKSUMDELTA >> 0);
|
|
buffer[off+2] = 0;
|
|
buffer[off+3] = 0x80 | (0x7f & (size >> 0));
|
|
buffer[off+4] = 0x80 | (0x7f & (size >> 7));
|
|
buffer[off+5] = 0x80 | (0x7f & (size >> 14));
|
|
buffer[off+6] = 0x00 | (0x7f & (size >> 21));
|
|
cksum = lfs3_crc32c(cksum ^ ((uint32_t)lfs3_parity(cksum) << 7),
|
|
&buffer[16], 7+size);
|
|
|
|
// make next tag look valid to make errors look more likely
|
|
if (OVERFLOW < 0) {
|
|
buffer[BLOCK_SIZE + OVERFLOW] = ((uint8_t)lfs3_parity(cksum) << 7);
|
|
}
|
|
|
|
// write to both mroot blocks
|
|
for (int i = 0; i < 2; i++) {
|
|
CFG->erase(CFG, i) => 0;
|
|
CFG->prog(CFG, i, 0, buffer, BLOCK_SIZE) => 0;
|
|
}
|
|
|
|
// try to mount, this should fail
|
|
lfs3_t lfs3;
|
|
lfs3_mount(&lfs3, LFS3_M_RDWR | M_FLAGS, CFG) => LFS3_ERR_CORRUPT;
|
|
'''
|
|
|
|
|
|
## Magic consistency ##
|
|
|
|
# make sure our magic string ("littlefs") shows up in the same place (off=8)
|
|
[cases.test_mtree_magic]
|
|
code = '''
|
|
lfs3_t lfs3;
|
|
lfs3_format(&lfs3, LFS3_F_RDWR | F_FLAGS, 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[LFS3_MAX(16, READ_SIZE)];
|
|
CFG->read(CFG, 0, 0, magic, LFS3_MAX(16, READ_SIZE)) => 0;
|
|
assert(memcmp(&magic[8], "littlefs", 8) == 0);
|
|
CFG->read(CFG, 1, 0, magic, LFS3_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_RECYCLES = 0
|
|
in = 'lfs3.c'
|
|
code = '''
|
|
lfs3_t lfs3;
|
|
lfs3_format(&lfs3, LFS3_F_RDWR | F_FLAGS, CFG) => 0;
|
|
lfs3_mount(&lfs3, LFS3_M_RDWR | M_FLAGS, CFG) => 0;
|
|
lfs3_alloc_ckpoint(&lfs3);
|
|
lfs3_data_t data;
|
|
|
|
// prepare mroot with an entry
|
|
uint8_t buffer[SIZE];
|
|
buffer[0] = '\0';
|
|
memset(buffer+1, 'a', SIZE-1);
|
|
lfs3_mdir_t mdir;
|
|
lfs3_mtree_namelookup(&lfs3, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL) => LFS3_ERR_NOENT;
|
|
lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS(
|
|
LFS3_RATTR_NAME(
|
|
LFS3_TAG_REG, +1,
|
|
0, (const char*)buffer+1, SIZE-1))) => 0;
|
|
assert(mdir.r.weight == 2);
|
|
|
|
// force mroot to compact twice, this should extend the mroot
|
|
lfs3_mdir_t old_mroot = lfs3.mroot;
|
|
lfs3.mroot.r.eoff = -1;
|
|
lfs3_mdir_commit(&lfs3, &lfs3.mroot, NULL, 0) => 0;
|
|
lfs3.mroot.r.eoff = -1;
|
|
lfs3_mdir_commit(&lfs3, &lfs3.mroot, NULL, 0) => 0;
|
|
// assert we relocated
|
|
assert(lfs3_mdir_cmp(&old_mroot, &lfs3.mroot) != 0);
|
|
|
|
// assert that our entry is still in the mroot
|
|
lfs3_mtree_lookup(&lfs3, (0 << lfs3.mbits)+1, &mdir) => 0;
|
|
assert(mdir.r.weight == 2);
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_REG,
|
|
&data) => LFS3_TAG_REG;
|
|
lfs3_data_read(&lfs3, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'a');
|
|
|
|
lfs3_unmount(&lfs3) => 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[LFS3_MAX(16, READ_SIZE)];
|
|
CFG->read(CFG, 0, 0, magic, LFS3_MAX(16, READ_SIZE)) => 0;
|
|
assert(memcmp(&magic[8], "littlefs", 8) == 0);
|
|
CFG->read(CFG, 1, 0, magic, LFS3_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_RECYCLES = 0
|
|
# 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 = 'lfs3.c'
|
|
code = '''
|
|
lfs3_t lfs3;
|
|
lfs3_format(&lfs3, LFS3_F_RDWR | F_FLAGS, CFG) => 0;
|
|
lfs3_mount(&lfs3, LFS3_M_RDWR | M_FLAGS, CFG) => 0;
|
|
lfs3_alloc_ckpoint(&lfs3);
|
|
lfs3_data_t data;
|
|
|
|
// prepare mroot with an entry
|
|
uint8_t buffer[SIZE];
|
|
buffer[0] = '\0';
|
|
memset(buffer+1, 'a', SIZE-1);
|
|
lfs3_mdir_t mdir;
|
|
lfs3_mtree_namelookup(&lfs3, 0, (const char*)buffer+1, SIZE-1,
|
|
&mdir, NULL) => LFS3_ERR_NOENT;
|
|
lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS(
|
|
LFS3_RATTR_NAME(
|
|
LFS3_TAG_REG, +1,
|
|
0, (const char*)buffer+1, SIZE-1))) => 0;
|
|
assert(mdir.r.weight == 2);
|
|
|
|
// force mroot to compact twice, this should extend the mroot
|
|
lfs3_mdir_t old_mroot = lfs3.mroot;
|
|
lfs3.mroot.r.eoff = -1;
|
|
lfs3_mdir_commit(&lfs3, &lfs3.mroot, NULL, 0) => 0;
|
|
lfs3.mroot.r.eoff = -1;
|
|
lfs3_mdir_commit(&lfs3, &lfs3.mroot, NULL, 0) => 0;
|
|
// assert we relocated
|
|
assert(lfs3_mdir_cmp(&old_mroot, &lfs3.mroot) != 0);
|
|
|
|
// force mroot to compact four times, this should relocate the mroot
|
|
// twice, forcing a second mroot extension
|
|
old_mroot = lfs3.mroot;
|
|
lfs3.mroot.r.eoff = -1;
|
|
lfs3_mdir_commit(&lfs3, &lfs3.mroot, NULL, 0) => 0;
|
|
lfs3.mroot.r.eoff = -1;
|
|
lfs3_mdir_commit(&lfs3, &lfs3.mroot, NULL, 0) => 0;
|
|
lfs3.mroot.r.eoff = -1;
|
|
lfs3_mdir_commit(&lfs3, &lfs3.mroot, NULL, 0) => 0;
|
|
lfs3.mroot.r.eoff = -1;
|
|
lfs3_mdir_commit(&lfs3, &lfs3.mroot, NULL, 0) => 0;
|
|
// assert we relocated
|
|
assert(lfs3_mdir_cmp(&old_mroot, &lfs3.mroot) != 0);
|
|
|
|
// assert that our rattr is still in the mroot
|
|
lfs3_mtree_lookup(&lfs3, (0 << lfs3.mbits)+1, &mdir) => 0;
|
|
assert(mdir.r.weight == 2);
|
|
lfs3_mdir_lookup(&lfs3, &mdir, LFS3_TAG_REG,
|
|
&data) => LFS3_TAG_REG;
|
|
lfs3_data_read(&lfs3, &data, buffer, SIZE) => SIZE;
|
|
assert(buffer[1] == 'a');
|
|
|
|
lfs3_unmount(&lfs3) => 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[LFS3_MAX(16, READ_SIZE)];
|
|
CFG->read(CFG, 0, 0, magic, LFS3_MAX(16, READ_SIZE)) => 0;
|
|
assert(memcmp(&magic[8], "littlefs", 8) == 0);
|
|
CFG->read(CFG, 1, 0, magic, LFS3_MAX(16, READ_SIZE)) => 0;
|
|
assert(memcmp(&magic[8], "littlefs", 8) == 0);
|
|
'''
|