Added some more mtree tests, fixed mroot extension bug
- Finally figured out how to test multiple mroot extensions without an allocator, though hopefully forcing PROG_SIZE doesn't break test framework things at some point... - Added tests that magic string is always in the same place. This isn't strictly required for littlefs to work, but is a nice feature to have. Of course, the new tests found a bug, but it was in a surprisingly place. Accidentally allowed the revision count to be uninitialized when compacting the mroot. At least there's a test that covers this now.
This commit is contained in:
@@ -4846,41 +4846,40 @@ static int lfsr_mdir_compact_(lfs_t *lfs, lfsr_mdir_t *mdir,
|
||||
// - mid = manchor => never alloc (mroot anchor)
|
||||
// - mid = wl => only alloc if mdir is tired (wear-leveling)
|
||||
// - otherwise => always alloc, use this mid (new mdir)
|
||||
|
||||
// first thing we need to do is read our current revision count
|
||||
uint32_t rev;
|
||||
if (mid != LFSR_MID_MANCHOR) {
|
||||
// first thing we need to do is read our current revision count
|
||||
int err = lfsr_bd_read(lfs, source->rbyd.block, 0, sizeof(uint32_t),
|
||||
int err = lfsr_bd_read(lfs, source->rbyd.block, 0, sizeof(uint32_t),
|
||||
&rev, sizeof(uint32_t));
|
||||
if (err && err != LFS_ERR_CORRUPT) {
|
||||
return err;
|
||||
}
|
||||
// note we allow corrupt errors here, as long as they are consistent
|
||||
rev = (err != LFS_ERR_CORRUPT ? lfs_fromle32_(&rev) : 0);
|
||||
|
||||
// decide if we need to relocate
|
||||
if (mid != LFSR_MID_MANCHOR && (mid != LFSR_MID_WL || (
|
||||
lfs->cfg->block_cycles > 0
|
||||
// TODO rev things
|
||||
&& (rev + 1) % lfs->cfg->block_cycles == 0))) {
|
||||
// allocate a new mdir for relocation
|
||||
err = lfsr_mdir_alloc(lfs, mdir,
|
||||
(mid != LFSR_MID_WL ? mid : mdir->mid));
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
// read the new revision count
|
||||
//
|
||||
// we use whatever is on-disk to avoid needing to rewrite the
|
||||
// redund block
|
||||
err = lfsr_bd_read(lfs, mdir->rbyd.block, 0, sizeof(uint32_t),
|
||||
&rev, sizeof(uint32_t));
|
||||
if (err && err != LFS_ERR_CORRUPT) {
|
||||
return err;
|
||||
}
|
||||
// note we allow corrupt errors here, as long as they are consistent
|
||||
rev = (err != LFS_ERR_CORRUPT ? lfs_fromle32_(&rev) : 0);
|
||||
|
||||
// decide if we need to relocate
|
||||
if (mid != LFSR_MID_WL || (
|
||||
lfs->cfg->block_cycles > 0
|
||||
// TODO rev things
|
||||
&& (rev + 1) % lfs->cfg->block_cycles == 0)) {
|
||||
// allocate a new mdir for relocation
|
||||
err = lfsr_mdir_alloc(lfs, mdir,
|
||||
(mid != LFSR_MID_WL ? mid : mdir->mid));
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
// read the new revision count
|
||||
//
|
||||
// we use whatever is on-disk to avoid needing to rewrite the
|
||||
// redund block
|
||||
err = lfsr_bd_read(lfs, mdir->rbyd.block, 0, sizeof(uint32_t),
|
||||
&rev, sizeof(uint32_t));
|
||||
if (err && err != LFS_ERR_CORRUPT) {
|
||||
return err;
|
||||
}
|
||||
// note we allow corrupt errors here, as long as they are consistent
|
||||
rev = (err != LFS_ERR_CORRUPT ? lfs_fromle32_(&rev) : 0);
|
||||
}
|
||||
}
|
||||
|
||||
// swap our rbyds
|
||||
@@ -4892,7 +4891,7 @@ static int lfsr_mdir_compact_(lfs_t *lfs, lfsr_mdir_t *mdir,
|
||||
mdir->rbyd.crc = 0;
|
||||
|
||||
// erase, preparing for compact
|
||||
int err = lfsr_bd_erase(lfs, mdir->rbyd.block);
|
||||
err = lfsr_bd_erase(lfs, mdir->rbyd.block);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
@@ -1787,6 +1787,64 @@ code = '''
|
||||
lfsr_unmount(&lfs) => 0;
|
||||
'''
|
||||
|
||||
[cases.test_mtree_extend_twice]
|
||||
# this should be set so only one entry can fit in a metadata block
|
||||
defines.SIZE = 'BLOCK_SIZE / 4'
|
||||
# make it so blocks relocate every two compacts
|
||||
defines.BLOCK_CYCLES = 2
|
||||
# force our block to compact by setting prog_size=block_size, we don't have
|
||||
# any way to indirectly force the intermediary mroots to compact otherwise
|
||||
defines.PROG_SIZE = 'BLOCK_SIZE'
|
||||
in = 'lfs.c'
|
||||
code = '''
|
||||
const char *alphas = "abcdefghijklmnopqrstuvwxyz";
|
||||
lfs_t lfs;
|
||||
lfsr_format(&lfs, cfg) => 0;
|
||||
lfsr_mount(&lfs, cfg) => 0;
|
||||
|
||||
// prepare mroot with an attr
|
||||
uint8_t buffer[SIZE];
|
||||
memset(buffer, alphas[0 % 26], SIZE);
|
||||
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS(
|
||||
LFSR_ATTR(-1, UATTR(1), 0, buffer, SIZE))) => 0;
|
||||
|
||||
// force mroot to compact 2x2 times, this should extend the mroot twice
|
||||
lfsr_mdir_t old_mroot = lfs.mroot;
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
lfs.mroot.rbyd.off = BLOCK_SIZE;
|
||||
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, NULL, 0) => 0;
|
||||
}
|
||||
lfs.mroot.rbyd.off = BLOCK_SIZE;
|
||||
memset(buffer, alphas[1 % 26], SIZE);
|
||||
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS(
|
||||
LFSR_ATTR(-1, UATTR(1), 0, buffer, SIZE))) => 0;
|
||||
|
||||
// assert we relocated
|
||||
assert(!lfsr_mdir_eq(&old_mroot, &lfs.mroot));
|
||||
|
||||
// assert that our attr is still in the mroot
|
||||
lfsr_mdir_get(&lfs, &lfs.mroot, -1, LFSR_TAG_UATTR(1),
|
||||
buffer, SIZE) => SIZE;
|
||||
assert(memcmp(buffer, &alphas[1 % 26], 1) == 0);
|
||||
|
||||
lfsr_unmount(&lfs) => 0;
|
||||
|
||||
|
||||
// check things stay sane after remount
|
||||
lfsr_mount(&lfs, cfg) => 0;
|
||||
|
||||
// assert we relocated
|
||||
assert(!lfsr_mdir_eq(&old_mroot, &lfs.mroot));
|
||||
|
||||
// assert that our attr is still in the mroot
|
||||
lfsr_mdir_get(&lfs, &lfs.mroot, -1, LFSR_TAG_UATTR(1),
|
||||
buffer, SIZE) => SIZE;
|
||||
assert(memcmp(buffer, &alphas[1 % 26], 1) == 0);
|
||||
|
||||
lfsr_unmount(&lfs) => 0;
|
||||
'''
|
||||
|
||||
[cases.test_mtree_relocate_mroot]
|
||||
# this should be set so only one entry can fit in a metadata block
|
||||
defines.SIZE = 'BLOCK_SIZE / 4'
|
||||
@@ -3802,3 +3860,133 @@ code = '''
|
||||
lfsr_unmount(&lfs) => 0;
|
||||
'''
|
||||
|
||||
|
||||
## Magic consistency ##
|
||||
|
||||
# make sure our magic string ("littlefs") shows up in the same place (off=8)
|
||||
[cases.test_mtree_magic]
|
||||
# this should be set so only one entry can fit in a metadata block
|
||||
defines.SIZE = 'BLOCK_SIZE / 4'
|
||||
code = '''
|
||||
lfs_t lfs;
|
||||
lfsr_format(&lfs, cfg) => 0;
|
||||
|
||||
// check our magic string
|
||||
//
|
||||
// note if we lose power we may not have the magic string in both blocks!
|
||||
// but we don't lose power in this test so we can assert the magic string
|
||||
// is present in both
|
||||
uint8_t magic[lfs_max(16, READ_SIZE)];
|
||||
cfg->read(cfg, 0, 0, magic, lfs_max(16, READ_SIZE)) => 0;
|
||||
assert(memcmp(&magic[8], "littlefs", 8) == 0);
|
||||
cfg->read(cfg, 1, 0, magic, lfs_max(16, READ_SIZE)) => 0;
|
||||
assert(memcmp(&magic[8], "littlefs", 8) == 0);
|
||||
'''
|
||||
|
||||
[cases.test_mtree_magic_extend]
|
||||
# this should be set so only one entry can fit in a metadata block
|
||||
defines.SIZE = 'BLOCK_SIZE / 4'
|
||||
# make it so blocks relocate every two compacts
|
||||
defines.BLOCK_CYCLES = 2
|
||||
in = 'lfs.c'
|
||||
code = '''
|
||||
const char *alphas = "abcdefghijklmnopqrstuvwxyz";
|
||||
lfs_t lfs;
|
||||
lfsr_format(&lfs, cfg) => 0;
|
||||
lfsr_mount(&lfs, cfg) => 0;
|
||||
|
||||
// prepare mroot with an attr
|
||||
uint8_t buffer[SIZE];
|
||||
memset(buffer, alphas[0 % 26], SIZE);
|
||||
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS(
|
||||
LFSR_ATTR(-1, UATTR(1), 0, buffer, SIZE))) => 0;
|
||||
|
||||
// force mroot to compact twice, this should extend the mroot
|
||||
lfsr_mdir_t old_mroot = lfs.mroot;
|
||||
|
||||
lfs.mroot.rbyd.off = BLOCK_SIZE;
|
||||
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, NULL, 0) => 0;
|
||||
lfs.mroot.rbyd.off = BLOCK_SIZE;
|
||||
memset(buffer, alphas[1 % 26], SIZE);
|
||||
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS(
|
||||
LFSR_ATTR(-1, UATTR(1), 0, buffer, SIZE))) => 0;
|
||||
|
||||
// assert we relocated
|
||||
assert(!lfsr_mdir_eq(&old_mroot, &lfs.mroot));
|
||||
|
||||
// assert that our attr is still in the mroot
|
||||
lfsr_mdir_get(&lfs, &lfs.mroot, -1, LFSR_TAG_UATTR(1),
|
||||
buffer, SIZE) => SIZE;
|
||||
assert(memcmp(buffer, &alphas[1 % 26], 1) == 0);
|
||||
|
||||
lfsr_unmount(&lfs) => 0;
|
||||
|
||||
|
||||
// check our magic string
|
||||
//
|
||||
// note if we lose power we may not have the magic string in both blocks!
|
||||
// but we don't lose power in this test so we can assert the magic string
|
||||
// is present in both
|
||||
uint8_t magic[lfs_max(16, READ_SIZE)];
|
||||
cfg->read(cfg, 0, 0, magic, lfs_max(16, READ_SIZE)) => 0;
|
||||
assert(memcmp(&magic[8], "littlefs", 8) == 0);
|
||||
cfg->read(cfg, 1, 0, magic, lfs_max(16, READ_SIZE)) => 0;
|
||||
assert(memcmp(&magic[8], "littlefs", 8) == 0);
|
||||
'''
|
||||
|
||||
[cases.test_mtree_magic_extend_twice]
|
||||
# this should be set so only one entry can fit in a metadata block
|
||||
defines.SIZE = 'BLOCK_SIZE / 4'
|
||||
# make it so blocks relocate every two compacts
|
||||
defines.BLOCK_CYCLES = 2
|
||||
# force our block to compact by setting prog_size=block_size, we don't have
|
||||
# any way to indirectly force the intermediary mroots to compact otherwise
|
||||
defines.PROG_SIZE = 'BLOCK_SIZE'
|
||||
in = 'lfs.c'
|
||||
code = '''
|
||||
const char *alphas = "abcdefghijklmnopqrstuvwxyz";
|
||||
lfs_t lfs;
|
||||
lfsr_format(&lfs, cfg) => 0;
|
||||
lfsr_mount(&lfs, cfg) => 0;
|
||||
|
||||
// prepare mroot with an attr
|
||||
uint8_t buffer[SIZE];
|
||||
memset(buffer, alphas[0 % 26], SIZE);
|
||||
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS(
|
||||
LFSR_ATTR(-1, UATTR(1), 0, buffer, SIZE))) => 0;
|
||||
|
||||
// force mroot to compact 2x2 times, this should extend the mroot twice
|
||||
lfsr_mdir_t old_mroot = lfs.mroot;
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
lfs.mroot.rbyd.off = BLOCK_SIZE;
|
||||
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, NULL, 0) => 0;
|
||||
}
|
||||
lfs.mroot.rbyd.off = BLOCK_SIZE;
|
||||
memset(buffer, alphas[1 % 26], SIZE);
|
||||
lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS(
|
||||
LFSR_ATTR(-1, UATTR(1), 0, buffer, SIZE))) => 0;
|
||||
|
||||
// assert we relocated
|
||||
assert(!lfsr_mdir_eq(&old_mroot, &lfs.mroot));
|
||||
|
||||
// assert that our attr is still in the mroot
|
||||
lfsr_mdir_get(&lfs, &lfs.mroot, -1, LFSR_TAG_UATTR(1),
|
||||
buffer, SIZE) => SIZE;
|
||||
assert(memcmp(buffer, &alphas[1 % 26], 1) == 0);
|
||||
|
||||
lfsr_unmount(&lfs) => 0;
|
||||
|
||||
|
||||
// check our magic string
|
||||
//
|
||||
// note if we lose power we may not have the magic string in both blocks!
|
||||
// but we don't lose power in this test so we can assert the magic string
|
||||
// is present in both
|
||||
uint8_t magic[lfs_max(16, READ_SIZE)];
|
||||
cfg->read(cfg, 0, 0, magic, lfs_max(16, READ_SIZE)) => 0;
|
||||
assert(memcmp(&magic[8], "littlefs", 8) == 0);
|
||||
cfg->read(cfg, 1, 0, magic, lfs_max(16, READ_SIZE)) => 0;
|
||||
assert(memcmp(&magic[8], "littlefs", 8) == 0);
|
||||
'''
|
||||
|
||||
|
||||
Reference in New Issue
Block a user