Files
littlefs/tests/test_grow.toml
Christopher Haster bc9562c64e tests: Accidentally found a couple buffer overruns
- Off-by-one in test_btree_find_general[_sparse]_fuzz

  Because we can only create named btrees via splitting, these always
  start with one entry. If all operations are randomly selected to be
  splits, this can lead to an overflow of the sim buffer (sounds
  unlikely, but relatively easily for small N).

  The fix is to use a `for (lfs3_size_t i = 1; i < N; i++)` loop to
  account for the initial entry. Note we already use this in the
  test_btree_split_* tests.

  An alternative is allocating space for N+1 entries, but this seems
  unintuitive with N usually being associated with the upper bound on
  btree size.

- Off-by-one in our sim rename pattern

  When renaming, we don't bother to update sim_size, because after the
  rename the sim_size size will be unchanged. But this means the
  sim_size is out-of-date during the memmove that reinserts the renamed
  entry. Buffer overflow!

  To fix we just need to use sim_size-1 to account for the temporarily
  deleted entry.

  This is messy C code, so not surprised it went unnoticed, even though
  this pattern ended up in quite a few tests.

Found while running with HEAP=1. This was just intended to test HEAP=1,
but I guess the injected heap hooks result in a more fragile heap? They
increase all allocations by one word, and maybe this reduces alignment
padding? Not exactly sure.

But it's a good argument for maybe adding heap canaries in the future.
Previously we ran Valgrind on all tests, but it's unclear if this will
still be reasonable with the number of tests we have now.
2026-02-19 12:39:20 -06:00

3162 lines
108 KiB
TOML

# Test variable block counts and grow related things
after = [
'test_dirs',
'test_files',
'test_fwrite',
'test_stickynotes',
'test_alloc',
'test_mount',
]
# Test both with and without the gbmap if available
defines.GBMAP = [false, true]
if = 'LFS3_IFYES_GBMAP(GBMAP, true, !GBMAP)'
defines.FORMAT_BLOCK_COUNT = '(GBMAP) ? 3 : 2'
# test we can mount a filesystem with fewer blocks
[cases.test_grow_mount_smaller]
defines.SMALLER_BLOCK_COUNT = [
'BLOCK_COUNT-1',
'BLOCK_COUNT/2',
'BLOCK_COUNT/4',
'FORMAT_BLOCK_COUNT',
]
defines.BIGGER_BLOCK_COUNT = [
'BLOCK_COUNT',
'BLOCK_COUNT-1',
'BLOCK_COUNT/2',
'BLOCK_COUNT/4',
]
if = 'BIGGER_BLOCK_COUNT > SMALLER_BLOCK_COUNT'
code = '''
// create a smaller fs
struct lfs3_cfg cfg = *CFG;
cfg.block_count = SMALLER_BLOCK_COUNT;
lfs3_t lfs3;
lfs3_format(&lfs3,
LFS3_F_RDWR
| ((GBMAP) ? LFS3_IFDEF_GBMAP(LFS3_F_GBMAP, -1) : 0),
&cfg) => 0;
lfs3_mount(&lfs3, LFS3_M_RDWR, &cfg) => 0;
// fsstat up to date?
struct lfs3_fsinfo fsinfo;
lfs3_fs_stat(&lfs3, &fsinfo) => 0;
assert(fsinfo.block_size == BLOCK_SIZE);
assert(fsinfo.block_count == SMALLER_BLOCK_COUNT);
assert(fsinfo.name_limit == LFS3_NAME_MAX);
assert(fsinfo.file_limit == LFS3_FILE_MAX);
// do some work
lfs3_file_t file;
lfs3_file_open(&lfs3, &file, "hello",
LFS3_O_WRONLY | LFS3_O_CREAT | LFS3_O_EXCL) => 0;
uint8_t wbuf[1024] = "Hello World!";
lfs3_size_t wsize = strlen((char*)wbuf);
lfs3_file_write(&lfs3, &file, wbuf, wsize) => wsize;
lfs3_file_close(&lfs3, &file) => 0;
struct lfs3_info info;
lfs3_stat(&lfs3, "hello", &info) => 0;
assert(strcmp(info.name, "hello") == 0);
assert(info.type == LFS3_TYPE_REG);
assert(info.size == wsize);
uint8_t rbuf[1024];
lfs3_file_open(&lfs3, &file, "hello", LFS3_O_RDONLY) => 0;
lfs3_file_read(&lfs3, &file, rbuf, wsize) => wsize;
lfs3_file_close(&lfs3, &file) => 0;
assert(memcmp(rbuf, wbuf, wsize) == 0);
lfs3_unmount(&lfs3) => 0;
//////
// try to mount with a bigger block count
cfg = *CFG;
cfg.block_count = BIGGER_BLOCK_COUNT;
lfs3_mount(&lfs3, LFS3_M_RDWR, &cfg) => 0;
// fsstat up to date?
lfs3_fs_stat(&lfs3, &fsinfo) => 0;
assert(fsinfo.block_size == BLOCK_SIZE);
assert(fsinfo.block_count == SMALLER_BLOCK_COUNT);
assert(fsinfo.name_limit == LFS3_NAME_MAX);
assert(fsinfo.file_limit == LFS3_FILE_MAX);
// file still exists?
lfs3_stat(&lfs3, "hello", &info) => 0;
assert(strcmp(info.name, "hello") == 0);
assert(info.type == LFS3_TYPE_REG);
assert(info.size == wsize);
lfs3_file_open(&lfs3, &file, "hello", LFS3_O_RDONLY) => 0;
lfs3_file_read(&lfs3, &file, rbuf, wsize) => wsize;
lfs3_file_close(&lfs3, &file) => 0;
assert(memcmp(rbuf, wbuf, wsize) == 0);
// do some work
lfs3_file_open(&lfs3, &file, "hello",
LFS3_O_WRONLY | LFS3_O_TRUNC) => 0;
strcpy((char*)wbuf, "Chris was here!");
wsize = strlen((char*)wbuf);
lfs3_file_write(&lfs3, &file, wbuf, wsize) => wsize;
lfs3_file_close(&lfs3, &file) => 0;
lfs3_stat(&lfs3, "hello", &info) => 0;
assert(strcmp(info.name, "hello") == 0);
assert(info.type == LFS3_TYPE_REG);
assert(info.size == wsize);
lfs3_file_open(&lfs3, &file, "hello", LFS3_O_RDONLY) => 0;
lfs3_file_read(&lfs3, &file, rbuf, wsize) => wsize;
lfs3_file_close(&lfs3, &file) => 0;
assert(memcmp(rbuf, wbuf, wsize) == 0);
lfs3_unmount(&lfs3) => 0;
// stays after a mount?
lfs3_mount(&lfs3, LFS3_M_RDWR, &cfg) => 0;
// fsstat up to date?
lfs3_fs_stat(&lfs3, &fsinfo) => 0;
assert(fsinfo.block_size == BLOCK_SIZE);
assert(fsinfo.block_count == SMALLER_BLOCK_COUNT);
assert(fsinfo.name_limit == LFS3_NAME_MAX);
assert(fsinfo.file_limit == LFS3_FILE_MAX);
// file still exists?
lfs3_stat(&lfs3, "hello", &info) => 0;
assert(strcmp(info.name, "hello") == 0);
assert(info.type == LFS3_TYPE_REG);
assert(info.size == wsize);
lfs3_file_open(&lfs3, &file, "hello", LFS3_O_RDONLY) => 0;
lfs3_file_read(&lfs3, &file, rbuf, wsize) => wsize;
lfs3_file_close(&lfs3, &file) => 0;
assert(memcmp(rbuf, wbuf, wsize) == 0);
lfs3_unmount(&lfs3) => 0;
'''
# test we _can't_ mount a filesystem with more blocks
[cases.test_grow_mount_bigger]
defines.SMALLER_BLOCK_COUNT = [
'BLOCK_COUNT-1',
'BLOCK_COUNT/2',
'BLOCK_COUNT/4',
'FORMAT_BLOCK_COUNT',
]
defines.BIGGER_BLOCK_COUNT = [
'BLOCK_COUNT',
'BLOCK_COUNT-1',
'BLOCK_COUNT/2',
'BLOCK_COUNT/4',
]
if = 'BIGGER_BLOCK_COUNT > SMALLER_BLOCK_COUNT'
code = '''
// create a bigger fs
struct lfs3_cfg cfg = *CFG;
cfg.block_count = BIGGER_BLOCK_COUNT;
lfs3_t lfs3;
lfs3_format(&lfs3,
LFS3_F_RDWR
| ((GBMAP) ? LFS3_IFDEF_GBMAP(LFS3_F_GBMAP, -1) : 0),
&cfg) => 0;
lfs3_mount(&lfs3, LFS3_M_RDWR, &cfg) => 0;
// fsstat up to date?
struct lfs3_fsinfo fsinfo;
lfs3_fs_stat(&lfs3, &fsinfo) => 0;
assert(fsinfo.block_size == BLOCK_SIZE);
assert(fsinfo.block_count == BIGGER_BLOCK_COUNT);
assert(fsinfo.name_limit == LFS3_NAME_MAX);
assert(fsinfo.file_limit == LFS3_FILE_MAX);
// do some work
lfs3_file_t file;
lfs3_file_open(&lfs3, &file, "hello",
LFS3_O_WRONLY | LFS3_O_CREAT | LFS3_O_EXCL) => 0;
uint8_t wbuf[1024] = "Hello World!";
lfs3_size_t wsize = strlen((char*)wbuf);
lfs3_file_write(&lfs3, &file, wbuf, wsize) => wsize;
lfs3_file_close(&lfs3, &file) => 0;
struct lfs3_info info;
lfs3_stat(&lfs3, "hello", &info) => 0;
assert(strcmp(info.name, "hello") == 0);
assert(info.type == LFS3_TYPE_REG);
assert(info.size == wsize);
uint8_t rbuf[1024];
lfs3_file_open(&lfs3, &file, "hello", LFS3_O_RDONLY) => 0;
lfs3_file_read(&lfs3, &file, rbuf, wsize) => wsize;
lfs3_file_close(&lfs3, &file) => 0;
assert(memcmp(rbuf, wbuf, wsize) == 0);
lfs3_unmount(&lfs3) => 0;
//////
// try to mount with a smaller block count
cfg = *CFG;
cfg.block_count = SMALLER_BLOCK_COUNT;
lfs3_mount(&lfs3, LFS3_M_RDWR, &cfg) => LFS3_ERR_NOTSUP;
'''
# test we can grow a filesystem
[cases.test_grow_grow]
defines.SMALLER_BLOCK_COUNT = [
'BLOCK_COUNT-1',
'BLOCK_COUNT/2',
'BLOCK_COUNT/4',
'FORMAT_BLOCK_COUNT',
]
defines.BIGGER_BLOCK_COUNT = [
'BLOCK_COUNT',
'BLOCK_COUNT-1',
'BLOCK_COUNT/2',
'BLOCK_COUNT/4',
]
if = 'BIGGER_BLOCK_COUNT > SMALLER_BLOCK_COUNT'
code = '''
// create a smaller fs
struct lfs3_cfg cfg = *CFG;
cfg.block_count = SMALLER_BLOCK_COUNT;
lfs3_t lfs3;
lfs3_format(&lfs3,
LFS3_F_RDWR
| ((GBMAP) ? LFS3_IFDEF_GBMAP(LFS3_F_GBMAP, -1) : 0),
&cfg) => 0;
lfs3_mount(&lfs3, LFS3_M_RDWR, &cfg) => 0;
// fsstat up to date?
struct lfs3_fsinfo fsinfo;
lfs3_fs_stat(&lfs3, &fsinfo) => 0;
assert(fsinfo.block_size == BLOCK_SIZE);
assert(fsinfo.block_count == SMALLER_BLOCK_COUNT);
assert(fsinfo.name_limit == LFS3_NAME_MAX);
assert(fsinfo.file_limit == LFS3_FILE_MAX);
// do some work
lfs3_file_t file;
lfs3_file_open(&lfs3, &file, "hello",
LFS3_O_WRONLY | LFS3_O_CREAT | LFS3_O_EXCL) => 0;
uint8_t wbuf[1024] = "Hello World!";
lfs3_size_t wsize = strlen((char*)wbuf);
lfs3_file_write(&lfs3, &file, wbuf, wsize) => wsize;
lfs3_file_close(&lfs3, &file) => 0;
struct lfs3_info info;
lfs3_stat(&lfs3, "hello", &info) => 0;
assert(strcmp(info.name, "hello") == 0);
assert(info.type == LFS3_TYPE_REG);
assert(info.size == wsize);
uint8_t rbuf[1024];
lfs3_file_open(&lfs3, &file, "hello", LFS3_O_RDONLY) => 0;
lfs3_file_read(&lfs3, &file, rbuf, wsize) => wsize;
lfs3_file_close(&lfs3, &file) => 0;
assert(memcmp(rbuf, wbuf, wsize) == 0);
lfs3_unmount(&lfs3) => 0;
//////
// try to grow our filesystem
cfg = *CFG;
cfg.block_count = BIGGER_BLOCK_COUNT;
lfs3_mount(&lfs3, LFS3_M_RDWR, &cfg) => 0;
lfs3_fs_grow(&lfs3, BIGGER_BLOCK_COUNT) => 0;
// fsstat up to date?
lfs3_fs_stat(&lfs3, &fsinfo) => 0;
assert(fsinfo.block_size == BLOCK_SIZE);
assert(fsinfo.block_count == BIGGER_BLOCK_COUNT);
assert(fsinfo.name_limit == LFS3_NAME_MAX);
assert(fsinfo.file_limit == LFS3_FILE_MAX);
// file still exists?
lfs3_stat(&lfs3, "hello", &info) => 0;
assert(strcmp(info.name, "hello") == 0);
assert(info.type == LFS3_TYPE_REG);
assert(info.size == wsize);
lfs3_file_open(&lfs3, &file, "hello", LFS3_O_RDONLY) => 0;
lfs3_file_read(&lfs3, &file, rbuf, wsize) => wsize;
lfs3_file_close(&lfs3, &file) => 0;
assert(memcmp(rbuf, wbuf, wsize) == 0);
// do some work
lfs3_file_open(&lfs3, &file, "hello",
LFS3_O_WRONLY | LFS3_O_TRUNC) => 0;
strcpy((char*)wbuf, "Chris was here!");
wsize = strlen((char*)wbuf);
lfs3_file_write(&lfs3, &file, wbuf, wsize) => wsize;
lfs3_file_close(&lfs3, &file) => 0;
lfs3_stat(&lfs3, "hello", &info) => 0;
assert(strcmp(info.name, "hello") == 0);
assert(info.type == LFS3_TYPE_REG);
assert(info.size == wsize);
lfs3_file_open(&lfs3, &file, "hello", LFS3_O_RDONLY) => 0;
lfs3_file_read(&lfs3, &file, rbuf, wsize) => wsize;
lfs3_file_close(&lfs3, &file) => 0;
assert(memcmp(rbuf, wbuf, wsize) == 0);
lfs3_unmount(&lfs3) => 0;
// stays after a mount?
lfs3_mount(&lfs3, LFS3_M_RDWR, &cfg) => 0;
// fsstat up to date?
lfs3_fs_stat(&lfs3, &fsinfo) => 0;
assert(fsinfo.block_size == BLOCK_SIZE);
assert(fsinfo.block_count == BIGGER_BLOCK_COUNT);
assert(fsinfo.name_limit == LFS3_NAME_MAX);
assert(fsinfo.file_limit == LFS3_FILE_MAX);
// file still exists?
lfs3_stat(&lfs3, "hello", &info) => 0;
assert(strcmp(info.name, "hello") == 0);
assert(info.type == LFS3_TYPE_REG);
assert(info.size == wsize);
lfs3_file_open(&lfs3, &file, "hello", LFS3_O_RDONLY) => 0;
lfs3_file_read(&lfs3, &file, rbuf, wsize) => wsize;
lfs3_file_close(&lfs3, &file) => 0;
assert(memcmp(rbuf, wbuf, wsize) == 0);
lfs3_unmount(&lfs3) => 0;
'''
# growing to the same size should do nothing
[cases.test_grow_noop]
defines.SMALLER_BLOCK_COUNT = [
'BLOCK_COUNT',
'BLOCK_COUNT-1',
'BLOCK_COUNT/2',
'BLOCK_COUNT/4',
'FORMAT_BLOCK_COUNT',
]
code = '''
// create a smaller fs
struct lfs3_cfg cfg = *CFG;
cfg.block_count = SMALLER_BLOCK_COUNT;
lfs3_t lfs3;
lfs3_format(&lfs3,
LFS3_F_RDWR
| ((GBMAP) ? LFS3_IFDEF_GBMAP(LFS3_F_GBMAP, -1) : 0),
&cfg) => 0;
lfs3_mount(&lfs3, LFS3_M_RDWR, &cfg) => 0;
// fsstat up to date?
struct lfs3_fsinfo fsinfo;
lfs3_fs_stat(&lfs3, &fsinfo) => 0;
assert(fsinfo.block_size == BLOCK_SIZE);
assert(fsinfo.block_count == SMALLER_BLOCK_COUNT);
assert(fsinfo.name_limit == LFS3_NAME_MAX);
assert(fsinfo.file_limit == LFS3_FILE_MAX);
// do some work
lfs3_file_t file;
lfs3_file_open(&lfs3, &file, "hello",
LFS3_O_WRONLY | LFS3_O_CREAT | LFS3_O_EXCL) => 0;
uint8_t wbuf[1024] = "Hello World!";
lfs3_size_t wsize = strlen((char*)wbuf);
lfs3_file_write(&lfs3, &file, wbuf, wsize) => wsize;
lfs3_file_close(&lfs3, &file) => 0;
struct lfs3_info info;
lfs3_stat(&lfs3, "hello", &info) => 0;
assert(strcmp(info.name, "hello") == 0);
assert(info.type == LFS3_TYPE_REG);
assert(info.size == wsize);
uint8_t rbuf[1024];
lfs3_file_open(&lfs3, &file, "hello", LFS3_O_RDONLY) => 0;
lfs3_file_read(&lfs3, &file, rbuf, wsize) => wsize;
lfs3_file_close(&lfs3, &file) => 0;
assert(memcmp(rbuf, wbuf, wsize) == 0);
lfs3_unmount(&lfs3) => 0;
//////
// try to grow to same size
cfg = *CFG;
cfg.block_count = SMALLER_BLOCK_COUNT;
lfs3_mount(&lfs3, LFS3_M_RDWR, &cfg) => 0;
lfs3_fs_grow(&lfs3, SMALLER_BLOCK_COUNT) => 0;
// fsstat up to date?
lfs3_fs_stat(&lfs3, &fsinfo) => 0;
assert(fsinfo.block_size == BLOCK_SIZE);
assert(fsinfo.block_count == SMALLER_BLOCK_COUNT);
assert(fsinfo.name_limit == LFS3_NAME_MAX);
assert(fsinfo.file_limit == LFS3_FILE_MAX);
// file still exists?
lfs3_stat(&lfs3, "hello", &info) => 0;
assert(strcmp(info.name, "hello") == 0);
assert(info.type == LFS3_TYPE_REG);
assert(info.size == wsize);
lfs3_file_open(&lfs3, &file, "hello", LFS3_O_RDONLY) => 0;
lfs3_file_read(&lfs3, &file, rbuf, wsize) => wsize;
lfs3_file_close(&lfs3, &file) => 0;
assert(memcmp(rbuf, wbuf, wsize) == 0);
// do some work
lfs3_file_open(&lfs3, &file, "hello",
LFS3_O_WRONLY | LFS3_O_TRUNC) => 0;
strcpy((char*)wbuf, "Chris was here!");
wsize = strlen((char*)wbuf);
lfs3_file_write(&lfs3, &file, wbuf, wsize) => wsize;
lfs3_file_close(&lfs3, &file) => 0;
lfs3_stat(&lfs3, "hello", &info) => 0;
assert(strcmp(info.name, "hello") == 0);
assert(info.type == LFS3_TYPE_REG);
assert(info.size == wsize);
lfs3_file_open(&lfs3, &file, "hello", LFS3_O_RDONLY) => 0;
lfs3_file_read(&lfs3, &file, rbuf, wsize) => wsize;
lfs3_file_close(&lfs3, &file) => 0;
assert(memcmp(rbuf, wbuf, wsize) == 0);
lfs3_unmount(&lfs3) => 0;
// stays after a mount?
lfs3_mount(&lfs3, LFS3_M_RDWR, &cfg) => 0;
// fsstat up to date?
lfs3_fs_stat(&lfs3, &fsinfo) => 0;
assert(fsinfo.block_size == BLOCK_SIZE);
assert(fsinfo.block_count == SMALLER_BLOCK_COUNT);
assert(fsinfo.name_limit == LFS3_NAME_MAX);
assert(fsinfo.file_limit == LFS3_FILE_MAX);
// file still exists?
lfs3_stat(&lfs3, "hello", &info) => 0;
assert(strcmp(info.name, "hello") == 0);
assert(info.type == LFS3_TYPE_REG);
assert(info.size == wsize);
lfs3_file_open(&lfs3, &file, "hello", LFS3_O_RDONLY) => 0;
lfs3_file_read(&lfs3, &file, rbuf, wsize) => wsize;
lfs3_file_close(&lfs3, &file) => 0;
assert(memcmp(rbuf, wbuf, wsize) == 0);
lfs3_unmount(&lfs3) => 0;
'''
# These tests try various fuzz tests while incrementally growing the
# filesystem. When encountering LFS3_ERR_NOSPC, the filesystem is grown by
# one block. Hopefully this will catch most grow-related bugs.
#
[cases.test_grow_incr_spam_dir_many]
defines.REMOUNT = [false, true]
defines.N = [1, 2, 4, 8, 16, 32, 64, 128, 256, 512]
code = '''
// start with a small number of blocks
struct lfs3_cfg cfg = *CFG;
cfg.block_count = FORMAT_BLOCK_COUNT;
lfs3_t lfs3;
lfs3_format(&lfs3,
LFS3_F_RDWR
| ((GBMAP) ? LFS3_IFDEF_GBMAP(LFS3_F_GBMAP, -1) : 0),
&cfg) => 0;
// mount with maximum block count
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0;
// fsstat up to date?
struct lfs3_fsinfo fsinfo;
lfs3_fs_stat(&lfs3, &fsinfo) => 0;
assert(fsinfo.block_size == BLOCK_SIZE);
assert(fsinfo.block_count == FORMAT_BLOCK_COUNT);
assert(fsinfo.name_limit == LFS3_NAME_MAX);
assert(fsinfo.file_limit == LFS3_FILE_MAX);
// make this many directories
for (lfs3_size_t i = 0; i < N; i++) {
again:;
char name[256];
sprintf(name, "dir%03x", i);
int err = lfs3_mkdir(&lfs3, name);
assert(!err || err == LFS3_ERR_NOSPC);
if (err == LFS3_ERR_NOSPC) {
goto grow;
}
continue;
grow:;
// try growing the filesystem
struct lfs3_fsinfo fsinfo;
lfs3_fs_stat(&lfs3, &fsinfo) => 0;
assert(fsinfo.block_count >= FORMAT_BLOCK_COUNT);
assert(fsinfo.block_count <= BLOCK_COUNT);
lfs3_ssize_t used = lfs3_fs_usage(&lfs3);
assert(used >= 0);
// we may need to grow multiple blocks before the system gets unstuck
lfs3_size_t block_count_ = fsinfo.block_count;
while (true) {
assert(block_count_ < BLOCK_COUNT);
block_count_ += 1;
err = lfs3_fs_grow(&lfs3, block_count_);
assert(!err || err == LFS3_ERR_NOSPC);
if (err == LFS3_ERR_NOSPC) {
continue;
}
break;
}
printf("grew %d/%d -> %d/%d\n",
used, fsinfo.block_count,
used, block_count_);
// remount?
if (REMOUNT) {
lfs3_unmount(&lfs3) => 0;
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0;
}
// fsstat up to date?
lfs3_fs_stat(&lfs3, &fsinfo) => 0;
assert(fsinfo.block_size == BLOCK_SIZE);
assert(fsinfo.block_count == block_count_);
assert(fsinfo.name_limit == LFS3_NAME_MAX);
assert(fsinfo.file_limit == LFS3_FILE_MAX);
goto again;
}
for (int remount = 0; remount < 2; remount++) {
// remount?
if (remount) {
lfs3_unmount(&lfs3) => 0;
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0;
}
// grm should be zero here
assert(lfs3.grm_p[0] == 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;
for (lfs3_size_t i = 0; i < N; i++) {
char name[256];
sprintf(name, "dir%03x", i);
lfs3_dir_open(&lfs3, &dir, name) => 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);
lfs3_dir_read(&lfs3, &dir, &info) => LFS3_ERR_NOENT;
lfs3_dir_close(&lfs3, &dir) => 0;
}
}
lfs3_unmount(&lfs3) => 0;
'''
[cases.test_grow_incr_spam_dir_fuzz]
defines.REMOUNT = [false, true]
defines.N = [1, 2, 4, 8, 16, 32, 64, 128, 256]
defines.OPS = 1024
defines.SEED = 'range(10)'
fuzz = 'SEED'
code = '''
// start with a small number of blocks
struct lfs3_cfg cfg = *CFG;
cfg.block_count = FORMAT_BLOCK_COUNT;
lfs3_t lfs3;
lfs3_format(&lfs3,
LFS3_F_RDWR
| ((GBMAP) ? LFS3_IFDEF_GBMAP(LFS3_F_GBMAP, -1) : 0),
&cfg) => 0;
// mount with maximum block count
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0;
// fsstat up to date?
struct lfs3_fsinfo fsinfo;
lfs3_fs_stat(&lfs3, &fsinfo) => 0;
assert(fsinfo.block_size == BLOCK_SIZE);
assert(fsinfo.block_count == FORMAT_BLOCK_COUNT);
assert(fsinfo.name_limit == LFS3_NAME_MAX);
assert(fsinfo.file_limit == LFS3_FILE_MAX);
// set up a simulation to compare against
lfs3_size_t *sim = malloc(N*sizeof(lfs3_size_t));
lfs3_size_t sim_size = 0;
uint32_t prng = SEED;
for (lfs3_size_t i = 0; i < OPS; i++) {
again:;
uint32_t prng_ = prng;
// choose a pseudo-random op, either mkdir, remove, or rename
uint8_t op = TEST_PRNG(&prng_) % 3;
if (op == 0 || sim_size == 0) {
// choose a pseudo-random number, truncate to 3 hexadecimals
lfs3_size_t x = TEST_PRNG(&prng_) % N;
// create a directory here
char name[256];
sprintf(name, "dir%03x", x);
int err = lfs3_mkdir(&lfs3, name);
assert(!err || err == LFS3_ERR_EXIST || err == LFS3_ERR_NOSPC);
if (err == LFS3_ERR_NOSPC) {
goto grow;
}
// insert into our sim
for (lfs3_size_t j = 0;; j++) {
if (j >= sim_size || sim[j] >= x) {
// already seen?
if (j < sim_size && sim[j] == x) {
// do nothing
} else {
// insert
memmove(&sim[j+1], &sim[j],
(sim_size-j)*sizeof(lfs3_size_t));
sim_size += 1;
sim[j] = x;
}
break;
}
}
} else if (op == 1) {
// choose a pseudo-random entry to delete
lfs3_size_t j = TEST_PRNG(&prng_) % sim_size;
lfs3_size_t x = sim[j];
// remove this directory
char name[256];
sprintf(name, "dir%03x", x);
int err = lfs3_remove(&lfs3, name);
assert(!err || err == LFS3_ERR_NOSPC);
if (err == LFS3_ERR_NOSPC) {
goto grow;
}
// delete from our sim
memmove(&sim[j], &sim[j+1],
(sim_size-(j+1))*sizeof(lfs3_size_t));
sim_size -= 1;
} else {
// choose a pseudo-random entry to rename, and a pseudo-random
// number to rename to
lfs3_size_t j = TEST_PRNG(&prng_) % sim_size;
lfs3_size_t x = sim[j];
lfs3_size_t y = TEST_PRNG(&prng_) % N;
// rename this directory
char old_name[256];
sprintf(old_name, "dir%03x", x);
char new_name[256];
sprintf(new_name, "dir%03x", y);
int err = lfs3_rename(&lfs3, old_name, new_name);
assert(!err || err == LFS3_ERR_NOSPC);
if (err == LFS3_ERR_NOSPC) {
goto grow;
}
for (lfs3_size_t k = 0;; k++) {
if (k >= sim_size || sim[k] >= y) {
// already seen and not a noop?
if (k < sim_size && sim[k] == y && x != y) {
// just delete the original entry
memmove(&sim[j], &sim[j+1],
(sim_size-(j+1))*sizeof(lfs3_size_t));
sim_size -= 1;
} else {
// first delete
memmove(&sim[j], &sim[j+1],
(sim_size-(j+1))*sizeof(lfs3_size_t));
if (k > j) {
k -= 1;
}
// then insert
memmove(&sim[k+1], &sim[k],
(sim_size-1-k)*sizeof(lfs3_size_t));
sim[k] = y;
}
break;
}
}
}
prng = prng_;
continue;
grow:;
// try growing the filesystem
struct lfs3_fsinfo fsinfo;
lfs3_fs_stat(&lfs3, &fsinfo) => 0;
assert(fsinfo.block_count >= FORMAT_BLOCK_COUNT);
assert(fsinfo.block_count <= BLOCK_COUNT);
lfs3_ssize_t used = lfs3_fs_usage(&lfs3);
assert(used >= 0);
// we may need to grow multiple blocks before the system gets unstuck
lfs3_size_t block_count_ = fsinfo.block_count;
while (true) {
assert(block_count_ < BLOCK_COUNT);
block_count_ += 1;
int err = lfs3_fs_grow(&lfs3, block_count_);
assert(!err || err == LFS3_ERR_NOSPC);
if (err == LFS3_ERR_NOSPC) {
continue;
}
break;
}
printf("grew %d/%d -> %d/%d\n",
used, fsinfo.block_count,
used, block_count_);
// remount?
if (REMOUNT) {
lfs3_unmount(&lfs3) => 0;
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0;
}
// fsstat up to date?
lfs3_fs_stat(&lfs3, &fsinfo) => 0;
assert(fsinfo.block_size == BLOCK_SIZE);
assert(fsinfo.block_count == block_count_);
assert(fsinfo.name_limit == LFS3_NAME_MAX);
assert(fsinfo.file_limit == LFS3_FILE_MAX);
goto again;
}
for (int remount = 0; remount < 2; remount++) {
// remount?
if (remount) {
lfs3_unmount(&lfs3) => 0;
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0;
}
// grm should be zero here
assert(lfs3.grm_p[0] == 0);
// test that our directories match our simulation
for (lfs3_size_t j = 0; j < sim_size; j++) {
char name[256];
sprintf(name, "dir%03x", sim[j]);
struct lfs3_info info;
lfs3_stat(&lfs3, name, &info) => 0;
char name2[256];
sprintf(name2, "dir%03x", sim[j]);
assert(strcmp(info.name, name2) == 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 j = 0; j < sim_size; j++) {
char name[256];
sprintf(name, "dir%03x", sim[j]);
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;
}
// clean up sim/lfs3
free(sim);
lfs3_unmount(&lfs3) => 0;
'''
[cases.test_grow_incr_spam_file_many]
defines.REMOUNT = [false, true]
defines.N = [1, 2, 4, 8, 16, 32, 64]
defines.SIZE = [
'0',
'FCACHE_SIZE/2',
'2*FCACHE_SIZE',
'BLOCK_SIZE/2',
'BLOCK_SIZE',
'2*BLOCK_SIZE',
'4*BLOCK_SIZE',
]
if = '(SIZE*N)/BLOCK_SIZE <= 32'
code = '''
// start with a small number of blocks
struct lfs3_cfg cfg = *CFG;
cfg.block_count = FORMAT_BLOCK_COUNT;
lfs3_t lfs3;
lfs3_format(&lfs3,
LFS3_F_RDWR
| ((GBMAP) ? LFS3_IFDEF_GBMAP(LFS3_F_GBMAP, -1) : 0),
&cfg) => 0;
// mount with maximum block count
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0;
// fsstat up to date?
struct lfs3_fsinfo fsinfo;
lfs3_fs_stat(&lfs3, &fsinfo) => 0;
assert(fsinfo.block_size == BLOCK_SIZE);
assert(fsinfo.block_count == FORMAT_BLOCK_COUNT);
assert(fsinfo.name_limit == LFS3_NAME_MAX);
assert(fsinfo.file_limit == LFS3_FILE_MAX);
uint32_t prng = 42;
for (lfs3_size_t i = 0; i < N; i++) {
again:;
uint32_t prng_ = prng;
// create this many files
char name[256];
sprintf(name, "amethyst%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;
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) {
goto grow;
}
lfs3_ssize_t d = lfs3_file_write(&lfs3, &file, wbuf, SIZE);
assert(d == SIZE || d == LFS3_ERR_NOSPC);
if (d == LFS3_ERR_NOSPC) {
lfs3_file_close(&lfs3, &file) => 0;
goto grow;
}
err = lfs3_file_close(&lfs3, &file);
assert(!err || err == LFS3_ERR_NOSPC);
if (err == LFS3_ERR_NOSPC) {
goto grow;
}
prng = prng_;
continue;
grow:;
// try growing the filesystem
struct lfs3_fsinfo fsinfo;
lfs3_fs_stat(&lfs3, &fsinfo) => 0;
assert(fsinfo.block_count >= FORMAT_BLOCK_COUNT);
assert(fsinfo.block_count <= BLOCK_COUNT);
lfs3_ssize_t used = lfs3_fs_usage(&lfs3);
assert(used >= 0);
// we may need to grow multiple blocks before the system gets unstuck
lfs3_size_t block_count_ = fsinfo.block_count;
while (true) {
assert(block_count_ < BLOCK_COUNT);
block_count_ += 1;
err = lfs3_fs_grow(&lfs3, block_count_);
assert(!err || err == LFS3_ERR_NOSPC);
if (err == LFS3_ERR_NOSPC) {
continue;
}
break;
}
printf("grew %d/%d -> %d/%d\n",
used, fsinfo.block_count,
used, block_count_);
// remount?
if (REMOUNT) {
lfs3_unmount(&lfs3) => 0;
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0;
}
// fsstat up to date?
lfs3_fs_stat(&lfs3, &fsinfo) => 0;
assert(fsinfo.block_size == BLOCK_SIZE);
assert(fsinfo.block_count == block_count_);
assert(fsinfo.name_limit == LFS3_NAME_MAX);
assert(fsinfo.file_limit == LFS3_FILE_MAX);
goto again;
}
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 writes worked
prng = 42;
for (lfs3_size_t i = 0; i < N; i++) {
// check with stat
char name[256];
sprintf(name, "amethyst%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;
'''
[cases.test_grow_incr_spam_file_fuzz]
defines.REMOUNT = [false, true]
defines.N = [1, 2, 4, 8, 16, 32, 64]
defines.OPS = 1024
defines.SIZE = [
'0',
'FCACHE_SIZE/2',
'2*FCACHE_SIZE',
'BLOCK_SIZE/2',
'BLOCK_SIZE',
'2*BLOCK_SIZE',
'4*BLOCK_SIZE',
]
defines.SEED = 'range(10)'
fuzz = 'SEED'
if = '(SIZE*N)/BLOCK_SIZE <= 16'
code = '''
// start with a small number of blocks
struct lfs3_cfg cfg = *CFG;
cfg.block_count = FORMAT_BLOCK_COUNT;
lfs3_t lfs3;
lfs3_format(&lfs3,
LFS3_F_RDWR
| ((GBMAP) ? LFS3_IFDEF_GBMAP(LFS3_F_GBMAP, -1) : 0),
&cfg) => 0;
// mount with maximum block count
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0;
// fsstat up to date?
struct lfs3_fsinfo fsinfo;
lfs3_fs_stat(&lfs3, &fsinfo) => 0;
assert(fsinfo.block_size == BLOCK_SIZE);
assert(fsinfo.block_count == FORMAT_BLOCK_COUNT);
assert(fsinfo.name_limit == LFS3_NAME_MAX);
assert(fsinfo.file_limit == LFS3_FILE_MAX);
// set up a simulation to compare against
lfs3_size_t *sim = malloc(N*sizeof(lfs3_size_t));
uint32_t *sim_prngs = malloc(N*sizeof(uint32_t));
lfs3_size_t sim_size = 0;
uint32_t prng = SEED;
for (lfs3_size_t i = 0; i < OPS; i++) {
again:;
uint32_t prng_ = prng;
// choose which operation to do
uint8_t op = TEST_PRNG(&prng_) % 3;
// creating a new file?
if (op == 0 || sim_size == 0) {
// choose a pseudo-random number
lfs3_size_t x = TEST_PRNG(&prng_) % N;
// associate each file with a prng that generates its contents
uint32_t wprng = TEST_PRNG(&prng_);
// create a file here
char name[256];
sprintf(name, "amethyst%03x", x);
uint8_t wbuf[SIZE];
uint32_t wprng_ = wprng;
for (lfs3_size_t j = 0; j < SIZE; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&wprng_) % 26);
}
lfs3_file_t file;
int err = lfs3_file_open(&lfs3, &file, name,
LFS3_O_WRONLY | LFS3_O_CREAT | LFS3_O_TRUNC);
assert(!err || err == LFS3_ERR_NOSPC);
if (err == LFS3_ERR_NOSPC) {
goto grow;
}
lfs3_ssize_t d = lfs3_file_write(&lfs3, &file, wbuf, SIZE);
assert(d == SIZE || d == LFS3_ERR_NOSPC);
if (d == LFS3_ERR_NOSPC) {
lfs3_file_close(&lfs3, &file) => 0;
goto grow;
}
err = lfs3_file_close(&lfs3, &file);
assert(!err || err == LFS3_ERR_NOSPC);
if (err == LFS3_ERR_NOSPC) {
goto grow;
}
// insert into our sim
for (lfs3_size_t j = 0;; j++) {
if (j >= sim_size || sim[j] >= x) {
// already seen?
if (j < sim_size && sim[j] == x) {
// new prng
sim_prngs[j] = wprng;
} else {
// insert
memmove(&sim[j+1], &sim[j],
(sim_size-j)*sizeof(lfs3_size_t));
memmove(&sim_prngs[j+1], &sim_prngs[j],
(sim_size-j)*sizeof(uint32_t));
sim_size += 1;
sim[j] = x;
sim_prngs[j] = wprng;
}
break;
}
}
// deleting a file?
} else if (op == 1) {
// choose a random file to delete
lfs3_size_t j = TEST_PRNG(&prng_) % sim_size;
lfs3_size_t x = sim[j];
// delete this file
char name[256];
sprintf(name, "amethyst%03x", x);
int err = lfs3_remove(&lfs3, name);
assert(!err || err == LFS3_ERR_NOSPC);
if (err == LFS3_ERR_NOSPC) {
goto grow;
}
// delete from our sim
memmove(&sim[j], &sim[j+1],
(sim_size-(j+1))*sizeof(lfs3_size_t));
memmove(&sim_prngs[j], &sim_prngs[j+1],
(sim_size-(j+1))*sizeof(uint32_t));
sim_size -= 1;
// renaming a file?
} else {
// choose a random file to rename, and a random number to
// rename to
lfs3_size_t j = TEST_PRNG(&prng_) % sim_size;
lfs3_size_t x = sim[j];
lfs3_size_t y = TEST_PRNG(&prng_) % N;
uint32_t wprng = sim_prngs[j];
// rename this file
char old_name[256];
sprintf(old_name, "amethyst%03x", x);
char new_name[256];
sprintf(new_name, "amethyst%03x", y);
int err = lfs3_rename(&lfs3, old_name, new_name);
assert(!err || err == LFS3_ERR_NOSPC);
if (err == LFS3_ERR_NOSPC) {
goto grow;
}
// update our sim
for (lfs3_size_t k = 0;; k++) {
if (k >= sim_size || sim[k] >= y) {
// renaming and replacing
if (k < sim_size && sim[k] == y && x != y) {
// delete the original entry
memmove(&sim[j], &sim[j+1],
(sim_size-(j+1))*sizeof(lfs3_size_t));
memmove(&sim_prngs[j], &sim_prngs[j+1],
(sim_size-(j+1))*sizeof(uint32_t));
sim_size -= 1;
if (k > j) {
k -= 1;
}
// update the prng
sim_prngs[k] = wprng;
// just renaming
} else {
// first delete
memmove(&sim[j], &sim[j+1],
(sim_size-(j+1))*sizeof(lfs3_size_t));
memmove(&sim_prngs[j], &sim_prngs[j+1],
(sim_size-(j+1))*sizeof(uint32_t));
if (k > j) {
k -= 1;
}
// then insert
memmove(&sim[k+1], &sim[k],
(sim_size-1-k)*sizeof(lfs3_size_t));
memmove(&sim_prngs[k+1], &sim_prngs[k],
(sim_size-1-k)*sizeof(uint32_t));
sim[k] = y;
sim_prngs[k] = wprng;
}
break;
}
}
}
prng = prng_;
continue;
grow:;
// try growing the filesystem
struct lfs3_fsinfo fsinfo;
lfs3_fs_stat(&lfs3, &fsinfo) => 0;
assert(fsinfo.block_count >= FORMAT_BLOCK_COUNT);
assert(fsinfo.block_count <= BLOCK_COUNT);
lfs3_ssize_t used = lfs3_fs_usage(&lfs3);
assert(used >= 0);
// we may need to grow multiple blocks before the system gets unstuck
lfs3_size_t block_count_ = fsinfo.block_count;
while (true) {
assert(block_count_ < BLOCK_COUNT);
block_count_ += 1;
int err = lfs3_fs_grow(&lfs3, block_count_);
assert(!err || err == LFS3_ERR_NOSPC);
if (err == LFS3_ERR_NOSPC) {
continue;
}
break;
}
printf("grew %d/%d -> %d/%d\n",
used, fsinfo.block_count,
used, block_count_);
// remount?
if (REMOUNT) {
lfs3_unmount(&lfs3) => 0;
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0;
}
// fsstat up to date?
lfs3_fs_stat(&lfs3, &fsinfo) => 0;
assert(fsinfo.block_size == BLOCK_SIZE);
assert(fsinfo.block_count == block_count_);
assert(fsinfo.name_limit == LFS3_NAME_MAX);
assert(fsinfo.file_limit == LFS3_FILE_MAX);
goto again;
}
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 files match our simulation
for (lfs3_size_t j = 0; j < sim_size; j++) {
char name[256];
sprintf(name, "amethyst%03x", sim[j]);
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);
}
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 j = 0; j < sim_size; j++) {
char name[256];
sprintf(name, "amethyst%03x", sim[j]);
lfs3_dir_read(&lfs3, &dir, &info) => 0;
assert(strcmp(info.name, name) == 0);
assert(info.type == LFS3_TYPE_REG);
assert(info.size == SIZE);
}
lfs3_dir_read(&lfs3, &dir, &info) => LFS3_ERR_NOENT;
lfs3_dir_close(&lfs3, &dir) => 0;
// check the file contents
for (lfs3_size_t j = 0; j < sim_size; j++) {
char name[256];
sprintf(name, "amethyst%03x", sim[j]);
lfs3_file_t file;
lfs3_file_open(&lfs3, &file, name, LFS3_O_RDONLY) => 0;
uint32_t wprng = sim_prngs[j];
uint8_t wbuf[SIZE];
for (lfs3_size_t j = 0; j < SIZE; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26);
}
uint8_t rbuf[SIZE];
lfs3_file_read(&lfs3, &file, rbuf, SIZE) => SIZE;
assert(memcmp(rbuf, wbuf, SIZE) == 0);
lfs3_file_close(&lfs3, &file) => 0;
}
}
// clean up sim/lfs3
free(sim);
free(sim_prngs);
lfs3_unmount(&lfs3) => 0;
'''
[cases.test_grow_incr_spam_uz_fuzz]
# you probably need to flush if you expect errors
defines.FLUSH = true
defines.N = [1, 2, 4, 8, 16, 32, 64]
defines.OPS = 1024
defines.SIZE = [
'0',
'FCACHE_SIZE/2',
'2*FCACHE_SIZE',
'BLOCK_SIZE/2',
'BLOCK_SIZE',
'2*BLOCK_SIZE',
'4*BLOCK_SIZE',
]
defines.SEED = 'range(10)'
fuzz = 'SEED'
if = '(SIZE*N)/BLOCK_SIZE <= 16'
code = '''
// start with a small number of blocks
struct lfs3_cfg cfg = *CFG;
cfg.block_count = FORMAT_BLOCK_COUNT;
lfs3_t lfs3;
lfs3_format(&lfs3,
LFS3_F_RDWR
| ((GBMAP) ? LFS3_IFDEF_GBMAP(LFS3_F_GBMAP, -1) : 0),
&cfg) => 0;
// mount with maximum block count
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0;
// fsstat up to date?
struct lfs3_fsinfo fsinfo;
lfs3_fs_stat(&lfs3, &fsinfo) => 0;
assert(fsinfo.block_size == BLOCK_SIZE);
assert(fsinfo.block_count == FORMAT_BLOCK_COUNT);
assert(fsinfo.name_limit == LFS3_NAME_MAX);
assert(fsinfo.file_limit == LFS3_FILE_MAX);
// set up a simulation to compare against
lfs3_size_t *sim = malloc(N*sizeof(lfs3_size_t));
uint32_t *sim_prngs = malloc(N*sizeof(uint32_t));
bool *sim_isstickys = malloc(N*sizeof(bool));
lfs3_size_t sim_size = 0;
typedef struct sim_file {
lfs3_size_t x;
bool sticky;
bool zombie;
uint32_t prng;
lfs3_file_t file;
} sim_file_t;
sim_file_t **sim_files = malloc(N*sizeof(sim_file_t*));
lfs3_size_t sim_file_count = 0;
uint32_t prng = SEED;
for (lfs3_size_t i = 0; i < OPS; i++) {
again:;
uint32_t prng_ = prng;
nonsense:;
// choose which operation to do
uint8_t op = TEST_PRNG(&prng_) % 5;
// open a new file?
if (op == 0) {
if (sim_file_count >= N) {
goto nonsense;
}
// choose a pseudo-random number
lfs3_size_t x = TEST_PRNG(&prng_) % N;
// already exists?
bool exist = false;
uint32_t wprng = 0;
bool sticky = true;
for (lfs3_size_t j = 0; j < sim_size; j++) {
if (sim[j] == x) {
exist = true;
wprng = sim_prngs[j];
sticky = sim_isstickys[j];
break;
}
}
// choose a random seed if we don't exist
if (!exist) {
wprng = TEST_PRNG(&prng_);
sticky = true;
}
lfs3_size_t j = sim_file_count;
sim_files[j] = malloc(sizeof(sim_file_t));
// open the actual file
char name[256];
sprintf(name, "batman%03x", x);
int err = lfs3_file_open(&lfs3, &sim_files[j]->file, name,
LFS3_O_RDWR
| LFS3_O_CREAT
| ((FLUSH) ? LFS3_O_FLUSH : 0));
assert(!err || err == LFS3_ERR_NOSPC);
if (err == LFS3_ERR_NOSPC) {
free(sim_files[j]);
goto grow;
}
// write some initial data if we don't exist
if (!exist || sticky) {
uint8_t wbuf[SIZE];
uint32_t wprng_ = wprng;
for (lfs3_size_t k = 0; k < SIZE; k++) {
wbuf[k] = 'a' + (TEST_PRNG(&wprng_) % 26);
}
lfs3_ssize_t d = lfs3_file_write(&lfs3, &sim_files[j]->file,
wbuf, SIZE);
LFS3_ASSERT(d == SIZE || d == LFS3_ERR_NOSPC);
if (d == LFS3_ERR_NOSPC) {
lfs3_file_close(&lfs3, &sim_files[j]->file) => 0;
free(sim_files[j]);
goto grow;
}
}
// open in our sim
sim_files[j]->x = x;
sim_files[j]->sticky = sticky;
sim_files[j]->zombie = false;
sim_files[j]->prng = wprng;
sim_file_count++;
// insert into our sim
for (lfs3_size_t k = 0;; k++) {
if (k >= sim_size || sim[k] >= x) {
// already seen?
if (k < sim_size && sim[k] == x) {
// new prng
sim_prngs[k] = wprng;
} else {
// insert
memmove(&sim[k+1], &sim[k],
(sim_size-k)*sizeof(lfs3_size_t));
memmove(&sim_prngs[k+1], &sim_prngs[k],
(sim_size-k)*sizeof(uint32_t));
memmove(&sim_isstickys[k+1], &sim_isstickys[k],
(sim_size-k)*sizeof(bool));
sim_size += 1;
sim[k] = x;
sim_prngs[k] = wprng;
sim_isstickys[k] = sticky;
}
break;
}
}
// write/rewrite a file?
} else if (op == 1) {
if (sim_file_count == 0) {
goto nonsense;
}
// choose a random file handle
lfs3_size_t j = TEST_PRNG(&prng_) % sim_file_count;
lfs3_size_t x = sim_files[j]->x;
// choose a random seed
uint32_t wprng = TEST_PRNG(&prng_);
// write to the file
lfs3_file_rewind(&lfs3, &sim_files[j]->file) => 0;
uint8_t wbuf[SIZE];
uint32_t wprng_ = wprng;
for (lfs3_size_t k = 0; k < SIZE; k++) {
wbuf[k] = 'a' + (TEST_PRNG(&wprng_) % 26);
}
lfs3_ssize_t d = lfs3_file_write(&lfs3, &sim_files[j]->file,
wbuf, SIZE);
assert(d == SIZE || d == LFS3_ERR_NOSPC);
if (d == LFS3_ERR_NOSPC) {
goto grow;
}
int err = lfs3_file_sync(&lfs3, &sim_files[j]->file);
assert(err == 0 || err == LFS3_ERR_NOSPC);
if (err == LFS3_ERR_NOSPC) {
goto grow;
}
// update sim
sim_files[j]->prng = wprng;
if (!sim_files[j]->zombie) {
// update in our sim
for (lfs3_size_t k = 0;; k++) {
if (sim[k] == x) {
// new prng
sim_prngs[k] = wprng;
// no longer sticky
sim_isstickys[k] = false;
break;
}
}
// update related sim files
for (lfs3_size_t k = 0; k < sim_file_count; k++) {
if (sim_files[k]->x == x && !sim_files[k]->zombie) {
// new prng
sim_files[k]->prng = wprng;
// no longer sticky
sim_files[k]->sticky = false;
}
}
}
// close a file?
} else if (op == 2) {
if (sim_file_count == 0) {
goto nonsense;
}
// choose a random file handle
lfs3_size_t j = TEST_PRNG(&prng_) % sim_file_count;
lfs3_size_t x = sim_files[j]->x;
bool sticky = sim_files[j]->sticky;
bool zombie = sim_files[j]->zombie;
// this doesn't really test anything, but if we don't close
// files eventually everything will end up zombies
// close the file without affected disk
lfs3_file_desync(&lfs3, &sim_files[j]->file) => 0;
lfs3_file_close(&lfs3, &sim_files[j]->file) => 0;
// clobber closed files to try to catch lingering references
memset(&sim_files[j]->file, 0xcc, sizeof(lfs3_file_t));
// remove from list
free(sim_files[j]);
sim_files[j] = sim_files[sim_file_count-1];
sim_file_count -= 1;
// update our sim
if (sticky && !zombie) {
// orphaned?
bool orphan = true;
for (lfs3_size_t k = 0; k < sim_file_count; k++) {
if (sim_files[k]->x == x && !sim_files[k]->zombie) {
orphan = false;
}
}
// if we were never synced, delete from sim
if (orphan) {
for (lfs3_size_t k = 0;; k++) {
if (sim[k] == x) {
memmove(&sim[k], &sim[k+1],
(sim_size-(k+1))*sizeof(lfs3_size_t));
memmove(&sim_prngs[k], &sim_prngs[k+1],
(sim_size-(k+1))*sizeof(uint32_t));
memmove(&sim_isstickys[k], &sim_isstickys[k+1],
(sim_size-(k+1))*sizeof(bool));
sim_size -= 1;
break;
}
}
}
}
// remove a file?
} else if (op == 3) {
if (sim_size == 0) {
goto nonsense;
}
// choose a random file to delete
lfs3_size_t j = TEST_PRNG(&prng_) % sim_size;
lfs3_size_t x = sim[j];
// delete this file
char name[256];
sprintf(name, "batman%03x", x);
int err = lfs3_remove(&lfs3, name);
assert(!err || err == LFS3_ERR_NOSPC);
if (err == LFS3_ERR_NOSPC) {
goto grow;
}
// delete from our sim
memmove(&sim[j], &sim[j+1],
(sim_size-(j+1))*sizeof(lfs3_size_t));
memmove(&sim_prngs[j], &sim_prngs[j+1],
(sim_size-(j+1))*sizeof(uint32_t));
memmove(&sim_isstickys[j], &sim_isstickys[j+1],
(sim_size-(j+1))*sizeof(bool));
sim_size -= 1;
// mark any related sim files as zombied
for (lfs3_size_t k = 0; k < sim_file_count; k++) {
if (sim_files[k]->x == x) {
sim_files[k]->zombie = true;
}
}
// rename a file?
} else if (op == 4) {
if (sim_size == 0) {
goto nonsense;
}
// choose a random file to rename, and a random number to
// rename to
lfs3_size_t j = TEST_PRNG(&prng_) % sim_size;
lfs3_size_t x = sim[j];
lfs3_size_t y = TEST_PRNG(&prng_) % N;
uint32_t wprng = sim_prngs[j];
bool sticky = sim_isstickys[j];
// rename this file
char old_name[256];
sprintf(old_name, "batman%03x", x);
char new_name[256];
sprintf(new_name, "batman%03x", y);
int err = lfs3_rename(&lfs3, old_name, new_name);
assert(!err || err == LFS3_ERR_NOSPC);
if (err == LFS3_ERR_NOSPC) {
goto grow;
}
// update our sim
for (lfs3_size_t k = 0;; k++) {
if (k >= sim_size || sim[k] >= y) {
// renaming and replacing
if (k < sim_size && sim[k] == y && x != y) {
// delete the original entry
memmove(&sim[j], &sim[j+1],
(sim_size-(j+1))*sizeof(lfs3_size_t));
memmove(&sim_prngs[j], &sim_prngs[j+1],
(sim_size-(j+1))*sizeof(uint32_t));
memmove(&sim_isstickys[j], &sim_isstickys[j+1],
(sim_size-(j+1))*sizeof(bool));
sim_size -= 1;
if (k > j) {
k -= 1;
}
// update the prng/sticky
sim_prngs[k] = wprng;
sim_isstickys[k] = sticky;
// just renaming
} else {
// first delete
memmove(&sim[j], &sim[j+1],
(sim_size-(j+1))*sizeof(lfs3_size_t));
memmove(&sim_prngs[j], &sim_prngs[j+1],
(sim_size-(j+1))*sizeof(uint32_t));
memmove(&sim_isstickys[j], &sim_isstickys[j+1],
(sim_size-(j+1))*sizeof(bool));
if (k > j) {
k -= 1;
}
// then insert
memmove(&sim[k+1], &sim[k],
(sim_size-1-k)*sizeof(lfs3_size_t));
memmove(&sim_prngs[k+1], &sim_prngs[k],
(sim_size-1-k)*sizeof(uint32_t));
memmove(&sim_isstickys[k+1], &sim_isstickys[k],
(sim_size-1-k)*sizeof(bool));
sim[k] = y;
sim_prngs[k] = wprng;
sim_isstickys[k] = sticky;
}
break;
}
}
// update any related sim files
for (lfs3_size_t k = 0; k < sim_file_count; k++) {
// move source files
if (sim_files[k]->x == x) {
sim_files[k]->x = y;
// mark target files as zombied
} else if (sim_files[k]->x == y) {
sim_files[k]->zombie = true;
}
}
}
prng = prng_;
continue;
grow:;
// try growing the filesystem
struct lfs3_fsinfo fsinfo;
lfs3_fs_stat(&lfs3, &fsinfo) => 0;
assert(fsinfo.block_count >= FORMAT_BLOCK_COUNT);
assert(fsinfo.block_count <= BLOCK_COUNT);
lfs3_ssize_t used = lfs3_fs_usage(&lfs3);
assert(used >= 0);
// we may need to grow multiple blocks before the system gets unstuck
lfs3_size_t block_count_ = fsinfo.block_count;
while (true) {
assert(block_count_ < BLOCK_COUNT);
block_count_ += 1;
int err = lfs3_fs_grow(&lfs3, block_count_);
assert(!err || err == LFS3_ERR_NOSPC);
if (err == LFS3_ERR_NOSPC) {
continue;
}
break;
}
printf("grew %d/%d -> %d/%d\n",
used, fsinfo.block_count,
used, block_count_);
// fsstat up to date?
lfs3_fs_stat(&lfs3, &fsinfo) => 0;
assert(fsinfo.block_size == BLOCK_SIZE);
assert(fsinfo.block_count == block_count_);
assert(fsinfo.name_limit == LFS3_NAME_MAX);
assert(fsinfo.file_limit == LFS3_FILE_MAX);
goto again;
}
// check that disk matches our simulation
for (lfs3_size_t j = 0; j < sim_size; j++) {
char name[256];
sprintf(name, "batman%03x", sim[j]);
struct lfs3_info info;
lfs3_stat(&lfs3, name, &info) => 0;
assert(strcmp(info.name, name) == 0);
if (sim_isstickys[j]) {
assert(info.type == LFS3_TYPE_STICKYNOTE);
assert(info.size == 0);
} else {
assert(info.type == LFS3_TYPE_REG);
assert(info.size == SIZE);
}
}
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 j = 0; j < sim_size; j++) {
char name[256];
sprintf(name, "batman%03x", sim[j]);
lfs3_dir_read(&lfs3, &dir, &info) => 0;
assert(strcmp(info.name, name) == 0);
if (sim_isstickys[j]) {
assert(info.type == LFS3_TYPE_STICKYNOTE);
assert(info.size == 0);
} else {
assert(info.type == LFS3_TYPE_REG);
assert(info.size == SIZE);
}
}
lfs3_dir_read(&lfs3, &dir, &info) => LFS3_ERR_NOENT;
lfs3_dir_close(&lfs3, &dir) => 0;
for (lfs3_size_t j = 0; j < sim_size; j++) {
char name[256];
sprintf(name, "batman%03x", sim[j]);
lfs3_file_t file;
lfs3_file_open(&lfs3, &file, name, LFS3_O_RDONLY) => 0;
uint32_t wprng = sim_prngs[j];
uint8_t wbuf[SIZE];
for (lfs3_size_t j = 0; j < SIZE; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26);
}
uint8_t rbuf[SIZE];
if (sim_isstickys[j]) {
lfs3_file_read(&lfs3, &file, rbuf, SIZE) => 0;
} else {
lfs3_file_read(&lfs3, &file, rbuf, SIZE) => SIZE;
assert(memcmp(rbuf, wbuf, SIZE) == 0);
}
lfs3_file_close(&lfs3, &file) => 0;
}
// check that our file handles match our simulation
for (lfs3_size_t j = 0; j < sim_file_count; j++) {
uint32_t wprng = sim_files[j]->prng;
uint8_t wbuf[SIZE];
for (lfs3_size_t j = 0; j < SIZE; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26);
}
lfs3_file_rewind(&lfs3, &sim_files[j]->file) => 0;
uint8_t rbuf[SIZE];
lfs3_file_read(&lfs3, &sim_files[j]->file, rbuf, SIZE) => SIZE;
assert(memcmp(rbuf, wbuf, SIZE) == 0);
}
// clean up sim/lfs3
free(sim);
free(sim_prngs);
free(sim_isstickys);
for (lfs3_size_t j = 0; j < sim_file_count; j++) {
lfs3_file_close(&lfs3, &sim_files[j]->file) => 0;
free(sim_files[j]);
}
free(sim_files);
lfs3_unmount(&lfs3) => 0;
'''
[cases.test_grow_incr_spam_uzd_fuzz]
# you probably need to flush if you expect errors
defines.FLUSH = true
defines.N = [1, 2, 4, 8, 16, 32, 64]
defines.OPS = 1024
defines.SIZE = [
'0',
'FCACHE_SIZE/2',
'2*FCACHE_SIZE',
'BLOCK_SIZE/2',
'BLOCK_SIZE',
'2*BLOCK_SIZE',
'4*BLOCK_SIZE',
]
defines.SEED = 'range(10)'
fuzz = 'SEED'
if = '(SIZE*N)/BLOCK_SIZE <= 16'
code = '''
// start with a small number of blocks
struct lfs3_cfg cfg = *CFG;
cfg.block_count = FORMAT_BLOCK_COUNT;
lfs3_t lfs3;
lfs3_format(&lfs3,
LFS3_F_RDWR
| ((GBMAP) ? LFS3_IFDEF_GBMAP(LFS3_F_GBMAP, -1) : 0),
&cfg) => 0;
// mount with maximum block count
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0;
// fsstat up to date?
struct lfs3_fsinfo fsinfo;
lfs3_fs_stat(&lfs3, &fsinfo) => 0;
assert(fsinfo.block_size == BLOCK_SIZE);
assert(fsinfo.block_count == FORMAT_BLOCK_COUNT);
assert(fsinfo.name_limit == LFS3_NAME_MAX);
assert(fsinfo.file_limit == LFS3_FILE_MAX);
// set up a simulation to compare against
lfs3_size_t *sim = malloc(N*sizeof(lfs3_size_t));
uint32_t *sim_prngs = malloc(N*sizeof(uint32_t));
bool *sim_isstickys = malloc(N*sizeof(bool));
bool *sim_isdirs = malloc(N*sizeof(bool));
lfs3_size_t sim_size = 0;
typedef struct sim_file {
lfs3_size_t x;
bool sticky;
bool zombie;
uint32_t prng;
lfs3_file_t file;
} sim_file_t;
sim_file_t **sim_files = malloc(N*sizeof(sim_file_t*));
lfs3_size_t sim_file_count = 0;
uint32_t prng = SEED;
for (lfs3_size_t i = 0; i < OPS; i++) {
again:;
uint32_t prng_ = prng;
nonsense:;
// choose which operation to do
uint8_t op = TEST_PRNG(&prng_) % 8;
// open a new file?
if (op == 0) {
if (sim_file_count >= N) {
goto nonsense;
}
// choose a pseudo-random number
lfs3_size_t x = TEST_PRNG(&prng_) % N;
// already exists?
bool exist = true;
uint32_t wprng = 0;
bool sticky = true;
for (lfs3_size_t j = 0; j < sim_size; j++) {
if (sim[j] == x) {
if (sim_isdirs[j]) {
goto nonsense;
}
exist = true;
wprng = sim_prngs[j];
sticky = sim_isstickys[j];
break;
}
}
// choose a random seed if we don't exist
if (!exist) {
wprng = TEST_PRNG(&prng);
sticky = true;
}
lfs3_size_t j = sim_file_count;
sim_files[j] = malloc(sizeof(sim_file_t));
// open the actual file
char name[256];
sprintf(name, "batman%03x", x);
int err = lfs3_file_open(&lfs3, &sim_files[j]->file, name,
LFS3_O_RDWR
| LFS3_O_CREAT
| ((FLUSH) ? LFS3_O_FLUSH : 0));
assert(!err || err == LFS3_ERR_NOSPC);
if (err == LFS3_ERR_NOSPC) {
free(sim_files[j]);
goto grow;
}
// write some initial data if we don't exist
if (!exist || sticky) {
uint8_t wbuf[SIZE];
uint32_t wprng_ = wprng;
for (lfs3_size_t k = 0; k < SIZE; k++) {
wbuf[k] = 'a' + (TEST_PRNG(&wprng_) % 26);
}
lfs3_ssize_t d = lfs3_file_write(&lfs3, &sim_files[j]->file,
wbuf, SIZE);
assert(d == SIZE || d == LFS3_ERR_NOSPC);
if (d == LFS3_ERR_NOSPC) {
free(sim_files[j]);
lfs3_file_close(&lfs3, &sim_files[j]->file) => 0;
goto grow;
}
}
// open in our sim
sim_files[j]->x = x;
sim_files[j]->sticky = sticky;
sim_files[j]->zombie = false;
sim_files[j]->prng = wprng;
sim_file_count++;
// insert into our sim
for (lfs3_size_t k = 0;; k++) {
if (k >= sim_size || sim[k] >= x) {
// already seen?
if (k < sim_size && sim[k] == x) {
// new prng
sim_prngs[k] = wprng;
} else {
// insert
memmove(&sim[k+1], &sim[k],
(sim_size-k)*sizeof(lfs3_size_t));
memmove(&sim_prngs[k+1], &sim_prngs[k],
(sim_size-k)*sizeof(uint32_t));
memmove(&sim_isstickys[k+1], &sim_isstickys[k],
(sim_size-k)*sizeof(bool));
memmove(&sim_isdirs[k+1], &sim_isdirs[k],
(sim_size-k)*sizeof(bool));
sim_size += 1;
sim[k] = x;
sim_prngs[k] = wprng;
sim_isstickys[k] = sticky;
sim_isdirs[k] = false;
}
break;
}
}
// write/rewrite a file?
} else if (op == 1) {
if (sim_file_count == 0) {
goto nonsense;
}
// choose a random file handle
lfs3_size_t j = TEST_PRNG(&prng_) % sim_file_count;
lfs3_size_t x = sim_files[j]->x;
// choose a random seed
uint32_t wprng = TEST_PRNG(&prng_);
// write to the file
lfs3_file_rewind(&lfs3, &sim_files[j]->file) => 0;
uint8_t wbuf[SIZE];
uint32_t wprng_ = wprng;
for (lfs3_size_t k = 0; k < SIZE; k++) {
wbuf[k] = 'a' + (TEST_PRNG(&wprng_) % 26);
}
lfs3_ssize_t d = lfs3_file_write(&lfs3, &sim_files[j]->file,
wbuf, SIZE);
assert(d == SIZE || d == LFS3_ERR_NOSPC);
if (d == LFS3_ERR_NOSPC) {
goto grow;
}
int err = lfs3_file_sync(&lfs3, &sim_files[j]->file);
assert(err == 0 || err == LFS3_ERR_NOSPC);
if (err == LFS3_ERR_NOSPC) {
goto grow;
}
// update sim
sim_files[j]->prng = wprng;
if (!sim_files[j]->zombie) {
// update in our sim
for (lfs3_size_t k = 0;; k++) {
if (k >= sim_size || sim[k] >= x) {
// new prng
sim_prngs[k] = wprng;
// no longer sticky
sim_isstickys[k] = false;
break;
}
}
// update related sim files
for (lfs3_size_t k = 0; k < sim_file_count; k++) {
if (sim_files[k]->x == x && !sim_files[k]->zombie) {
// new prng
sim_files[k]->prng = wprng;
// no longer sticky
sim_files[k]->sticky = false;
}
}
}
// close a file?
} else if (op == 2) {
if (sim_file_count == 0) {
goto nonsense;
}
// choose a random file handle
lfs3_size_t j = TEST_PRNG(&prng_) % sim_file_count;
lfs3_size_t x = sim_files[j]->x;
lfs3_size_t sticky = sim_files[j]->sticky;
lfs3_size_t zombie = sim_files[j]->zombie;
// this doesn't really test anything, but if we don't close
// files eventually everything will end up zombies
// close the file without affected disk
lfs3_file_desync(&lfs3, &sim_files[j]->file) => 0;
lfs3_file_close(&lfs3, &sim_files[j]->file) => 0;
// clobber closed files to try to catch lingering references
memset(&sim_files[j]->file, 0xcc, sizeof(lfs3_file_t));
// remove from list
free(sim_files[j]);
sim_files[j] = sim_files[sim_file_count-1];
sim_file_count -= 1;
// update our sim
if (sticky && !zombie) {
// orphaned?
bool orphan = true;
for (lfs3_size_t k = 0; k < sim_file_count; k++) {
if (sim_files[k]->x == x && !sim_files[k]->zombie) {
orphan = false;
}
}
// if we were never synced, delete from sim
if (orphan) {
for (lfs3_size_t k = 0;; k++) {
if (sim[k] == x) {
memmove(&sim[k], &sim[k+1],
(sim_size-(k+1))*sizeof(lfs3_size_t));
memmove(&sim_prngs[k], &sim_prngs[k+1],
(sim_size-(k+1))*sizeof(uint32_t));
memmove(&sim_isstickys[k], &sim_isstickys[k+1],
(sim_size-(k+1))*sizeof(bool));
memmove(&sim_isdirs[k], &sim_isdirs[k+1],
(sim_size-(k+1))*sizeof(bool));
sim_size -= 1;
break;
}
}
}
}
// remove a file?
} else if (op == 3) {
if (sim_size == 0) {
goto nonsense;
}
// choose a random file to delete
lfs3_size_t j = TEST_PRNG(&prng_) % sim_size;
lfs3_size_t x = sim[j];
// delete this file
char name[256];
sprintf(name, "batman%03x", x);
int err = lfs3_remove(&lfs3, name);
assert(!err || err == LFS3_ERR_NOSPC);
if (err == LFS3_ERR_NOSPC) {
goto grow;
}
// delete from our sim
memmove(&sim[j], &sim[j+1],
(sim_size-(j+1))*sizeof(lfs3_size_t));
memmove(&sim_prngs[j], &sim_prngs[j+1],
(sim_size-(j+1))*sizeof(uint32_t));
memmove(&sim_isstickys[j], &sim_isstickys[j+1],
(sim_size-(j+1))*sizeof(bool));
memmove(&sim_isdirs[j], &sim_isdirs[j+1],
(sim_size-(j+1))*sizeof(bool));
sim_size -= 1;
// mark any related sim files as zombied
for (lfs3_size_t k = 0; k < sim_file_count; k++) {
if (sim_files[k]->x == x) {
sim_files[k]->zombie = true;
}
}
// rename a file?
} else if (op == 4) {
if (sim_size == 0) {
goto nonsense;
}
// choose a random file to rename, and a random number to
// rename to
lfs3_size_t j = TEST_PRNG(&prng_) % sim_size;
lfs3_size_t x = sim[j];
lfs3_size_t y = TEST_PRNG(&prng_) % N;
uint32_t wprng = sim_prngs[j];
bool sticky = sim_isstickys[j];
bool dir = sim_isdirs[j];
for (lfs3_size_t k = 0;; k++) {
if (k >= sim_size || sim[k] >= y) {
// renaming and replacing
if (k < sim_size && sim[k] == y && x != y) {
// type mismatch?
if (sim_isdirs[k] != dir) {
goto nonsense;
}
}
break;
}
}
// rename this file
char old_name[256];
sprintf(old_name, "batman%03x", x);
char new_name[256];
sprintf(new_name, "batman%03x", y);
int err = lfs3_rename(&lfs3, old_name, new_name);
assert(!err || err == LFS3_ERR_NOSPC);
if (err == LFS3_ERR_NOSPC) {
goto grow;
}
// update our sim
for (lfs3_size_t k = 0;; k++) {
if (k >= sim_size || sim[k] >= y) {
// renaming and replacing
if (k < sim_size && sim[k] == y && x != y) {
// delete the original entry
memmove(&sim[j], &sim[j+1],
(sim_size-(j+1))*sizeof(lfs3_size_t));
memmove(&sim_prngs[j], &sim_prngs[j+1],
(sim_size-(j+1))*sizeof(uint32_t));
memmove(&sim_isstickys[j], &sim_isstickys[j+1],
(sim_size-(j+1))*sizeof(bool));
memmove(&sim_isdirs[j], &sim_isdirs[j+1],
(sim_size-(j+1))*sizeof(bool));
sim_size -= 1;
if (k > j) {
k -= 1;
}
// update the prng/sticky/dir
sim_prngs[k] = wprng;
sim_isstickys[k] = sticky;
sim_isdirs[k] = dir;
// just renaming
} else {
// first delete
memmove(&sim[j], &sim[j+1],
(sim_size-(j+1))*sizeof(lfs3_size_t));
memmove(&sim_prngs[j], &sim_prngs[j+1],
(sim_size-(j+1))*sizeof(uint32_t));
memmove(&sim_isstickys[j], &sim_isstickys[j+1],
(sim_size-(j+1))*sizeof(bool));
memmove(&sim_isdirs[j], &sim_isdirs[j+1],
(sim_size-(j+1))*sizeof(bool));
if (k > j) {
k -= 1;
}
// then insert
memmove(&sim[k+1], &sim[k],
(sim_size-1-k)*sizeof(lfs3_size_t));
memmove(&sim_prngs[k+1], &sim_prngs[k],
(sim_size-1-k)*sizeof(uint32_t));
memmove(&sim_isstickys[k+1], &sim_isstickys[k],
(sim_size-1-k)*sizeof(bool));
memmove(&sim_isdirs[k+1], &sim_isdirs[k],
(sim_size-1-k)*sizeof(bool));
sim[k] = y;
sim_prngs[k] = wprng;
sim_isstickys[k] = sticky;
sim_isdirs[k] = dir;
}
break;
}
}
// update any related sim files
for (lfs3_size_t k = 0; k < sim_file_count; k++) {
// move source files
if (sim_files[k]->x == x) {
sim_files[k]->x = y;
// mark target files as zombied
} else if (sim_files[k]->x == y) {
sim_files[k]->zombie = true;
}
}
// toss a directory into the mix
} else if (op == 5) {
// choose a pseudo-random number
lfs3_size_t x = TEST_PRNG(&prng_) % N;
for (lfs3_size_t k = 0;; k++) {
if (k >= sim_size || sim[k] >= x) {
// already seen?
if (k < sim_size && sim[k] == x) {
goto nonsense;
}
break;
}
}
// make the directory
char name[256];
sprintf(name, "batman%03x", x);
int err = lfs3_mkdir(&lfs3, name);
assert(!err || err == LFS3_ERR_NOSPC);
if (err == LFS3_ERR_NOSPC) {
goto grow;
}
// insert into our sim
for (lfs3_size_t k = 0;; k++) {
if (k >= sim_size || sim[k] >= x) {
// insert
memmove(&sim[k+1], &sim[k],
(sim_size-k)*sizeof(lfs3_size_t));
memmove(&sim_prngs[k+1], &sim_prngs[k],
(sim_size-k)*sizeof(uint32_t));
memmove(&sim_isstickys[k+1], &sim_isstickys[k],
(sim_size-k)*sizeof(bool));
memmove(&sim_isdirs[k+1], &sim_isdirs[k],
(sim_size-k)*sizeof(bool));
sim_size += 1;
sim[k] = x;
sim_prngs[k] = 0;
sim_isdirs[k] = true;
break;
}
}
// mark any related sim files as zombied
for (lfs3_size_t k = 0; k < sim_file_count; k++) {
if (sim_files[k]->x == x) {
sim_files[k]->zombie = true;
}
}
}
prng = prng_;
continue;
grow:;
// try growing the filesystem
struct lfs3_fsinfo fsinfo;
lfs3_fs_stat(&lfs3, &fsinfo) => 0;
assert(fsinfo.block_count >= FORMAT_BLOCK_COUNT);
assert(fsinfo.block_count <= BLOCK_COUNT);
lfs3_ssize_t used = lfs3_fs_usage(&lfs3);
assert(used >= 0);
// we may need to grow multiple blocks before the system gets unstuck
lfs3_size_t block_count_ = fsinfo.block_count;
while (true) {
assert(block_count_ < BLOCK_COUNT);
block_count_ += 1;
int err = lfs3_fs_grow(&lfs3, block_count_);
assert(!err || err == LFS3_ERR_NOSPC);
if (err == LFS3_ERR_NOSPC) {
continue;
}
break;
}
printf("grew %d/%d -> %d/%d\n",
used, fsinfo.block_count,
used, block_count_);
// fsstat up to date?
lfs3_fs_stat(&lfs3, &fsinfo) => 0;
assert(fsinfo.block_size == BLOCK_SIZE);
assert(fsinfo.block_count == block_count_);
assert(fsinfo.name_limit == LFS3_NAME_MAX);
assert(fsinfo.file_limit == LFS3_FILE_MAX);
goto again;
}
// check that disk matches our simulation
for (lfs3_size_t j = 0; j < sim_size; j++) {
char name[256];
sprintf(name, "batman%03x", sim[j]);
struct lfs3_info info;
lfs3_stat(&lfs3, name, &info) => 0;
assert(strcmp(info.name, name) == 0);
if (sim_isdirs[j]) {
assert(info.type == LFS3_TYPE_DIR);
assert(info.size == 0);
} else if (sim_isstickys[j]) {
assert(info.type == LFS3_TYPE_STICKYNOTE);
assert(info.size == 0);
} else {
assert(info.type == LFS3_TYPE_REG);
assert(info.size == SIZE);
}
}
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 j = 0; j < sim_size; j++) {
char name[256];
sprintf(name, "batman%03x", sim[j]);
lfs3_dir_read(&lfs3, &dir, &info) => 0;
assert(strcmp(info.name, name) == 0);
if (sim_isdirs[j]) {
assert(info.type == LFS3_TYPE_DIR);
assert(info.size == 0);
} else if (sim_isstickys[j]) {
assert(info.type == LFS3_TYPE_STICKYNOTE);
assert(info.size == 0);
} else {
assert(info.type == LFS3_TYPE_REG);
assert(info.size == SIZE);
}
}
lfs3_dir_read(&lfs3, &dir, &info) => LFS3_ERR_NOENT;
lfs3_dir_close(&lfs3, &dir) => 0;
for (lfs3_size_t j = 0; j < sim_size; j++) {
if (sim_isdirs[j]) {
char name[256];
sprintf(name, "batman%03x", sim[j]);
lfs3_file_t file;
lfs3_file_open(&lfs3, &file, name, LFS3_O_RDONLY)
=> LFS3_ERR_ISDIR;
} else {
char name[256];
sprintf(name, "batman%03x", sim[j]);
lfs3_file_t file;
lfs3_file_open(&lfs3, &file, name, LFS3_O_RDONLY) => 0;
uint32_t wprng = sim_prngs[j];
uint8_t wbuf[SIZE];
for (lfs3_size_t j = 0; j < SIZE; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26);
}
uint8_t rbuf[SIZE];
if (sim_isstickys[j]) {
lfs3_file_read(&lfs3, &file, rbuf, SIZE) => 0;
} else {
lfs3_file_read(&lfs3, &file, rbuf, SIZE) => SIZE;
assert(memcmp(rbuf, wbuf, SIZE) == 0);
}
lfs3_file_close(&lfs3, &file) => 0;
}
}
// check that our file handles match our simulation
for (lfs3_size_t j = 0; j < sim_file_count; j++) {
uint32_t wprng = sim_files[j]->prng;
uint8_t wbuf[SIZE];
for (lfs3_size_t j = 0; j < SIZE; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26);
}
lfs3_file_rewind(&lfs3, &sim_files[j]->file) => 0;
uint8_t rbuf[SIZE];
lfs3_file_read(&lfs3, &sim_files[j]->file, rbuf, SIZE) => SIZE;
assert(memcmp(rbuf, wbuf, SIZE) == 0);
}
// clean up sim/lfs3
free(sim);
free(sim_prngs);
free(sim_isstickys);
free(sim_isdirs);
for (lfs3_size_t j = 0; j < sim_file_count; j++) {
lfs3_file_close(&lfs3, &sim_files[j]->file) => 0;
free(sim_files[j]);
}
free(sim_files);
lfs3_unmount(&lfs3) => 0;
'''
# A general purpose powerloss fuzz test
#
#
# Under powerloss, we can't really keep track of a sim reliably/
# efficiently, instead just do random operations, store a counter in a
# special file so we know how much progress has been made, and hope for
# the best. Most likely an internal assert will trigger if anything goes
# wrong.
#
[cases.test_grow_incr_spam_f_pl_fuzz]
defines.N = [1, 2, 4, 8, 16, 32, 64]
defines.OPS = 256
defines.SIZE = [
'0',
'FCACHE_SIZE/2',
'2*FCACHE_SIZE',
'BLOCK_SIZE/2',
'BLOCK_SIZE',
'2*BLOCK_SIZE',
'4*BLOCK_SIZE',
]
defines.SEED = 'range(10)'
fuzz = 'SEED'
if = '(SIZE*N)/BLOCK_SIZE <= 16'
reentrant = true
code = '''
// format once per test
lfs3_t lfs3;
int err = lfs3_mount(&lfs3, LFS3_M_RDWR, CFG);
if (err) {
// start with a small number of blocks
struct lfs3_cfg cfg = *CFG;
cfg.block_count = FORMAT_BLOCK_COUNT;
lfs3_format(&lfs3,
LFS3_F_RDWR
| ((GBMAP) ? LFS3_IFDEF_GBMAP(LFS3_F_GBMAP, -1) : 0),
&cfg) => 0;
// mount with maximum block count
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0;
// fsstat up to date?
struct lfs3_fsinfo fsinfo;
lfs3_fs_stat(&lfs3, &fsinfo) => 0;
assert(fsinfo.block_size == BLOCK_SIZE);
assert(fsinfo.block_count == FORMAT_BLOCK_COUNT);
assert(fsinfo.name_limit == LFS3_NAME_MAX);
assert(fsinfo.file_limit == LFS3_FILE_MAX);
}
// keep some test state on disk to survive powerloss
typedef struct fuzz_state {
lfs3_size_t i;
uint32_t prng;
} fuzz_state_t;
fuzz_state_t state = {.i = 0, .prng = SEED};
lfs3_file_t state_file;
err = lfs3_file_open(&lfs3, &state_file, "state", LFS3_O_RDONLY);
assert(!err || err == LFS3_ERR_NOENT);
if (!err) {
lfs3_file_read(&lfs3, &state_file,
&state, sizeof(state)) => sizeof(state);
lfs3_file_close(&lfs3, &state_file) => 0;
}
uint32_t prng = state.prng;
for (lfs3_size_t i = state.i; i < OPS; i++) {
again:;
uint32_t prng_ = prng;
// choose which operation to do
uint8_t op = TEST_PRNG(&prng_) % 3;
// keep test files in a separate directory
err = lfs3_mkdir(&lfs3, "test");
assert(!err || err == LFS3_ERR_EXIST || err == LFS3_ERR_NOSPC);
if (err == LFS3_ERR_NOSPC) {
goto grow;
}
// how many files do we have?
lfs3_size_t count = 0;
lfs3_dir_t dir;
lfs3_dir_open(&lfs3, &dir, "test") => 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);
while (true) {
err = lfs3_dir_read(&lfs3, &dir, &info);
assert(!err || err == LFS3_ERR_NOENT);
if (err == LFS3_ERR_NOENT) {
break;
}
assert(strlen(info.name) == strlen("amethyst..."));
assert(memcmp(info.name, "amethyst", strlen("amethyst")) == 0);
assert(info.type == LFS3_TYPE_REG);
assert(info.size == SIZE);
count++;
}
lfs3_dir_close(&lfs3, &dir) => 0;
// creating a new file?
if (op == 0 || count == 0) {
// choose a pseudo-random number
lfs3_size_t x = TEST_PRNG(&prng_) % N;
uint32_t wprng = TEST_PRNG(&prng_);
// create a file here
char name[256];
sprintf(name, "test/amethyst%03x", x);
uint8_t wbuf[SIZE];
uint8_t ck = 0;
for (lfs3_size_t j = 0; j < SIZE-1; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26);
ck = (ck + (wbuf[j] - 'a')) % 26;
}
// make the sum equal to 'a' mod 26
if (SIZE > 0) {
wbuf[SIZE-1] = 'a' + ((26 - ck) % 26);
}
lfs3_file_t file;
err = lfs3_file_open(&lfs3, &file, name,
LFS3_O_WRONLY | LFS3_O_CREAT | LFS3_O_TRUNC);
assert(!err || err == LFS3_ERR_NOSPC);
if (err == LFS3_ERR_NOSPC) {
goto grow;
}
lfs3_ssize_t d = lfs3_file_write(&lfs3, &file, wbuf, SIZE);
assert(d == SIZE || d == LFS3_ERR_NOSPC);
if (d == LFS3_ERR_NOSPC) {
lfs3_file_close(&lfs3, &file) => 0;
goto grow;
}
err = lfs3_file_close(&lfs3, &file);
assert(!err || err == LFS3_ERR_NOSPC);
if (err == LFS3_ERR_NOSPC) {
goto grow;
}
// deleting a file?
} else if (op == 1) {
// choose a random file to delete
lfs3_size_t j = TEST_PRNG(&prng_) % count;
// find the file
lfs3_dir_open(&lfs3, &dir, "test") => 0;
lfs3_dir_read(&lfs3, &dir, &info) => 0;
lfs3_dir_read(&lfs3, &dir, &info) => 0;
for (lfs3_size_t k = 0; k <= j; k++) {
lfs3_dir_read(&lfs3, &dir, &info) => 0;
}
lfs3_dir_close(&lfs3, &dir) => 0;
// delete this file
char name[256];
assert(strlen(info.name) == strlen("amethyst..."));
sprintf(name, "test/%s", info.name);
err = lfs3_remove(&lfs3, name);
assert(!err || err == LFS3_ERR_NOSPC);
if (err == LFS3_ERR_NOSPC) {
goto grow;
}
// renaming a file?
} else {
// choose a random file to rename, and a random number to
// rename to
lfs3_size_t j = TEST_PRNG(&prng_) % count;
lfs3_size_t y = TEST_PRNG(&prng_) % N;
// find the file
lfs3_dir_open(&lfs3, &dir, "test") => 0;
lfs3_dir_read(&lfs3, &dir, &info) => 0;
lfs3_dir_read(&lfs3, &dir, &info) => 0;
for (lfs3_size_t k = 0; k <= j; k++) {
lfs3_dir_read(&lfs3, &dir, &info) => 0;
}
lfs3_dir_close(&lfs3, &dir) => 0;
// rename this file
char old_name[256];
assert(strlen(info.name) == strlen("amethyst..."));
sprintf(old_name, "test/%s", info.name);
char new_name[256];
sprintf(new_name, "test/amethyst%03x", y);
err = lfs3_rename(&lfs3, old_name, new_name);
assert(!err || err == LFS3_ERR_NOSPC);
if (err == LFS3_ERR_NOSPC) {
goto grow;
}
}
// update our state file
state.i = i;
state.prng = prng_;
err = lfs3_file_open(&lfs3, &state_file, "state",
LFS3_O_WRONLY | LFS3_O_CREAT | LFS3_O_TRUNC);
assert(!err || err == LFS3_ERR_NOSPC);
if (err == LFS3_ERR_NOSPC) {
goto grow;
}
lfs3_ssize_t d = lfs3_file_write(&lfs3, &state_file,
&state, sizeof(state));
assert(d == sizeof(state) || d == LFS3_ERR_NOSPC);
if (d == LFS3_ERR_NOSPC) {
lfs3_file_close(&lfs3, &state_file) => 0;
goto grow;
}
err = lfs3_file_close(&lfs3, &state_file);
assert(!err || err == LFS3_ERR_NOSPC);
if (err == LFS3_ERR_NOSPC) {
goto grow;
}
prng = prng_;
continue;
grow:;
// try growing the filesystem
struct lfs3_fsinfo fsinfo;
lfs3_fs_stat(&lfs3, &fsinfo) => 0;
assert(fsinfo.block_count >= FORMAT_BLOCK_COUNT);
assert(fsinfo.block_count <= BLOCK_COUNT);
lfs3_ssize_t used = lfs3_fs_usage(&lfs3);
assert(used >= 0);
// we may need to grow multiple blocks before the system gets unstuck
lfs3_size_t block_count_ = fsinfo.block_count;
while (true) {
assert(block_count_ < BLOCK_COUNT);
block_count_ += 1;
err = lfs3_fs_grow(&lfs3, block_count_);
assert(!err || err == LFS3_ERR_NOSPC);
if (err == LFS3_ERR_NOSPC) {
continue;
}
break;
}
printf("grew %d/%d -> %d/%d\n",
used, fsinfo.block_count,
used, block_count_);
// fsstat up to date?
lfs3_fs_stat(&lfs3, &fsinfo) => 0;
assert(fsinfo.block_size == BLOCK_SIZE);
assert(fsinfo.block_count == block_count_);
assert(fsinfo.name_limit == LFS3_NAME_MAX);
assert(fsinfo.file_limit == LFS3_FILE_MAX);
goto again;
}
for (int remount = 0; remount < 2; remount++) {
// remount?
if (remount) {
lfs3_unmount(&lfs3) => 0;
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0;
}
// check that things look more-or-less ok
lfs3_dir_t dir;
lfs3_dir_open(&lfs3, &dir, "test") => 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);
while (true) {
int err = lfs3_dir_read(&lfs3, &dir, &info);
assert(!err || err == LFS3_ERR_NOENT);
if (err == LFS3_ERR_NOENT) {
break;
}
assert(strlen(info.name) == strlen("amethyst..."));
assert(memcmp(info.name, "amethyst", strlen("amethyst")) == 0);
assert(info.type == LFS3_TYPE_REG);
assert(info.size == SIZE);
// at least try to read the files
char name[256];
sprintf(name, "test/%s", info.name);
lfs3_file_t file;
lfs3_file_open(&lfs3, &file, name, LFS3_O_RDONLY) => 0;
uint8_t rbuf[SIZE];
lfs3_file_read(&lfs3, &file, rbuf, SIZE) => SIZE;
// all data should be lowercase ascii
for (lfs3_size_t j = 0; j < SIZE; j++) {
assert(rbuf[j] >= 'a' && rbuf[j] <= 'z');
}
// sum should be equal to 'a' mod 26
uint8_t ck = 0;
for (lfs3_size_t j = 0; j < SIZE; j++) {
ck = (ck + (rbuf[j] - 'a')) % 26;
}
assert(ck == 0);
lfs3_file_close(&lfs3, &file) => 0;
}
lfs3_dir_close(&lfs3, &dir) => 0;
}
lfs3_unmount(&lfs3) => 0;
'''
# A general purpose powerloss fuzz test, with directories!
#
# Under powerloss, we can't really keep track of a sim reliably/
# efficiently, instead just do random operations, store a counter in a
# special file so we know how much progress has been made, and hope for
# the best. Most likely an internal assert will trigger if anything goes
# wrong.
#
[cases.test_grow_incr_spam_fd_pl_fuzz]
# note dirs x files grows O(n^2)
defines.N = [1, 2, 4, 8]
defines.M = 'N'
defines.OPS = 256
defines.SIZE = [
'0',
'FCACHE_SIZE/2',
'2*FCACHE_SIZE',
'BLOCK_SIZE/2',
'BLOCK_SIZE',
'2*BLOCK_SIZE',
'4*BLOCK_SIZE',
]
defines.SEED = 'range(10)'
fuzz = 'SEED'
if = '(SIZE*N)/BLOCK_SIZE <= 16'
reentrant = true
code = '''
// format once per test
lfs3_t lfs3;
int err = lfs3_mount(&lfs3, LFS3_M_RDWR, CFG);
if (err) {
// start with a small number of blocks
struct lfs3_cfg cfg = *CFG;
cfg.block_count = FORMAT_BLOCK_COUNT;
lfs3_format(&lfs3,
LFS3_F_RDWR
| ((GBMAP) ? LFS3_IFDEF_GBMAP(LFS3_F_GBMAP, -1) : 0),
&cfg) => 0;
// mount with maximum block count
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0;
// fsstat up to date?
struct lfs3_fsinfo fsinfo;
lfs3_fs_stat(&lfs3, &fsinfo) => 0;
assert(fsinfo.block_size == BLOCK_SIZE);
assert(fsinfo.block_count == FORMAT_BLOCK_COUNT);
assert(fsinfo.name_limit == LFS3_NAME_MAX);
assert(fsinfo.file_limit == LFS3_FILE_MAX);
}
// keep some test state on disk to survive powerloss
typedef struct fuzz_state {
lfs3_size_t i;
uint32_t prng;
} fuzz_state_t;
fuzz_state_t state = {.i = 0, .prng = SEED};
lfs3_file_t state_file;
err = lfs3_file_open(&lfs3, &state_file, "state", LFS3_O_RDONLY);
assert(!err || err == LFS3_ERR_NOENT);
if (!err) {
lfs3_file_read(&lfs3, &state_file,
&state, sizeof(state)) => sizeof(state);
lfs3_file_close(&lfs3, &state_file) => 0;
}
uint32_t prng = state.prng;
for (lfs3_size_t i = state.i; i < OPS; i++) {
again:;
uint32_t prng_ = prng;
// choose which operation to do
uint8_t op = TEST_PRNG(&prng_) % 6;
// keep test files in a separate directory
err = lfs3_mkdir(&lfs3, "test");
assert(!err || err == LFS3_ERR_EXIST || err == LFS3_ERR_NOSPC);
if (err == LFS3_ERR_NOSPC) {
goto grow;
}
// how many dirs do we have?
lfs3_size_t dir_count = 0;
lfs3_dir_t dir;
lfs3_dir_open(&lfs3, &dir, "test") => 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);
while (true) {
err = lfs3_dir_read(&lfs3, &dir, &info);
assert(!err || err == LFS3_ERR_NOENT);
if (err == LFS3_ERR_NOENT) {
break;
}
assert(strlen(info.name) == strlen("quartz..."));
assert(memcmp(info.name, "quartz", strlen("quartz")) == 0);
assert(info.type == LFS3_TYPE_DIR);
assert(info.size == 0);
dir_count++;
}
lfs3_dir_close(&lfs3, &dir) => 0;
// dir op?
if (op < 3 || dir_count == 0) {
// creating a new dir?
if (op == 0 || dir_count == 0) {
// choose a pseudo-random number
lfs3_size_t x = TEST_PRNG(&prng_) % N;
// create a dir here
char name[256];
sprintf(name, "test/quartz%03x", x);
err = lfs3_mkdir(&lfs3, name);
assert(!err || err == LFS3_ERR_EXIST || err == LFS3_ERR_NOSPC);
if (err == LFS3_ERR_NOSPC) {
goto grow;
}
// deleting a dir?
} else if (op == 1) {
// choose a random dir to delete
lfs3_size_t j = TEST_PRNG(&prng_) % dir_count;
// find the dir
lfs3_dir_open(&lfs3, &dir, "test") => 0;
lfs3_dir_read(&lfs3, &dir, &info) => 0;
lfs3_dir_read(&lfs3, &dir, &info) => 0;
for (lfs3_size_t k = 0; k <= j; k++) {
lfs3_dir_read(&lfs3, &dir, &info) => 0;
}
lfs3_dir_close(&lfs3, &dir) => 0;
// try to delete this dir, ignore non-empty dirs!
char name[256];
assert(strlen(info.name) == strlen("quartz..."));
sprintf(name, "test/%s", info.name);
err = lfs3_remove(&lfs3, name);
assert(!err
|| err == LFS3_ERR_NOTEMPTY
|| err == LFS3_ERR_NOSPC);
if (err == LFS3_ERR_NOSPC) {
goto grow;
}
// renaming a dir?
} else {
// choose a random dir to rename, and a random number to
// rename to
lfs3_size_t j = TEST_PRNG(&prng_) % dir_count;
lfs3_size_t y = TEST_PRNG(&prng_) % N;
// find the dir
lfs3_dir_open(&lfs3, &dir, "test") => 0;
lfs3_dir_read(&lfs3, &dir, &info) => 0;
lfs3_dir_read(&lfs3, &dir, &info) => 0;
for (lfs3_size_t k = 0; k <= j; k++) {
lfs3_dir_read(&lfs3, &dir, &info) => 0;
}
lfs3_dir_close(&lfs3, &dir) => 0;
// rename this dir, ignore conflicts!
char old_name[256];
assert(strlen(info.name) == strlen("quartz..."));
sprintf(old_name, "test/%s", info.name);
char new_name[256];
sprintf(new_name, "test/quartz%03x", y);
err = lfs3_rename(&lfs3, old_name, new_name);
assert(!err
|| err == LFS3_ERR_NOTEMPTY
|| err == LFS3_ERR_NOSPC);
if (err == LFS3_ERR_NOSPC) {
goto grow;
}
}
// file op?
} else {
// choose a pseudo-random dir
lfs3_size_t dir_i = TEST_PRNG(&prng_) % dir_count;
// find the dir
lfs3_dir_open(&lfs3, &dir, "test") => 0;
lfs3_dir_read(&lfs3, &dir, &info) => 0;
lfs3_dir_read(&lfs3, &dir, &info) => 0;
for (lfs3_size_t k = 0; k <= dir_i; k++) {
lfs3_dir_read(&lfs3, &dir, &info) => 0;
}
lfs3_dir_close(&lfs3, &dir) => 0;
char dir_path[256];
sprintf(dir_path, "test/%s", info.name);
// how many files do we have?
lfs3_size_t count = 0;
lfs3_dir_open(&lfs3, &dir, dir_path) => 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);
while (true) {
err = lfs3_dir_read(&lfs3, &dir, &info);
assert(!err || err == LFS3_ERR_NOENT);
if (err == LFS3_ERR_NOENT) {
break;
}
assert(strlen(info.name) == strlen("amethyst..."));
assert(memcmp(
info.name,
"amethyst", strlen("amethyst")) == 0);
assert(info.type == LFS3_TYPE_REG);
assert(info.size == SIZE);
count++;
}
lfs3_dir_close(&lfs3, &dir) => 0;
// creating a new file?
if (op == 3 || count == 0) {
// choose a pseudo-random number
lfs3_size_t x = TEST_PRNG(&prng_) % M;
uint32_t wprng = TEST_PRNG(&prng_);
// create a file here
char name[256];
sprintf(name, "%s/amethyst%03x", dir_path, x);
uint8_t wbuf[SIZE];
uint8_t ck = 0;
for (lfs3_size_t j = 0; j < SIZE-1; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26);
ck = (ck + (wbuf[j] - 'a')) % 26;
}
// make the sum equal to 'a' mod 26
if (SIZE > 0) {
wbuf[SIZE-1] = 'a' + ((26 - ck) % 26);
}
lfs3_file_t file;
err = lfs3_file_open(&lfs3, &file, name,
LFS3_O_WRONLY | LFS3_O_CREAT | LFS3_O_TRUNC);
assert(!err || err == LFS3_ERR_NOSPC);
if (err == LFS3_ERR_NOSPC) {
goto grow;
}
lfs3_ssize_t d = lfs3_file_write(&lfs3, &file, wbuf, SIZE);
assert(d == SIZE || d == LFS3_ERR_NOSPC);
if (d == LFS3_ERR_NOSPC) {
lfs3_file_close(&lfs3, &file) => 0;
goto grow;
}
err = lfs3_file_close(&lfs3, &file);
assert(!err || err == LFS3_ERR_NOSPC);
if (err == LFS3_ERR_NOSPC) {
goto grow;
}
// deleting a file?
} else if (op == 4) {
// choose a random file to delete
lfs3_size_t j = TEST_PRNG(&prng_) % count;
// find the file
lfs3_dir_open(&lfs3, &dir, dir_path) => 0;
lfs3_dir_read(&lfs3, &dir, &info) => 0;
lfs3_dir_read(&lfs3, &dir, &info) => 0;
for (lfs3_size_t k = 0; k <= j; k++) {
lfs3_dir_read(&lfs3, &dir, &info) => 0;
}
lfs3_dir_close(&lfs3, &dir) => 0;
// delete this file
char name[256];
assert(strlen(info.name) == strlen("amethyst..."));
sprintf(name, "%s/%s", dir_path, info.name);
err = lfs3_remove(&lfs3, name);
assert(!err || err == LFS3_ERR_NOSPC);
if (err == LFS3_ERR_NOSPC) {
goto grow;
}
// renaming a file?
} else {
// choose a random file to rename
lfs3_size_t j = TEST_PRNG(&prng_) % count;
// find the file
lfs3_dir_open(&lfs3, &dir, dir_path) => 0;
lfs3_dir_read(&lfs3, &dir, &info) => 0;
lfs3_dir_read(&lfs3, &dir, &info) => 0;
for (lfs3_size_t k = 0; k <= j; k++) {
lfs3_dir_read(&lfs3, &dir, &info) => 0;
}
lfs3_dir_close(&lfs3, &dir) => 0;
// choose a random dir to rename to
lfs3_size_t dir_j = TEST_PRNG(&prng_) % dir_count;
// find the dir
struct lfs3_info info_;
lfs3_dir_open(&lfs3, &dir, "test") => 0;
lfs3_dir_read(&lfs3, &dir, &info_) => 0;
lfs3_dir_read(&lfs3, &dir, &info_) => 0;
for (lfs3_size_t k = 0; k <= dir_j; k++) {
lfs3_dir_read(&lfs3, &dir, &info_) => 0;
}
lfs3_dir_close(&lfs3, &dir) => 0;
// choose a random file to rename to
lfs3_size_t y = TEST_PRNG(&prng_) % M;
// rename this file
char old_name[256];
assert(strlen(info.name) == strlen("amethyst..."));
sprintf(old_name, "%s/%s", dir_path, info.name);
char new_name[256];
sprintf(new_name, "test/%s/amethyst%03x", info_.name, y);
err = lfs3_rename(&lfs3, old_name, new_name);
assert(!err || err == LFS3_ERR_NOSPC);
if (err == LFS3_ERR_NOSPC) {
goto grow;
}
}
}
// update our state file
state.i = i;
state.prng = prng_;
err = lfs3_file_open(&lfs3, &state_file, "state",
LFS3_O_WRONLY | LFS3_O_CREAT | LFS3_O_TRUNC);
assert(!err || err == LFS3_ERR_NOSPC);
if (err == LFS3_ERR_NOSPC) {
goto grow;
}
lfs3_ssize_t d = lfs3_file_write(&lfs3, &state_file,
&state, sizeof(state));
assert(d == sizeof(state) || d == LFS3_ERR_NOSPC);
if (d == LFS3_ERR_NOSPC) {
lfs3_file_close(&lfs3, &state_file) => 0;
goto grow;
}
err = lfs3_file_close(&lfs3, &state_file);
assert(!err || err == LFS3_ERR_NOSPC);
if (err == LFS3_ERR_NOSPC) {
goto grow;
}
prng = prng_;
continue;
grow:;
// try growing the filesystem
struct lfs3_fsinfo fsinfo;
lfs3_fs_stat(&lfs3, &fsinfo) => 0;
assert(fsinfo.block_count >= FORMAT_BLOCK_COUNT);
assert(fsinfo.block_count <= BLOCK_COUNT);
lfs3_ssize_t used = lfs3_fs_usage(&lfs3);
assert(used >= 0);
// we may need to grow multiple blocks before the system gets unstuck
lfs3_size_t block_count_ = fsinfo.block_count;
while (true) {
assert(block_count_ < BLOCK_COUNT);
block_count_ += 1;
err = lfs3_fs_grow(&lfs3, block_count_);
assert(!err || err == LFS3_ERR_NOSPC);
if (err == LFS3_ERR_NOSPC) {
continue;
}
break;
}
printf("grew %d/%d -> %d/%d\n",
used, fsinfo.block_count,
used, block_count_);
// fsstat up to date?
lfs3_fs_stat(&lfs3, &fsinfo) => 0;
assert(fsinfo.block_size == BLOCK_SIZE);
assert(fsinfo.block_count == block_count_);
assert(fsinfo.name_limit == LFS3_NAME_MAX);
assert(fsinfo.file_limit == LFS3_FILE_MAX);
goto again;
}
for (int remount = 0; remount < 2; remount++) {
// remount?
if (remount) {
lfs3_unmount(&lfs3) => 0;
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0;
}
// check that things look more-or-less ok
lfs3_dir_t dir;
lfs3_dir_open(&lfs3, &dir, "test") => 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);
while (true) {
int err = lfs3_dir_read(&lfs3, &dir, &info);
assert(!err || err == LFS3_ERR_NOENT);
if (err == LFS3_ERR_NOENT) {
break;
}
assert(strlen(info.name) == strlen("quartz..."));
assert(memcmp(info.name, "quartz", strlen("quartz")) == 0);
assert(info.type == LFS3_TYPE_DIR);
assert(info.size == 0);
// check that our dirs look more-or-less ok
char name[256];
sprintf(name, "test/%s", info.name);
lfs3_dir_t dir_;
lfs3_dir_open(&lfs3, &dir_, name) => 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);
while (true) {
err = lfs3_dir_read(&lfs3, &dir_, &info_);
assert(!err || err == LFS3_ERR_NOENT);
if (err == LFS3_ERR_NOENT) {
break;
}
assert(strlen(info_.name) == strlen("amethyst..."));
assert(memcmp(
info_.name,
"amethyst", strlen("amethyst")) == 0);
assert(info_.type == LFS3_TYPE_REG);
assert(info_.size == SIZE);
// at least try to read the files
sprintf(name, "test/%s/%s", info.name, info_.name);
lfs3_file_t file;
lfs3_file_open(&lfs3, &file, name, LFS3_O_RDONLY) => 0;
uint8_t rbuf[SIZE];
lfs3_file_read(&lfs3, &file, rbuf, SIZE) => SIZE;
// all data should be lowercase ascii
for (lfs3_size_t j = 0; j < SIZE; j++) {
assert(rbuf[j] >= 'a' && rbuf[j] <= 'z');
}
// sum should be equal to 'a' mod 26
uint8_t ck = 0;
for (lfs3_size_t j = 0; j < SIZE; j++) {
ck = (ck + (rbuf[j] - 'a')) % 26;
}
assert(ck == 0);
lfs3_file_close(&lfs3, &file) => 0;
}
lfs3_dir_close(&lfs3, &dir_) => 0;
}
lfs3_dir_close(&lfs3, &dir) => 0;
}
lfs3_unmount(&lfs3) => 0;
'''