726cccfe76
There's really no reason to immediately commit the bmap to disk, at
least no until the first mdir commit, when we need to at least discard
the previous bmap state.
We already do all the gstate handling in lfs3_mdir_commit anyways, and
piggybacking on mdir commit lets us get rid of the annoying extra mdir
param in lfs3_alloc_ckpoint.
This does mean a slightly higher risk of needing to re-rebuild the bmap
after a powerloss, but in theory only if the user does something weird
like writing to a file and never calling sync. Most on-disk operations
terminate in an mdir commit as that's how any state change becomes
atomically visibile in littlefs.
Saves a nice bit of stack:
code stack ctx
before: 36920 2368 684
after: 36920 (+0.0%) 2368 (+0.0%) 684 (+0.0%)
code stack ctx
bmap before: 38552 2472 812
bmap after: 38464 (-0.2%) 2400 (-2.9%) 812 (+0.0%)
812 lines
24 KiB
TOML
812 lines
24 KiB
TOML
# Tests covering properties of the block allocator
|
|
|
|
# The ordering of these tests vs higher-level tests (files/dirs/etc) gets
|
|
# a bit weird because there is an inherent cyclic dependency
|
|
#
|
|
# It's counter-intuitive, but we run the alloc tests _after_ file/dir tests,
|
|
# since you can usually ignore allocator issues temporarily by making the test
|
|
# device really big
|
|
#
|
|
after = ['test_mtree', 'test_bmap', 'test_dirs', 'test_files']
|
|
|
|
# TODO bmap workaround?
|
|
ifndef = 'LFS3_BMAP'
|
|
|
|
# test that we can alloc
|
|
[cases.test_alloc_alloc]
|
|
defines.COUNT = [
|
|
'BLOCK_COUNT',
|
|
'BLOCK_COUNT-1',
|
|
'BLOCK_COUNT/2',
|
|
'BLOCK_COUNT/4',
|
|
'5',
|
|
'2',
|
|
]
|
|
defines.ERASE = [false, true]
|
|
in = 'lfs3.c'
|
|
code = '''
|
|
// test various block counts
|
|
struct lfs3_cfg cfg = *CFG;
|
|
cfg.block_count = COUNT;
|
|
lfs3_t lfs3;
|
|
lfs3_format(&lfs3, LFS3_F_RDWR, &cfg) => 0;
|
|
lfs3_mount(&lfs3, LFS3_M_RDWR, &cfg) => 0;
|
|
|
|
// start allocating
|
|
lfs3_alloc_ckpoint(&lfs3);
|
|
lfs3_size_t alloced = 0;
|
|
while (true) {
|
|
lfs3_sblock_t block = lfs3_alloc(&lfs3, ERASE);
|
|
assert(block >= 0 || block == LFS3_ERR_NOSPC);
|
|
|
|
if (block == LFS3_ERR_NOSPC) {
|
|
break;
|
|
}
|
|
alloced += 1;
|
|
|
|
// our allocator should stop at some point...
|
|
assert(alloced < 2*COUNT);
|
|
}
|
|
|
|
// excluding our mroot, we should have allocated exactly
|
|
// block_count-2 blocks
|
|
printf("alloced %d/%d blocks\n", alloced, (lfs3_block_t)COUNT);
|
|
assert(alloced == COUNT-2);
|
|
|
|
lfs3_unmount(&lfs3) => 0;
|
|
'''
|
|
|
|
# test that we can realloc after an ack
|
|
[cases.test_alloc_reuse]
|
|
defines.COUNT = [
|
|
'BLOCK_COUNT',
|
|
'BLOCK_COUNT-1',
|
|
'BLOCK_COUNT/2',
|
|
'BLOCK_COUNT/4',
|
|
'5',
|
|
'2',
|
|
]
|
|
defines.ERASE = [false, true]
|
|
in = 'lfs3.c'
|
|
code = '''
|
|
// test various block counts
|
|
struct lfs3_cfg cfg = *CFG;
|
|
cfg.block_count = COUNT;
|
|
lfs3_t lfs3;
|
|
lfs3_format(&lfs3, LFS3_F_RDWR, &cfg) => 0;
|
|
lfs3_mount(&lfs3, LFS3_M_RDWR, &cfg) => 0;
|
|
|
|
// start allocating
|
|
lfs3_alloc_ckpoint(&lfs3);
|
|
lfs3_size_t alloced = 0;
|
|
while (true) {
|
|
lfs3_sblock_t block = lfs3_alloc(&lfs3, ERASE);
|
|
assert(block >= 0 || block == LFS3_ERR_NOSPC);
|
|
|
|
if (block == LFS3_ERR_NOSPC) {
|
|
break;
|
|
}
|
|
alloced += 1;
|
|
|
|
// our allocator should stop at some point...
|
|
assert(alloced < 2*COUNT);
|
|
}
|
|
|
|
// excluding our mroot, we should have allocated exactly
|
|
// block_count-2 blocks
|
|
printf("alloced %d/%d blocks\n", alloced, (lfs3_block_t)COUNT);
|
|
assert(alloced == COUNT-2);
|
|
|
|
// ack again, effectively releasing all the previously alloced blocks
|
|
lfs3_alloc_ckpoint(&lfs3);
|
|
alloced = 0;
|
|
while (true) {
|
|
lfs3_sblock_t block = lfs3_alloc(&lfs3, ERASE);
|
|
assert(block >= 0 || block == LFS3_ERR_NOSPC);
|
|
|
|
if (block == LFS3_ERR_NOSPC) {
|
|
break;
|
|
}
|
|
alloced += 1;
|
|
|
|
// our allocator should stop at some point...
|
|
assert(alloced < 2*COUNT);
|
|
}
|
|
|
|
// excluding our mroot, we should have allocated exactly
|
|
// block_count-2 blocks
|
|
printf("alloced %d/%d blocks\n", alloced, (lfs3_block_t)COUNT);
|
|
assert(alloced == COUNT-2);
|
|
|
|
lfs3_unmount(&lfs3) => 0;
|
|
'''
|
|
|
|
|
|
# clobber tests test that our traversal algorithm works
|
|
[cases.test_alloc_clobber_dirs]
|
|
defines.N = [1, 2, 4, 8, 16, 32, 64, 128, 256, 512]
|
|
defines.CKMETA = [false, true]
|
|
defines.REMOUNT = [false, true]
|
|
in = 'lfs3.c'
|
|
code = '''
|
|
lfs3_t lfs3;
|
|
lfs3_format(&lfs3, LFS3_F_RDWR, CFG) => 0;
|
|
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0;
|
|
|
|
// create this many directories
|
|
for (lfs3_size_t i = 0; i < N; i++) {
|
|
char name[256];
|
|
sprintf(name, "dir%03x", i);
|
|
lfs3_mkdir(&lfs3, name) => 0;
|
|
}
|
|
|
|
// check that our mkdir worked
|
|
for (lfs3_size_t i = 0; i < N; i++) {
|
|
char name[256];
|
|
sprintf(name, "dir%03x", i);
|
|
struct lfs3_info info;
|
|
lfs3_stat(&lfs3, name, &info) => 0;
|
|
assert(strcmp(info.name, name) == 0);
|
|
assert(info.type == LFS3_TYPE_DIR);
|
|
assert(info.size == 0);
|
|
}
|
|
|
|
lfs3_dir_t dir;
|
|
lfs3_dir_open(&lfs3, &dir, "/") => 0;
|
|
struct lfs3_info info;
|
|
lfs3_dir_read(&lfs3, &dir, &info) => 0;
|
|
assert(strcmp(info.name, ".") == 0);
|
|
assert(info.type == LFS3_TYPE_DIR);
|
|
assert(info.size == 0);
|
|
lfs3_dir_read(&lfs3, &dir, &info) => 0;
|
|
assert(strcmp(info.name, "..") == 0);
|
|
assert(info.type == LFS3_TYPE_DIR);
|
|
assert(info.size == 0);
|
|
for (lfs3_size_t i = 0; i < N; i++) {
|
|
char name[256];
|
|
sprintf(name, "dir%03x", i);
|
|
lfs3_dir_read(&lfs3, &dir, &info) => 0;
|
|
assert(strcmp(info.name, name) == 0);
|
|
assert(info.type == LFS3_TYPE_DIR);
|
|
assert(info.size == 0);
|
|
}
|
|
lfs3_dir_read(&lfs3, &dir, &info) => LFS3_ERR_NOENT;
|
|
lfs3_dir_close(&lfs3, &dir) => 0;
|
|
|
|
// remount?
|
|
if (REMOUNT) {
|
|
lfs3_unmount(&lfs3) => 0;
|
|
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0;
|
|
}
|
|
|
|
// first traverse the tree to find all blocks in use
|
|
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
|
|
| ((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);
|
|
}
|
|
}
|
|
|
|
// then clobber every other block
|
|
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);
|
|
|
|
// then check that we can read our directories after clobbering
|
|
for (int remount = 0; remount < 2; remount++) {
|
|
// remount?
|
|
if (remount) {
|
|
lfs3_unmount(&lfs3) => 0;
|
|
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0;
|
|
}
|
|
|
|
for (lfs3_size_t i = 0; i < N; i++) {
|
|
char name[256];
|
|
sprintf(name, "dir%03x", i);
|
|
struct lfs3_info info;
|
|
lfs3_stat(&lfs3, name, &info) => 0;
|
|
assert(strcmp(info.name, name) == 0);
|
|
assert(info.type == LFS3_TYPE_DIR);
|
|
assert(info.size == 0);
|
|
}
|
|
|
|
lfs3_dir_open(&lfs3, &dir, "/") => 0;
|
|
lfs3_dir_read(&lfs3, &dir, &info) => 0;
|
|
assert(strcmp(info.name, ".") == 0);
|
|
assert(info.type == LFS3_TYPE_DIR);
|
|
assert(info.size == 0);
|
|
lfs3_dir_read(&lfs3, &dir, &info) => 0;
|
|
assert(strcmp(info.name, "..") == 0);
|
|
assert(info.type == LFS3_TYPE_DIR);
|
|
assert(info.size == 0);
|
|
for (lfs3_size_t i = 0; i < N; i++) {
|
|
char name[256];
|
|
sprintf(name, "dir%03x", i);
|
|
lfs3_dir_read(&lfs3, &dir, &info) => 0;
|
|
assert(strcmp(info.name, name) == 0);
|
|
assert(info.type == LFS3_TYPE_DIR);
|
|
assert(info.size == 0);
|
|
}
|
|
lfs3_dir_read(&lfs3, &dir, &info) => LFS3_ERR_NOENT;
|
|
lfs3_dir_close(&lfs3, &dir) => 0;
|
|
}
|
|
|
|
lfs3_unmount(&lfs3) => 0;
|
|
'''
|
|
|
|
[cases.test_alloc_clobber_files]
|
|
defines.N = [1, 2, 4, 8, 16, 32, 64]
|
|
defines.SIZE = [
|
|
'0',
|
|
'FILE_CACHE_SIZE/2',
|
|
'2*FILE_CACHE_SIZE',
|
|
'BLOCK_SIZE/2',
|
|
'BLOCK_SIZE',
|
|
'2*BLOCK_SIZE',
|
|
'8*BLOCK_SIZE',
|
|
]
|
|
defines.CKMETA = [false, true]
|
|
defines.REMOUNT = [false, true]
|
|
in = 'lfs3.c'
|
|
if = '(SIZE*N)/BLOCK_SIZE <= 32'
|
|
code = '''
|
|
lfs3_t lfs3;
|
|
lfs3_format(&lfs3, LFS3_F_RDWR, CFG) => 0;
|
|
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0;
|
|
|
|
// create this many files
|
|
uint32_t prng = 42;
|
|
for (lfs3_size_t i = 0; i < N; i++) {
|
|
char name[256];
|
|
sprintf(name, "file%03x", i);
|
|
|
|
uint8_t wbuf[SIZE];
|
|
for (lfs3_size_t j = 0; j < SIZE; j++) {
|
|
wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26);
|
|
}
|
|
|
|
lfs3_file_t file;
|
|
lfs3_file_open(&lfs3, &file, name,
|
|
LFS3_O_WRONLY | LFS3_O_CREAT | LFS3_O_EXCL) => 0;
|
|
lfs3_file_write(&lfs3, &file, wbuf, SIZE) => SIZE;
|
|
lfs3_file_close(&lfs3, &file) => 0;
|
|
}
|
|
|
|
// check that our writes worked
|
|
prng = 42;
|
|
for (lfs3_size_t i = 0; i < N; i++) {
|
|
// check with stat
|
|
char name[256];
|
|
sprintf(name, "file%03x", i);
|
|
struct lfs3_info info;
|
|
lfs3_stat(&lfs3, name, &info) => 0;
|
|
assert(strcmp(info.name, name) == 0);
|
|
assert(info.type == LFS3_TYPE_REG);
|
|
assert(info.size == SIZE);
|
|
|
|
// try reading the file, note we reset prng above
|
|
uint8_t wbuf[SIZE];
|
|
for (lfs3_size_t j = 0; j < SIZE; j++) {
|
|
wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26);
|
|
}
|
|
|
|
lfs3_file_t file;
|
|
uint8_t rbuf[SIZE];
|
|
lfs3_file_open(&lfs3, &file, name, LFS3_O_RDONLY) => 0;
|
|
lfs3_file_read(&lfs3, &file, rbuf, SIZE) => SIZE;
|
|
assert(memcmp(rbuf, wbuf, SIZE) == 0);
|
|
lfs3_file_close(&lfs3, &file) => 0;
|
|
}
|
|
|
|
// remount?
|
|
if (REMOUNT) {
|
|
lfs3_unmount(&lfs3) => 0;
|
|
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0;
|
|
}
|
|
|
|
// first traverse the tree to find all blocks in use
|
|
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
|
|
| ((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 if (tag == LFS3_TAG_BLOCK) {
|
|
printf("traversal: 0x%x block 0x%x\n",
|
|
tag,
|
|
bptr.d.u.disk.block);
|
|
|
|
// keep track of seen blocks
|
|
seen[bptr.d.u.disk.block / 8]
|
|
|= 1 << (bptr.d.u.disk.block % 8);
|
|
|
|
} else {
|
|
// this shouldn't happen
|
|
printf("traversal: 0x%x\n", tag);
|
|
assert(false);
|
|
}
|
|
}
|
|
|
|
// then clobber every other block
|
|
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);
|
|
|
|
// then check that reading our files still works after clobbering
|
|
for (int remount = 0; remount < 2; remount++) {
|
|
// remount?
|
|
if (remount) {
|
|
lfs3_unmount(&lfs3) => 0;
|
|
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0;
|
|
}
|
|
|
|
prng = 42;
|
|
for (lfs3_size_t i = 0; i < N; i++) {
|
|
// check with stat
|
|
char name[256];
|
|
sprintf(name, "file%03x", i);
|
|
struct lfs3_info info;
|
|
lfs3_stat(&lfs3, name, &info) => 0;
|
|
assert(strcmp(info.name, name) == 0);
|
|
assert(info.type == LFS3_TYPE_REG);
|
|
assert(info.size == SIZE);
|
|
|
|
// try reading the file, note we reset prng above
|
|
uint8_t wbuf[SIZE];
|
|
for (lfs3_size_t j = 0; j < SIZE; j++) {
|
|
wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26);
|
|
}
|
|
|
|
lfs3_file_t file;
|
|
uint8_t rbuf[SIZE];
|
|
lfs3_file_open(&lfs3, &file, name, LFS3_O_RDONLY) => 0;
|
|
lfs3_file_read(&lfs3, &file, rbuf, SIZE) => SIZE;
|
|
assert(memcmp(rbuf, wbuf, SIZE) == 0);
|
|
lfs3_file_close(&lfs3, &file) => 0;
|
|
}
|
|
}
|
|
|
|
lfs3_unmount(&lfs3) => 0;
|
|
'''
|
|
|
|
# open files need to be tracked internally to make sure this doesn't break
|
|
[cases.test_alloc_clobber_open_files]
|
|
defines.N = [1, 2, 4, 8, 16, 32, 64]
|
|
defines.SIZE = [
|
|
'0',
|
|
'FILE_CACHE_SIZE/2',
|
|
'2*FILE_CACHE_SIZE',
|
|
'BLOCK_SIZE/2',
|
|
'BLOCK_SIZE',
|
|
'2*BLOCK_SIZE',
|
|
'8*BLOCK_SIZE',
|
|
]
|
|
defines.CKMETA = [false, true]
|
|
in = 'lfs3.c'
|
|
if = '(SIZE*N)/BLOCK_SIZE <= 32'
|
|
code = '''
|
|
lfs3_t lfs3;
|
|
lfs3_format(&lfs3, LFS3_F_RDWR, CFG) => 0;
|
|
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0;
|
|
|
|
// create this many files
|
|
lfs3_file_t files[N];
|
|
uint32_t prng = 42;
|
|
for (lfs3_size_t i = 0; i < N; i++) {
|
|
char name[256];
|
|
sprintf(name, "file%03x", i);
|
|
|
|
uint8_t wbuf[SIZE];
|
|
for (lfs3_size_t j = 0; j < SIZE; j++) {
|
|
wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26);
|
|
}
|
|
|
|
lfs3_file_open(&lfs3, &files[i], name,
|
|
LFS3_O_RDWR | LFS3_O_CREAT | LFS3_O_EXCL) => 0;
|
|
lfs3_file_write(&lfs3, &files[i], wbuf, SIZE) => SIZE;
|
|
}
|
|
|
|
// check that our writes worked
|
|
prng = 42;
|
|
for (lfs3_size_t i = 0; i < N; i++) {
|
|
// try reading the file, note we reset prng above
|
|
uint8_t wbuf[SIZE];
|
|
for (lfs3_size_t j = 0; j < SIZE; j++) {
|
|
wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26);
|
|
}
|
|
|
|
uint8_t rbuf[SIZE];
|
|
lfs3_file_rewind(&lfs3, &files[i]) => 0;
|
|
lfs3_file_read(&lfs3, &files[i], rbuf, SIZE) => SIZE;
|
|
assert(memcmp(rbuf, wbuf, SIZE) == 0);
|
|
}
|
|
|
|
// first traverse the tree to find all blocks in use
|
|
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
|
|
| ((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 if (tag == LFS3_TAG_BLOCK) {
|
|
printf("traversal: 0x%x block 0x%x\n",
|
|
tag,
|
|
bptr.d.u.disk.block);
|
|
|
|
// keep track of seen blocks
|
|
seen[bptr.d.u.disk.block / 8]
|
|
|= 1 << (bptr.d.u.disk.block % 8);
|
|
|
|
} else {
|
|
// this shouldn't happen
|
|
printf("traversal: 0x%x\n", tag);
|
|
assert(false);
|
|
}
|
|
}
|
|
|
|
// then clobber every other block
|
|
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);
|
|
|
|
// then check that reading our files still works after clobbering
|
|
prng = 42;
|
|
for (lfs3_size_t i = 0; i < N; i++) {
|
|
// try reading the file, note we reset prng above
|
|
uint8_t wbuf[SIZE];
|
|
for (lfs3_size_t j = 0; j < SIZE; j++) {
|
|
wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26);
|
|
}
|
|
|
|
uint8_t rbuf[SIZE];
|
|
lfs3_file_rewind(&lfs3, &files[i]) => 0;
|
|
lfs3_file_read(&lfs3, &files[i], rbuf, SIZE) => SIZE;
|
|
assert(memcmp(rbuf, wbuf, SIZE) == 0);
|
|
}
|
|
|
|
// and everything is fine after saving the files
|
|
for (lfs3_size_t i = 0; i < N; i++) {
|
|
lfs3_file_close(&lfs3, &files[i]) => 0;
|
|
}
|
|
|
|
for (int remount = 0; remount < 2; remount++) {
|
|
// remount?
|
|
if (remount) {
|
|
lfs3_unmount(&lfs3) => 0;
|
|
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0;
|
|
}
|
|
|
|
prng = 42;
|
|
for (lfs3_size_t i = 0; i < N; i++) {
|
|
// check with stat
|
|
char name[256];
|
|
sprintf(name, "file%03x", i);
|
|
struct lfs3_info info;
|
|
lfs3_stat(&lfs3, name, &info) => 0;
|
|
assert(strcmp(info.name, name) == 0);
|
|
assert(info.type == LFS3_TYPE_REG);
|
|
assert(info.size == SIZE);
|
|
|
|
// try reading the file, note we reset prng above
|
|
uint8_t wbuf[SIZE];
|
|
for (lfs3_size_t j = 0; j < SIZE; j++) {
|
|
wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26);
|
|
}
|
|
|
|
lfs3_file_t file;
|
|
uint8_t rbuf[SIZE];
|
|
lfs3_file_open(&lfs3, &file, name, LFS3_O_RDONLY) => 0;
|
|
lfs3_file_read(&lfs3, &file, rbuf, SIZE) => SIZE;
|
|
assert(memcmp(rbuf, wbuf, SIZE) == 0);
|
|
lfs3_file_close(&lfs3, &file) => 0;
|
|
}
|
|
}
|
|
|
|
lfs3_unmount(&lfs3) => 0;
|
|
'''
|
|
|
|
|
|
# TODO more nospc tests (opened files? other?)
|
|
|
|
# nospc tests mostly test that things still work when block allocation
|
|
# wraparound occurs
|
|
[cases.test_alloc_nospc_dirs]
|
|
defines.COUNT = [
|
|
'BLOCK_COUNT',
|
|
'BLOCK_COUNT-1',
|
|
'BLOCK_COUNT/2',
|
|
'BLOCK_COUNT/4',
|
|
'5',
|
|
'2',
|
|
]
|
|
code = '''
|
|
// test various block counts
|
|
struct lfs3_cfg cfg = *CFG;
|
|
cfg.block_count = COUNT;
|
|
lfs3_t lfs3;
|
|
lfs3_format(&lfs3, LFS3_F_RDWR, &cfg) => 0;
|
|
lfs3_mount(&lfs3, LFS3_M_RDWR, &cfg) => 0;
|
|
|
|
// create directories until we run out of space
|
|
lfs3_size_t n = 0;
|
|
for (;; n++) {
|
|
char name[256];
|
|
sprintf(name, "dir%08d", n);
|
|
int err = lfs3_mkdir(&lfs3, name);
|
|
assert(!err || err == LFS3_ERR_NOSPC);
|
|
if (err == LFS3_ERR_NOSPC) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
for (int remount = 0; remount < 2; remount++) {
|
|
// remount?
|
|
if (remount) {
|
|
lfs3_unmount(&lfs3) => 0;
|
|
lfs3_mount(&lfs3, LFS3_M_RDWR, &cfg) => 0;
|
|
}
|
|
|
|
// check that our mkdir worked until we ran out of space
|
|
for (lfs3_size_t i = 0; i < n; i++) {
|
|
char name[256];
|
|
sprintf(name, "dir%08d", i);
|
|
struct lfs3_info info;
|
|
lfs3_stat(&lfs3, name, &info) => 0;
|
|
assert(strcmp(info.name, name) == 0);
|
|
assert(info.type == LFS3_TYPE_DIR);
|
|
assert(info.size == 0);
|
|
}
|
|
|
|
lfs3_dir_t dir;
|
|
lfs3_dir_open(&lfs3, &dir, "/") => 0;
|
|
struct lfs3_info info;
|
|
lfs3_dir_read(&lfs3, &dir, &info) => 0;
|
|
assert(strcmp(info.name, ".") == 0);
|
|
assert(info.type == LFS3_TYPE_DIR);
|
|
assert(info.size == 0);
|
|
lfs3_dir_read(&lfs3, &dir, &info) => 0;
|
|
assert(strcmp(info.name, "..") == 0);
|
|
assert(info.type == LFS3_TYPE_DIR);
|
|
assert(info.size == 0);
|
|
for (lfs3_size_t i = 0; i < n; i++) {
|
|
char name[256];
|
|
sprintf(name, "dir%08d", i);
|
|
lfs3_dir_read(&lfs3, &dir, &info) => 0;
|
|
assert(strcmp(info.name, name) == 0);
|
|
assert(info.type == LFS3_TYPE_DIR);
|
|
assert(info.size == 0);
|
|
}
|
|
lfs3_dir_read(&lfs3, &dir, &info) => LFS3_ERR_NOENT;
|
|
lfs3_dir_close(&lfs3, &dir) => 0;
|
|
}
|
|
|
|
lfs3_unmount(&lfs3) => 0;
|
|
'''
|
|
|
|
[cases.test_alloc_nospc_files]
|
|
defines.COUNT = [
|
|
'BLOCK_COUNT',
|
|
'BLOCK_COUNT-1',
|
|
'BLOCK_COUNT/2',
|
|
'BLOCK_COUNT/4',
|
|
'5',
|
|
'2',
|
|
]
|
|
defines.SIZE = [
|
|
'0',
|
|
'FILE_CACHE_SIZE/2',
|
|
'2*FILE_CACHE_SIZE',
|
|
'BLOCK_SIZE/2',
|
|
'BLOCK_SIZE',
|
|
'2*BLOCK_SIZE',
|
|
'8*BLOCK_SIZE',
|
|
]
|
|
code = '''
|
|
// test various block counts
|
|
struct lfs3_cfg cfg = *CFG;
|
|
cfg.block_count = COUNT;
|
|
lfs3_t lfs3;
|
|
lfs3_format(&lfs3, LFS3_F_RDWR, &cfg) => 0;
|
|
lfs3_mount(&lfs3, LFS3_M_RDWR, &cfg) => 0;
|
|
|
|
// create files until we run out of space
|
|
uint32_t prng = 42;
|
|
lfs3_size_t n = 0;
|
|
for (;; n++) {
|
|
char name[256];
|
|
sprintf(name, "file%08d", n);
|
|
|
|
uint8_t wbuf[SIZE];
|
|
for (lfs3_size_t j = 0; j < SIZE; j++) {
|
|
wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26);
|
|
}
|
|
|
|
lfs3_file_t file;
|
|
int err = lfs3_file_open(&lfs3, &file, name,
|
|
LFS3_O_WRONLY | LFS3_O_CREAT | LFS3_O_EXCL);
|
|
assert(!err || err == LFS3_ERR_NOSPC);
|
|
if (err == LFS3_ERR_NOSPC) {
|
|
break;
|
|
}
|
|
|
|
lfs3_ssize_t size = lfs3_file_write(&lfs3, &file, wbuf, SIZE);
|
|
assert(size == SIZE || size == LFS3_ERR_NOSPC);
|
|
if (size == LFS3_ERR_NOSPC) {
|
|
lfs3_file_close(&lfs3, &file) => 0;
|
|
break;
|
|
}
|
|
|
|
err = lfs3_file_close(&lfs3, &file);
|
|
assert(!err || err == LFS3_ERR_NOSPC);
|
|
if (err == LFS3_ERR_NOSPC) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
for (int remount = 0; remount < 2; remount++) {
|
|
// remount?
|
|
if (remount) {
|
|
lfs3_unmount(&lfs3) => 0;
|
|
lfs3_mount(&lfs3, LFS3_M_RDWR, &cfg) => 0;
|
|
}
|
|
|
|
// check that our file writes worked until we ran out of space
|
|
prng = 42;
|
|
for (lfs3_size_t i = 0; i < n; i++) {
|
|
// check with stat
|
|
char name[256];
|
|
sprintf(name, "file%08d", i);
|
|
struct lfs3_info info;
|
|
lfs3_stat(&lfs3, name, &info) => 0;
|
|
assert(strcmp(info.name, name) == 0);
|
|
assert(info.type == LFS3_TYPE_REG);
|
|
assert(info.size == SIZE);
|
|
|
|
// try reading the file, note we reset prng above
|
|
uint8_t wbuf[SIZE];
|
|
for (lfs3_size_t j = 0; j < SIZE; j++) {
|
|
wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26);
|
|
}
|
|
|
|
lfs3_file_t file;
|
|
uint8_t rbuf[SIZE];
|
|
lfs3_file_open(&lfs3, &file, name, LFS3_O_RDONLY) => 0;
|
|
lfs3_file_read(&lfs3, &file, rbuf, SIZE) => SIZE;
|
|
assert(memcmp(rbuf, wbuf, SIZE) == 0);
|
|
lfs3_file_close(&lfs3, &file) => 0;
|
|
}
|
|
}
|
|
|
|
lfs3_unmount(&lfs3) => 0;
|
|
'''
|
|
|