4e3dca0b81
This became surprisingly tricky. The main issue is knowing when to split mdirs, and how to determine this without wasting erase cycles. Unlike splitting btree nodes, we can't salvage failed compacts here. As soon as the salvage commit is written to disk, the commit becomes immediately visibile to the filesystem because it still exists in the mtree. This is a problem if we lose power. We're likely going to need to implement rbyd estimates. This is something I hoped to avoid because it brings in quite a bit of complexity and might lead to an annoying amount of storage waste since our estimates will need to be conservative to avoid unrecoverable situations. --- Also changed the on-disk btree/branch struct to store a copy of the weight. This was already required for the root of the btree, requiring the weight to be stored in every btree pointer allows better code deduplication at the cost of some redundancy on btree branches, where the weight is already implied by the rbyd structure. This weight is usually a single byte for most branches anyways. This may be worth revisiting at some point to see if there's any other unexpected tradeoffs.
212 lines
5.5 KiB
TOML
212 lines
5.5 KiB
TOML
# simple format test
|
|
[cases.test_superblocks_format]
|
|
code = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, cfg) => 0;
|
|
'''
|
|
|
|
# simple mount/unmount test
|
|
[cases.test_superblocks_mount]
|
|
code = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, cfg) => 0;
|
|
lfsr_mount(&lfs, cfg) => 0;
|
|
lfsr_unmount(&lfs) => 0;
|
|
'''
|
|
|
|
# reentrant format
|
|
[cases.test_superblocks_reentrant_format]
|
|
reentrant = true
|
|
code = '''
|
|
lfs_t lfs;
|
|
int err = lfsr_mount(&lfs, cfg);
|
|
if (err) {
|
|
lfsr_format(&lfs, cfg) => 0;
|
|
lfsr_mount(&lfs, cfg) => 0;
|
|
}
|
|
lfsr_unmount(&lfs) => 0;
|
|
'''
|
|
|
|
# invalid mount
|
|
[cases.test_superblocks_invalid]
|
|
code = '''
|
|
lfs_t lfs;
|
|
lfsr_mount(&lfs, cfg) => LFS_ERR_INVAL;
|
|
'''
|
|
|
|
# superblock cycle detection
|
|
[cases.test_superblocks_cycle]
|
|
in = 'lfs.c'
|
|
code = '''
|
|
// create a cycle
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, cfg) => 0;
|
|
lfsr_mount(&lfs, cfg) => 0;
|
|
uint8_t buf[LFSR_MPAIR_DSIZE];
|
|
lfs_ssize_t d = lfsr_mpair_todisk(&lfs,
|
|
lfsr_mdir_mpair(&lfs.supermdir), buf);
|
|
assert(d >= 0);
|
|
lfsr_mdir_commit(&lfs, &lfs.supermdir, LFSR_ATTRS(
|
|
LFSR_ATTR(-1, SUPERMDIR, 0, buf, d))) => 0;
|
|
lfsr_unmount(&lfs) => 0;
|
|
|
|
// now detect the cycle
|
|
lfsr_mount(&lfs, cfg) => LFS_ERR_CORRUPT;
|
|
'''
|
|
|
|
|
|
|
|
|
|
|
|
## 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_CORRUPT;
|
|
#'''
|
|
#
|
|
## 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;
|
|
#'''
|