Files
littlefs/tests/test_grow.toml
T
Christopher Haster 76493142e7 Reworked stickynote API, exposed LFS_TYPE_STICKYNOTE to users
This adds the LFS_TYPE_STICKYNOTE type, allowing users to interact with
stickynotes as long as they aren't orphaned.

This hopefully solves the long-standing mess that was the LFS_O_EXCL
API.

---

As for what I mean by orphaned vs non-orphaned stickynotes:

Non-orphaned stickynotes represent files that have been "created" (via
LFS_O_CREAT), but not "committed" (via sync/close). You can still close
and convert the stickynote to a reg file, so these aren't orphans. These
are also called "uncreated" files in some parts of the codebase:

- open+O_CREAT -> non-orphaned stickynote (uncreated file)

Orphaned stickynotes are possible by either removing an open file, or
desyncing a file before sync/close. These are still invisible to the
user and will be eventually cleaned up after the last file handle is
closed:

- open+remove               -> orphaned stickynote (zombied file)
- open+O_CREAT+desync+close -> orphaned stickynote (orphaned file)

Desynced files are a bit special. Even though they technically aren't
orphaned, they also behave like orphaned file handles:

- open+O_CREAT+close -> orphaned stickynote (desynced file)

The idea is this mimics the state of files post-close, and allows for
some tricks like using a desync file as a temporary file with no
observable effects on the filesystem.

---

The motivation for this comes from staring at the LFS_O_EXCL API for too
long and realizing the problem is that littlefs's API contradicts itself
when it comes to whether or not uncreated files exist.

This solution is to consistently treat uncreated files as though they
exist (the alternative would make LFS_O_EXCL pretty much useless), but I
really didn't want to do this as having what appears to be normal files
disappear after powerloss risks confusion.

The compromise here is to give these files a special type, repurposing
the internal LFS_TAG_STICKYNOTE, which hopefully hints to the user these
won't behave like normal files.

If the user is more interested in POSIX compatibility, they can always
map these to either LFS_TYPE_REG or LFS_ERR_NOENT, whichever they think
is the least confusing.

As a quirk of littlefs's API, stickynotes should never actually contain
any data, and will always have size 0.

However they can have custom attributes assigned now (which is I guess
ok? also TODO should probably test this).

---

The implementation right now is a bit naive, I mostly just wanted to get
the tests working again in this new model. It may be possible to claw
back some of this code cost:

           code          stack          ctx
  before: 35740           2440          640
  after:  35952 (+0.6%)   2440 (+0.0%)  640 (+0.0%)
2025-04-23 23:22:09 -05:00

3123 lines
105 KiB
TOML

# Test variable block counts and grow related things
after = [
'test_dirs',
'test_files',
'test_fwrite',
'test_forphans',
'test_alloc',
'test_mount',
]
# 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',
'2',
]
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 lfs_config cfg = *CFG;
cfg.block_count = SMALLER_BLOCK_COUNT;
lfs_t lfs;
lfsr_format(&lfs, LFS_F_RDWR, &cfg) => 0;
lfsr_mount(&lfs, LFS_M_RDWR, &cfg) => 0;
// fsstat up to date?
struct lfs_fsinfo fsinfo;
lfsr_fs_stat(&lfs, &fsinfo) => 0;
assert(fsinfo.block_size == BLOCK_SIZE);
assert(fsinfo.block_count == SMALLER_BLOCK_COUNT);
assert(fsinfo.name_limit == LFS_NAME_MAX);
assert(fsinfo.file_limit == LFS_FILE_MAX);
// do some work
lfsr_file_t file;
lfsr_file_open(&lfs, &file, "hello",
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
uint8_t wbuf[1024] = "Hello World!";
lfs_size_t wsize = strlen((char*)wbuf);
lfsr_file_write(&lfs, &file, wbuf, wsize) => wsize;
lfsr_file_close(&lfs, &file) => 0;
struct lfs_info info;
lfsr_stat(&lfs, "hello", &info) => 0;
assert(strcmp(info.name, "hello") == 0);
assert(info.type == LFS_TYPE_REG);
assert(info.size == wsize);
uint8_t rbuf[1024];
lfsr_file_open(&lfs, &file, "hello", LFS_O_RDONLY) => 0;
lfsr_file_read(&lfs, &file, rbuf, wsize) => wsize;
lfsr_file_close(&lfs, &file) => 0;
assert(memcmp(rbuf, wbuf, wsize) == 0);
lfsr_unmount(&lfs) => 0;
//////
// try to mount with a bigger block count
cfg = *CFG;
cfg.block_count = BIGGER_BLOCK_COUNT;
lfsr_mount(&lfs, LFS_M_RDWR, &cfg) => 0;
// fsstat up to date?
lfsr_fs_stat(&lfs, &fsinfo) => 0;
assert(fsinfo.block_size == BLOCK_SIZE);
assert(fsinfo.block_count == SMALLER_BLOCK_COUNT);
assert(fsinfo.name_limit == LFS_NAME_MAX);
assert(fsinfo.file_limit == LFS_FILE_MAX);
// file still exists?
lfsr_stat(&lfs, "hello", &info) => 0;
assert(strcmp(info.name, "hello") == 0);
assert(info.type == LFS_TYPE_REG);
assert(info.size == wsize);
lfsr_file_open(&lfs, &file, "hello", LFS_O_RDONLY) => 0;
lfsr_file_read(&lfs, &file, rbuf, wsize) => wsize;
lfsr_file_close(&lfs, &file) => 0;
assert(memcmp(rbuf, wbuf, wsize) == 0);
// do some work
lfsr_file_open(&lfs, &file, "hello",
LFS_O_WRONLY | LFS_O_TRUNC) => 0;
strcpy((char*)wbuf, "Chris was here!");
wsize = strlen((char*)wbuf);
lfsr_file_write(&lfs, &file, wbuf, wsize) => wsize;
lfsr_file_close(&lfs, &file) => 0;
lfsr_stat(&lfs, "hello", &info) => 0;
assert(strcmp(info.name, "hello") == 0);
assert(info.type == LFS_TYPE_REG);
assert(info.size == wsize);
lfsr_file_open(&lfs, &file, "hello", LFS_O_RDONLY) => 0;
lfsr_file_read(&lfs, &file, rbuf, wsize) => wsize;
lfsr_file_close(&lfs, &file) => 0;
assert(memcmp(rbuf, wbuf, wsize) == 0);
lfsr_unmount(&lfs) => 0;
// stays after a mount?
lfsr_mount(&lfs, LFS_M_RDWR, &cfg) => 0;
// fsstat up to date?
lfsr_fs_stat(&lfs, &fsinfo) => 0;
assert(fsinfo.block_size == BLOCK_SIZE);
assert(fsinfo.block_count == SMALLER_BLOCK_COUNT);
assert(fsinfo.name_limit == LFS_NAME_MAX);
assert(fsinfo.file_limit == LFS_FILE_MAX);
// file still exists?
lfsr_stat(&lfs, "hello", &info) => 0;
assert(strcmp(info.name, "hello") == 0);
assert(info.type == LFS_TYPE_REG);
assert(info.size == wsize);
lfsr_file_open(&lfs, &file, "hello", LFS_O_RDONLY) => 0;
lfsr_file_read(&lfs, &file, rbuf, wsize) => wsize;
lfsr_file_close(&lfs, &file) => 0;
assert(memcmp(rbuf, wbuf, wsize) == 0);
lfsr_unmount(&lfs) => 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',
'2',
]
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 lfs_config cfg = *CFG;
cfg.block_count = BIGGER_BLOCK_COUNT;
lfs_t lfs;
lfsr_format(&lfs, LFS_F_RDWR, &cfg) => 0;
lfsr_mount(&lfs, LFS_M_RDWR, &cfg) => 0;
// fsstat up to date?
struct lfs_fsinfo fsinfo;
lfsr_fs_stat(&lfs, &fsinfo) => 0;
assert(fsinfo.block_size == BLOCK_SIZE);
assert(fsinfo.block_count == BIGGER_BLOCK_COUNT);
assert(fsinfo.name_limit == LFS_NAME_MAX);
assert(fsinfo.file_limit == LFS_FILE_MAX);
// do some work
lfsr_file_t file;
lfsr_file_open(&lfs, &file, "hello",
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
uint8_t wbuf[1024] = "Hello World!";
lfs_size_t wsize = strlen((char*)wbuf);
lfsr_file_write(&lfs, &file, wbuf, wsize) => wsize;
lfsr_file_close(&lfs, &file) => 0;
struct lfs_info info;
lfsr_stat(&lfs, "hello", &info) => 0;
assert(strcmp(info.name, "hello") == 0);
assert(info.type == LFS_TYPE_REG);
assert(info.size == wsize);
uint8_t rbuf[1024];
lfsr_file_open(&lfs, &file, "hello", LFS_O_RDONLY) => 0;
lfsr_file_read(&lfs, &file, rbuf, wsize) => wsize;
lfsr_file_close(&lfs, &file) => 0;
assert(memcmp(rbuf, wbuf, wsize) == 0);
lfsr_unmount(&lfs) => 0;
//////
// try to mount with a smaller block count
cfg = *CFG;
cfg.block_count = SMALLER_BLOCK_COUNT;
lfsr_mount(&lfs, LFS_M_RDWR, &cfg) => LFS_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',
'2',
]
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 lfs_config cfg = *CFG;
cfg.block_count = SMALLER_BLOCK_COUNT;
lfs_t lfs;
lfsr_format(&lfs, LFS_F_RDWR, &cfg) => 0;
lfsr_mount(&lfs, LFS_M_RDWR, &cfg) => 0;
// fsstat up to date?
struct lfs_fsinfo fsinfo;
lfsr_fs_stat(&lfs, &fsinfo) => 0;
assert(fsinfo.block_size == BLOCK_SIZE);
assert(fsinfo.block_count == SMALLER_BLOCK_COUNT);
assert(fsinfo.name_limit == LFS_NAME_MAX);
assert(fsinfo.file_limit == LFS_FILE_MAX);
// do some work
lfsr_file_t file;
lfsr_file_open(&lfs, &file, "hello",
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
uint8_t wbuf[1024] = "Hello World!";
lfs_size_t wsize = strlen((char*)wbuf);
lfsr_file_write(&lfs, &file, wbuf, wsize) => wsize;
lfsr_file_close(&lfs, &file) => 0;
struct lfs_info info;
lfsr_stat(&lfs, "hello", &info) => 0;
assert(strcmp(info.name, "hello") == 0);
assert(info.type == LFS_TYPE_REG);
assert(info.size == wsize);
uint8_t rbuf[1024];
lfsr_file_open(&lfs, &file, "hello", LFS_O_RDONLY) => 0;
lfsr_file_read(&lfs, &file, rbuf, wsize) => wsize;
lfsr_file_close(&lfs, &file) => 0;
assert(memcmp(rbuf, wbuf, wsize) == 0);
lfsr_unmount(&lfs) => 0;
//////
// try to grow our filesystem
cfg = *CFG;
cfg.block_count = BIGGER_BLOCK_COUNT;
lfsr_mount(&lfs, LFS_M_RDWR, &cfg) => 0;
lfsr_fs_grow(&lfs, BIGGER_BLOCK_COUNT) => 0;
// fsstat up to date?
lfsr_fs_stat(&lfs, &fsinfo) => 0;
assert(fsinfo.block_size == BLOCK_SIZE);
assert(fsinfo.block_count == BIGGER_BLOCK_COUNT);
assert(fsinfo.name_limit == LFS_NAME_MAX);
assert(fsinfo.file_limit == LFS_FILE_MAX);
// file still exists?
lfsr_stat(&lfs, "hello", &info) => 0;
assert(strcmp(info.name, "hello") == 0);
assert(info.type == LFS_TYPE_REG);
assert(info.size == wsize);
lfsr_file_open(&lfs, &file, "hello", LFS_O_RDONLY) => 0;
lfsr_file_read(&lfs, &file, rbuf, wsize) => wsize;
lfsr_file_close(&lfs, &file) => 0;
assert(memcmp(rbuf, wbuf, wsize) == 0);
// do some work
lfsr_file_open(&lfs, &file, "hello",
LFS_O_WRONLY | LFS_O_TRUNC) => 0;
strcpy((char*)wbuf, "Chris was here!");
wsize = strlen((char*)wbuf);
lfsr_file_write(&lfs, &file, wbuf, wsize) => wsize;
lfsr_file_close(&lfs, &file) => 0;
lfsr_stat(&lfs, "hello", &info) => 0;
assert(strcmp(info.name, "hello") == 0);
assert(info.type == LFS_TYPE_REG);
assert(info.size == wsize);
lfsr_file_open(&lfs, &file, "hello", LFS_O_RDONLY) => 0;
lfsr_file_read(&lfs, &file, rbuf, wsize) => wsize;
lfsr_file_close(&lfs, &file) => 0;
assert(memcmp(rbuf, wbuf, wsize) == 0);
lfsr_unmount(&lfs) => 0;
// stays after a mount?
lfsr_mount(&lfs, LFS_M_RDWR, &cfg) => 0;
// fsstat up to date?
lfsr_fs_stat(&lfs, &fsinfo) => 0;
assert(fsinfo.block_size == BLOCK_SIZE);
assert(fsinfo.block_count == BIGGER_BLOCK_COUNT);
assert(fsinfo.name_limit == LFS_NAME_MAX);
assert(fsinfo.file_limit == LFS_FILE_MAX);
// file still exists?
lfsr_stat(&lfs, "hello", &info) => 0;
assert(strcmp(info.name, "hello") == 0);
assert(info.type == LFS_TYPE_REG);
assert(info.size == wsize);
lfsr_file_open(&lfs, &file, "hello", LFS_O_RDONLY) => 0;
lfsr_file_read(&lfs, &file, rbuf, wsize) => wsize;
lfsr_file_close(&lfs, &file) => 0;
assert(memcmp(rbuf, wbuf, wsize) == 0);
lfsr_unmount(&lfs) => 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',
'2',
]
code = '''
// create a smaller fs
struct lfs_config cfg = *CFG;
cfg.block_count = SMALLER_BLOCK_COUNT;
lfs_t lfs;
lfsr_format(&lfs, LFS_F_RDWR, &cfg) => 0;
lfsr_mount(&lfs, LFS_M_RDWR, &cfg) => 0;
// fsstat up to date?
struct lfs_fsinfo fsinfo;
lfsr_fs_stat(&lfs, &fsinfo) => 0;
assert(fsinfo.block_size == BLOCK_SIZE);
assert(fsinfo.block_count == SMALLER_BLOCK_COUNT);
assert(fsinfo.name_limit == LFS_NAME_MAX);
assert(fsinfo.file_limit == LFS_FILE_MAX);
// do some work
lfsr_file_t file;
lfsr_file_open(&lfs, &file, "hello",
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
uint8_t wbuf[1024] = "Hello World!";
lfs_size_t wsize = strlen((char*)wbuf);
lfsr_file_write(&lfs, &file, wbuf, wsize) => wsize;
lfsr_file_close(&lfs, &file) => 0;
struct lfs_info info;
lfsr_stat(&lfs, "hello", &info) => 0;
assert(strcmp(info.name, "hello") == 0);
assert(info.type == LFS_TYPE_REG);
assert(info.size == wsize);
uint8_t rbuf[1024];
lfsr_file_open(&lfs, &file, "hello", LFS_O_RDONLY) => 0;
lfsr_file_read(&lfs, &file, rbuf, wsize) => wsize;
lfsr_file_close(&lfs, &file) => 0;
assert(memcmp(rbuf, wbuf, wsize) == 0);
lfsr_unmount(&lfs) => 0;
//////
// try to grow to same size
cfg = *CFG;
cfg.block_count = SMALLER_BLOCK_COUNT;
lfsr_mount(&lfs, LFS_M_RDWR, &cfg) => 0;
lfsr_fs_grow(&lfs, SMALLER_BLOCK_COUNT) => 0;
// fsstat up to date?
lfsr_fs_stat(&lfs, &fsinfo) => 0;
assert(fsinfo.block_size == BLOCK_SIZE);
assert(fsinfo.block_count == SMALLER_BLOCK_COUNT);
assert(fsinfo.name_limit == LFS_NAME_MAX);
assert(fsinfo.file_limit == LFS_FILE_MAX);
// file still exists?
lfsr_stat(&lfs, "hello", &info) => 0;
assert(strcmp(info.name, "hello") == 0);
assert(info.type == LFS_TYPE_REG);
assert(info.size == wsize);
lfsr_file_open(&lfs, &file, "hello", LFS_O_RDONLY) => 0;
lfsr_file_read(&lfs, &file, rbuf, wsize) => wsize;
lfsr_file_close(&lfs, &file) => 0;
assert(memcmp(rbuf, wbuf, wsize) == 0);
// do some work
lfsr_file_open(&lfs, &file, "hello",
LFS_O_WRONLY | LFS_O_TRUNC) => 0;
strcpy((char*)wbuf, "Chris was here!");
wsize = strlen((char*)wbuf);
lfsr_file_write(&lfs, &file, wbuf, wsize) => wsize;
lfsr_file_close(&lfs, &file) => 0;
lfsr_stat(&lfs, "hello", &info) => 0;
assert(strcmp(info.name, "hello") == 0);
assert(info.type == LFS_TYPE_REG);
assert(info.size == wsize);
lfsr_file_open(&lfs, &file, "hello", LFS_O_RDONLY) => 0;
lfsr_file_read(&lfs, &file, rbuf, wsize) => wsize;
lfsr_file_close(&lfs, &file) => 0;
assert(memcmp(rbuf, wbuf, wsize) == 0);
lfsr_unmount(&lfs) => 0;
// stays after a mount?
lfsr_mount(&lfs, LFS_M_RDWR, &cfg) => 0;
// fsstat up to date?
lfsr_fs_stat(&lfs, &fsinfo) => 0;
assert(fsinfo.block_size == BLOCK_SIZE);
assert(fsinfo.block_count == SMALLER_BLOCK_COUNT);
assert(fsinfo.name_limit == LFS_NAME_MAX);
assert(fsinfo.file_limit == LFS_FILE_MAX);
// file still exists?
lfsr_stat(&lfs, "hello", &info) => 0;
assert(strcmp(info.name, "hello") == 0);
assert(info.type == LFS_TYPE_REG);
assert(info.size == wsize);
lfsr_file_open(&lfs, &file, "hello", LFS_O_RDONLY) => 0;
lfsr_file_read(&lfs, &file, rbuf, wsize) => wsize;
lfsr_file_close(&lfs, &file) => 0;
assert(memcmp(rbuf, wbuf, wsize) == 0);
lfsr_unmount(&lfs) => 0;
'''
# These tests try various fuzz tests while incrementally growing the
# filesystem. When encountering LFS_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.INIT_BLOCK_COUNT = 2
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 lfs_config cfg = *CFG;
cfg.block_count = INIT_BLOCK_COUNT;
lfs_t lfs;
lfsr_format(&lfs, LFS_F_RDWR, &cfg) => 0;
// mount with maximum block count
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
// fsstat up to date?
struct lfs_fsinfo fsinfo;
lfsr_fs_stat(&lfs, &fsinfo) => 0;
assert(fsinfo.block_size == BLOCK_SIZE);
assert(fsinfo.block_count == INIT_BLOCK_COUNT);
assert(fsinfo.name_limit == LFS_NAME_MAX);
assert(fsinfo.file_limit == LFS_FILE_MAX);
// make this many directories
for (lfs_size_t i = 0; i < N; i++) {
again:;
char name[256];
sprintf(name, "dir%03x", i);
int err = lfsr_mkdir(&lfs, name);
assert(!err || err == LFS_ERR_NOSPC);
if (err == LFS_ERR_NOSPC) {
goto grow;
}
continue;
grow:;
// try growing the filesystem
struct lfs_fsinfo fsinfo;
lfsr_fs_stat(&lfs, &fsinfo) => 0;
assert(fsinfo.block_count >= INIT_BLOCK_COUNT);
assert(fsinfo.block_count <= BLOCK_COUNT);
lfs_ssize_t used = lfsr_fs_size(&lfs);
assert(used >= 0);
// we may need to grow multiple blocks before the system gets unstuck
lfs_size_t block_count_ = fsinfo.block_count;
while (true) {
assert(block_count_ < BLOCK_COUNT);
block_count_ += 1;
err = lfsr_fs_grow(&lfs, block_count_);
assert(!err || err == LFS_ERR_NOSPC);
if (err == LFS_ERR_NOSPC) {
continue;
}
break;
}
printf("grew %d/%d -> %d/%d\n",
used, fsinfo.block_count,
used, block_count_);
// remount?
if (REMOUNT) {
lfsr_unmount(&lfs) => 0;
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
}
// fsstat up to date?
lfsr_fs_stat(&lfs, &fsinfo) => 0;
assert(fsinfo.block_size == BLOCK_SIZE);
assert(fsinfo.block_count == block_count_);
assert(fsinfo.name_limit == LFS_NAME_MAX);
assert(fsinfo.file_limit == LFS_FILE_MAX);
goto again;
}
for (int remount = 0; remount < 2; remount++) {
// remount?
if (remount) {
lfsr_unmount(&lfs) => 0;
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
}
// grm should be zero here
assert(lfs.grm_p[0] == 0);
// check that our mkdir worked
for (lfs_size_t i = 0; i < N; i++) {
char name[256];
sprintf(name, "dir%03x", i);
struct lfs_info info;
lfsr_stat(&lfs, name, &info) => 0;
assert(strcmp(info.name, name) == 0);
assert(info.type == LFS_TYPE_DIR);
assert(info.size == 0);
}
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);
assert(info.size == 0);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "..") == 0);
assert(info.type == LFS_TYPE_DIR);
assert(info.size == 0);
for (lfs_size_t i = 0; i < N; i++) {
char name[256];
sprintf(name, "dir%03x", i);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, name) == 0);
assert(info.type == LFS_TYPE_DIR);
assert(info.size == 0);
}
lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT;
lfsr_dir_close(&lfs, &dir) => 0;
for (lfs_size_t i = 0; i < N; i++) {
char name[256];
sprintf(name, "dir%03x", i);
lfsr_dir_open(&lfs, &dir, name) => 0;
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, ".") == 0);
assert(info.type == LFS_TYPE_DIR);
assert(info.size == 0);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "..") == 0);
assert(info.type == LFS_TYPE_DIR);
assert(info.size == 0);
lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT;
lfsr_dir_close(&lfs, &dir) => 0;
}
}
lfsr_unmount(&lfs) => 0;
'''
[cases.test_grow_incr_spam_dir_fuzz]
defines.INIT_BLOCK_COUNT = 2
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 lfs_config cfg = *CFG;
cfg.block_count = INIT_BLOCK_COUNT;
lfs_t lfs;
lfsr_format(&lfs, LFS_F_RDWR, &cfg) => 0;
// mount with maximum block count
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
// fsstat up to date?
struct lfs_fsinfo fsinfo;
lfsr_fs_stat(&lfs, &fsinfo) => 0;
assert(fsinfo.block_size == BLOCK_SIZE);
assert(fsinfo.block_count == INIT_BLOCK_COUNT);
assert(fsinfo.name_limit == LFS_NAME_MAX);
assert(fsinfo.file_limit == LFS_FILE_MAX);
// set up a simulation to compare against
lfs_size_t *sim = malloc(N*sizeof(lfs_size_t));
lfs_size_t sim_size = 0;
uint32_t prng = SEED;
for (lfs_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
lfs_size_t x = TEST_PRNG(&prng_) % N;
// create a directory here
char name[256];
sprintf(name, "dir%03x", x);
int err = lfsr_mkdir(&lfs, name);
assert(!err || err == LFS_ERR_EXIST || err == LFS_ERR_NOSPC);
if (err == LFS_ERR_NOSPC) {
goto grow;
}
// insert into our sim
for (lfs_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(lfs_size_t));
sim_size += 1;
sim[j] = x;
}
break;
}
}
} else if (op == 1) {
// choose a pseudo-random entry to delete
lfs_size_t j = TEST_PRNG(&prng_) % sim_size;
lfs_size_t x = sim[j];
// remove this directory
char name[256];
sprintf(name, "dir%03x", x);
int err = lfsr_remove(&lfs, name);
assert(!err || err == LFS_ERR_NOSPC);
if (err == LFS_ERR_NOSPC) {
goto grow;
}
// delete from our sim
memmove(&sim[j], &sim[j+1],
(sim_size-(j+1))*sizeof(lfs_size_t));
sim_size -= 1;
} else {
// choose a pseudo-random entry to rename, and a pseudo-random
// number to rename to
lfs_size_t j = TEST_PRNG(&prng_) % sim_size;
lfs_size_t x = sim[j];
lfs_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 = lfsr_rename(&lfs, old_name, new_name);
assert(!err || err == LFS_ERR_NOSPC);
if (err == LFS_ERR_NOSPC) {
goto grow;
}
for (lfs_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(lfs_size_t));
sim_size -= 1;
} else {
// first delete
memmove(&sim[j], &sim[j+1],
(sim_size-(j+1))*sizeof(lfs_size_t));
if (k > j) {
k -= 1;
}
// then insert
memmove(&sim[k+1], &sim[k],
(sim_size-k)*sizeof(lfs_size_t));
sim[k] = y;
}
break;
}
}
}
prng = prng_;
continue;
grow:;
// try growing the filesystem
struct lfs_fsinfo fsinfo;
lfsr_fs_stat(&lfs, &fsinfo) => 0;
assert(fsinfo.block_count >= INIT_BLOCK_COUNT);
assert(fsinfo.block_count <= BLOCK_COUNT);
lfs_ssize_t used = lfsr_fs_size(&lfs);
assert(used >= 0);
// we may need to grow multiple blocks before the system gets unstuck
lfs_size_t block_count_ = fsinfo.block_count;
while (true) {
assert(block_count_ < BLOCK_COUNT);
block_count_ += 1;
int err = lfsr_fs_grow(&lfs, block_count_);
assert(!err || err == LFS_ERR_NOSPC);
if (err == LFS_ERR_NOSPC) {
continue;
}
break;
}
printf("grew %d/%d -> %d/%d\n",
used, fsinfo.block_count,
used, block_count_);
// remount?
if (REMOUNT) {
lfsr_unmount(&lfs) => 0;
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
}
// fsstat up to date?
lfsr_fs_stat(&lfs, &fsinfo) => 0;
assert(fsinfo.block_size == BLOCK_SIZE);
assert(fsinfo.block_count == block_count_);
assert(fsinfo.name_limit == LFS_NAME_MAX);
assert(fsinfo.file_limit == LFS_FILE_MAX);
goto again;
}
for (int remount = 0; remount < 2; remount++) {
// remount?
if (remount) {
lfsr_unmount(&lfs) => 0;
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
}
// grm should be zero here
assert(lfs.grm_p[0] == 0);
// test that our directories match our simulation
for (lfs_size_t j = 0; j < sim_size; j++) {
char name[256];
sprintf(name, "dir%03x", sim[j]);
struct lfs_info info;
lfsr_stat(&lfs, name, &info) => 0;
char name2[256];
sprintf(name2, "dir%03x", sim[j]);
assert(strcmp(info.name, name2) == 0);
assert(info.type == LFS_TYPE_DIR);
assert(info.size == 0);
}
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);
assert(info.size == 0);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "..") == 0);
assert(info.type == LFS_TYPE_DIR);
assert(info.size == 0);
for (lfs_size_t j = 0; j < sim_size; j++) {
char name[256];
sprintf(name, "dir%03x", sim[j]);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, name) == 0);
assert(info.type == LFS_TYPE_DIR);
assert(info.size == 0);
}
lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT;
lfsr_dir_close(&lfs, &dir) => 0;
}
// clean up sim/lfs
free(sim);
lfsr_unmount(&lfs) => 0;
'''
[cases.test_grow_incr_spam_file_many]
defines.INIT_BLOCK_COUNT = 2
defines.REMOUNT = [false, true]
defines.N = [1, 2, 4, 8, 16, 32, 64]
defines.SIZE = [
'0',
'FILE_CACHE_SIZE/2',
'2*FILE_CACHE_SIZE',
'BLOCK_SIZE/2',
'BLOCK_SIZE',
'2*BLOCK_SIZE',
'4*BLOCK_SIZE',
]
if = '(SIZE*N)/BLOCK_SIZE <= 32'
code = '''
// start with a small number of blocks
struct lfs_config cfg = *CFG;
cfg.block_count = INIT_BLOCK_COUNT;
lfs_t lfs;
lfsr_format(&lfs, LFS_F_RDWR, &cfg) => 0;
// mount with maximum block count
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
// fsstat up to date?
struct lfs_fsinfo fsinfo;
lfsr_fs_stat(&lfs, &fsinfo) => 0;
assert(fsinfo.block_size == BLOCK_SIZE);
assert(fsinfo.block_count == INIT_BLOCK_COUNT);
assert(fsinfo.name_limit == LFS_NAME_MAX);
assert(fsinfo.file_limit == LFS_FILE_MAX);
uint32_t prng = 42;
for (lfs_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 (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) {
goto grow;
}
lfs_ssize_t d = lfsr_file_write(&lfs, &file, wbuf, SIZE);
assert(d == SIZE || d == LFS_ERR_NOSPC);
if (d == LFS_ERR_NOSPC) {
lfsr_file_close(&lfs, &file) => 0;
goto grow;
}
err = lfsr_file_close(&lfs, &file);
assert(!err || err == LFS_ERR_NOSPC);
if (err == LFS_ERR_NOSPC) {
goto grow;
}
prng = prng_;
continue;
grow:;
// try growing the filesystem
struct lfs_fsinfo fsinfo;
lfsr_fs_stat(&lfs, &fsinfo) => 0;
assert(fsinfo.block_count >= INIT_BLOCK_COUNT);
assert(fsinfo.block_count <= BLOCK_COUNT);
lfs_ssize_t used = lfsr_fs_size(&lfs);
assert(used >= 0);
// we may need to grow multiple blocks before the system gets unstuck
lfs_size_t block_count_ = fsinfo.block_count;
while (true) {
assert(block_count_ < BLOCK_COUNT);
block_count_ += 1;
err = lfsr_fs_grow(&lfs, block_count_);
assert(!err || err == LFS_ERR_NOSPC);
if (err == LFS_ERR_NOSPC) {
continue;
}
break;
}
printf("grew %d/%d -> %d/%d\n",
used, fsinfo.block_count,
used, block_count_);
// remount?
if (REMOUNT) {
lfsr_unmount(&lfs) => 0;
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
}
// fsstat up to date?
lfsr_fs_stat(&lfs, &fsinfo) => 0;
assert(fsinfo.block_size == BLOCK_SIZE);
assert(fsinfo.block_count == block_count_);
assert(fsinfo.name_limit == LFS_NAME_MAX);
assert(fsinfo.file_limit == LFS_FILE_MAX);
goto again;
}
for (int remount = 0; remount < 2; remount++) {
// remount?
if (remount) {
lfsr_unmount(&lfs) => 0;
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 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, "amethyst%03x", 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;
'''
[cases.test_grow_incr_spam_file_fuzz]
defines.INIT_BLOCK_COUNT = 2
defines.REMOUNT = [false, true]
defines.N = [1, 2, 4, 8, 16, 32, 64]
defines.OPS = 1024
defines.SIZE = [
'0',
'FILE_CACHE_SIZE/2',
'2*FILE_CACHE_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 lfs_config cfg = *CFG;
cfg.block_count = INIT_BLOCK_COUNT;
lfs_t lfs;
lfsr_format(&lfs, LFS_F_RDWR, &cfg) => 0;
// mount with maximum block count
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
// fsstat up to date?
struct lfs_fsinfo fsinfo;
lfsr_fs_stat(&lfs, &fsinfo) => 0;
assert(fsinfo.block_size == BLOCK_SIZE);
assert(fsinfo.block_count == INIT_BLOCK_COUNT);
assert(fsinfo.name_limit == LFS_NAME_MAX);
assert(fsinfo.file_limit == LFS_FILE_MAX);
// set up a simulation to compare against
lfs_size_t *sim = malloc(N*sizeof(lfs_size_t));
uint32_t *sim_prngs = malloc(N*sizeof(uint32_t));
lfs_size_t sim_size = 0;
uint32_t prng = SEED;
for (lfs_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
lfs_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 (lfs_size_t j = 0; j < SIZE; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&wprng_) % 26);
}
lfsr_file_t file;
int err = lfsr_file_open(&lfs, &file, name,
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC);
assert(!err || err == LFS_ERR_NOSPC);
if (err == LFS_ERR_NOSPC) {
goto grow;
}
lfs_ssize_t d = lfsr_file_write(&lfs, &file, wbuf, SIZE);
assert(d == SIZE || d == LFS_ERR_NOSPC);
if (d == LFS_ERR_NOSPC) {
lfsr_file_close(&lfs, &file) => 0;
goto grow;
}
err = lfsr_file_close(&lfs, &file);
assert(!err || err == LFS_ERR_NOSPC);
if (err == LFS_ERR_NOSPC) {
goto grow;
}
// insert into our sim
for (lfs_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(lfs_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
lfs_size_t j = TEST_PRNG(&prng_) % sim_size;
lfs_size_t x = sim[j];
// delete this file
char name[256];
sprintf(name, "amethyst%03x", x);
int err = lfsr_remove(&lfs, name);
assert(!err || err == LFS_ERR_NOSPC);
if (err == LFS_ERR_NOSPC) {
goto grow;
}
// delete from our sim
memmove(&sim[j], &sim[j+1],
(sim_size-(j+1))*sizeof(lfs_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
lfs_size_t j = TEST_PRNG(&prng_) % sim_size;
lfs_size_t x = sim[j];
lfs_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 = lfsr_rename(&lfs, old_name, new_name);
assert(!err || err == LFS_ERR_NOSPC);
if (err == LFS_ERR_NOSPC) {
goto grow;
}
// update our sim
for (lfs_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(lfs_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(lfs_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-k)*sizeof(lfs_size_t));
memmove(&sim_prngs[k+1], &sim_prngs[k],
(sim_size-k)*sizeof(uint32_t));
sim[k] = y;
sim_prngs[k] = wprng;
}
break;
}
}
}
prng = prng_;
continue;
grow:;
// try growing the filesystem
struct lfs_fsinfo fsinfo;
lfsr_fs_stat(&lfs, &fsinfo) => 0;
assert(fsinfo.block_count >= INIT_BLOCK_COUNT);
assert(fsinfo.block_count <= BLOCK_COUNT);
lfs_ssize_t used = lfsr_fs_size(&lfs);
assert(used >= 0);
// we may need to grow multiple blocks before the system gets unstuck
lfs_size_t block_count_ = fsinfo.block_count;
while (true) {
assert(block_count_ < BLOCK_COUNT);
block_count_ += 1;
int err = lfsr_fs_grow(&lfs, block_count_);
assert(!err || err == LFS_ERR_NOSPC);
if (err == LFS_ERR_NOSPC) {
continue;
}
break;
}
printf("grew %d/%d -> %d/%d\n",
used, fsinfo.block_count,
used, block_count_);
// remount?
if (REMOUNT) {
lfsr_unmount(&lfs) => 0;
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
}
// fsstat up to date?
lfsr_fs_stat(&lfs, &fsinfo) => 0;
assert(fsinfo.block_size == BLOCK_SIZE);
assert(fsinfo.block_count == block_count_);
assert(fsinfo.name_limit == LFS_NAME_MAX);
assert(fsinfo.file_limit == LFS_FILE_MAX);
goto again;
}
for (int remount = 0; remount < 2; remount++) {
// remount?
if (remount) {
lfsr_unmount(&lfs) => 0;
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
}
// check that our files match our simulation
for (lfs_size_t j = 0; j < sim_size; j++) {
char name[256];
sprintf(name, "amethyst%03x", sim[j]);
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);
}
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);
assert(info.size == 0);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "..") == 0);
assert(info.type == LFS_TYPE_DIR);
assert(info.size == 0);
for (lfs_size_t j = 0; j < sim_size; j++) {
char name[256];
sprintf(name, "amethyst%03x", sim[j]);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, name) == 0);
assert(info.type == LFS_TYPE_REG);
assert(info.size == SIZE);
}
lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT;
lfsr_dir_close(&lfs, &dir) => 0;
// check the file contents
for (lfs_size_t j = 0; j < sim_size; j++) {
char name[256];
sprintf(name, "amethyst%03x", sim[j]);
lfsr_file_t file;
lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => 0;
uint32_t wprng = sim_prngs[j];
uint8_t wbuf[SIZE];
for (lfs_size_t j = 0; j < SIZE; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26);
}
uint8_t rbuf[SIZE];
lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE;
assert(memcmp(rbuf, wbuf, SIZE) == 0);
lfsr_file_close(&lfs, &file) => 0;
}
}
// clean up sim/lfs
free(sim);
free(sim_prngs);
lfsr_unmount(&lfs) => 0;
'''
[cases.test_grow_incr_spam_uz_fuzz]
defines.INIT_BLOCK_COUNT = 2
defines.N = [1, 2, 4, 8, 16, 32, 64]
defines.OPS = 1024
defines.SIZE = [
'0',
'FILE_CACHE_SIZE/2',
'2*FILE_CACHE_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 lfs_config cfg = *CFG;
cfg.block_count = INIT_BLOCK_COUNT;
lfs_t lfs;
lfsr_format(&lfs, LFS_F_RDWR, &cfg) => 0;
// mount with maximum block count
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
// fsstat up to date?
struct lfs_fsinfo fsinfo;
lfsr_fs_stat(&lfs, &fsinfo) => 0;
assert(fsinfo.block_size == BLOCK_SIZE);
assert(fsinfo.block_count == INIT_BLOCK_COUNT);
assert(fsinfo.name_limit == LFS_NAME_MAX);
assert(fsinfo.file_limit == LFS_FILE_MAX);
// set up a simulation to compare against
lfs_size_t *sim = malloc(N*sizeof(lfs_size_t));
uint32_t *sim_prngs = malloc(N*sizeof(uint32_t));
bool *sim_isstickys = malloc(N*sizeof(bool));
lfs_size_t sim_size = 0;
typedef struct sim_file {
lfs_size_t x;
bool sticky;
bool zombie;
uint32_t prng;
lfsr_file_t file;
} sim_file_t;
sim_file_t **sim_files = malloc(N*sizeof(sim_file_t*));
lfs_size_t sim_file_count = 0;
uint32_t prng = SEED;
for (lfs_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
lfs_size_t x = TEST_PRNG(&prng_) % N;
// already exists?
bool exist = false;
uint32_t wprng = 0;
bool sticky = true;
for (lfs_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;
}
lfs_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 = lfsr_file_open(&lfs, &sim_files[j]->file, name,
LFS_O_RDWR | LFS_O_CREAT);
assert(!err || err == LFS_ERR_NOSPC);
if (err == LFS_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 (lfs_size_t k = 0; k < SIZE; k++) {
wbuf[k] = 'a' + (TEST_PRNG(&wprng_) % 26);
}
lfs_ssize_t d = lfsr_file_write(&lfs, &sim_files[j]->file,
wbuf, SIZE);
LFS_ASSERT(d == SIZE || d == LFS_ERR_NOSPC);
if (d == LFS_ERR_NOSPC) {
lfsr_file_close(&lfs, &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 (lfs_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(lfs_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
lfs_size_t j = TEST_PRNG(&prng_) % sim_file_count;
lfs_size_t x = sim_files[j]->x;
// choose a random seed
uint32_t wprng = TEST_PRNG(&prng_);
// write to the file
lfsr_file_rewind(&lfs, &sim_files[j]->file) => 0;
uint8_t wbuf[SIZE];
uint32_t wprng_ = wprng;
for (lfs_size_t k = 0; k < SIZE; k++) {
wbuf[k] = 'a' + (TEST_PRNG(&wprng_) % 26);
}
lfs_ssize_t d = lfsr_file_write(&lfs, &sim_files[j]->file,
wbuf, SIZE);
assert(d == SIZE || d == LFS_ERR_NOSPC);
if (d == LFS_ERR_NOSPC) {
goto grow;
}
int err = lfsr_file_sync(&lfs, &sim_files[j]->file);
assert(err == ((!sim_files[j]->zombie) ? 0 : LFS_ERR_NOENT)
|| err == LFS_ERR_NOSPC);
if (err == LFS_ERR_NOSPC) {
goto grow;
}
// update sim
sim_files[j]->prng = wprng;
if (!sim_files[j]->zombie) {
// update in our sim
for (lfs_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 (lfs_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
lfs_size_t j = TEST_PRNG(&prng_) % sim_file_count;
lfs_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
lfsr_file_desync(&lfs, &sim_files[j]->file) => 0;
lfsr_file_close(&lfs, &sim_files[j]->file) => 0;
// clobber closed files to try to catch lingering references
memset(&sim_files[j]->file, 0xcc, sizeof(lfsr_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 (lfs_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 (lfs_size_t k = 0;; k++) {
if (sim[k] == x) {
memmove(&sim[k], &sim[k+1],
(sim_size-(k+1))*sizeof(lfs_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
lfs_size_t j = TEST_PRNG(&prng_) % sim_size;
lfs_size_t x = sim[j];
// delete this file
char name[256];
sprintf(name, "batman%03x", x);
int err = lfsr_remove(&lfs, name);
assert(!err || err == LFS_ERR_NOSPC);
if (err == LFS_ERR_NOSPC) {
goto grow;
}
// delete from our sim
memmove(&sim[j], &sim[j+1],
(sim_size-(j+1))*sizeof(lfs_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 (lfs_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
lfs_size_t j = TEST_PRNG(&prng_) % sim_size;
lfs_size_t x = sim[j];
lfs_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 = lfsr_rename(&lfs, old_name, new_name);
assert(!err || err == LFS_ERR_NOSPC);
if (err == LFS_ERR_NOSPC) {
goto grow;
}
// update our sim
for (lfs_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(lfs_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(lfs_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-k)*sizeof(lfs_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[k] = y;
sim_prngs[k] = wprng;
sim_isstickys[k] = sticky;
}
break;
}
}
// update any related sim files
for (lfs_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 lfs_fsinfo fsinfo;
lfsr_fs_stat(&lfs, &fsinfo) => 0;
assert(fsinfo.block_count >= INIT_BLOCK_COUNT);
assert(fsinfo.block_count <= BLOCK_COUNT);
lfs_ssize_t used = lfsr_fs_size(&lfs);
assert(used >= 0);
// we may need to grow multiple blocks before the system gets unstuck
lfs_size_t block_count_ = fsinfo.block_count;
while (true) {
assert(block_count_ < BLOCK_COUNT);
block_count_ += 1;
int err = lfsr_fs_grow(&lfs, block_count_);
assert(!err || err == LFS_ERR_NOSPC);
if (err == LFS_ERR_NOSPC) {
continue;
}
break;
}
printf("grew %d/%d -> %d/%d\n",
used, fsinfo.block_count,
used, block_count_);
// fsstat up to date?
lfsr_fs_stat(&lfs, &fsinfo) => 0;
assert(fsinfo.block_size == BLOCK_SIZE);
assert(fsinfo.block_count == block_count_);
assert(fsinfo.name_limit == LFS_NAME_MAX);
assert(fsinfo.file_limit == LFS_FILE_MAX);
goto again;
}
// check that disk matches our simulation
for (lfs_size_t j = 0; j < sim_size; j++) {
char name[256];
sprintf(name, "batman%03x", sim[j]);
struct lfs_info info;
lfsr_stat(&lfs, name, &info) => 0;
assert(strcmp(info.name, name) == 0);
if (sim_isstickys[j]) {
assert(info.type == LFS_TYPE_STICKYNOTE);
assert(info.size == 0);
} else {
assert(info.type == LFS_TYPE_REG);
assert(info.size == SIZE);
}
}
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);
assert(info.size == 0);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "..") == 0);
assert(info.type == LFS_TYPE_DIR);
assert(info.size == 0);
for (lfs_size_t j = 0; j < sim_size; j++) {
char name[256];
sprintf(name, "batman%03x", sim[j]);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, name) == 0);
if (sim_isstickys[j]) {
assert(info.type == LFS_TYPE_STICKYNOTE);
assert(info.size == 0);
} else {
assert(info.type == LFS_TYPE_REG);
assert(info.size == SIZE);
}
}
lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT;
lfsr_dir_close(&lfs, &dir) => 0;
for (lfs_size_t j = 0; j < sim_size; j++) {
char name[256];
sprintf(name, "batman%03x", sim[j]);
lfsr_file_t file;
lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => 0;
uint32_t wprng = sim_prngs[j];
uint8_t wbuf[SIZE];
for (lfs_size_t j = 0; j < SIZE; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26);
}
uint8_t rbuf[SIZE];
if (sim_isstickys[j]) {
lfsr_file_read(&lfs, &file, rbuf, SIZE) => 0;
} else {
lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE;
assert(memcmp(rbuf, wbuf, SIZE) == 0);
}
lfsr_file_close(&lfs, &file) => 0;
}
// check that our file handles match our simulation
for (lfs_size_t j = 0; j < sim_file_count; j++) {
uint32_t wprng = sim_files[j]->prng;
uint8_t wbuf[SIZE];
for (lfs_size_t j = 0; j < SIZE; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26);
}
lfsr_file_rewind(&lfs, &sim_files[j]->file) => 0;
uint8_t rbuf[SIZE];
lfsr_file_read(&lfs, &sim_files[j]->file, rbuf, SIZE) => SIZE;
assert(memcmp(rbuf, wbuf, SIZE) == 0);
}
// clean up sim/lfs
free(sim);
free(sim_prngs);
free(sim_isstickys);
for (lfs_size_t j = 0; j < sim_file_count; j++) {
lfsr_file_close(&lfs, &sim_files[j]->file) => 0;
free(sim_files[j]);
}
free(sim_files);
lfsr_unmount(&lfs) => 0;
'''
[cases.test_grow_incr_spam_uzd_fuzz]
defines.INIT_BLOCK_COUNT = 2
defines.N = [1, 2, 4, 8, 16, 32, 64]
defines.OPS = 1024
defines.SIZE = [
'0',
'FILE_CACHE_SIZE/2',
'2*FILE_CACHE_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 lfs_config cfg = *CFG;
cfg.block_count = INIT_BLOCK_COUNT;
lfs_t lfs;
lfsr_format(&lfs, LFS_F_RDWR, &cfg) => 0;
// mount with maximum block count
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
// fsstat up to date?
struct lfs_fsinfo fsinfo;
lfsr_fs_stat(&lfs, &fsinfo) => 0;
assert(fsinfo.block_size == BLOCK_SIZE);
assert(fsinfo.block_count == INIT_BLOCK_COUNT);
assert(fsinfo.name_limit == LFS_NAME_MAX);
assert(fsinfo.file_limit == LFS_FILE_MAX);
// set up a simulation to compare against
lfs_size_t *sim = malloc(N*sizeof(lfs_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));
lfs_size_t sim_size = 0;
typedef struct sim_file {
lfs_size_t x;
bool sticky;
bool zombie;
uint32_t prng;
lfsr_file_t file;
} sim_file_t;
sim_file_t **sim_files = malloc(N*sizeof(sim_file_t*));
lfs_size_t sim_file_count = 0;
uint32_t prng = SEED;
for (lfs_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
lfs_size_t x = TEST_PRNG(&prng_) % N;
// already exists?
bool exist = true;
uint32_t wprng = 0;
bool sticky = true;
for (lfs_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;
}
lfs_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 = lfsr_file_open(&lfs, &sim_files[j]->file, name,
LFS_O_RDWR | LFS_O_CREAT);
assert(!err || err == LFS_ERR_NOSPC);
if (err == LFS_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 (lfs_size_t k = 0; k < SIZE; k++) {
wbuf[k] = 'a' + (TEST_PRNG(&wprng_) % 26);
}
lfs_ssize_t d = lfsr_file_write(&lfs, &sim_files[j]->file,
wbuf, SIZE);
assert(d == SIZE || d == LFS_ERR_NOSPC);
if (d == LFS_ERR_NOSPC) {
free(sim_files[j]);
lfsr_file_close(&lfs, &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 (lfs_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(lfs_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
lfs_size_t j = TEST_PRNG(&prng_) % sim_file_count;
lfs_size_t x = sim_files[j]->x;
// choose a random seed
uint32_t wprng = TEST_PRNG(&prng_);
// write to the file
lfsr_file_rewind(&lfs, &sim_files[j]->file) => 0;
uint8_t wbuf[SIZE];
uint32_t wprng_ = wprng;
for (lfs_size_t k = 0; k < SIZE; k++) {
wbuf[k] = 'a' + (TEST_PRNG(&wprng_) % 26);
}
lfs_ssize_t d = lfsr_file_write(&lfs, &sim_files[j]->file,
wbuf, SIZE);
assert(d == SIZE || d == LFS_ERR_NOSPC);
if (d == LFS_ERR_NOSPC) {
goto grow;
}
int err = lfsr_file_sync(&lfs, &sim_files[j]->file);
assert(err == ((!sim_files[j]->zombie) ? 0 : LFS_ERR_NOENT)
|| err == LFS_ERR_NOSPC);
if (err == LFS_ERR_NOSPC) {
goto grow;
}
// update sim
sim_files[j]->prng = wprng;
if (!sim_files[j]->zombie) {
// update in our sim
for (lfs_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 (lfs_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
lfs_size_t j = TEST_PRNG(&prng_) % sim_file_count;
lfs_size_t x = sim_files[j]->x;
lfs_size_t sticky = sim_files[j]->sticky;
lfs_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
lfsr_file_desync(&lfs, &sim_files[j]->file) => 0;
lfsr_file_close(&lfs, &sim_files[j]->file) => 0;
// clobber closed files to try to catch lingering references
memset(&sim_files[j]->file, 0xcc, sizeof(lfsr_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 (lfs_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 (lfs_size_t k = 0;; k++) {
if (sim[k] == x) {
memmove(&sim[k], &sim[k+1],
(sim_size-(k+1))*sizeof(lfs_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
lfs_size_t j = TEST_PRNG(&prng_) % sim_size;
lfs_size_t x = sim[j];
// delete this file
char name[256];
sprintf(name, "batman%03x", x);
int err = lfsr_remove(&lfs, name);
assert(!err || err == LFS_ERR_NOSPC);
if (err == LFS_ERR_NOSPC) {
goto grow;
}
// delete from our sim
memmove(&sim[j], &sim[j+1],
(sim_size-(j+1))*sizeof(lfs_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 (lfs_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
lfs_size_t j = TEST_PRNG(&prng_) % sim_size;
lfs_size_t x = sim[j];
lfs_size_t y = TEST_PRNG(&prng_) % N;
uint32_t wprng = sim_prngs[j];
bool sticky = sim_isstickys[j];
bool dir = sim_isdirs[j];
for (lfs_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 = lfsr_rename(&lfs, old_name, new_name);
assert(!err || err == LFS_ERR_NOSPC);
if (err == LFS_ERR_NOSPC) {
goto grow;
}
// update our sim
for (lfs_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(lfs_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(lfs_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-k)*sizeof(lfs_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[k] = y;
sim_prngs[k] = wprng;
sim_isstickys[k] = sticky;
sim_isdirs[k] = dir;
}
break;
}
}
// update any related sim files
for (lfs_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
lfs_size_t x = TEST_PRNG(&prng_) % N;
for (lfs_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 = lfsr_mkdir(&lfs, name);
assert(!err || err == LFS_ERR_NOSPC);
if (err == LFS_ERR_NOSPC) {
goto grow;
}
// insert into our sim
for (lfs_size_t k = 0;; k++) {
if (k >= sim_size || sim[k] >= x) {
// insert
memmove(&sim[k+1], &sim[k],
(sim_size-k)*sizeof(lfs_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 (lfs_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 lfs_fsinfo fsinfo;
lfsr_fs_stat(&lfs, &fsinfo) => 0;
assert(fsinfo.block_count >= INIT_BLOCK_COUNT);
assert(fsinfo.block_count <= BLOCK_COUNT);
lfs_ssize_t used = lfsr_fs_size(&lfs);
assert(used >= 0);
// we may need to grow multiple blocks before the system gets unstuck
lfs_size_t block_count_ = fsinfo.block_count;
while (true) {
assert(block_count_ < BLOCK_COUNT);
block_count_ += 1;
int err = lfsr_fs_grow(&lfs, block_count_);
assert(!err || err == LFS_ERR_NOSPC);
if (err == LFS_ERR_NOSPC) {
continue;
}
break;
}
printf("grew %d/%d -> %d/%d\n",
used, fsinfo.block_count,
used, block_count_);
// fsstat up to date?
lfsr_fs_stat(&lfs, &fsinfo) => 0;
assert(fsinfo.block_size == BLOCK_SIZE);
assert(fsinfo.block_count == block_count_);
assert(fsinfo.name_limit == LFS_NAME_MAX);
assert(fsinfo.file_limit == LFS_FILE_MAX);
goto again;
}
// check that disk matches our simulation
for (lfs_size_t j = 0; j < sim_size; j++) {
char name[256];
sprintf(name, "batman%03x", sim[j]);
struct lfs_info info;
lfsr_stat(&lfs, name, &info) => 0;
assert(strcmp(info.name, name) == 0);
if (sim_isdirs[j]) {
assert(info.type == LFS_TYPE_DIR);
assert(info.size == 0);
} else if (sim_isstickys[j]) {
assert(info.type == LFS_TYPE_STICKYNOTE);
assert(info.size == 0);
} else {
assert(info.type == LFS_TYPE_REG);
assert(info.size == SIZE);
}
}
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);
assert(info.size == 0);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "..") == 0);
assert(info.type == LFS_TYPE_DIR);
assert(info.size == 0);
for (lfs_size_t j = 0; j < sim_size; j++) {
char name[256];
sprintf(name, "batman%03x", sim[j]);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, name) == 0);
if (sim_isdirs[j]) {
assert(info.type == LFS_TYPE_DIR);
assert(info.size == 0);
} else if (sim_isstickys[j]) {
assert(info.type == LFS_TYPE_STICKYNOTE);
assert(info.size == 0);
} else {
assert(info.type == LFS_TYPE_REG);
assert(info.size == SIZE);
}
}
lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT;
lfsr_dir_close(&lfs, &dir) => 0;
for (lfs_size_t j = 0; j < sim_size; j++) {
if (sim_isdirs[j]) {
char name[256];
sprintf(name, "batman%03x", sim[j]);
lfsr_file_t file;
lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY)
=> LFS_ERR_ISDIR;
} else {
char name[256];
sprintf(name, "batman%03x", sim[j]);
lfsr_file_t file;
lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => 0;
uint32_t wprng = sim_prngs[j];
uint8_t wbuf[SIZE];
for (lfs_size_t j = 0; j < SIZE; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26);
}
uint8_t rbuf[SIZE];
if (sim_isstickys[j]) {
lfsr_file_read(&lfs, &file, rbuf, SIZE) => 0;
} else {
lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE;
assert(memcmp(rbuf, wbuf, SIZE) == 0);
}
lfsr_file_close(&lfs, &file) => 0;
}
}
// check that our file handles match our simulation
for (lfs_size_t j = 0; j < sim_file_count; j++) {
uint32_t wprng = sim_files[j]->prng;
uint8_t wbuf[SIZE];
for (lfs_size_t j = 0; j < SIZE; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26);
}
lfsr_file_rewind(&lfs, &sim_files[j]->file) => 0;
uint8_t rbuf[SIZE];
lfsr_file_read(&lfs, &sim_files[j]->file, rbuf, SIZE) => SIZE;
assert(memcmp(rbuf, wbuf, SIZE) == 0);
}
// clean up sim/lfs
free(sim);
free(sim_prngs);
free(sim_isstickys);
free(sim_isdirs);
for (lfs_size_t j = 0; j < sim_file_count; j++) {
lfsr_file_close(&lfs, &sim_files[j]->file) => 0;
free(sim_files[j]);
}
free(sim_files);
lfsr_unmount(&lfs) => 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.INIT_BLOCK_COUNT = 2
defines.N = [1, 2, 4, 8, 16, 32, 64]
defines.OPS = 256
defines.SIZE = [
'0',
'FILE_CACHE_SIZE/2',
'2*FILE_CACHE_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
lfs_t lfs;
int err = lfsr_mount(&lfs, LFS_M_RDWR, CFG);
if (err) {
// start with a small number of blocks
struct lfs_config cfg = *CFG;
cfg.block_count = INIT_BLOCK_COUNT;
lfsr_format(&lfs, LFS_F_RDWR, &cfg) => 0;
// mount with maximum block count
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
// fsstat up to date?
struct lfs_fsinfo fsinfo;
lfsr_fs_stat(&lfs, &fsinfo) => 0;
assert(fsinfo.block_size == BLOCK_SIZE);
assert(fsinfo.block_count == INIT_BLOCK_COUNT);
assert(fsinfo.name_limit == LFS_NAME_MAX);
assert(fsinfo.file_limit == LFS_FILE_MAX);
}
// keep some test state on disk to survive powerloss
typedef struct fuzz_state {
lfs_size_t i;
uint32_t prng;
} fuzz_state_t;
fuzz_state_t state = {.i = 0, .prng = SEED};
lfsr_file_t state_file;
err = lfsr_file_open(&lfs, &state_file, "state", LFS_O_RDONLY);
assert(!err || err == LFS_ERR_NOENT);
if (!err) {
lfsr_file_read(&lfs, &state_file,
&state, sizeof(state)) => sizeof(state);
lfsr_file_close(&lfs, &state_file) => 0;
}
uint32_t prng = state.prng;
for (lfs_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 = lfsr_mkdir(&lfs, "test");
assert(!err || err == LFS_ERR_EXIST || err == LFS_ERR_NOSPC);
if (err == LFS_ERR_NOSPC) {
goto grow;
}
// how many files do we have?
lfs_size_t count = 0;
lfsr_dir_t dir;
lfsr_dir_open(&lfs, &dir, "test") => 0;
struct lfs_info info;
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, ".") == 0);
assert(info.type == LFS_TYPE_DIR);
assert(info.size == 0);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "..") == 0);
assert(info.type == LFS_TYPE_DIR);
assert(info.size == 0);
while (true) {
err = lfsr_dir_read(&lfs, &dir, &info);
assert(!err || err == LFS_ERR_NOENT);
if (err == LFS_ERR_NOENT) {
break;
}
assert(strlen(info.name) == strlen("amethyst..."));
assert(memcmp(info.name, "amethyst", strlen("amethyst")) == 0);
assert(info.type == LFS_TYPE_REG);
assert(info.size == SIZE);
count++;
}
lfsr_dir_close(&lfs, &dir) => 0;
// creating a new file?
if (op == 0 || count == 0) {
// choose a pseudo-random number
lfs_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 (lfs_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);
}
lfsr_file_t file;
err = lfsr_file_open(&lfs, &file, name,
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC);
assert(!err || err == LFS_ERR_NOSPC);
if (err == LFS_ERR_NOSPC) {
goto grow;
}
lfs_ssize_t d = lfsr_file_write(&lfs, &file, wbuf, SIZE);
assert(d == SIZE || d == LFS_ERR_NOSPC);
if (d == LFS_ERR_NOSPC) {
lfsr_file_close(&lfs, &file) => 0;
goto grow;
}
err = lfsr_file_close(&lfs, &file);
assert(!err || err == LFS_ERR_NOSPC);
if (err == LFS_ERR_NOSPC) {
goto grow;
}
// deleting a file?
} else if (op == 1) {
// choose a random file to delete
lfs_size_t j = TEST_PRNG(&prng_) % count;
// find the file
lfsr_dir_open(&lfs, &dir, "test") => 0;
lfsr_dir_read(&lfs, &dir, &info) => 0;
lfsr_dir_read(&lfs, &dir, &info) => 0;
for (lfs_size_t k = 0; k <= j; k++) {
lfsr_dir_read(&lfs, &dir, &info) => 0;
}
lfsr_dir_close(&lfs, &dir) => 0;
// delete this file
char name[256];
assert(strlen(info.name) == strlen("amethyst..."));
sprintf(name, "test/%s", info.name);
err = lfsr_remove(&lfs, name);
assert(!err || err == LFS_ERR_NOSPC);
if (err == LFS_ERR_NOSPC) {
goto grow;
}
// renaming a file?
} else {
// choose a random file to rename, and a random number to
// rename to
lfs_size_t j = TEST_PRNG(&prng_) % count;
lfs_size_t y = TEST_PRNG(&prng_) % N;
// find the file
lfsr_dir_open(&lfs, &dir, "test") => 0;
lfsr_dir_read(&lfs, &dir, &info) => 0;
lfsr_dir_read(&lfs, &dir, &info) => 0;
for (lfs_size_t k = 0; k <= j; k++) {
lfsr_dir_read(&lfs, &dir, &info) => 0;
}
lfsr_dir_close(&lfs, &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 = lfsr_rename(&lfs, old_name, new_name);
assert(!err || err == LFS_ERR_NOSPC);
if (err == LFS_ERR_NOSPC) {
goto grow;
}
}
// update our state file
state.i = i;
state.prng = prng_;
err = lfsr_file_open(&lfs, &state_file, "state",
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC);
assert(!err || err == LFS_ERR_NOSPC);
if (err == LFS_ERR_NOSPC) {
goto grow;
}
lfs_ssize_t d = lfsr_file_write(&lfs, &state_file,
&state, sizeof(state));
assert(d == sizeof(state) || d == LFS_ERR_NOSPC);
if (d == LFS_ERR_NOSPC) {
lfsr_file_close(&lfs, &state_file) => 0;
goto grow;
}
err = lfsr_file_close(&lfs, &state_file);
assert(!err || err == LFS_ERR_NOSPC);
if (err == LFS_ERR_NOSPC) {
goto grow;
}
prng = prng_;
continue;
grow:;
// try growing the filesystem
struct lfs_fsinfo fsinfo;
lfsr_fs_stat(&lfs, &fsinfo) => 0;
assert(fsinfo.block_count >= INIT_BLOCK_COUNT);
assert(fsinfo.block_count <= BLOCK_COUNT);
lfs_ssize_t used = lfsr_fs_size(&lfs);
assert(used >= 0);
// we may need to grow multiple blocks before the system gets unstuck
lfs_size_t block_count_ = fsinfo.block_count;
while (true) {
assert(block_count_ < BLOCK_COUNT);
block_count_ += 1;
err = lfsr_fs_grow(&lfs, block_count_);
assert(!err || err == LFS_ERR_NOSPC);
if (err == LFS_ERR_NOSPC) {
continue;
}
break;
}
printf("grew %d/%d -> %d/%d\n",
used, fsinfo.block_count,
used, block_count_);
// fsstat up to date?
lfsr_fs_stat(&lfs, &fsinfo) => 0;
assert(fsinfo.block_size == BLOCK_SIZE);
assert(fsinfo.block_count == block_count_);
assert(fsinfo.name_limit == LFS_NAME_MAX);
assert(fsinfo.file_limit == LFS_FILE_MAX);
goto again;
}
for (int remount = 0; remount < 2; remount++) {
// remount?
if (remount) {
lfsr_unmount(&lfs) => 0;
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
}
// check that things look more-or-less ok
lfsr_dir_t dir;
lfsr_dir_open(&lfs, &dir, "test") => 0;
struct lfs_info info;
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, ".") == 0);
assert(info.type == LFS_TYPE_DIR);
assert(info.size == 0);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "..") == 0);
assert(info.type == LFS_TYPE_DIR);
assert(info.size == 0);
while (true) {
int err = lfsr_dir_read(&lfs, &dir, &info);
assert(!err || err == LFS_ERR_NOENT);
if (err == LFS_ERR_NOENT) {
break;
}
assert(strlen(info.name) == strlen("amethyst..."));
assert(memcmp(info.name, "amethyst", strlen("amethyst")) == 0);
assert(info.type == LFS_TYPE_REG);
assert(info.size == SIZE);
// at least try to read the files
char name[256];
sprintf(name, "test/%s", info.name);
lfsr_file_t file;
lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => 0;
uint8_t rbuf[SIZE];
lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE;
// all data should be lowercase ascii
for (lfs_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 (lfs_size_t j = 0; j < SIZE; j++) {
ck = (ck + (rbuf[j] - 'a')) % 26;
}
assert(ck == 0);
lfsr_file_close(&lfs, &file) => 0;
}
lfsr_dir_close(&lfs, &dir) => 0;
}
lfsr_unmount(&lfs) => 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]
defines.INIT_BLOCK_COUNT = 2
# note dirs x files grows O(n^2)
defines.N = [1, 2, 4, 8]
defines.M = 'N'
defines.OPS = 256
defines.SIZE = [
'0',
'FILE_CACHE_SIZE/2',
'2*FILE_CACHE_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
lfs_t lfs;
int err = lfsr_mount(&lfs, LFS_M_RDWR, CFG);
if (err) {
// start with a small number of blocks
struct lfs_config cfg = *CFG;
cfg.block_count = INIT_BLOCK_COUNT;
lfsr_format(&lfs, LFS_F_RDWR, &cfg) => 0;
// mount with maximum block count
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
// fsstat up to date?
struct lfs_fsinfo fsinfo;
lfsr_fs_stat(&lfs, &fsinfo) => 0;
assert(fsinfo.block_size == BLOCK_SIZE);
assert(fsinfo.block_count == INIT_BLOCK_COUNT);
assert(fsinfo.name_limit == LFS_NAME_MAX);
assert(fsinfo.file_limit == LFS_FILE_MAX);
}
// keep some test state on disk to survive powerloss
typedef struct fuzz_state {
lfs_size_t i;
uint32_t prng;
} fuzz_state_t;
fuzz_state_t state = {.i = 0, .prng = SEED};
lfsr_file_t state_file;
err = lfsr_file_open(&lfs, &state_file, "state", LFS_O_RDONLY);
assert(!err || err == LFS_ERR_NOENT);
if (!err) {
lfsr_file_read(&lfs, &state_file,
&state, sizeof(state)) => sizeof(state);
lfsr_file_close(&lfs, &state_file) => 0;
}
uint32_t prng = state.prng;
for (lfs_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 = lfsr_mkdir(&lfs, "test");
assert(!err || err == LFS_ERR_EXIST || err == LFS_ERR_NOSPC);
if (err == LFS_ERR_NOSPC) {
goto grow;
}
// how many dirs do we have?
lfs_size_t dir_count = 0;
lfsr_dir_t dir;
lfsr_dir_open(&lfs, &dir, "test") => 0;
struct lfs_info info;
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, ".") == 0);
assert(info.type == LFS_TYPE_DIR);
assert(info.size == 0);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "..") == 0);
assert(info.type == LFS_TYPE_DIR);
assert(info.size == 0);
while (true) {
err = lfsr_dir_read(&lfs, &dir, &info);
assert(!err || err == LFS_ERR_NOENT);
if (err == LFS_ERR_NOENT) {
break;
}
assert(strlen(info.name) == strlen("quartz..."));
assert(memcmp(info.name, "quartz", strlen("quartz")) == 0);
assert(info.type == LFS_TYPE_DIR);
assert(info.size == 0);
dir_count++;
}
lfsr_dir_close(&lfs, &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
lfs_size_t x = TEST_PRNG(&prng_) % N;
// create a dir here
char name[256];
sprintf(name, "test/quartz%03x", x);
err = lfsr_mkdir(&lfs, name);
assert(!err || err == LFS_ERR_EXIST || err == LFS_ERR_NOSPC);
if (err == LFS_ERR_NOSPC) {
goto grow;
}
// deleting a dir?
} else if (op == 1) {
// choose a random dir to delete
lfs_size_t j = TEST_PRNG(&prng_) % dir_count;
// find the dir
lfsr_dir_open(&lfs, &dir, "test") => 0;
lfsr_dir_read(&lfs, &dir, &info) => 0;
lfsr_dir_read(&lfs, &dir, &info) => 0;
for (lfs_size_t k = 0; k <= j; k++) {
lfsr_dir_read(&lfs, &dir, &info) => 0;
}
lfsr_dir_close(&lfs, &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 = lfsr_remove(&lfs, name);
assert(!err
|| err == LFS_ERR_NOTEMPTY
|| err == LFS_ERR_NOSPC);
if (err == LFS_ERR_NOSPC) {
goto grow;
}
// renaming a dir?
} else {
// choose a random dir to rename, and a random number to
// rename to
lfs_size_t j = TEST_PRNG(&prng_) % dir_count;
lfs_size_t y = TEST_PRNG(&prng_) % N;
// find the dir
lfsr_dir_open(&lfs, &dir, "test") => 0;
lfsr_dir_read(&lfs, &dir, &info) => 0;
lfsr_dir_read(&lfs, &dir, &info) => 0;
for (lfs_size_t k = 0; k <= j; k++) {
lfsr_dir_read(&lfs, &dir, &info) => 0;
}
lfsr_dir_close(&lfs, &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 = lfsr_rename(&lfs, old_name, new_name);
assert(!err
|| err == LFS_ERR_NOTEMPTY
|| err == LFS_ERR_NOSPC);
if (err == LFS_ERR_NOSPC) {
goto grow;
}
}
// file op?
} else {
// choose a pseudo-random dir
lfs_size_t dir_i = TEST_PRNG(&prng_) % dir_count;
// find the dir
lfsr_dir_open(&lfs, &dir, "test") => 0;
lfsr_dir_read(&lfs, &dir, &info) => 0;
lfsr_dir_read(&lfs, &dir, &info) => 0;
for (lfs_size_t k = 0; k <= dir_i; k++) {
lfsr_dir_read(&lfs, &dir, &info) => 0;
}
lfsr_dir_close(&lfs, &dir) => 0;
char dir_path[256];
sprintf(dir_path, "test/%s", info.name);
// how many files do we have?
lfs_size_t count = 0;
lfsr_dir_open(&lfs, &dir, dir_path) => 0;
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, ".") == 0);
assert(info.type == LFS_TYPE_DIR);
assert(info.size == 0);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "..") == 0);
assert(info.type == LFS_TYPE_DIR);
assert(info.size == 0);
while (true) {
err = lfsr_dir_read(&lfs, &dir, &info);
assert(!err || err == LFS_ERR_NOENT);
if (err == LFS_ERR_NOENT) {
break;
}
assert(strlen(info.name) == strlen("amethyst..."));
assert(memcmp(
info.name,
"amethyst", strlen("amethyst")) == 0);
assert(info.type == LFS_TYPE_REG);
assert(info.size == SIZE);
count++;
}
lfsr_dir_close(&lfs, &dir) => 0;
// creating a new file?
if (op == 3 || count == 0) {
// choose a pseudo-random number
lfs_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 (lfs_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);
}
lfsr_file_t file;
err = lfsr_file_open(&lfs, &file, name,
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC);
assert(!err || err == LFS_ERR_NOSPC);
if (err == LFS_ERR_NOSPC) {
goto grow;
}
lfs_ssize_t d = lfsr_file_write(&lfs, &file, wbuf, SIZE);
assert(d == SIZE || d == LFS_ERR_NOSPC);
if (d == LFS_ERR_NOSPC) {
lfsr_file_close(&lfs, &file) => 0;
goto grow;
}
err = lfsr_file_close(&lfs, &file);
assert(!err || err == LFS_ERR_NOSPC);
if (err == LFS_ERR_NOSPC) {
goto grow;
}
// deleting a file?
} else if (op == 4) {
// choose a random file to delete
lfs_size_t j = TEST_PRNG(&prng_) % count;
// find the file
lfsr_dir_open(&lfs, &dir, dir_path) => 0;
lfsr_dir_read(&lfs, &dir, &info) => 0;
lfsr_dir_read(&lfs, &dir, &info) => 0;
for (lfs_size_t k = 0; k <= j; k++) {
lfsr_dir_read(&lfs, &dir, &info) => 0;
}
lfsr_dir_close(&lfs, &dir) => 0;
// delete this file
char name[256];
assert(strlen(info.name) == strlen("amethyst..."));
sprintf(name, "%s/%s", dir_path, info.name);
err = lfsr_remove(&lfs, name);
assert(!err || err == LFS_ERR_NOSPC);
if (err == LFS_ERR_NOSPC) {
goto grow;
}
// renaming a file?
} else {
// choose a random file to rename
lfs_size_t j = TEST_PRNG(&prng_) % count;
// find the file
lfsr_dir_open(&lfs, &dir, dir_path) => 0;
lfsr_dir_read(&lfs, &dir, &info) => 0;
lfsr_dir_read(&lfs, &dir, &info) => 0;
for (lfs_size_t k = 0; k <= j; k++) {
lfsr_dir_read(&lfs, &dir, &info) => 0;
}
lfsr_dir_close(&lfs, &dir) => 0;
// choose a random dir to rename to
lfs_size_t dir_j = TEST_PRNG(&prng_) % dir_count;
// find the dir
struct lfs_info info_;
lfsr_dir_open(&lfs, &dir, "test") => 0;
lfsr_dir_read(&lfs, &dir, &info_) => 0;
lfsr_dir_read(&lfs, &dir, &info_) => 0;
for (lfs_size_t k = 0; k <= dir_j; k++) {
lfsr_dir_read(&lfs, &dir, &info_) => 0;
}
lfsr_dir_close(&lfs, &dir) => 0;
// choose a random file to rename to
lfs_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 = lfsr_rename(&lfs, old_name, new_name);
assert(!err || err == LFS_ERR_NOSPC);
if (err == LFS_ERR_NOSPC) {
goto grow;
}
}
}
// update our state file
state.i = i;
state.prng = prng_;
err = lfsr_file_open(&lfs, &state_file, "state",
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC);
assert(!err || err == LFS_ERR_NOSPC);
if (err == LFS_ERR_NOSPC) {
goto grow;
}
lfs_ssize_t d = lfsr_file_write(&lfs, &state_file,
&state, sizeof(state));
assert(d == sizeof(state) || d == LFS_ERR_NOSPC);
if (d == LFS_ERR_NOSPC) {
lfsr_file_close(&lfs, &state_file) => 0;
goto grow;
}
err = lfsr_file_close(&lfs, &state_file);
assert(!err || err == LFS_ERR_NOSPC);
if (err == LFS_ERR_NOSPC) {
goto grow;
}
prng = prng_;
continue;
grow:;
// try growing the filesystem
struct lfs_fsinfo fsinfo;
lfsr_fs_stat(&lfs, &fsinfo) => 0;
assert(fsinfo.block_count >= INIT_BLOCK_COUNT);
assert(fsinfo.block_count <= BLOCK_COUNT);
lfs_ssize_t used = lfsr_fs_size(&lfs);
assert(used >= 0);
// we may need to grow multiple blocks before the system gets unstuck
lfs_size_t block_count_ = fsinfo.block_count;
while (true) {
assert(block_count_ < BLOCK_COUNT);
block_count_ += 1;
err = lfsr_fs_grow(&lfs, block_count_);
assert(!err || err == LFS_ERR_NOSPC);
if (err == LFS_ERR_NOSPC) {
continue;
}
break;
}
printf("grew %d/%d -> %d/%d\n",
used, fsinfo.block_count,
used, block_count_);
// fsstat up to date?
lfsr_fs_stat(&lfs, &fsinfo) => 0;
assert(fsinfo.block_size == BLOCK_SIZE);
assert(fsinfo.block_count == block_count_);
assert(fsinfo.name_limit == LFS_NAME_MAX);
assert(fsinfo.file_limit == LFS_FILE_MAX);
goto again;
}
for (int remount = 0; remount < 2; remount++) {
// remount?
if (remount) {
lfsr_unmount(&lfs) => 0;
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
}
// check that things look more-or-less ok
lfsr_dir_t dir;
lfsr_dir_open(&lfs, &dir, "test") => 0;
struct lfs_info info;
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, ".") == 0);
assert(info.type == LFS_TYPE_DIR);
assert(info.size == 0);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "..") == 0);
assert(info.type == LFS_TYPE_DIR);
assert(info.size == 0);
while (true) {
int err = lfsr_dir_read(&lfs, &dir, &info);
assert(!err || err == LFS_ERR_NOENT);
if (err == LFS_ERR_NOENT) {
break;
}
assert(strlen(info.name) == strlen("quartz..."));
assert(memcmp(info.name, "quartz", strlen("quartz")) == 0);
assert(info.type == LFS_TYPE_DIR);
assert(info.size == 0);
// check that our dirs look more-or-less ok
char name[256];
sprintf(name, "test/%s", info.name);
lfsr_dir_t dir_;
lfsr_dir_open(&lfs, &dir_, name) => 0;
struct lfs_info info_;
lfsr_dir_read(&lfs, &dir_, &info_) => 0;
assert(strcmp(info_.name, ".") == 0);
assert(info_.type == LFS_TYPE_DIR);
assert(info_.size == 0);
lfsr_dir_read(&lfs, &dir_, &info_) => 0;
assert(strcmp(info_.name, "..") == 0);
assert(info_.type == LFS_TYPE_DIR);
assert(info_.size == 0);
while (true) {
err = lfsr_dir_read(&lfs, &dir_, &info_);
assert(!err || err == LFS_ERR_NOENT);
if (err == LFS_ERR_NOENT) {
break;
}
assert(strlen(info_.name) == strlen("amethyst..."));
assert(memcmp(
info_.name,
"amethyst", strlen("amethyst")) == 0);
assert(info_.type == LFS_TYPE_REG);
assert(info_.size == SIZE);
// at least try to read the files
sprintf(name, "test/%s/%s", info.name, info_.name);
lfsr_file_t file;
lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => 0;
uint8_t rbuf[SIZE];
lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE;
// all data should be lowercase ascii
for (lfs_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 (lfs_size_t j = 0; j < SIZE; j++) {
ck = (ck + (rbuf[j] - 'a')) % 26;
}
assert(ck == 0);
lfsr_file_close(&lfs, &file) => 0;
}
lfsr_dir_close(&lfs, &dir_) => 0;
}
lfsr_dir_close(&lfs, &dir) => 0;
}
lfsr_unmount(&lfs) => 0;
'''