mirror of
				https://github.com/eledio-devices/thirdparty-littlefs.git
				synced 2025-10-31 00:32:38 +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: | ||||
| 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 | ||||
| 	./$< | ||||
|  | ||||
|   | ||||
| @@ -144,13 +144,24 @@ int lfs_emubd_prog(const struct lfs_config *cfg, lfs_block_t block, | ||||
|         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); | ||||
|     if (err) { | ||||
|         return -errno; | ||||
|     } | ||||
|  | ||||
|     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) { | ||||
|   | ||||
							
								
								
									
										9
									
								
								lfs.h
									
									
									
									
									
								
							
							
						
						
									
										9
									
								
								lfs.h
									
									
									
									
									
								
							| @@ -43,7 +43,7 @@ enum lfs_error { | ||||
| enum lfs_type { | ||||
|     LFS_TYPE_REG        = 0x01, | ||||
|     LFS_TYPE_DIR        = 0x02, | ||||
|     LFS_TYPE_SUPERBLOCK = 0x10, | ||||
|     LFS_TYPE_SUPERBLOCK = 0x12, | ||||
| }; | ||||
|  | ||||
| enum lfs_open_flags { | ||||
| @@ -193,15 +193,16 @@ typedef struct lfs_superblock { | ||||
|     struct lfs_disk_superblock { | ||||
|         uint16_t type; | ||||
|         uint16_t len; | ||||
|         lfs_block_t root[2]; | ||||
|         uint32_t version; | ||||
|         char magic[8]; | ||||
|         uint32_t block_size; | ||||
|         uint32_t block_count; | ||||
|         lfs_block_t root[2]; | ||||
|     } d; | ||||
| } lfs_superblock_t; | ||||
|  | ||||
| typedef struct lfs_free { | ||||
|     lfs_block_t end; | ||||
|     lfs_block_t start; | ||||
|     lfs_block_t off; | ||||
|     uint32_t *lookahead; | ||||
| @@ -212,8 +213,8 @@ typedef struct lfs { | ||||
|     const struct lfs_config *cfg; | ||||
|  | ||||
|     lfs_block_t root[2]; | ||||
|     lfs_dir_t *scratch; | ||||
|     lfs_file_t *files; | ||||
|     bool deorphaned; | ||||
|  | ||||
|     lfs_cache_t rcache; | ||||
|     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); | ||||
|  | ||||
| // 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_deorphan(lfs_t *lfs); | ||||
|  | ||||
|  | ||||
| #endif | ||||
|   | ||||
| @@ -13,11 +13,17 @@ void test_log(const char *s, uintmax_t v) {{ | ||||
|  | ||||
| void test_assert(const char *file, unsigned line, | ||||
|         const char *s, uintmax_t v, uintmax_t e) {{ | ||||
|     static const char *last[2] = {{0, 0}}; | ||||
|     if (v != e || !(last[0] == s || last[1] == s)) {{ | ||||
|     static const char *last[6] = {{0, 0}}; | ||||
|     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); | ||||
|         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) {{ | ||||
|   | ||||
							
								
								
									
										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 | ||||
|  | ||||
| echo "--- Directory remove ---" | ||||
| # TESTING HERE | ||||
| tests/test.py << TEST | ||||
|     lfs_mount(&lfs, &cfg) => 0; | ||||
|     lfs_remove(&lfs, "potato") => LFS_ERR_INVAL; | ||||
|   | ||||
| @@ -10,8 +10,8 @@ tests/test.py << TEST | ||||
| TEST | ||||
|  | ||||
| echo "--- Invalid superblocks ---" | ||||
| ln -f -s /dev/null blocks/0 | ||||
| ln -f -s /dev/null blocks/1 | ||||
| ln -f -s /dev/zero blocks/0 | ||||
| ln -f -s /dev/zero blocks/1 | ||||
| tests/test.py << TEST | ||||
|     lfs_format(&lfs, &cfg) => LFS_ERR_CORRUPT; | ||||
| TEST | ||||
|   | ||||
		Reference in New Issue
	
	Block a user