mirror of
				https://github.com/eledio-devices/thirdparty-littlefs.git
				synced 2025-10-31 16:14:16 +01:00 
			
		
		
		
	Added support for handling corrupted blocks
This provides a limited form of wear leveling. While wear is not actually balanced across blocks, the filesystem can recover from corrupted blocks and extend the lifetime of a device nearly as much as dynamic wear leveling. For use-cases where wear is important, it would be better to use a full form of dynamic wear-leveling at the block level. (or consider a logging filesystem). Corrupted block handling was simply added on top of the existing logic in place for the filesystem, so it's a bit more noodly than it may have to be, but it gets the work done.
This commit is contained in:
		
							
								
								
									
										2
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								Makefile
									
									
									
									
									
								
							| @@ -32,7 +32,7 @@ size: $(OBJ) | |||||||
|  |  | ||||||
| .SUFFIXES: | .SUFFIXES: | ||||||
| test: test_format test_dirs test_files test_seek test_parallel \ | test: test_format test_dirs test_files test_seek test_parallel \ | ||||||
| 	test_alloc test_paths test_orphan | 	test_alloc test_paths test_orphan test_corrupt | ||||||
| test_%: tests/test_%.sh | test_%: tests/test_%.sh | ||||||
| 	./$< | 	./$< | ||||||
|  |  | ||||||
|   | |||||||
| @@ -144,13 +144,24 @@ int lfs_emubd_prog(const struct lfs_config *cfg, lfs_block_t block, | |||||||
|         return -errno; |         return -errno; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     err = fseek(f, off, SEEK_SET); | ||||||
|  |     if (err) { | ||||||
|  |         return -errno; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     uint8_t dat; | ||||||
|  |     res = fread(&dat, 1, 1, f); | ||||||
|  |     if (res < 1) { | ||||||
|  |         return -errno; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     err = fclose(f); |     err = fclose(f); | ||||||
|     if (err) { |     if (err) { | ||||||
|         return -errno; |         return -errno; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     emu->stats.prog_count += 1; |     emu->stats.prog_count += 1; | ||||||
|     return 0; |     return (dat != data[0]) ? LFS_ERR_CORRUPT : 0; | ||||||
| } | } | ||||||
|  |  | ||||||
| int lfs_emubd_erase(const struct lfs_config *cfg, lfs_block_t block) { | int lfs_emubd_erase(const struct lfs_config *cfg, lfs_block_t block) { | ||||||
|   | |||||||
							
								
								
									
										9
									
								
								lfs.h
									
									
									
									
									
								
							
							
						
						
									
										9
									
								
								lfs.h
									
									
									
									
									
								
							| @@ -43,7 +43,7 @@ enum lfs_error { | |||||||
| enum lfs_type { | enum lfs_type { | ||||||
|     LFS_TYPE_REG        = 0x01, |     LFS_TYPE_REG        = 0x01, | ||||||
|     LFS_TYPE_DIR        = 0x02, |     LFS_TYPE_DIR        = 0x02, | ||||||
|     LFS_TYPE_SUPERBLOCK = 0x10, |     LFS_TYPE_SUPERBLOCK = 0x12, | ||||||
| }; | }; | ||||||
|  |  | ||||||
| enum lfs_open_flags { | enum lfs_open_flags { | ||||||
| @@ -193,15 +193,16 @@ typedef struct lfs_superblock { | |||||||
|     struct lfs_disk_superblock { |     struct lfs_disk_superblock { | ||||||
|         uint16_t type; |         uint16_t type; | ||||||
|         uint16_t len; |         uint16_t len; | ||||||
|  |         lfs_block_t root[2]; | ||||||
|         uint32_t version; |         uint32_t version; | ||||||
|         char magic[8]; |         char magic[8]; | ||||||
|         uint32_t block_size; |         uint32_t block_size; | ||||||
|         uint32_t block_count; |         uint32_t block_count; | ||||||
|         lfs_block_t root[2]; |  | ||||||
|     } d; |     } d; | ||||||
| } lfs_superblock_t; | } lfs_superblock_t; | ||||||
|  |  | ||||||
| typedef struct lfs_free { | typedef struct lfs_free { | ||||||
|  |     lfs_block_t end; | ||||||
|     lfs_block_t start; |     lfs_block_t start; | ||||||
|     lfs_block_t off; |     lfs_block_t off; | ||||||
|     uint32_t *lookahead; |     uint32_t *lookahead; | ||||||
| @@ -212,8 +213,8 @@ typedef struct lfs { | |||||||
|     const struct lfs_config *cfg; |     const struct lfs_config *cfg; | ||||||
|  |  | ||||||
|     lfs_block_t root[2]; |     lfs_block_t root[2]; | ||||||
|     lfs_dir_t *scratch; |  | ||||||
|     lfs_file_t *files; |     lfs_file_t *files; | ||||||
|  |     bool deorphaned; | ||||||
|  |  | ||||||
|     lfs_cache_t rcache; |     lfs_cache_t rcache; | ||||||
|     lfs_cache_t pcache; |     lfs_cache_t pcache; | ||||||
| @@ -257,8 +258,8 @@ int lfs_file_rewind(lfs_t *lfs, lfs_file_t *file); | |||||||
| lfs_soff_t lfs_file_size(lfs_t *lfs, lfs_file_t *file); | lfs_soff_t lfs_file_size(lfs_t *lfs, lfs_file_t *file); | ||||||
|  |  | ||||||
| // miscellaneous lfs specific operations | // miscellaneous lfs specific operations | ||||||
| int lfs_deorphan(lfs_t *lfs); |  | ||||||
| int lfs_traverse(lfs_t *lfs, int (*cb)(void*, lfs_block_t), void *data); | int lfs_traverse(lfs_t *lfs, int (*cb)(void*, lfs_block_t), void *data); | ||||||
|  | int lfs_deorphan(lfs_t *lfs); | ||||||
|  |  | ||||||
|  |  | ||||||
| #endif | #endif | ||||||
|   | |||||||
| @@ -13,11 +13,17 @@ void test_log(const char *s, uintmax_t v) {{ | |||||||
|  |  | ||||||
| void test_assert(const char *file, unsigned line, | void test_assert(const char *file, unsigned line, | ||||||
|         const char *s, uintmax_t v, uintmax_t e) {{ |         const char *s, uintmax_t v, uintmax_t e) {{ | ||||||
|     static const char *last[2] = {{0, 0}}; |     static const char *last[6] = {{0, 0}}; | ||||||
|     if (v != e || !(last[0] == s || last[1] == s)) {{ |     if (v != e || !(last[0] == s || last[1] == s || | ||||||
|  |             last[2] == s || last[3] == s || | ||||||
|  |             last[4] == s || last[5] == s)) {{ | ||||||
|         test_log(s, v); |         test_log(s, v); | ||||||
|         last[0] = last[1]; |         last[0] = last[1]; | ||||||
|         last[1] = s; |         last[1] = last[2]; | ||||||
|  |         last[2] = last[3]; | ||||||
|  |         last[3] = last[4]; | ||||||
|  |         last[4] = last[5]; | ||||||
|  |         last[5] = s; | ||||||
|     }} |     }} | ||||||
|  |  | ||||||
|     if (v != e) {{ |     if (v != e) {{ | ||||||
|   | |||||||
							
								
								
									
										106
									
								
								tests/test_corrupt.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										106
									
								
								tests/test_corrupt.sh
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,106 @@ | |||||||
|  | #!/bin/bash | ||||||
|  | set -eu | ||||||
|  |  | ||||||
|  | echo "=== Corrupt tests ===" | ||||||
|  |  | ||||||
|  | NAMEMULT=64 | ||||||
|  | FILEMULT=1 | ||||||
|  |  | ||||||
|  | lfs_mktree() { | ||||||
|  | tests/test.py ${1:-} << TEST | ||||||
|  |     lfs_format(&lfs, &cfg) => 0; | ||||||
|  |  | ||||||
|  |     lfs_mount(&lfs, &cfg) => 0; | ||||||
|  |     for (int i = 1; i < 10; i++) { | ||||||
|  |         for (int j = 0; j < $NAMEMULT; j++) { | ||||||
|  |             buffer[j] = '0'+i; | ||||||
|  |         } | ||||||
|  |         buffer[$NAMEMULT] = '\0'; | ||||||
|  |         lfs_mkdir(&lfs, (char*)buffer) => 0; | ||||||
|  |  | ||||||
|  |         buffer[$NAMEMULT] = '/'; | ||||||
|  |         for (int j = 0; j < $NAMEMULT; j++) { | ||||||
|  |             buffer[j+$NAMEMULT+1] = '0'+i; | ||||||
|  |         } | ||||||
|  |         buffer[2*$NAMEMULT+1] = '\0'; | ||||||
|  |         lfs_file_open(&lfs, &file[0], (char*)buffer, | ||||||
|  |                 LFS_O_WRONLY | LFS_O_CREAT) => 0; | ||||||
|  |          | ||||||
|  |         size = $NAMEMULT; | ||||||
|  |         for (int j = 0; j < i*$FILEMULT; j++) { | ||||||
|  |             lfs_file_write(&lfs, &file[0], buffer, size) => size; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         lfs_file_close(&lfs, &file[0]) => 0; | ||||||
|  |     } | ||||||
|  |     lfs_unmount(&lfs) => 0; | ||||||
|  | TEST | ||||||
|  | } | ||||||
|  |  | ||||||
|  | lfs_chktree() { | ||||||
|  | tests/test.py ${1:-} << TEST | ||||||
|  |     lfs_mount(&lfs, &cfg) => 0; | ||||||
|  |     for (int i = 1; i < 10; i++) { | ||||||
|  |         for (int j = 0; j < $NAMEMULT; j++) { | ||||||
|  |             buffer[j] = '0'+i; | ||||||
|  |         } | ||||||
|  |         buffer[$NAMEMULT] = '\0'; | ||||||
|  |         lfs_stat(&lfs, (char*)buffer, &info) => 0; | ||||||
|  |         info.type => LFS_TYPE_DIR; | ||||||
|  |  | ||||||
|  |         buffer[$NAMEMULT] = '/'; | ||||||
|  |         for (int j = 0; j < $NAMEMULT; j++) { | ||||||
|  |             buffer[j+$NAMEMULT+1] = '0'+i; | ||||||
|  |         } | ||||||
|  |         buffer[2*$NAMEMULT+1] = '\0'; | ||||||
|  |         lfs_file_open(&lfs, &file[0], (char*)buffer, LFS_O_RDONLY) => 0; | ||||||
|  |          | ||||||
|  |         size = $NAMEMULT; | ||||||
|  |         for (int j = 0; j < i*$FILEMULT; j++) { | ||||||
|  |             lfs_file_read(&lfs, &file[0], rbuffer, size) => size; | ||||||
|  |             memcmp(buffer, rbuffer, size) => 0; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         lfs_file_close(&lfs, &file[0]) => 0; | ||||||
|  |     } | ||||||
|  |     lfs_unmount(&lfs) => 0; | ||||||
|  | TEST | ||||||
|  | } | ||||||
|  |  | ||||||
|  | echo "--- Sanity check ---" | ||||||
|  | rm -rf blocks | ||||||
|  | lfs_mktree | ||||||
|  | lfs_chktree | ||||||
|  |  | ||||||
|  | echo "--- Block corruption ---" | ||||||
|  | for i in {0..33} | ||||||
|  | do  | ||||||
|  |     rm -rf blocks | ||||||
|  |     mkdir blocks | ||||||
|  |     ln -s /dev/zero blocks/$(printf '%x' $i) | ||||||
|  |     lfs_mktree | ||||||
|  |     lfs_chktree | ||||||
|  | done | ||||||
|  |  | ||||||
|  | echo "--- Big region corruption ---" | ||||||
|  | rm -rf blocks | ||||||
|  | mkdir blocks | ||||||
|  | for i in {2..255} | ||||||
|  | do | ||||||
|  |     ln -s /dev/zero blocks/$(printf '%x' $i) | ||||||
|  | done | ||||||
|  | lfs_mktree | ||||||
|  | lfs_chktree | ||||||
|  |  | ||||||
|  | echo "--- Alternating corruption ---" | ||||||
|  | rm -rf blocks | ||||||
|  | mkdir blocks | ||||||
|  | for i in {2..511..2} | ||||||
|  | do | ||||||
|  |     ln -s /dev/zero blocks/$(printf '%x' $i) | ||||||
|  | done | ||||||
|  | lfs_mktree | ||||||
|  | lfs_chktree | ||||||
|  |  | ||||||
|  | echo "--- Results ---" | ||||||
|  | tests/stats.py | ||||||
| @@ -124,6 +124,7 @@ tests/test.py << TEST | |||||||
| TEST | TEST | ||||||
|  |  | ||||||
| echo "--- Directory remove ---" | echo "--- Directory remove ---" | ||||||
|  | # TESTING HERE | ||||||
| tests/test.py << TEST | tests/test.py << TEST | ||||||
|     lfs_mount(&lfs, &cfg) => 0; |     lfs_mount(&lfs, &cfg) => 0; | ||||||
|     lfs_remove(&lfs, "potato") => LFS_ERR_INVAL; |     lfs_remove(&lfs, "potato") => LFS_ERR_INVAL; | ||||||
|   | |||||||
| @@ -10,8 +10,8 @@ tests/test.py << TEST | |||||||
| TEST | TEST | ||||||
|  |  | ||||||
| echo "--- Invalid superblocks ---" | echo "--- Invalid superblocks ---" | ||||||
| ln -f -s /dev/null blocks/0 | ln -f -s /dev/zero blocks/0 | ||||||
| ln -f -s /dev/null blocks/1 | ln -f -s /dev/zero blocks/1 | ||||||
| tests/test.py << TEST | tests/test.py << TEST | ||||||
|     lfs_format(&lfs, &cfg) => LFS_ERR_CORRUPT; |     lfs_format(&lfs, &cfg) => LFS_ERR_CORRUPT; | ||||||
| TEST | TEST | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user