mirror of
				https://github.com/eledio-devices/thirdparty-littlefs.git
				synced 2025-10-31 16:14:16 +01:00 
			
		
		
		
	As an embedded library, littlefs's configuration straddles two worlds.
In most cases the configuration is usually constant at build time, but
when integrated into OSs, the configuration needs to be dynamically
configurable.
To help with this, littlefs has a separate lfs_config struct that can be
placed into ROM when possible.
But you know what's better than ROM configuration? Truely inlinable
static configuration known at compile-time. In addition to avoiding the
RAM cost, compile-time configuration allows for additional compiler
optimizations, such as constexpr-elimination and removal of unused
code-paths.
So how to enable static configuration?
1. define LFS_STATICCFG
2. implement callbacks as global functions:
   - lfs_read
   - lfs_prog
   - lfs_erase
   - lfs_sync
2. define the now-required constants that configure littlefs:
   - LFS_READ_SIZE
   - LFS_PROG_SIZE
   - LFS_BLOCK_SIZE
   - LFS_BLOCK_COUNT
   - LFS_BLOCK_CYCLES
   - LFS_CACHE_SIZE
   - LFS_LOOKAHEAD_SIZE
   - LFS_READ_BUFFER (optional)
   - LFS_PROG_BUFFER (optional)
   - LFS_LOOKAHEAD_BUFFER (optional)
   - LFS_NAME_MAX (optional)
   - LFS_FILE_MAX (optional)
   - LFS_ATTR_MAX (optional)
Note, there is a separate configuration for the file configuration, this
can be enabled/disabled independently of LFS_STATICCFG. You will likely
want to define this as well if you are looking for the smallest code
size.
In order to avoid a mess of #ifdefs, the internals of littlefs use a
simple macro that redirects to either the dynamic or static config at
compile time:
    #ifdef LFS_STATICCFG
    #define LFS_CFG_READ_SIZE(lfs) ((void)lfs, LFS_READ_SIZE)
    #else
    #define LFS_CFG_READ_SIZE(lfs) lfs->cfg->read_size
    #endif
Unfortunately it does look like there still may be a lot of issues
related to warnings of comparisons against constants... If only C had
a way to ignore warnings on individual statements...
Original idea by apmorton
		
	
		
			
				
	
	
		
			297 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			TOML
		
	
	
	
	
	
			
		
		
	
	
			297 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.cfg = &cfg;
 | |
|     lfs_initcommon(&lfs) => 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.cfg = &cfg;
 | |
|     lfs_initcommon(&lfs) => 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.cfg = &cfg;
 | |
|     lfs_initcommon(&lfs) => 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.cfg = &cfg;
 | |
|     lfs_initcommon(&lfs) => 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_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.cfg = &cfg;
 | |
|     lfs_initcommon(&lfs) => 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.cfg = &cfg;
 | |
|     lfs_initcommon(&lfs) => 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.cfg = &cfg;
 | |
|     lfs_initcommon(&lfs) => 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.cfg = &cfg;
 | |
|     lfs_initcommon(&lfs) => 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;
 | |
| '''
 |