mirror of
https://gitee.com/openharmony/third_party_littlefs
synced 2024-11-27 00:50:38 +00:00
4677421aba
These two features have been much requested by users, and have even had several PRs proposed to fix these in several cases. Before this, these error conditions usually were caught by internal asserts, however asserts prevented users from implementing their own workarounds. It's taken me a while to provide/accept a useful recovery mechanism (returning LFS_ERR_CORRUPT instead of asserting) because my original thinking was that these error conditions only occur due to bugs in the filesystem, and these bugs should be fixed properly. While I still think this is mostly true, the point has been made clear that being able to recover from these conditions is definitely worth the code cost. Hopefully this new behaviour helps the longevity of devices even if the storage code fails. Another, less important, reason I didn't want to accept fixes for these situations was the lack of tests that prove the code's value. This has been fixed with the new testing framework thanks to the additional of "internal tests" which can call C static functions and really take advantage of the internal information of the filesystem.
289 lines
9.9 KiB
TOML
289 lines
9.9 KiB
TOML
# Tests for recovering from conditions which shouldn't normally
|
|
# happen during normal operation of littlefs
|
|
|
|
# invalid pointer tests (outside of block_count)
|
|
|
|
[[case]] # invalid tail-pointer test
|
|
define.TAIL_TYPE = ['LFS_TYPE_HARDTAIL', 'LFS_TYPE_SOFTTAIL']
|
|
define.INVALSET = [0x3, 0x1, 0x2]
|
|
in = "lfs.c"
|
|
code = '''
|
|
// create littlefs
|
|
lfs_format(&lfs, &cfg) => 0;
|
|
|
|
// change tail-pointer to invalid pointers
|
|
lfs_init(&lfs, &cfg) => 0;
|
|
lfs_mdir_t mdir;
|
|
lfs_dir_fetch(&lfs, &mdir, (lfs_block_t[2]){0, 1}) => 0;
|
|
lfs_dir_commit(&lfs, &mdir, LFS_MKATTRS(
|
|
{LFS_MKTAG(LFS_TYPE_HARDTAIL, 0x3ff, 8),
|
|
(lfs_block_t[2]){
|
|
(INVALSET & 0x1) ? 0xcccccccc : 0,
|
|
(INVALSET & 0x2) ? 0xcccccccc : 0}})) => 0;
|
|
lfs_deinit(&lfs) => 0;
|
|
|
|
// test that mount fails gracefully
|
|
lfs_mount(&lfs, &cfg) => LFS_ERR_CORRUPT;
|
|
'''
|
|
|
|
[[case]] # invalid dir pointer test
|
|
define.INVALSET = [0x3, 0x1, 0x2]
|
|
in = "lfs.c"
|
|
code = '''
|
|
// create littlefs
|
|
lfs_format(&lfs, &cfg) => 0;
|
|
// make a dir
|
|
lfs_mount(&lfs, &cfg) => 0;
|
|
lfs_mkdir(&lfs, "dir_here") => 0;
|
|
lfs_unmount(&lfs) => 0;
|
|
|
|
// change the dir pointer to be invalid
|
|
lfs_init(&lfs, &cfg) => 0;
|
|
lfs_mdir_t mdir;
|
|
lfs_dir_fetch(&lfs, &mdir, (lfs_block_t[2]){0, 1}) => 0;
|
|
// make sure id 1 == our directory
|
|
lfs_dir_get(&lfs, &mdir,
|
|
LFS_MKTAG(0x700, 0x3ff, 0),
|
|
LFS_MKTAG(LFS_TYPE_NAME, 1, strlen("dir_here")), buffer)
|
|
=> LFS_MKTAG(LFS_TYPE_DIR, 1, strlen("dir_here"));
|
|
assert(memcmp((char*)buffer, "dir_here", strlen("dir_here")) == 0);
|
|
// change dir pointer
|
|
lfs_dir_commit(&lfs, &mdir, LFS_MKATTRS(
|
|
{LFS_MKTAG(LFS_TYPE_DIRSTRUCT, 1, 8),
|
|
(lfs_block_t[2]){
|
|
(INVALSET & 0x1) ? 0xcccccccc : 0,
|
|
(INVALSET & 0x2) ? 0xcccccccc : 0}})) => 0;
|
|
lfs_deinit(&lfs) => 0;
|
|
|
|
// test that accessing our bad dir fails, note there's a number
|
|
// of ways to access the dir, some can fail, but some don't
|
|
lfs_mount(&lfs, &cfg) => 0;
|
|
lfs_stat(&lfs, "dir_here", &info) => 0;
|
|
assert(strcmp(info.name, "dir_here") == 0);
|
|
assert(info.type == LFS_TYPE_DIR);
|
|
|
|
lfs_dir_open(&lfs, &dir, "dir_here") => LFS_ERR_CORRUPT;
|
|
lfs_stat(&lfs, "dir_here/file_here", &info) => LFS_ERR_CORRUPT;
|
|
lfs_dir_open(&lfs, &dir, "dir_here/dir_here") => LFS_ERR_CORRUPT;
|
|
lfs_file_open(&lfs, &file, "dir_here/file_here",
|
|
LFS_O_RDONLY) => LFS_ERR_CORRUPT;
|
|
lfs_file_open(&lfs, &file, "dir_here/file_here",
|
|
LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_CORRUPT;
|
|
lfs_unmount(&lfs) => 0;
|
|
'''
|
|
|
|
[[case]] # invalid file pointer test
|
|
in = "lfs.c"
|
|
define.SIZE = [10, 1000, 100000] # faked file size
|
|
code = '''
|
|
// create littlefs
|
|
lfs_format(&lfs, &cfg) => 0;
|
|
// make a file
|
|
lfs_mount(&lfs, &cfg) => 0;
|
|
lfs_file_open(&lfs, &file, "file_here",
|
|
LFS_O_WRONLY | LFS_O_CREAT) => 0;
|
|
lfs_file_close(&lfs, &file) => 0;
|
|
lfs_unmount(&lfs) => 0;
|
|
|
|
// change the file pointer to be invalid
|
|
lfs_init(&lfs, &cfg) => 0;
|
|
lfs_mdir_t mdir;
|
|
lfs_dir_fetch(&lfs, &mdir, (lfs_block_t[2]){0, 1}) => 0;
|
|
// make sure id 1 == our file
|
|
lfs_dir_get(&lfs, &mdir,
|
|
LFS_MKTAG(0x700, 0x3ff, 0),
|
|
LFS_MKTAG(LFS_TYPE_NAME, 1, strlen("file_here")), buffer)
|
|
=> LFS_MKTAG(LFS_TYPE_REG, 1, strlen("file_here"));
|
|
assert(memcmp((char*)buffer, "file_here", strlen("file_here")) == 0);
|
|
// change file pointer
|
|
lfs_dir_commit(&lfs, &mdir, LFS_MKATTRS(
|
|
{LFS_MKTAG(LFS_TYPE_CTZSTRUCT, 1, sizeof(struct lfs_ctz)),
|
|
&(struct lfs_ctz){0xcccccccc, lfs_tole32(SIZE)}})) => 0;
|
|
lfs_deinit(&lfs) => 0;
|
|
|
|
// test that accessing our bad file fails, note there's a number
|
|
// of ways to access the dir, some can fail, but some don't
|
|
lfs_mount(&lfs, &cfg) => 0;
|
|
lfs_stat(&lfs, "file_here", &info) => 0;
|
|
assert(strcmp(info.name, "file_here") == 0);
|
|
assert(info.type == LFS_TYPE_REG);
|
|
assert(info.size == SIZE);
|
|
|
|
lfs_file_open(&lfs, &file, "file_here", LFS_O_RDONLY) => 0;
|
|
lfs_file_read(&lfs, &file, buffer, SIZE) => LFS_ERR_CORRUPT;
|
|
lfs_file_close(&lfs, &file) => 0;
|
|
|
|
// any allocs that traverse CTZ must unfortunately must fail
|
|
if (SIZE > 2*LFS_BLOCK_SIZE) {
|
|
lfs_mkdir(&lfs, "dir_here") => LFS_ERR_CORRUPT;
|
|
}
|
|
lfs_unmount(&lfs) => 0;
|
|
'''
|
|
|
|
[[case]] # invalid pointer in CTZ skip-list test
|
|
define.SIZE = ['2*LFS_BLOCK_SIZE', '3*LFS_BLOCK_SIZE', '4*LFS_BLOCK_SIZE']
|
|
in = "lfs.c"
|
|
code = '''
|
|
// create littlefs
|
|
lfs_format(&lfs, &cfg) => 0;
|
|
// make a file
|
|
lfs_mount(&lfs, &cfg) => 0;
|
|
lfs_file_open(&lfs, &file, "file_here",
|
|
LFS_O_WRONLY | LFS_O_CREAT) => 0;
|
|
for (int i = 0; i < SIZE; i++) {
|
|
char c = 'c';
|
|
lfs_file_write(&lfs, &file, &c, 1) => 1;
|
|
}
|
|
lfs_file_close(&lfs, &file) => 0;
|
|
lfs_unmount(&lfs) => 0;
|
|
// change pointer in CTZ skip-list to be invalid
|
|
lfs_init(&lfs, &cfg) => 0;
|
|
lfs_mdir_t mdir;
|
|
lfs_dir_fetch(&lfs, &mdir, (lfs_block_t[2]){0, 1}) => 0;
|
|
// make sure id 1 == our file and get our CTZ structure
|
|
lfs_dir_get(&lfs, &mdir,
|
|
LFS_MKTAG(0x700, 0x3ff, 0),
|
|
LFS_MKTAG(LFS_TYPE_NAME, 1, strlen("file_here")), buffer)
|
|
=> LFS_MKTAG(LFS_TYPE_REG, 1, strlen("file_here"));
|
|
assert(memcmp((char*)buffer, "file_here", strlen("file_here")) == 0);
|
|
struct lfs_ctz ctz;
|
|
lfs_dir_get(&lfs, &mdir,
|
|
LFS_MKTAG(0x700, 0x3ff, 0),
|
|
LFS_MKTAG(LFS_TYPE_STRUCT, 1, sizeof(struct lfs_ctz)), &ctz)
|
|
=> LFS_MKTAG(LFS_TYPE_CTZSTRUCT, 1, sizeof(struct lfs_ctz));
|
|
lfs_ctz_fromle32(&ctz);
|
|
// rewrite block to contain bad pointer
|
|
uint8_t bbuffer[LFS_BLOCK_SIZE];
|
|
cfg.read(&cfg, ctz.head, 0, bbuffer, LFS_BLOCK_SIZE) => 0;
|
|
uint32_t bad = lfs_tole32(0xcccccccc);
|
|
memcpy(&bbuffer[0], &bad, sizeof(bad));
|
|
memcpy(&bbuffer[4], &bad, sizeof(bad));
|
|
cfg.erase(&cfg, ctz.head) => 0;
|
|
cfg.prog(&cfg, ctz.head, 0, bbuffer, LFS_BLOCK_SIZE) => 0;
|
|
lfs_deinit(&lfs) => 0;
|
|
|
|
// test that accessing our bad file fails, note there's a number
|
|
// of ways to access the dir, some can fail, but some don't
|
|
lfs_mount(&lfs, &cfg) => 0;
|
|
lfs_stat(&lfs, "file_here", &info) => 0;
|
|
assert(strcmp(info.name, "file_here") == 0);
|
|
assert(info.type == LFS_TYPE_REG);
|
|
assert(info.size == SIZE);
|
|
|
|
lfs_file_open(&lfs, &file, "file_here", LFS_O_RDONLY) => 0;
|
|
lfs_file_read(&lfs, &file, buffer, SIZE) => LFS_ERR_CORRUPT;
|
|
lfs_file_close(&lfs, &file) => 0;
|
|
|
|
// any allocs that traverse CTZ must unfortunately must fail
|
|
if (SIZE > 2*LFS_BLOCK_SIZE) {
|
|
lfs_mkdir(&lfs, "dir_here") => LFS_ERR_CORRUPT;
|
|
}
|
|
lfs_unmount(&lfs) => 0;
|
|
'''
|
|
|
|
|
|
[[case]] # invalid gstate pointer
|
|
define.INVALSET = [0x3, 0x1, 0x2]
|
|
in = "lfs.c"
|
|
code = '''
|
|
// create littlefs
|
|
lfs_format(&lfs, &cfg) => 0;
|
|
|
|
// create an invalid gstate
|
|
lfs_init(&lfs, &cfg) => 0;
|
|
lfs_mdir_t mdir;
|
|
lfs_dir_fetch(&lfs, &mdir, (lfs_block_t[2]){0, 1}) => 0;
|
|
lfs_fs_prepmove(&lfs, 1, (lfs_block_t [2]){
|
|
(INVALSET & 0x1) ? 0xcccccccc : 0,
|
|
(INVALSET & 0x2) ? 0xcccccccc : 0});
|
|
lfs_dir_commit(&lfs, &mdir, NULL, 0) => 0;
|
|
lfs_deinit(&lfs) => 0;
|
|
|
|
// test that mount fails gracefully
|
|
// mount may not fail, but our first alloc should fail when
|
|
// we try to fix the gstate
|
|
lfs_mount(&lfs, &cfg) => 0;
|
|
lfs_mkdir(&lfs, "should_fail") => LFS_ERR_CORRUPT;
|
|
lfs_unmount(&lfs) => 0;
|
|
'''
|
|
|
|
# cycle detection/recovery tests
|
|
|
|
[[case]] # metadata-pair threaded-list loop test
|
|
in = "lfs.c"
|
|
code = '''
|
|
// create littlefs
|
|
lfs_format(&lfs, &cfg) => 0;
|
|
|
|
// change tail-pointer to point to ourself
|
|
lfs_init(&lfs, &cfg) => 0;
|
|
lfs_mdir_t mdir;
|
|
lfs_dir_fetch(&lfs, &mdir, (lfs_block_t[2]){0, 1}) => 0;
|
|
lfs_dir_commit(&lfs, &mdir, LFS_MKATTRS(
|
|
{LFS_MKTAG(LFS_TYPE_HARDTAIL, 0x3ff, 8),
|
|
(lfs_block_t[2]){0, 1}})) => 0;
|
|
lfs_deinit(&lfs) => 0;
|
|
|
|
// test that mount fails gracefully
|
|
lfs_mount(&lfs, &cfg) => LFS_ERR_CORRUPT;
|
|
'''
|
|
|
|
[[case]] # metadata-pair threaded-list 2-length loop test
|
|
in = "lfs.c"
|
|
code = '''
|
|
// create littlefs with child dir
|
|
lfs_format(&lfs, &cfg) => 0;
|
|
lfs_mount(&lfs, &cfg) => 0;
|
|
lfs_mkdir(&lfs, "child") => 0;
|
|
lfs_unmount(&lfs) => 0;
|
|
|
|
// find child
|
|
lfs_init(&lfs, &cfg) => 0;
|
|
lfs_mdir_t mdir;
|
|
lfs_block_t pair[2];
|
|
lfs_dir_fetch(&lfs, &mdir, (lfs_block_t[2]){0, 1}) => 0;
|
|
lfs_dir_get(&lfs, &mdir,
|
|
LFS_MKTAG(0x7ff, 0x3ff, 0),
|
|
LFS_MKTAG(LFS_TYPE_DIRSTRUCT, 1, sizeof(pair)), pair)
|
|
=> LFS_MKTAG(LFS_TYPE_DIRSTRUCT, 1, sizeof(pair));
|
|
lfs_pair_fromle32(pair);
|
|
// change tail-pointer to point to root
|
|
lfs_dir_fetch(&lfs, &mdir, pair) => 0;
|
|
lfs_dir_commit(&lfs, &mdir, LFS_MKATTRS(
|
|
{LFS_MKTAG(LFS_TYPE_HARDTAIL, 0x3ff, 8),
|
|
(lfs_block_t[2]){0, 1}})) => 0;
|
|
lfs_deinit(&lfs) => 0;
|
|
|
|
// test that mount fails gracefully
|
|
lfs_mount(&lfs, &cfg) => LFS_ERR_CORRUPT;
|
|
'''
|
|
|
|
[[case]] # metadata-pair threaded-list 1-length child loop test
|
|
in = "lfs.c"
|
|
code = '''
|
|
// create littlefs with child dir
|
|
lfs_format(&lfs, &cfg) => 0;
|
|
lfs_mount(&lfs, &cfg) => 0;
|
|
lfs_mkdir(&lfs, "child") => 0;
|
|
lfs_unmount(&lfs) => 0;
|
|
|
|
// find child
|
|
lfs_init(&lfs, &cfg) => 0;
|
|
lfs_mdir_t mdir;
|
|
lfs_block_t pair[2];
|
|
lfs_dir_fetch(&lfs, &mdir, (lfs_block_t[2]){0, 1}) => 0;
|
|
lfs_dir_get(&lfs, &mdir,
|
|
LFS_MKTAG(0x7ff, 0x3ff, 0),
|
|
LFS_MKTAG(LFS_TYPE_DIRSTRUCT, 1, sizeof(pair)), pair)
|
|
=> LFS_MKTAG(LFS_TYPE_DIRSTRUCT, 1, sizeof(pair));
|
|
lfs_pair_fromle32(pair);
|
|
// change tail-pointer to point to ourself
|
|
lfs_dir_fetch(&lfs, &mdir, pair) => 0;
|
|
lfs_dir_commit(&lfs, &mdir, LFS_MKATTRS(
|
|
{LFS_MKTAG(LFS_TYPE_HARDTAIL, 0x3ff, 8), pair})) => 0;
|
|
lfs_deinit(&lfs) => 0;
|
|
|
|
// test that mount fails gracefully
|
|
lfs_mount(&lfs, &cfg) => LFS_ERR_CORRUPT;
|
|
'''
|