mirror of
				https://github.com/eledio-devices/thirdparty-littlefs.git
				synced 2025-10-31 16:14:16 +01:00 
			
		
		
		
	Currently littlefs uses a separate mutable state struct and immutable
config struct. This lets users place the config struct in ROM where
possible.
However the recent addition of LFS_STATICCFG raises the question of if
this split is still valuable.
If the config is copied into the mutable struct at runtime, this allows
a couple things:
1. Easier user interface, config can be stack allocated, no need to store
   the config struct for the lifetime of littlefs in OSs.
2. Avoids duplication when littlefs would need to change config based on
   observed superblocks, such as LFS_NAME_MAX limits
3. In theory, access to a single struct is faster/smaller as it avoids
   an additional load instruction.
Unfortunately, inlining the dynamic config runs into several issues:
1. The code size actually increases with this change! From digging into
   this it's for a couple reasons:
   - Copying the config over takes code.
   - C has notorious problems with pointer aliasing, accessing
     constants from a const struct actually allows C to assume the
     values aren't going to change in more situations.
     This suggests it may be possible to reduce the code size more by
     loading the config pointer into a variable, but I haven't explored
     this probably not-worth-it optimization.
   - Even assuming deduplication of superblock-dependent configuration,
     the config struct is significantly larger than the mutable struct,
     and it turns out combining these two exceeds the limits of
     immediate-relative-loads, discarding the possible code size
     improvement from avoiding a second dereference.
2. The implementation of dynamic configuration differs significantly from
   the static configuration. This adds mess into the compile-time #ifdefs
   needed to support both options.
		
	
		
			
				
	
	
		
			289 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			TOML
		
	
	
	
	
	
			
		
		
	
	
			289 lines
		
	
	
		
			10 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_formatcfg(&lfs, &cfg) => 0;
 | |
| 
 | |
|     // change tail-pointer to invalid pointers
 | |
|     lfs_initcommon(&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_mountcfg(&lfs, &cfg) => LFS_ERR_CORRUPT;
 | |
| '''
 | |
| 
 | |
| [[case]] # invalid dir pointer test
 | |
| define.INVALSET = [0x3, 0x1, 0x2]
 | |
| in = "lfs.c"
 | |
| code = '''
 | |
|     // create littlefs
 | |
|     lfs_formatcfg(&lfs, &cfg) => 0;
 | |
|     // make a dir
 | |
|     lfs_mountcfg(&lfs, &cfg) => 0;
 | |
|     lfs_mkdir(&lfs, "dir_here") => 0;
 | |
|     lfs_unmount(&lfs) => 0;
 | |
| 
 | |
|     // change the dir pointer to be invalid
 | |
|     lfs_initcommon(&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_mountcfg(&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_formatcfg(&lfs, &cfg) => 0;
 | |
|     // make a file
 | |
|     lfs_mountcfg(&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_initcommon(&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_mountcfg(&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_formatcfg(&lfs, &cfg) => 0;
 | |
|     // make a file
 | |
|     lfs_mountcfg(&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_initcommon(&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];
 | |
|     lfs_testbd_read(&bd, 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));
 | |
|     lfs_testbd_erase(&bd, ctz.head) => 0;
 | |
|     lfs_testbd_prog(&bd, 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_mountcfg(&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_formatcfg(&lfs, &cfg) => 0;
 | |
| 
 | |
|     // create an invalid gstate
 | |
|     lfs_initcommon(&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_mountcfg(&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_formatcfg(&lfs, &cfg) => 0;
 | |
| 
 | |
|     // change tail-pointer to point to ourself
 | |
|     lfs_initcommon(&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_mountcfg(&lfs, &cfg) => LFS_ERR_CORRUPT;
 | |
| '''
 | |
| 
 | |
| [[case]] # metadata-pair threaded-list 2-length loop test
 | |
| in = "lfs.c"
 | |
| code = '''
 | |
|     // create littlefs with child dir
 | |
|     lfs_formatcfg(&lfs, &cfg) => 0;
 | |
|     lfs_mountcfg(&lfs, &cfg) => 0;
 | |
|     lfs_mkdir(&lfs, "child") => 0;
 | |
|     lfs_unmount(&lfs) => 0;
 | |
| 
 | |
|     // find child
 | |
|     lfs_initcommon(&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_mountcfg(&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_formatcfg(&lfs, &cfg) => 0;
 | |
|     lfs_mountcfg(&lfs, &cfg) => 0;
 | |
|     lfs_mkdir(&lfs, "child") => 0;
 | |
|     lfs_unmount(&lfs) => 0;
 | |
| 
 | |
|     // find child
 | |
|     lfs_initcommon(&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_mountcfg(&lfs, &cfg) => LFS_ERR_CORRUPT;
 | |
| '''
 |