forked from Imagelibrary/littlefs
Ended up changing the name of lfsr_mtree_traversal_t -> lfsr_traversal_t,
since this behaves more like a filesytem-wide traversal than an mtree
traversal (it returns several typed objects, not mdirs like the other
mtree functions for one).
As a part of this changeset, lfsr_btraversal_t (was lfsr_btree_traversal_t)
and lfsr_traversal_t no longer return untyped lfsr_data_ts, but instead
return specialized lfsr_{b,t}info_t structs. We weren't even using
lfsr_data_t for its original purpose in lfsr_traversal_t.
Also changed lfsr_traversal_next -> lfsr_traversal_read, you may notice
at this point the changes are intended to make lfsr_traversal_t look
more like lfsr_dir_t for consistency.
---
Internally lfsr_traversal_t now uses a full state machine with its own
enum due to the complexity of traversing the filesystem incrementally.
Because creating diagrams is fun, here's the current full state machine,
though note it will need to be extended for any
parity-trees/free-trees/etc:
mrootanchor
|
v
mrootchain
.-' |
| v
| mtree ---> openedblock
'-. | ^ | ^
v v | v |
mdirblock openedbtree
| ^
v |
mdirbtree
I'm not sure I'm happy with the current implementation, and eventually
it will need to be able to handle in-place repairs to the blocks it
sees, so this whole thing may need a rewrite.
But in the meantime, this passes the new clobber tests in test_alloc, so
it should be enough to prove the file implementation works. (which is
definitely is not fully tested yet, and some bugs had to be fixed for
the new tests in test_alloc to pass).
---
Speaking of test_alloc.
The inherent cyclic dependency between files/dirs/alloc makes it a bit
hard to know what order to test these bits of functionality in.
Originally I was testing alloc first, because it seems you need to be
confident in your block allocator before you can start testing
higher-level data structures.
But I've gone ahead and reversed this order, testing alloc after
files/dirs. This is because of an interesting observation that if alloc
is broken, you can always increase the test device's size to some absurd
number (-DDISK_SIZE=16777216, for example) to kick the can down the
road.
Testing in this order allows alloc to use more high-level APIs and
focus on corner cases where the allocator's behavior requires subtlety
to be correct (e.g. ENOSPC).
1440 lines
44 KiB
TOML
1440 lines
44 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 (-DDISK_SIZE=16777216, etc)
|
|
#
|
|
after = ['test_mtree', 'test_dtree', 'test_files']
|
|
|
|
|
|
# TODO test all of these with weird block sizes? would be nice to make this
|
|
# easy via the test_runner, either by handling it there or letting a single
|
|
# config limit the block count by a couple blocks
|
|
|
|
# test that we can alloc
|
|
[cases.test_alloc_alloc]
|
|
in = 'lfs.c'
|
|
code = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, CFG) => 0;
|
|
lfsr_mount(&lfs, CFG) => 0;
|
|
|
|
// start allocating
|
|
lfs_alloc_ack(&lfs);
|
|
lfs_size_t alloced = 0;
|
|
while (true) {
|
|
lfs_block_t block;
|
|
int err = lfs_alloc(&lfs, &block);
|
|
assert(!err || err == LFS_ERR_NOSPC);
|
|
|
|
if (err == LFS_ERR_NOSPC) {
|
|
break;
|
|
}
|
|
alloced += 1;
|
|
|
|
// our allocator should stop at some point...
|
|
assert(alloced < 2*BLOCK_COUNT);
|
|
}
|
|
|
|
// excluding our mroot, we should have allocated exactly
|
|
// block_count-2 blocks
|
|
printf("alloced %d/%d blocks\n", alloced, (lfs_block_t)BLOCK_COUNT);
|
|
assert(alloced == BLOCK_COUNT-2);
|
|
|
|
lfsr_unmount(&lfs) => 0;
|
|
'''
|
|
|
|
# test that we can realloc after an ack
|
|
[cases.test_alloc_reuse]
|
|
in = 'lfs.c'
|
|
code = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, CFG) => 0;
|
|
lfsr_mount(&lfs, CFG) => 0;
|
|
|
|
// start allocating
|
|
lfs_alloc_ack(&lfs);
|
|
lfs_size_t alloced = 0;
|
|
while (true) {
|
|
lfs_block_t block;
|
|
int err = lfs_alloc(&lfs, &block);
|
|
assert(!err || err == LFS_ERR_NOSPC);
|
|
|
|
if (err == LFS_ERR_NOSPC) {
|
|
break;
|
|
}
|
|
alloced += 1;
|
|
|
|
// our allocator should stop at some point...
|
|
assert(alloced < 2*BLOCK_COUNT);
|
|
}
|
|
|
|
// excluding our mroot, we should have allocated exactly
|
|
// block_count-2 blocks
|
|
printf("alloced %d/%d blocks\n", alloced, (lfs_block_t)BLOCK_COUNT);
|
|
assert(alloced == BLOCK_COUNT-2);
|
|
|
|
// ack again, effectively releasing all the previously alloced blocks
|
|
lfs_alloc_ack(&lfs);
|
|
alloced = 0;
|
|
while (true) {
|
|
lfs_block_t block;
|
|
int err = lfs_alloc(&lfs, &block);
|
|
assert(!err || err == LFS_ERR_NOSPC);
|
|
|
|
if (err == LFS_ERR_NOSPC) {
|
|
break;
|
|
}
|
|
alloced += 1;
|
|
|
|
// our allocator should stop at some point...
|
|
assert(alloced < 2*BLOCK_COUNT);
|
|
}
|
|
|
|
// excluding our mroot, we should have allocated exactly
|
|
// block_count-2 blocks
|
|
printf("alloced %d/%d blocks\n", alloced, (lfs_block_t)BLOCK_COUNT);
|
|
assert(alloced == BLOCK_COUNT-2);
|
|
|
|
lfsr_unmount(&lfs) => 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.VALIDATE = [false, true]
|
|
defines.REMOUNT = [false, true]
|
|
in = 'lfs.c'
|
|
code = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, CFG) => 0;
|
|
lfsr_mount(&lfs, CFG) => 0;
|
|
|
|
// create this many directories
|
|
for (lfs_size_t i = 0; i < N; i++) {
|
|
char name[256];
|
|
sprintf(name, "dir%04d", i);
|
|
lfsr_mkdir(&lfs, name) => 0;
|
|
}
|
|
|
|
// check that our mkdir worked
|
|
for (lfs_size_t i = 0; i < N; i++) {
|
|
char name[256];
|
|
sprintf(name, "dir%04d", i);
|
|
struct lfs_info info;
|
|
lfsr_stat(&lfs, name, &info) => 0;
|
|
assert(strcmp(info.name, name) == 0);
|
|
assert(info.type == LFS_TYPE_DIR);
|
|
}
|
|
|
|
lfsr_dir_t dir;
|
|
lfsr_dir_open(&lfs, &dir, "/") => 0;
|
|
struct lfs_info info;
|
|
lfsr_dir_read(&lfs, &dir, &info) => 0;
|
|
assert(strcmp(info.name, ".") == 0);
|
|
assert(info.type == LFS_TYPE_DIR);
|
|
lfsr_dir_read(&lfs, &dir, &info) => 0;
|
|
assert(strcmp(info.name, "..") == 0);
|
|
assert(info.type == LFS_TYPE_DIR);
|
|
for (lfs_size_t i = 0; i < N; i++) {
|
|
char name[256];
|
|
sprintf(name, "dir%04d", i);
|
|
lfsr_dir_read(&lfs, &dir, &info) => 0;
|
|
assert(strcmp(info.name, name) == 0);
|
|
assert(info.type == LFS_TYPE_DIR);
|
|
}
|
|
lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT;
|
|
lfsr_dir_close(&lfs, &dir) => 0;
|
|
|
|
// remount?
|
|
if (REMOUNT) {
|
|
lfsr_unmount(&lfs) => 0;
|
|
lfsr_mount(&lfs, 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);
|
|
|
|
lfsr_traversal_t traversal = LFSR_TRAVERSAL(
|
|
VALIDATE ? LFSR_TRAVERSAL_VALIDATE : 0);
|
|
for (lfs_block_t i = 0;; i++) {
|
|
// a bit hacky, but this catches infinite loops
|
|
assert(i < 2*BLOCK_COUNT);
|
|
|
|
lfsr_tinfo_t tinfo;
|
|
int err = lfsr_traversal_read(&lfs, &traversal, &tinfo);
|
|
assert(!err || err == LFS_ERR_NOENT);
|
|
if (err == LFS_ERR_NOENT) {
|
|
break;
|
|
}
|
|
|
|
if (tinfo.tag == LFSR_TAG_MDIR) {
|
|
printf("traversal: 0x%x mdir 0x{%x,%x}\n",
|
|
tinfo.tag,
|
|
tinfo.u.mdir.u.m.blocks[0], tinfo.u.mdir.u.m.blocks[1]);
|
|
|
|
// keep track of seen blocks
|
|
seen[tinfo.u.mdir.u.m.blocks[1] / 8]
|
|
|= 1 << (tinfo.u.mdir.u.m.blocks[1] % 8);
|
|
seen[tinfo.u.mdir.u.m.blocks[0] / 8]
|
|
|= 1 << (tinfo.u.mdir.u.m.blocks[0] % 8);
|
|
|
|
} else if (tinfo.tag == LFSR_TAG_BTREE) {
|
|
printf("traversal: 0x%x btree 0x%x.%x\n",
|
|
tinfo.tag,
|
|
tinfo.u.rbyd.block, tinfo.u.rbyd.trunk);
|
|
|
|
// keep track of seen blocks
|
|
seen[tinfo.u.rbyd.block / 8] |= 1 << (tinfo.u.rbyd.block % 8);
|
|
|
|
} else {
|
|
// this shouldn't happen
|
|
printf("traversal: 0x%x\n", tinfo.tag);
|
|
assert(false);
|
|
}
|
|
}
|
|
|
|
// then clobber every other block
|
|
uint8_t clobber_buf[BLOCK_SIZE];
|
|
memset(clobber_buf, 0xcc, BLOCK_SIZE);
|
|
for (lfs_block_t block = 0; block < BLOCK_COUNT; block++) {
|
|
if (!(seen[block / 8] & (1 << (block % 8)))) {
|
|
CFG->erase(CFG, block) => 0;
|
|
CFG->prog(CFG, block, 0, clobber_buf, BLOCK_SIZE) => 0;
|
|
}
|
|
}
|
|
free(seen);
|
|
|
|
// then check that we can read our directories after clobbering
|
|
for (lfs_size_t i = 0; i < N; i++) {
|
|
char name[256];
|
|
sprintf(name, "dir%04d", i);
|
|
struct lfs_info info;
|
|
lfsr_stat(&lfs, name, &info) => 0;
|
|
assert(strcmp(info.name, name) == 0);
|
|
assert(info.type == LFS_TYPE_DIR);
|
|
}
|
|
|
|
lfsr_dir_open(&lfs, &dir, "/") => 0;
|
|
lfsr_dir_read(&lfs, &dir, &info) => 0;
|
|
assert(strcmp(info.name, ".") == 0);
|
|
assert(info.type == LFS_TYPE_DIR);
|
|
lfsr_dir_read(&lfs, &dir, &info) => 0;
|
|
assert(strcmp(info.name, "..") == 0);
|
|
assert(info.type == LFS_TYPE_DIR);
|
|
for (lfs_size_t i = 0; i < N; i++) {
|
|
char name[256];
|
|
sprintf(name, "dir%04d", i);
|
|
lfsr_dir_read(&lfs, &dir, &info) => 0;
|
|
assert(strcmp(info.name, name) == 0);
|
|
assert(info.type == LFS_TYPE_DIR);
|
|
}
|
|
lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT;
|
|
lfsr_dir_close(&lfs, &dir) => 0;
|
|
|
|
lfsr_unmount(&lfs) => 0;
|
|
'''
|
|
|
|
[cases.test_alloc_clobber_files]
|
|
defines.N = [1, 2, 4, 8, 16, 32, 64]
|
|
defines.SIZE = [
|
|
'0',
|
|
'CACHE_SIZE/2',
|
|
'2*CACHE_SIZE',
|
|
'BLOCK_SIZE/2',
|
|
'BLOCK_SIZE',
|
|
'2*BLOCK_SIZE',
|
|
'8*BLOCK_SIZE',
|
|
]
|
|
defines.VALIDATE = [false, true]
|
|
defines.REMOUNT = [false, true]
|
|
in = 'lfs.c'
|
|
if = '(SIZE*N)/BLOCK_SIZE <= 32'
|
|
code = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, CFG) => 0;
|
|
lfsr_mount(&lfs, CFG) => 0;
|
|
|
|
// create this many files
|
|
uint32_t prng = 42;
|
|
for (lfs_size_t i = 0; i < N; i++) {
|
|
char name[256];
|
|
sprintf(name, "file%04d", i);
|
|
|
|
uint8_t wbuf[SIZE];
|
|
for (lfs_size_t j = 0; j < SIZE; j++) {
|
|
wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26);
|
|
}
|
|
|
|
lfsr_file_t file;
|
|
lfsr_file_open(&lfs, &file, name,
|
|
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
|
|
lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE;
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
}
|
|
|
|
// check that our writes worked
|
|
prng = 42;
|
|
for (lfs_size_t i = 0; i < N; i++) {
|
|
// check with stat
|
|
char name[256];
|
|
sprintf(name, "file%04d", i);
|
|
struct lfs_info info;
|
|
lfsr_stat(&lfs, name, &info) => 0;
|
|
assert(strcmp(info.name, name) == 0);
|
|
assert(info.type == LFS_TYPE_REG);
|
|
assert(info.size == SIZE);
|
|
|
|
// try reading the file, note we reset prng above
|
|
uint8_t wbuf[SIZE];
|
|
for (lfs_size_t j = 0; j < SIZE; j++) {
|
|
wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26);
|
|
}
|
|
|
|
lfsr_file_t file;
|
|
uint8_t rbuf[SIZE];
|
|
lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => 0;
|
|
lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE;
|
|
assert(memcmp(rbuf, wbuf, SIZE) == 0);
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
}
|
|
|
|
// remount?
|
|
if (REMOUNT) {
|
|
lfsr_unmount(&lfs) => 0;
|
|
lfsr_mount(&lfs, 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);
|
|
|
|
lfsr_traversal_t traversal = LFSR_TRAVERSAL(
|
|
(VALIDATE ? LFSR_TRAVERSAL_VALIDATE : 0)
|
|
| LFSR_TRAVERSAL_ALL);
|
|
for (lfs_block_t i = 0;; i++) {
|
|
// a bit hacky, but this catches infinite loops
|
|
assert(i < 2*BLOCK_COUNT);
|
|
|
|
lfsr_tinfo_t tinfo;
|
|
int err = lfsr_traversal_read(&lfs, &traversal, &tinfo);
|
|
assert(!err || err == LFS_ERR_NOENT);
|
|
if (err == LFS_ERR_NOENT) {
|
|
break;
|
|
}
|
|
|
|
if (tinfo.tag == LFSR_TAG_MDIR) {
|
|
printf("traversal: 0x%x mdir 0x{%x,%x}\n",
|
|
tinfo.tag,
|
|
tinfo.u.mdir.u.m.blocks[0], tinfo.u.mdir.u.m.blocks[1]);
|
|
|
|
// keep track of seen blocks
|
|
seen[tinfo.u.mdir.u.m.blocks[1] / 8]
|
|
|= 1 << (tinfo.u.mdir.u.m.blocks[1] % 8);
|
|
seen[tinfo.u.mdir.u.m.blocks[0] / 8]
|
|
|= 1 << (tinfo.u.mdir.u.m.blocks[0] % 8);
|
|
|
|
} else if (tinfo.tag == LFSR_TAG_BTREE) {
|
|
printf("traversal: 0x%x btree 0x%x.%x\n",
|
|
tinfo.tag,
|
|
tinfo.u.rbyd.block, tinfo.u.rbyd.trunk);
|
|
|
|
// keep track of seen blocks
|
|
seen[tinfo.u.rbyd.block / 8] |= 1 << (tinfo.u.rbyd.block % 8);
|
|
|
|
} else if (tinfo.tag == LFSR_TAG_BLOCK) {
|
|
printf("traversal: 0x%x block 0x%x\n",
|
|
tinfo.tag,
|
|
tinfo.u.bptr.block);
|
|
|
|
// keep track of seen blocks
|
|
seen[tinfo.u.bptr.block / 8] |= 1 << (tinfo.u.bptr.block % 8);
|
|
|
|
} else {
|
|
// this shouldn't happen
|
|
printf("traversal: 0x%x\n", tinfo.tag);
|
|
assert(false);
|
|
}
|
|
}
|
|
|
|
// then clobber every other block
|
|
uint8_t clobber_buf[BLOCK_SIZE];
|
|
memset(clobber_buf, 0xcc, BLOCK_SIZE);
|
|
for (lfs_block_t block = 0; block < BLOCK_COUNT; block++) {
|
|
if (!(seen[block / 8] & (1 << (block % 8)))) {
|
|
CFG->erase(CFG, block) => 0;
|
|
CFG->prog(CFG, block, 0, clobber_buf, BLOCK_SIZE) => 0;
|
|
}
|
|
}
|
|
free(seen);
|
|
|
|
// then check that reading our files still works after clobbering
|
|
prng = 42;
|
|
for (lfs_size_t i = 0; i < N; i++) {
|
|
// check with stat
|
|
char name[256];
|
|
sprintf(name, "file%04d", i);
|
|
struct lfs_info info;
|
|
lfsr_stat(&lfs, name, &info) => 0;
|
|
assert(strcmp(info.name, name) == 0);
|
|
assert(info.type == LFS_TYPE_REG);
|
|
assert(info.size == SIZE);
|
|
|
|
// try reading the file, note we reset prng above
|
|
uint8_t wbuf[SIZE];
|
|
for (lfs_size_t j = 0; j < SIZE; j++) {
|
|
wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26);
|
|
}
|
|
|
|
lfsr_file_t file;
|
|
uint8_t rbuf[SIZE];
|
|
lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => 0;
|
|
lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE;
|
|
assert(memcmp(rbuf, wbuf, SIZE) == 0);
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
}
|
|
|
|
lfsr_unmount(&lfs) => 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',
|
|
'CACHE_SIZE/2',
|
|
'2*CACHE_SIZE',
|
|
'BLOCK_SIZE/2',
|
|
'BLOCK_SIZE',
|
|
'2*BLOCK_SIZE',
|
|
'8*BLOCK_SIZE',
|
|
]
|
|
defines.VALIDATE = [false, true]
|
|
defines.REMOUNT = [false, true]
|
|
in = 'lfs.c'
|
|
if = '(SIZE*N)/BLOCK_SIZE <= 32'
|
|
code = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, CFG) => 0;
|
|
lfsr_mount(&lfs, CFG) => 0;
|
|
|
|
// create this many files
|
|
lfsr_file_t files[N];
|
|
uint32_t prng = 42;
|
|
for (lfs_size_t i = 0; i < N; i++) {
|
|
char name[256];
|
|
sprintf(name, "file%04d", i);
|
|
|
|
uint8_t wbuf[SIZE];
|
|
for (lfs_size_t j = 0; j < SIZE; j++) {
|
|
wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26);
|
|
}
|
|
|
|
lfsr_file_open(&lfs, &files[i], name,
|
|
LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0;
|
|
lfsr_file_write(&lfs, &files[i], wbuf, SIZE) => SIZE;
|
|
}
|
|
|
|
// check that our writes worked
|
|
prng = 42;
|
|
for (lfs_size_t i = 0; i < N; i++) {
|
|
// try reading the file, note we reset prng above
|
|
uint8_t wbuf[SIZE];
|
|
for (lfs_size_t j = 0; j < SIZE; j++) {
|
|
wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26);
|
|
}
|
|
|
|
uint8_t rbuf[SIZE];
|
|
lfsr_file_rewind(&lfs, &files[i]) => 0;
|
|
lfsr_file_read(&lfs, &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);
|
|
|
|
lfsr_traversal_t traversal = LFSR_TRAVERSAL(
|
|
(VALIDATE ? LFSR_TRAVERSAL_VALIDATE : 0)
|
|
| LFSR_TRAVERSAL_ALL);
|
|
for (lfs_block_t i = 0;; i++) {
|
|
// a bit hacky, but this catches infinite loops
|
|
assert(i < 2*BLOCK_COUNT);
|
|
|
|
lfsr_tinfo_t tinfo;
|
|
int err = lfsr_traversal_read(&lfs, &traversal, &tinfo);
|
|
assert(!err || err == LFS_ERR_NOENT);
|
|
if (err == LFS_ERR_NOENT) {
|
|
break;
|
|
}
|
|
|
|
if (tinfo.tag == LFSR_TAG_MDIR) {
|
|
printf("traversal: 0x%x mdir 0x{%x,%x}\n",
|
|
tinfo.tag,
|
|
tinfo.u.mdir.u.m.blocks[0], tinfo.u.mdir.u.m.blocks[1]);
|
|
|
|
// keep track of seen blocks
|
|
seen[tinfo.u.mdir.u.m.blocks[1] / 8]
|
|
|= 1 << (tinfo.u.mdir.u.m.blocks[1] % 8);
|
|
seen[tinfo.u.mdir.u.m.blocks[0] / 8]
|
|
|= 1 << (tinfo.u.mdir.u.m.blocks[0] % 8);
|
|
|
|
} else if (tinfo.tag == LFSR_TAG_BTREE) {
|
|
printf("traversal: 0x%x btree 0x%x.%x\n",
|
|
tinfo.tag,
|
|
tinfo.u.rbyd.block, tinfo.u.rbyd.trunk);
|
|
|
|
// keep track of seen blocks
|
|
seen[tinfo.u.rbyd.block / 8] |= 1 << (tinfo.u.rbyd.block % 8);
|
|
|
|
} else if (tinfo.tag == LFSR_TAG_BLOCK) {
|
|
printf("traversal: 0x%x block 0x%x\n",
|
|
tinfo.tag,
|
|
tinfo.u.bptr.block);
|
|
|
|
// keep track of seen blocks
|
|
seen[tinfo.u.bptr.block / 8] |= 1 << (tinfo.u.bptr.block % 8);
|
|
|
|
} else {
|
|
// this shouldn't happen
|
|
printf("traversal: 0x%x\n", tinfo.tag);
|
|
assert(false);
|
|
}
|
|
}
|
|
|
|
// then clobber every other block
|
|
uint8_t clobber_buf[BLOCK_SIZE];
|
|
memset(clobber_buf, 0xcc, BLOCK_SIZE);
|
|
for (lfs_block_t block = 0; block < BLOCK_COUNT; block++) {
|
|
if (!(seen[block / 8] & (1 << (block % 8)))) {
|
|
CFG->erase(CFG, block) => 0;
|
|
CFG->prog(CFG, block, 0, clobber_buf, BLOCK_SIZE) => 0;
|
|
}
|
|
}
|
|
free(seen);
|
|
|
|
// then check that reading our files still works after clobbering
|
|
prng = 42;
|
|
for (lfs_size_t i = 0; i < N; i++) {
|
|
// try reading the file, note we reset prng above
|
|
uint8_t wbuf[SIZE];
|
|
for (lfs_size_t j = 0; j < SIZE; j++) {
|
|
wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26);
|
|
}
|
|
|
|
uint8_t rbuf[SIZE];
|
|
lfsr_file_rewind(&lfs, &files[i]) => 0;
|
|
lfsr_file_read(&lfs, &files[i], rbuf, SIZE) => SIZE;
|
|
assert(memcmp(rbuf, wbuf, SIZE) == 0);
|
|
}
|
|
|
|
// and everything is fine after saving the files
|
|
for (lfs_size_t i = 0; i < N; i++) {
|
|
lfsr_file_close(&lfs, &files[i]) => 0;
|
|
}
|
|
|
|
if (REMOUNT) {
|
|
lfsr_unmount(&lfs) => 0;
|
|
lfsr_mount(&lfs, CFG) => 0;
|
|
}
|
|
|
|
prng = 42;
|
|
for (lfs_size_t i = 0; i < N; i++) {
|
|
// check with stat
|
|
char name[256];
|
|
sprintf(name, "file%04d", i);
|
|
struct lfs_info info;
|
|
lfsr_stat(&lfs, name, &info) => 0;
|
|
assert(strcmp(info.name, name) == 0);
|
|
assert(info.type == LFS_TYPE_REG);
|
|
assert(info.size == SIZE);
|
|
|
|
// try reading the file, note we reset prng above
|
|
uint8_t wbuf[SIZE];
|
|
for (lfs_size_t j = 0; j < SIZE; j++) {
|
|
wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26);
|
|
}
|
|
|
|
lfsr_file_t file;
|
|
uint8_t rbuf[SIZE];
|
|
lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => 0;
|
|
lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE;
|
|
assert(memcmp(rbuf, wbuf, SIZE) == 0);
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
}
|
|
|
|
lfsr_unmount(&lfs) => 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.REMOUNT = [false, true]
|
|
code = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, CFG) => 0;
|
|
lfsr_mount(&lfs, CFG) => 0;
|
|
|
|
// create directories until we run out of space
|
|
lfs_size_t n = 0;
|
|
for (;; n++) {
|
|
char name[256];
|
|
sprintf(name, "dir%08d", n);
|
|
int err = lfsr_mkdir(&lfs, name);
|
|
assert(!err || err == LFS_ERR_NOSPC);
|
|
if (err == LFS_ERR_NOSPC) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
// remount?
|
|
if (REMOUNT) {
|
|
lfsr_unmount(&lfs) => 0;
|
|
lfsr_mount(&lfs, CFG) => 0;
|
|
}
|
|
|
|
// check that our mkdir worked until we ran out of space
|
|
for (lfs_size_t i = 0; i < n; i++) {
|
|
char name[256];
|
|
sprintf(name, "dir%08d", i);
|
|
struct lfs_info info;
|
|
lfsr_stat(&lfs, name, &info) => 0;
|
|
assert(strcmp(info.name, name) == 0);
|
|
assert(info.type == LFS_TYPE_DIR);
|
|
}
|
|
|
|
lfsr_dir_t dir;
|
|
lfsr_dir_open(&lfs, &dir, "/") => 0;
|
|
struct lfs_info info;
|
|
lfsr_dir_read(&lfs, &dir, &info) => 0;
|
|
assert(strcmp(info.name, ".") == 0);
|
|
assert(info.type == LFS_TYPE_DIR);
|
|
lfsr_dir_read(&lfs, &dir, &info) => 0;
|
|
assert(strcmp(info.name, "..") == 0);
|
|
assert(info.type == LFS_TYPE_DIR);
|
|
for (lfs_size_t i = 0; i < n; i++) {
|
|
char name[256];
|
|
sprintf(name, "dir%08d", i);
|
|
lfsr_dir_read(&lfs, &dir, &info) => 0;
|
|
assert(strcmp(info.name, name) == 0);
|
|
assert(info.type == LFS_TYPE_DIR);
|
|
}
|
|
lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT;
|
|
lfsr_dir_close(&lfs, &dir) => 0;
|
|
|
|
lfsr_unmount(&lfs) => 0;
|
|
'''
|
|
|
|
[cases.test_alloc_nospc_files]
|
|
defines.SIZE = [
|
|
'0',
|
|
'CACHE_SIZE/2',
|
|
'2*CACHE_SIZE',
|
|
'BLOCK_SIZE/2',
|
|
'BLOCK_SIZE',
|
|
'2*BLOCK_SIZE',
|
|
'8*BLOCK_SIZE',
|
|
]
|
|
defines.REMOUNT = [false, true]
|
|
code = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, CFG) => 0;
|
|
lfsr_mount(&lfs, CFG) => 0;
|
|
|
|
// create files until we run out of space
|
|
uint32_t prng = 42;
|
|
lfs_size_t n = 0;
|
|
for (;; n++) {
|
|
char name[256];
|
|
sprintf(name, "file%08d", n);
|
|
|
|
uint8_t wbuf[SIZE];
|
|
for (lfs_size_t j = 0; j < SIZE; j++) {
|
|
wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26);
|
|
}
|
|
|
|
lfsr_file_t file;
|
|
int err = lfsr_file_open(&lfs, &file, name,
|
|
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL);
|
|
assert(!err || err == LFS_ERR_NOSPC);
|
|
if (err == LFS_ERR_NOSPC) {
|
|
break;
|
|
}
|
|
|
|
lfs_ssize_t size = lfsr_file_write(&lfs, &file, wbuf, SIZE);
|
|
assert(size == SIZE || size == LFS_ERR_NOSPC);
|
|
if (size == LFS_ERR_NOSPC) {
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
break;
|
|
}
|
|
|
|
err = lfsr_file_close(&lfs, &file);
|
|
assert(!err || err == LFS_ERR_NOSPC);
|
|
if (err == LFS_ERR_NOSPC) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
// remount?
|
|
if (REMOUNT) {
|
|
lfsr_unmount(&lfs) => 0;
|
|
lfsr_mount(&lfs, CFG) => 0;
|
|
}
|
|
|
|
// check that our file writes worked until we ran out of space
|
|
prng = 42;
|
|
for (lfs_size_t i = 0; i < n; i++) {
|
|
// check with stat
|
|
char name[256];
|
|
sprintf(name, "file%08d", i);
|
|
struct lfs_info info;
|
|
lfsr_stat(&lfs, name, &info) => 0;
|
|
assert(strcmp(info.name, name) == 0);
|
|
assert(info.type == LFS_TYPE_REG);
|
|
assert(info.size == SIZE);
|
|
|
|
// try reading the file, note we reset prng above
|
|
uint8_t wbuf[SIZE];
|
|
for (lfs_size_t j = 0; j < SIZE; j++) {
|
|
wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26);
|
|
}
|
|
|
|
lfsr_file_t file;
|
|
uint8_t rbuf[SIZE];
|
|
lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => 0;
|
|
lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE;
|
|
assert(memcmp(rbuf, wbuf, SIZE) == 0);
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
}
|
|
|
|
lfsr_unmount(&lfs) => 0;
|
|
'''
|
|
|
|
|
|
|
|
## allocator tests
|
|
## note for these to work there are a number constraints on the device geometry
|
|
#if = 'BLOCK_CYCLES == -1'
|
|
#
|
|
## parallel allocation test
|
|
#[cases.test_alloc_parallel]
|
|
#defines.FILES = 3
|
|
#defines.SIZE = '(((BLOCK_SIZE-8)*(BLOCK_COUNT-6)) / FILES)'
|
|
#code = '''
|
|
# const char *names[] = {"bacon", "eggs", "pancakes"};
|
|
# lfs_file_t files[FILES];
|
|
#
|
|
# lfs_t lfs;
|
|
# lfs_format(&lfs, cfg) => 0;
|
|
# lfs_mount(&lfs, cfg) => 0;
|
|
# lfs_mkdir(&lfs, "breakfast") => 0;
|
|
# lfs_unmount(&lfs) => 0;
|
|
#
|
|
# lfs_mount(&lfs, cfg) => 0;
|
|
# for (int n = 0; n < FILES; n++) {
|
|
# char path[1024];
|
|
# sprintf(path, "breakfast/%s", names[n]);
|
|
# lfs_file_open(&lfs, &files[n], path,
|
|
# LFS_O_WRONLY | LFS_O_CREAT | LFS_O_APPEND) => 0;
|
|
# }
|
|
# for (int n = 0; n < FILES; n++) {
|
|
# size_t size = strlen(names[n]);
|
|
# for (lfs_size_t i = 0; i < SIZE; i += size) {
|
|
# lfs_file_write(&lfs, &files[n], names[n], size) => size;
|
|
# }
|
|
# }
|
|
# for (int n = 0; n < FILES; n++) {
|
|
# lfs_file_close(&lfs, &files[n]) => 0;
|
|
# }
|
|
# lfs_unmount(&lfs) => 0;
|
|
#
|
|
# lfs_mount(&lfs, cfg) => 0;
|
|
# for (int n = 0; n < FILES; n++) {
|
|
# char path[1024];
|
|
# sprintf(path, "breakfast/%s", names[n]);
|
|
# lfs_file_t file;
|
|
# lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0;
|
|
# size_t size = strlen(names[n]);
|
|
# for (lfs_size_t i = 0; i < SIZE; i += size) {
|
|
# uint8_t buffer[1024];
|
|
# lfs_file_read(&lfs, &file, buffer, size) => size;
|
|
# assert(memcmp(buffer, names[n], size) == 0);
|
|
# }
|
|
# lfs_file_close(&lfs, &file) => 0;
|
|
# }
|
|
# lfs_unmount(&lfs) => 0;
|
|
#'''
|
|
#
|
|
## serial allocation test
|
|
#[cases.test_alloc_serial]
|
|
#defines.FILES = 3
|
|
#defines.SIZE = '(((BLOCK_SIZE-8)*(BLOCK_COUNT-6)) / FILES)'
|
|
#code = '''
|
|
# const char *names[] = {"bacon", "eggs", "pancakes"};
|
|
#
|
|
# lfs_t lfs;
|
|
# lfs_format(&lfs, cfg) => 0;
|
|
# lfs_mount(&lfs, cfg) => 0;
|
|
# lfs_mkdir(&lfs, "breakfast") => 0;
|
|
# lfs_unmount(&lfs) => 0;
|
|
#
|
|
# for (int n = 0; n < FILES; n++) {
|
|
# lfs_mount(&lfs, cfg) => 0;
|
|
# char path[1024];
|
|
# sprintf(path, "breakfast/%s", names[n]);
|
|
# lfs_file_t file;
|
|
# lfs_file_open(&lfs, &file, path,
|
|
# LFS_O_WRONLY | LFS_O_CREAT | LFS_O_APPEND) => 0;
|
|
# size_t size = strlen(names[n]);
|
|
# uint8_t buffer[1024];
|
|
# memcpy(buffer, names[n], size);
|
|
# for (int i = 0; i < SIZE; i += size) {
|
|
# lfs_file_write(&lfs, &file, buffer, size) => size;
|
|
# }
|
|
# lfs_file_close(&lfs, &file) => 0;
|
|
# lfs_unmount(&lfs) => 0;
|
|
# }
|
|
#
|
|
# lfs_mount(&lfs, cfg) => 0;
|
|
# for (int n = 0; n < FILES; n++) {
|
|
# char path[1024];
|
|
# sprintf(path, "breakfast/%s", names[n]);
|
|
# lfs_file_t file;
|
|
# lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0;
|
|
# size_t size = strlen(names[n]);
|
|
# for (int i = 0; i < SIZE; i += size) {
|
|
# uint8_t buffer[1024];
|
|
# lfs_file_read(&lfs, &file, buffer, size) => size;
|
|
# assert(memcmp(buffer, names[n], size) == 0);
|
|
# }
|
|
# lfs_file_close(&lfs, &file) => 0;
|
|
# }
|
|
# lfs_unmount(&lfs) => 0;
|
|
#'''
|
|
#
|
|
## parallel allocation reuse test
|
|
#[cases.test_alloc_parallel_reuse]
|
|
#defines.FILES = 3
|
|
#defines.SIZE = '(((BLOCK_SIZE-8)*(BLOCK_COUNT-6)) / FILES)'
|
|
#defines.CYCLES = [1, 10]
|
|
#code = '''
|
|
# const char *names[] = {"bacon", "eggs", "pancakes"};
|
|
# lfs_file_t files[FILES];
|
|
#
|
|
# lfs_t lfs;
|
|
# lfs_format(&lfs, cfg) => 0;
|
|
#
|
|
# for (int c = 0; c < CYCLES; c++) {
|
|
# lfs_mount(&lfs, cfg) => 0;
|
|
# lfs_mkdir(&lfs, "breakfast") => 0;
|
|
# lfs_unmount(&lfs) => 0;
|
|
#
|
|
# lfs_mount(&lfs, cfg) => 0;
|
|
# for (int n = 0; n < FILES; n++) {
|
|
# char path[1024];
|
|
# sprintf(path, "breakfast/%s", names[n]);
|
|
# lfs_file_open(&lfs, &files[n], path,
|
|
# LFS_O_WRONLY | LFS_O_CREAT | LFS_O_APPEND) => 0;
|
|
# }
|
|
# for (int n = 0; n < FILES; n++) {
|
|
# size_t size = strlen(names[n]);
|
|
# for (int i = 0; i < SIZE; i += size) {
|
|
# lfs_file_write(&lfs, &files[n], names[n], size) => size;
|
|
# }
|
|
# }
|
|
# for (int n = 0; n < FILES; n++) {
|
|
# lfs_file_close(&lfs, &files[n]) => 0;
|
|
# }
|
|
# lfs_unmount(&lfs) => 0;
|
|
#
|
|
# lfs_mount(&lfs, cfg) => 0;
|
|
# for (int n = 0; n < FILES; n++) {
|
|
# char path[1024];
|
|
# sprintf(path, "breakfast/%s", names[n]);
|
|
# lfs_file_t file;
|
|
# lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0;
|
|
# size_t size = strlen(names[n]);
|
|
# for (int i = 0; i < SIZE; i += size) {
|
|
# uint8_t buffer[1024];
|
|
# lfs_file_read(&lfs, &file, buffer, size) => size;
|
|
# assert(memcmp(buffer, names[n], size) == 0);
|
|
# }
|
|
# lfs_file_close(&lfs, &file) => 0;
|
|
# }
|
|
# lfs_unmount(&lfs) => 0;
|
|
#
|
|
# lfs_mount(&lfs, cfg) => 0;
|
|
# for (int n = 0; n < FILES; n++) {
|
|
# char path[1024];
|
|
# sprintf(path, "breakfast/%s", names[n]);
|
|
# lfs_remove(&lfs, path) => 0;
|
|
# }
|
|
# lfs_remove(&lfs, "breakfast") => 0;
|
|
# lfs_unmount(&lfs) => 0;
|
|
# }
|
|
#'''
|
|
#
|
|
## serial allocation reuse test
|
|
#[cases.test_alloc_serial_reuse]
|
|
#defines.FILES = 3
|
|
#defines.SIZE = '(((BLOCK_SIZE-8)*(BLOCK_COUNT-6)) / FILES)'
|
|
#defines.CYCLES = [1, 10]
|
|
#code = '''
|
|
# const char *names[] = {"bacon", "eggs", "pancakes"};
|
|
#
|
|
# lfs_t lfs;
|
|
# lfs_format(&lfs, cfg) => 0;
|
|
#
|
|
# for (int c = 0; c < CYCLES; c++) {
|
|
# lfs_mount(&lfs, cfg) => 0;
|
|
# lfs_mkdir(&lfs, "breakfast") => 0;
|
|
# lfs_unmount(&lfs) => 0;
|
|
#
|
|
# for (int n = 0; n < FILES; n++) {
|
|
# lfs_mount(&lfs, cfg) => 0;
|
|
# char path[1024];
|
|
# sprintf(path, "breakfast/%s", names[n]);
|
|
# lfs_file_t file;
|
|
# lfs_file_open(&lfs, &file, path,
|
|
# LFS_O_WRONLY | LFS_O_CREAT | LFS_O_APPEND) => 0;
|
|
# size_t size = strlen(names[n]);
|
|
# uint8_t buffer[1024];
|
|
# memcpy(buffer, names[n], size);
|
|
# for (int i = 0; i < SIZE; i += size) {
|
|
# lfs_file_write(&lfs, &file, buffer, size) => size;
|
|
# }
|
|
# lfs_file_close(&lfs, &file) => 0;
|
|
# lfs_unmount(&lfs) => 0;
|
|
# }
|
|
#
|
|
# lfs_mount(&lfs, cfg) => 0;
|
|
# for (int n = 0; n < FILES; n++) {
|
|
# char path[1024];
|
|
# sprintf(path, "breakfast/%s", names[n]);
|
|
# lfs_file_t file;
|
|
# lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0;
|
|
# size_t size = strlen(names[n]);
|
|
# for (int i = 0; i < SIZE; i += size) {
|
|
# uint8_t buffer[1024];
|
|
# lfs_file_read(&lfs, &file, buffer, size) => size;
|
|
# assert(memcmp(buffer, names[n], size) == 0);
|
|
# }
|
|
# lfs_file_close(&lfs, &file) => 0;
|
|
# }
|
|
# lfs_unmount(&lfs) => 0;
|
|
#
|
|
# lfs_mount(&lfs, cfg) => 0;
|
|
# for (int n = 0; n < FILES; n++) {
|
|
# char path[1024];
|
|
# sprintf(path, "breakfast/%s", names[n]);
|
|
# lfs_remove(&lfs, path) => 0;
|
|
# }
|
|
# lfs_remove(&lfs, "breakfast") => 0;
|
|
# lfs_unmount(&lfs) => 0;
|
|
# }
|
|
#'''
|
|
#
|
|
## exhaustion test
|
|
#[cases.test_alloc_exhaustion]
|
|
#code = '''
|
|
# lfs_t lfs;
|
|
# lfs_format(&lfs, cfg) => 0;
|
|
# lfs_mount(&lfs, cfg) => 0;
|
|
# lfs_file_t file;
|
|
# lfs_file_open(&lfs, &file, "exhaustion", LFS_O_WRONLY | LFS_O_CREAT);
|
|
# size_t size = strlen("exhaustion");
|
|
# uint8_t buffer[1024];
|
|
# memcpy(buffer, "exhaustion", size);
|
|
# lfs_file_write(&lfs, &file, buffer, size) => size;
|
|
# lfs_file_sync(&lfs, &file) => 0;
|
|
#
|
|
# size = strlen("blahblahblahblah");
|
|
# memcpy(buffer, "blahblahblahblah", size);
|
|
# lfs_ssize_t res;
|
|
# while (true) {
|
|
# res = lfs_file_write(&lfs, &file, buffer, size);
|
|
# if (res < 0) {
|
|
# break;
|
|
# }
|
|
#
|
|
# res => size;
|
|
# }
|
|
# res => LFS_ERR_NOSPC;
|
|
#
|
|
# lfs_file_close(&lfs, &file) => 0;
|
|
# lfs_unmount(&lfs) => 0;
|
|
#
|
|
# lfs_mount(&lfs, cfg) => 0;
|
|
# lfs_file_open(&lfs, &file, "exhaustion", LFS_O_RDONLY);
|
|
# size = strlen("exhaustion");
|
|
# lfs_file_size(&lfs, &file) => size;
|
|
# lfs_file_read(&lfs, &file, buffer, size) => size;
|
|
# memcmp(buffer, "exhaustion", size) => 0;
|
|
# lfs_file_close(&lfs, &file) => 0;
|
|
# lfs_unmount(&lfs) => 0;
|
|
#'''
|
|
#
|
|
## exhaustion wraparound test
|
|
#[cases.test_alloc_exhaustion_wraparound]
|
|
#defines.SIZE = '(((BLOCK_SIZE-8)*(BLOCK_COUNT-4)) / 3)'
|
|
#code = '''
|
|
# lfs_t lfs;
|
|
# lfs_format(&lfs, cfg) => 0;
|
|
# lfs_mount(&lfs, cfg) => 0;
|
|
#
|
|
# lfs_file_t file;
|
|
# lfs_file_open(&lfs, &file, "padding", LFS_O_WRONLY | LFS_O_CREAT);
|
|
# size_t size = strlen("buffering");
|
|
# uint8_t buffer[1024];
|
|
# memcpy(buffer, "buffering", size);
|
|
# for (int i = 0; i < SIZE; i += size) {
|
|
# lfs_file_write(&lfs, &file, buffer, size) => size;
|
|
# }
|
|
# lfs_file_close(&lfs, &file) => 0;
|
|
# lfs_remove(&lfs, "padding") => 0;
|
|
#
|
|
# lfs_file_open(&lfs, &file, "exhaustion", LFS_O_WRONLY | LFS_O_CREAT);
|
|
# size = strlen("exhaustion");
|
|
# memcpy(buffer, "exhaustion", size);
|
|
# lfs_file_write(&lfs, &file, buffer, size) => size;
|
|
# lfs_file_sync(&lfs, &file) => 0;
|
|
#
|
|
# size = strlen("blahblahblahblah");
|
|
# memcpy(buffer, "blahblahblahblah", size);
|
|
# lfs_ssize_t res;
|
|
# while (true) {
|
|
# res = lfs_file_write(&lfs, &file, buffer, size);
|
|
# if (res < 0) {
|
|
# break;
|
|
# }
|
|
#
|
|
# res => size;
|
|
# }
|
|
# res => LFS_ERR_NOSPC;
|
|
#
|
|
# lfs_file_close(&lfs, &file) => 0;
|
|
# lfs_unmount(&lfs) => 0;
|
|
#
|
|
# lfs_mount(&lfs, cfg) => 0;
|
|
# lfs_file_open(&lfs, &file, "exhaustion", LFS_O_RDONLY);
|
|
# size = strlen("exhaustion");
|
|
# lfs_file_size(&lfs, &file) => size;
|
|
# lfs_file_read(&lfs, &file, buffer, size) => size;
|
|
# memcmp(buffer, "exhaustion", size) => 0;
|
|
# lfs_file_close(&lfs, &file) => 0;
|
|
# lfs_remove(&lfs, "exhaustion") => 0;
|
|
# lfs_unmount(&lfs) => 0;
|
|
#'''
|
|
#
|
|
## dir exhaustion test
|
|
#[cases.test_alloc_dir_exhaustion]
|
|
#code = '''
|
|
# lfs_t lfs;
|
|
# lfs_format(&lfs, cfg) => 0;
|
|
# lfs_mount(&lfs, cfg) => 0;
|
|
#
|
|
# // find out max file size
|
|
# lfs_mkdir(&lfs, "exhaustiondir") => 0;
|
|
# size_t size = strlen("blahblahblahblah");
|
|
# uint8_t buffer[1024];
|
|
# memcpy(buffer, "blahblahblahblah", size);
|
|
# lfs_file_t file;
|
|
# lfs_file_open(&lfs, &file, "exhaustion", LFS_O_WRONLY | LFS_O_CREAT);
|
|
# int count = 0;
|
|
# int err;
|
|
# while (true) {
|
|
# err = lfs_file_write(&lfs, &file, buffer, size);
|
|
# if (err < 0) {
|
|
# break;
|
|
# }
|
|
#
|
|
# count += 1;
|
|
# }
|
|
# err => LFS_ERR_NOSPC;
|
|
# lfs_file_close(&lfs, &file) => 0;
|
|
#
|
|
# lfs_remove(&lfs, "exhaustion") => 0;
|
|
# lfs_remove(&lfs, "exhaustiondir") => 0;
|
|
#
|
|
# // see if dir fits with max file size
|
|
# lfs_file_open(&lfs, &file, "exhaustion", LFS_O_WRONLY | LFS_O_CREAT);
|
|
# for (int i = 0; i < count; i++) {
|
|
# lfs_file_write(&lfs, &file, buffer, size) => size;
|
|
# }
|
|
# lfs_file_close(&lfs, &file) => 0;
|
|
#
|
|
# lfs_mkdir(&lfs, "exhaustiondir") => 0;
|
|
# lfs_remove(&lfs, "exhaustiondir") => 0;
|
|
# lfs_remove(&lfs, "exhaustion") => 0;
|
|
#
|
|
# // see if dir fits with > max file size
|
|
# lfs_file_open(&lfs, &file, "exhaustion", LFS_O_WRONLY | LFS_O_CREAT);
|
|
# for (int i = 0; i < count+1; i++) {
|
|
# lfs_file_write(&lfs, &file, buffer, size) => size;
|
|
# }
|
|
# lfs_file_close(&lfs, &file) => 0;
|
|
#
|
|
# lfs_mkdir(&lfs, "exhaustiondir") => LFS_ERR_NOSPC;
|
|
#
|
|
# lfs_remove(&lfs, "exhaustion") => 0;
|
|
# lfs_unmount(&lfs) => 0;
|
|
#'''
|
|
#
|
|
## what if we have a bad block during an allocation scan?
|
|
#[cases.test_alloc_bad_blocks]
|
|
#in = "lfs.c"
|
|
#defines.ERASE_CYCLES = 0xffffffff
|
|
#defines.BADBLOCK_BEHAVIOR = 'LFS_EMUBD_BADBLOCK_READERROR'
|
|
#code = '''
|
|
# lfs_t lfs;
|
|
# lfs_format(&lfs, cfg) => 0;
|
|
# lfs_mount(&lfs, cfg) => 0;
|
|
# // first fill to exhaustion to find available space
|
|
# lfs_file_t file;
|
|
# lfs_file_open(&lfs, &file, "pacman", LFS_O_WRONLY | LFS_O_CREAT) => 0;
|
|
# uint8_t buffer[1024];
|
|
# strcpy((char*)buffer, "waka");
|
|
# size_t size = strlen("waka");
|
|
# lfs_size_t filesize = 0;
|
|
# while (true) {
|
|
# lfs_ssize_t res = lfs_file_write(&lfs, &file, buffer, size);
|
|
# assert(res == (lfs_ssize_t)size || res == LFS_ERR_NOSPC);
|
|
# if (res == LFS_ERR_NOSPC) {
|
|
# break;
|
|
# }
|
|
# filesize += size;
|
|
# }
|
|
# lfs_file_close(&lfs, &file) => 0;
|
|
# // now fill all but a couple of blocks of the filesystem with data
|
|
# filesize -= 3*BLOCK_SIZE;
|
|
# lfs_file_open(&lfs, &file, "pacman", LFS_O_WRONLY | LFS_O_CREAT) => 0;
|
|
# strcpy((char*)buffer, "waka");
|
|
# size = strlen("waka");
|
|
# for (lfs_size_t i = 0; i < filesize/size; i++) {
|
|
# lfs_file_write(&lfs, &file, buffer, size) => size;
|
|
# }
|
|
# lfs_file_close(&lfs, &file) => 0;
|
|
# // also save head of file so we can error during lookahead scan
|
|
# lfs_block_t fileblock = file.ctz.head;
|
|
# lfs_unmount(&lfs) => 0;
|
|
#
|
|
# // remount to force an alloc scan
|
|
# lfs_mount(&lfs, cfg) => 0;
|
|
#
|
|
# // but mark the head of our file as a "bad block", this is force our
|
|
# // scan to bail early
|
|
# lfs_emubd_setwear(cfg, fileblock, 0xffffffff) => 0;
|
|
# lfs_file_open(&lfs, &file, "ghost", LFS_O_WRONLY | LFS_O_CREAT) => 0;
|
|
# strcpy((char*)buffer, "chomp");
|
|
# size = strlen("chomp");
|
|
# while (true) {
|
|
# lfs_ssize_t res = lfs_file_write(&lfs, &file, buffer, size);
|
|
# assert(res == (lfs_ssize_t)size || res == LFS_ERR_CORRUPT);
|
|
# if (res == LFS_ERR_CORRUPT) {
|
|
# break;
|
|
# }
|
|
# }
|
|
# lfs_file_close(&lfs, &file) => 0;
|
|
#
|
|
# // now reverse the "bad block" and try to write the file again until we
|
|
# // run out of space
|
|
# lfs_emubd_setwear(cfg, fileblock, 0) => 0;
|
|
# lfs_file_open(&lfs, &file, "ghost", LFS_O_WRONLY | LFS_O_CREAT) => 0;
|
|
# strcpy((char*)buffer, "chomp");
|
|
# size = strlen("chomp");
|
|
# while (true) {
|
|
# lfs_ssize_t res = lfs_file_write(&lfs, &file, buffer, size);
|
|
# assert(res == (lfs_ssize_t)size || res == LFS_ERR_NOSPC);
|
|
# if (res == LFS_ERR_NOSPC) {
|
|
# break;
|
|
# }
|
|
# }
|
|
# lfs_file_close(&lfs, &file) => 0;
|
|
#
|
|
# lfs_unmount(&lfs) => 0;
|
|
#
|
|
# // check that the disk isn't hurt
|
|
# lfs_mount(&lfs, cfg) => 0;
|
|
# lfs_file_open(&lfs, &file, "pacman", LFS_O_RDONLY) => 0;
|
|
# strcpy((char*)buffer, "waka");
|
|
# size = strlen("waka");
|
|
# for (lfs_size_t i = 0; i < filesize/size; i++) {
|
|
# uint8_t rbuffer[4];
|
|
# lfs_file_read(&lfs, &file, rbuffer, size) => size;
|
|
# assert(memcmp(rbuffer, buffer, size) == 0);
|
|
# }
|
|
# lfs_file_close(&lfs, &file) => 0;
|
|
# lfs_unmount(&lfs) => 0;
|
|
#'''
|
|
#
|
|
#
|
|
## Below, I don't like these tests. They're fragile and depend _heavily_
|
|
## on the geometry of the block device. But they are valuable. Eventually they
|
|
## should be removed and replaced with generalized tests.
|
|
#
|
|
## chained dir exhaustion test
|
|
#[cases.test_alloc_chained_dir_exhaustion]
|
|
#if = 'BLOCK_SIZE == 512'
|
|
#defines.BLOCK_COUNT = 1024
|
|
#code = '''
|
|
# lfs_t lfs;
|
|
# lfs_format(&lfs, cfg) => 0;
|
|
# lfs_mount(&lfs, cfg) => 0;
|
|
#
|
|
# // find out max file size
|
|
# lfs_mkdir(&lfs, "exhaustiondir") => 0;
|
|
# for (int i = 0; i < 10; i++) {
|
|
# char path[1024];
|
|
# sprintf(path, "dirwithanexhaustivelylongnameforpadding%d", i);
|
|
# lfs_mkdir(&lfs, path) => 0;
|
|
# }
|
|
# size_t size = strlen("blahblahblahblah");
|
|
# uint8_t buffer[1024];
|
|
# memcpy(buffer, "blahblahblahblah", size);
|
|
# lfs_file_t file;
|
|
# lfs_file_open(&lfs, &file, "exhaustion", LFS_O_WRONLY | LFS_O_CREAT);
|
|
# int count = 0;
|
|
# int err;
|
|
# while (true) {
|
|
# err = lfs_file_write(&lfs, &file, buffer, size);
|
|
# if (err < 0) {
|
|
# break;
|
|
# }
|
|
#
|
|
# count += 1;
|
|
# }
|
|
# err => LFS_ERR_NOSPC;
|
|
# lfs_file_close(&lfs, &file) => 0;
|
|
#
|
|
# lfs_remove(&lfs, "exhaustion") => 0;
|
|
# lfs_remove(&lfs, "exhaustiondir") => 0;
|
|
# for (int i = 0; i < 10; i++) {
|
|
# char path[1024];
|
|
# sprintf(path, "dirwithanexhaustivelylongnameforpadding%d", i);
|
|
# lfs_remove(&lfs, path) => 0;
|
|
# }
|
|
#
|
|
# // see that chained dir fails
|
|
# lfs_file_open(&lfs, &file, "exhaustion", LFS_O_WRONLY | LFS_O_CREAT);
|
|
# for (int i = 0; i < count+1; i++) {
|
|
# lfs_file_write(&lfs, &file, buffer, size) => size;
|
|
# }
|
|
# lfs_file_sync(&lfs, &file) => 0;
|
|
#
|
|
# for (int i = 0; i < 10; i++) {
|
|
# char path[1024];
|
|
# sprintf(path, "dirwithanexhaustivelylongnameforpadding%d", i);
|
|
# lfs_mkdir(&lfs, path) => 0;
|
|
# }
|
|
#
|
|
# lfs_mkdir(&lfs, "exhaustiondir") => LFS_ERR_NOSPC;
|
|
#
|
|
# // shorten file to try a second chained dir
|
|
# while (true) {
|
|
# err = lfs_mkdir(&lfs, "exhaustiondir");
|
|
# if (err != LFS_ERR_NOSPC) {
|
|
# break;
|
|
# }
|
|
#
|
|
# lfs_ssize_t filesize = lfs_file_size(&lfs, &file);
|
|
# filesize > 0 => true;
|
|
#
|
|
# lfs_file_truncate(&lfs, &file, filesize - size) => 0;
|
|
# lfs_file_sync(&lfs, &file) => 0;
|
|
# }
|
|
# err => 0;
|
|
#
|
|
# lfs_mkdir(&lfs, "exhaustiondir2") => LFS_ERR_NOSPC;
|
|
#
|
|
# lfs_file_close(&lfs, &file) => 0;
|
|
# lfs_unmount(&lfs) => 0;
|
|
#'''
|
|
#
|
|
## split dir test
|
|
#[cases.test_alloc_split_dir]
|
|
#if = 'BLOCK_SIZE == 512'
|
|
#defines.BLOCK_COUNT = 1024
|
|
#code = '''
|
|
# lfs_t lfs;
|
|
# lfs_format(&lfs, cfg) => 0;
|
|
# lfs_mount(&lfs, cfg) => 0;
|
|
#
|
|
# // create one block hole for half a directory
|
|
# lfs_file_t file;
|
|
# lfs_file_open(&lfs, &file, "bump", LFS_O_WRONLY | LFS_O_CREAT) => 0;
|
|
# for (lfs_size_t i = 0; i < cfg->block_size; i += 2) {
|
|
# uint8_t buffer[1024];
|
|
# memcpy(&buffer[i], "hi", 2);
|
|
# }
|
|
# uint8_t buffer[1024];
|
|
# lfs_file_write(&lfs, &file, buffer, cfg->block_size) => cfg->block_size;
|
|
# lfs_file_close(&lfs, &file) => 0;
|
|
#
|
|
# lfs_file_open(&lfs, &file, "exhaustion", LFS_O_WRONLY | LFS_O_CREAT);
|
|
# size_t size = strlen("blahblahblahblah");
|
|
# memcpy(buffer, "blahblahblahblah", size);
|
|
# for (lfs_size_t i = 0;
|
|
# i < (cfg->block_count-4)*(cfg->block_size-8);
|
|
# i += size) {
|
|
# lfs_file_write(&lfs, &file, buffer, size) => size;
|
|
# }
|
|
# lfs_file_close(&lfs, &file) => 0;
|
|
#
|
|
# // remount to force reset of lookahead
|
|
# lfs_unmount(&lfs) => 0;
|
|
# lfs_mount(&lfs, cfg) => 0;
|
|
#
|
|
# // open hole
|
|
# lfs_remove(&lfs, "bump") => 0;
|
|
#
|
|
# lfs_mkdir(&lfs, "splitdir") => 0;
|
|
# lfs_file_open(&lfs, &file, "splitdir/bump",
|
|
# LFS_O_WRONLY | LFS_O_CREAT) => 0;
|
|
# for (lfs_size_t i = 0; i < cfg->block_size; i += 2) {
|
|
# memcpy(&buffer[i], "hi", 2);
|
|
# }
|
|
# lfs_file_write(&lfs, &file, buffer, 2*cfg->block_size) => LFS_ERR_NOSPC;
|
|
# lfs_file_close(&lfs, &file) => 0;
|
|
#
|
|
# lfs_unmount(&lfs) => 0;
|
|
#'''
|
|
#
|
|
## outdated lookahead test
|
|
#[cases.test_alloc_outdated_lookahead]
|
|
#if = 'BLOCK_SIZE == 512'
|
|
#defines.BLOCK_COUNT = 1024
|
|
#code = '''
|
|
# lfs_t lfs;
|
|
# lfs_format(&lfs, cfg) => 0;
|
|
# lfs_mount(&lfs, cfg) => 0;
|
|
#
|
|
# // fill completely with two files
|
|
# lfs_file_t file;
|
|
# lfs_file_open(&lfs, &file, "exhaustion1",
|
|
# LFS_O_WRONLY | LFS_O_CREAT) => 0;
|
|
# size_t size = strlen("blahblahblahblah");
|
|
# uint8_t buffer[1024];
|
|
# memcpy(buffer, "blahblahblahblah", size);
|
|
# for (lfs_size_t i = 0;
|
|
# i < ((cfg->block_count-2)/2)*(cfg->block_size-8);
|
|
# i += size) {
|
|
# lfs_file_write(&lfs, &file, buffer, size) => size;
|
|
# }
|
|
# lfs_file_close(&lfs, &file) => 0;
|
|
#
|
|
# lfs_file_open(&lfs, &file, "exhaustion2",
|
|
# LFS_O_WRONLY | LFS_O_CREAT) => 0;
|
|
# size = strlen("blahblahblahblah");
|
|
# memcpy(buffer, "blahblahblahblah", size);
|
|
# for (lfs_size_t i = 0;
|
|
# i < ((cfg->block_count-2+1)/2)*(cfg->block_size-8);
|
|
# i += size) {
|
|
# lfs_file_write(&lfs, &file, buffer, size) => size;
|
|
# }
|
|
# lfs_file_close(&lfs, &file) => 0;
|
|
#
|
|
# // remount to force reset of lookahead
|
|
# lfs_unmount(&lfs) => 0;
|
|
# lfs_mount(&lfs, cfg) => 0;
|
|
#
|
|
# // rewrite one file
|
|
# lfs_file_open(&lfs, &file, "exhaustion1",
|
|
# LFS_O_WRONLY | LFS_O_TRUNC) => 0;
|
|
# lfs_file_sync(&lfs, &file) => 0;
|
|
# size = strlen("blahblahblahblah");
|
|
# memcpy(buffer, "blahblahblahblah", size);
|
|
# for (lfs_size_t i = 0;
|
|
# i < ((cfg->block_count-2)/2)*(cfg->block_size-8);
|
|
# i += size) {
|
|
# lfs_file_write(&lfs, &file, buffer, size) => size;
|
|
# }
|
|
# lfs_file_close(&lfs, &file) => 0;
|
|
#
|
|
# // rewrite second file, this requires lookahead does not
|
|
# // use old population
|
|
# lfs_file_open(&lfs, &file, "exhaustion2",
|
|
# LFS_O_WRONLY | LFS_O_TRUNC) => 0;
|
|
# lfs_file_sync(&lfs, &file) => 0;
|
|
# size = strlen("blahblahblahblah");
|
|
# memcpy(buffer, "blahblahblahblah", size);
|
|
# for (lfs_size_t i = 0;
|
|
# i < ((cfg->block_count-2+1)/2)*(cfg->block_size-8);
|
|
# i += size) {
|
|
# lfs_file_write(&lfs, &file, buffer, size) => size;
|
|
# }
|
|
# lfs_file_close(&lfs, &file) => 0;
|
|
#
|
|
# lfs_unmount(&lfs) => 0;
|
|
#'''
|
|
#
|
|
## outdated lookahead and split dir test
|
|
#[cases.test_alloc_outdated_lookahead_split_dir]
|
|
#if = 'BLOCK_SIZE == 512'
|
|
#defines.BLOCK_COUNT = 1024
|
|
#code = '''
|
|
# lfs_t lfs;
|
|
# lfs_format(&lfs, cfg) => 0;
|
|
# lfs_mount(&lfs, cfg) => 0;
|
|
#
|
|
# // fill completely with two files
|
|
# lfs_file_t file;
|
|
# lfs_file_open(&lfs, &file, "exhaustion1",
|
|
# LFS_O_WRONLY | LFS_O_CREAT) => 0;
|
|
# size_t size = strlen("blahblahblahblah");
|
|
# uint8_t buffer[1024];
|
|
# memcpy(buffer, "blahblahblahblah", size);
|
|
# for (lfs_size_t i = 0;
|
|
# i < ((cfg->block_count-2)/2)*(cfg->block_size-8);
|
|
# i += size) {
|
|
# lfs_file_write(&lfs, &file, buffer, size) => size;
|
|
# }
|
|
# lfs_file_close(&lfs, &file) => 0;
|
|
#
|
|
# lfs_file_open(&lfs, &file, "exhaustion2",
|
|
# LFS_O_WRONLY | LFS_O_CREAT) => 0;
|
|
# size = strlen("blahblahblahblah");
|
|
# memcpy(buffer, "blahblahblahblah", size);
|
|
# for (lfs_size_t i = 0;
|
|
# i < ((cfg->block_count-2+1)/2)*(cfg->block_size-8);
|
|
# i += size) {
|
|
# lfs_file_write(&lfs, &file, buffer, size) => size;
|
|
# }
|
|
# lfs_file_close(&lfs, &file) => 0;
|
|
#
|
|
# // remount to force reset of lookahead
|
|
# lfs_unmount(&lfs) => 0;
|
|
# lfs_mount(&lfs, cfg) => 0;
|
|
#
|
|
# // rewrite one file with a hole of one block
|
|
# lfs_file_open(&lfs, &file, "exhaustion1",
|
|
# LFS_O_WRONLY | LFS_O_TRUNC) => 0;
|
|
# lfs_file_sync(&lfs, &file) => 0;
|
|
# size = strlen("blahblahblahblah");
|
|
# memcpy(buffer, "blahblahblahblah", size);
|
|
# for (lfs_size_t i = 0;
|
|
# i < ((cfg->block_count-2)/2 - 1)*(cfg->block_size-8);
|
|
# i += size) {
|
|
# lfs_file_write(&lfs, &file, buffer, size) => size;
|
|
# }
|
|
# lfs_file_close(&lfs, &file) => 0;
|
|
#
|
|
# // try to allocate a directory, should fail!
|
|
# lfs_mkdir(&lfs, "split") => LFS_ERR_NOSPC;
|
|
#
|
|
# // file should not fail
|
|
# lfs_file_open(&lfs, &file, "notasplit",
|
|
# LFS_O_WRONLY | LFS_O_CREAT) => 0;
|
|
# lfs_file_write(&lfs, &file, "hi", 2) => 2;
|
|
# lfs_file_close(&lfs, &file) => 0;
|
|
#
|
|
# lfs_unmount(&lfs) => 0;
|
|
#'''
|