75f80aabd7
This adds two new configuration options: erase_size and erase_count,
allowing block_size and block_count to be loaded from the superblock
during mount. For backwards compability these default to block_size and
block_count if zero.
---
Unfortunately this is a bit easier said than done. littlefs keeps its
superblock in the metadata pair located at blocks {0,1}, which is also
where the root directory lives (keep in mind small littlefs images may
only have 2 blocks in total). If we mutate blocks {0,1}, we have to
erase before programming, which means it's possible to have the only
superblock in block 1. This presents a puzzle because how do you find
block 1 if you don't know the size of a block?
One solution presented here is to search for block 1 by trying different
sizes until we find a superblock. This isn't great but there are some
properties of this search that help:
1. If we do find a superblock, the search will never take longer than a
mount with a known block_size. This is because we stop at block 1,
searching at most O(block_size) bytes, and metadata fetch is already
a O(block_size) operation.
This means the concern is limited to how long it takes to fail when
littlefs is not present on the disk.
2. We can assume the on-disk block_size is probably a factor of the
total size of the disk. After a bit of digging into the math, this
apparently reduces the runtime to the divisor function, d(n), which
is sublinear.
According to a blog post by Terence Tao this is bounded by the
ridiculous O(e^O(log(n)/log(log(n)))):
https://terrytao.wordpress.com/2008/09/23/the-divisor-bound
This is apparently somewhere between O(sqrt(n)) and O(log(n)), but
conveniently O(log(n)) on average and O(log(n)) for powers of 2.
I've left it as O(d(n)) in the documentation, which might be a bit
confusing, but I'm not sure how best to capture "mostly log(n)"
correctly.
3. If we don't know the block_size, or don't know that block_size is
aligned to the disk size, the best we can do is a O(n) search.
In this case I've added a warning, so at least it's distinguishable
from an infinite loop if debugging.
152 lines
4.2 KiB
TOML
152 lines
4.2 KiB
TOML
# simple formatting test
|
|
[cases.test_superblocks_format]
|
|
code = '''
|
|
lfs_t lfs;
|
|
lfs_format(&lfs, cfg) => 0;
|
|
'''
|
|
|
|
# mount/unmount
|
|
[cases.test_superblocks_mount]
|
|
code = '''
|
|
lfs_t lfs;
|
|
lfs_format(&lfs, cfg) => 0;
|
|
lfs_mount(&lfs, cfg) => 0;
|
|
lfs_unmount(&lfs) => 0;
|
|
'''
|
|
|
|
# reentrant format
|
|
[cases.test_superblocks_reentrant_format]
|
|
reentrant = true
|
|
code = '''
|
|
lfs_t lfs;
|
|
int err = lfs_mount(&lfs, cfg);
|
|
if (err) {
|
|
lfs_format(&lfs, cfg) => 0;
|
|
lfs_mount(&lfs, cfg) => 0;
|
|
}
|
|
lfs_unmount(&lfs) => 0;
|
|
'''
|
|
|
|
# invalid mount
|
|
[cases.test_superblocks_invalid_mount]
|
|
code = '''
|
|
lfs_t lfs;
|
|
lfs_mount(&lfs, cfg) => LFS_ERR_INVAL;
|
|
'''
|
|
|
|
# expanding superblock
|
|
[cases.test_superblocks_expand]
|
|
defines.BLOCK_CYCLES = [32, 33, 1]
|
|
defines.N = [10, 100, 1000]
|
|
code = '''
|
|
lfs_t lfs;
|
|
lfs_format(&lfs, cfg) => 0;
|
|
lfs_mount(&lfs, cfg) => 0;
|
|
for (int i = 0; i < N; i++) {
|
|
lfs_file_t file;
|
|
lfs_file_open(&lfs, &file, "dummy",
|
|
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
|
|
lfs_file_close(&lfs, &file) => 0;
|
|
struct lfs_info info;
|
|
lfs_stat(&lfs, "dummy", &info) => 0;
|
|
assert(strcmp(info.name, "dummy") == 0);
|
|
assert(info.type == LFS_TYPE_REG);
|
|
lfs_remove(&lfs, "dummy") => 0;
|
|
}
|
|
lfs_unmount(&lfs) => 0;
|
|
|
|
// one last check after power-cycle
|
|
lfs_mount(&lfs, cfg) => 0;
|
|
lfs_file_t file;
|
|
lfs_file_open(&lfs, &file, "dummy",
|
|
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
|
|
lfs_file_close(&lfs, &file) => 0;
|
|
struct lfs_info info;
|
|
lfs_stat(&lfs, "dummy", &info) => 0;
|
|
assert(strcmp(info.name, "dummy") == 0);
|
|
assert(info.type == LFS_TYPE_REG);
|
|
lfs_unmount(&lfs) => 0;
|
|
'''
|
|
|
|
# expanding superblock with power cycle
|
|
[cases.test_superblocks_expand_power_cycle]
|
|
defines.BLOCK_CYCLES = [32, 33, 1]
|
|
defines.N = [10, 100, 1000]
|
|
code = '''
|
|
lfs_t lfs;
|
|
lfs_format(&lfs, cfg) => 0;
|
|
for (int i = 0; i < N; i++) {
|
|
lfs_mount(&lfs, cfg) => 0;
|
|
// remove lingering dummy?
|
|
struct lfs_info info;
|
|
int err = lfs_stat(&lfs, "dummy", &info);
|
|
assert(err == 0 || (err == LFS_ERR_NOENT && i == 0));
|
|
if (!err) {
|
|
assert(strcmp(info.name, "dummy") == 0);
|
|
assert(info.type == LFS_TYPE_REG);
|
|
lfs_remove(&lfs, "dummy") => 0;
|
|
}
|
|
|
|
lfs_file_t file;
|
|
lfs_file_open(&lfs, &file, "dummy",
|
|
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
|
|
lfs_file_close(&lfs, &file) => 0;
|
|
lfs_stat(&lfs, "dummy", &info) => 0;
|
|
assert(strcmp(info.name, "dummy") == 0);
|
|
assert(info.type == LFS_TYPE_REG);
|
|
lfs_unmount(&lfs) => 0;
|
|
}
|
|
|
|
// one last check after power-cycle
|
|
lfs_mount(&lfs, cfg) => 0;
|
|
struct lfs_info info;
|
|
lfs_stat(&lfs, "dummy", &info) => 0;
|
|
assert(strcmp(info.name, "dummy") == 0);
|
|
assert(info.type == LFS_TYPE_REG);
|
|
lfs_unmount(&lfs) => 0;
|
|
'''
|
|
|
|
# reentrant expanding superblock
|
|
[cases.test_superblocks_reentrant_expand]
|
|
defines.BLOCK_CYCLES = [2, 1]
|
|
defines.N = 24
|
|
reentrant = true
|
|
code = '''
|
|
lfs_t lfs;
|
|
int err = lfs_mount(&lfs, cfg);
|
|
if (err) {
|
|
lfs_format(&lfs, cfg) => 0;
|
|
lfs_mount(&lfs, cfg) => 0;
|
|
}
|
|
|
|
for (int i = 0; i < N; i++) {
|
|
// remove lingering dummy?
|
|
struct lfs_info info;
|
|
err = lfs_stat(&lfs, "dummy", &info);
|
|
assert(err == 0 || (err == LFS_ERR_NOENT && i == 0));
|
|
if (!err) {
|
|
assert(strcmp(info.name, "dummy") == 0);
|
|
assert(info.type == LFS_TYPE_REG);
|
|
lfs_remove(&lfs, "dummy") => 0;
|
|
}
|
|
|
|
lfs_file_t file;
|
|
lfs_file_open(&lfs, &file, "dummy",
|
|
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
|
|
lfs_file_close(&lfs, &file) => 0;
|
|
lfs_stat(&lfs, "dummy", &info) => 0;
|
|
assert(strcmp(info.name, "dummy") == 0);
|
|
assert(info.type == LFS_TYPE_REG);
|
|
}
|
|
|
|
lfs_unmount(&lfs) => 0;
|
|
|
|
// one last check after power-cycle
|
|
lfs_mount(&lfs, cfg) => 0;
|
|
struct lfs_info info;
|
|
lfs_stat(&lfs, "dummy", &info) => 0;
|
|
assert(strcmp(info.name, "dummy") == 0);
|
|
assert(info.type == LFS_TYPE_REG);
|
|
lfs_unmount(&lfs) => 0;
|
|
'''
|