| # 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; |
| ''' |