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:
@@ -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
|
||||||
./$<
|
./$<
|
||||||
|
|
||||||
|
|||||||
+12
-1
@@ -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) {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
+9
-3
@@ -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) {{
|
||||||
|
|||||||
Executable
+106
@@ -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