Compare commits

..

6 Commits

Author SHA1 Message Date
Christopher Haster
20b46ded5a WIP making good progress, found a solution for the cycle issue
Found solution for cycle issue for tail+branch solution, though it does
require creating an extra metadata-pair on relocate.

Need to get passing the advanced relocation tests, this is currently
failing. Also need to figure out why I added the assert on tail end, it
may be related.
2020-03-10 08:43:28 -05:00
Christopher Haster
eecb06a9dc WIP crazy new idea work in progress
passing non-reentrant tests already!
2020-03-06 20:14:27 -06:00
Christopher Haster
3ee291de59 WIP so close 2020-02-28 06:03:59 -06:00
Christopher Haster
f7bc22937a WIP branches in dir 2020-02-28 04:51:25 -06:00
Christopher Haster
26ed6dee7d WIP initial commit of branch idea to repair non-DAG trees 2020-02-26 15:50:38 -06:00
Christopher Haster
e33bef55d3 Added "evil" tests and detecion/recovery from bad pointers and infinite loops
These two features have been much requested by users, and have even had
several PRs proposed to fix these in several cases. Before this, these
error conditions usually were caught by internal asserts, however
asserts prevented users from implementing their own workarounds.

It's taken me a while to provide/accept a useful recovery mechanism
(returning LFS_ERR_CORRUPT instead of asserting) because my original thinking
was that these error conditions only occur due to bugs in the filesystem, and
these bugs should be fixed properly.

While I still think this is mostly true, the point has been made clear
that being able to recover from these conditions is definitely worth the
code cost. Hopefully this new behaviour helps the longevity of devices
even if the storage code fails.

Another, less important, reason I didn't want to accept fixes for these
situations was the lack of tests that prove the code's value. This has
been fixed with the new testing framework thanks to the additional of
"internal tests" which can call C static functions and really take
advantage of the internal information of the filesystem.
2020-02-24 08:18:28 -06:00
20 changed files with 1319 additions and 537 deletions

2
.gitignore vendored
View File

@@ -8,5 +8,3 @@ blocks/
lfs
test.c
tests/*.toml.*
scripts/__pycache__
.gdb_history

View File

@@ -26,6 +26,8 @@ endif
override CFLAGS += -I.
override CFLAGS += -std=c99 -Wall -pedantic
override CFLAGS += -Wextra -Wshadow -Wjump-misses-init -Wundef
# Remove missing-field-initializers because of GCC bug
override CFLAGS += -Wno-missing-field-initializers
ifdef VERBOSE
override TFLAGS += -v

View File

@@ -115,9 +115,6 @@ the filesystem until sync or close is called on the file.
## Other notes
Littlefs is written in C, and specifically should compile with any compiler
that conforms to the `C99` standard.
All littlefs calls have the potential to return a negative error code. The
errors can be either one of those found in the `enum lfs_error` in
[lfs.h](lfs.h), or an error returned by the user's block device operations.

10
SPEC.md
View File

@@ -289,7 +289,7 @@ Layout of the name tag:
```
tag data
[-- 32 --][--- variable length ---]
[1| 3| 8 | 10 | 10 ][--- (size * 8) ---]
[1| 3| 8 | 10 | 10 ][--- (size) ---]
^ ^ ^ ^ ^- size ^- file name
| | | '------ id
| | '----------- file type
@@ -470,7 +470,7 @@ Layout of the inline-struct tag:
```
tag data
[-- 32 --][--- variable length ---]
[1|- 11 -| 10 | 10 ][--- (size * 8) ---]
[1|- 11 -| 10 | 10 ][--- (size) ---]
^ ^ ^ ^- size ^- inline data
| | '------ id
| '------------ type (0x201)
@@ -556,7 +556,7 @@ Layout of the user-attr tag:
```
tag data
[-- 32 --][--- variable length ---]
[1| 3| 8 | 10 | 10 ][--- (size * 8) ---]
[1| 3| 8 | 10 | 10 ][--- (size) ---]
^ ^ ^ ^ ^- size ^- attr data
| | | '------ id
| | '----------- attr type
@@ -764,9 +764,9 @@ Layout of the CRC tag:
```
tag data
[-- 32 --][-- 32 --|--- variable length ---]
[1| 3| 8 | 10 | 10 ][-- 32 --|--- (size * 8 - 32) ---]
[1| 3| 8 | 10 | 10 ][-- 32 --|--- (size) ---]
^ ^ ^ ^ ^ ^- crc ^- padding
| | | | '- size
| | | | '- size (12)
| | | '------ id (0x3ff)
| | '----------- valid state
| '-------------- type1 (0x5)

View File

@@ -12,7 +12,7 @@
int lfs_filebd_createcfg(const struct lfs_config *cfg, const char *path,
const struct lfs_filebd_config *bdcfg) {
LFS_FILEBD_TRACE("lfs_filebd_createcfg(%p {.context=%p, "
LFS_TRACE("lfs_filebd_createcfg(%p {.context=%p, "
".read=%p, .prog=%p, .erase=%p, .sync=%p, "
".read_size=%"PRIu32", .prog_size=%"PRIu32", "
".block_size=%"PRIu32", .block_count=%"PRIu32"}, "
@@ -30,16 +30,16 @@ int lfs_filebd_createcfg(const struct lfs_config *cfg, const char *path,
bd->fd = open(path, O_RDWR | O_CREAT, 0666);
if (bd->fd < 0) {
int err = -errno;
LFS_FILEBD_TRACE("lfs_filebd_createcfg -> %d", err);
LFS_TRACE("lfs_filebd_createcfg -> %d", err);
return err;
}
LFS_FILEBD_TRACE("lfs_filebd_createcfg -> %d", 0);
LFS_TRACE("lfs_filebd_createcfg -> %d", 0);
return 0;
}
int lfs_filebd_create(const struct lfs_config *cfg, const char *path) {
LFS_FILEBD_TRACE("lfs_filebd_create(%p {.context=%p, "
LFS_TRACE("lfs_filebd_create(%p {.context=%p, "
".read=%p, .prog=%p, .erase=%p, .sync=%p, "
".read_size=%"PRIu32", .prog_size=%"PRIu32", "
".block_size=%"PRIu32", .block_count=%"PRIu32"}, "
@@ -51,27 +51,26 @@ int lfs_filebd_create(const struct lfs_config *cfg, const char *path) {
path);
static const struct lfs_filebd_config defaults = {.erase_value=-1};
int err = lfs_filebd_createcfg(cfg, path, &defaults);
LFS_FILEBD_TRACE("lfs_filebd_create -> %d", err);
LFS_TRACE("lfs_filebd_create -> %d", err);
return err;
}
int lfs_filebd_destroy(const struct lfs_config *cfg) {
LFS_FILEBD_TRACE("lfs_filebd_destroy(%p)", (void*)cfg);
LFS_TRACE("lfs_filebd_destroy(%p)", (void*)cfg);
lfs_filebd_t *bd = cfg->context;
int err = close(bd->fd);
if (err < 0) {
err = -errno;
LFS_FILEBD_TRACE("lfs_filebd_destroy -> %d", err);
LFS_TRACE("lfs_filebd_destroy -> %d", err);
return err;
}
LFS_FILEBD_TRACE("lfs_filebd_destroy -> %d", 0);
LFS_TRACE("lfs_filebd_destroy -> %d", 0);
return 0;
}
int lfs_filebd_read(const struct lfs_config *cfg, lfs_block_t block,
lfs_off_t off, void *buffer, lfs_size_t size) {
LFS_FILEBD_TRACE("lfs_filebd_read(%p, "
"0x%"PRIx32", %"PRIu32", %p, %"PRIu32")",
LFS_TRACE("lfs_filebd_read(%p, 0x%"PRIx32", %"PRIu32", %p, %"PRIu32")",
(void*)cfg, block, off, buffer, size);
lfs_filebd_t *bd = cfg->context;
@@ -90,24 +89,24 @@ int lfs_filebd_read(const struct lfs_config *cfg, lfs_block_t block,
(off_t)block*cfg->block_size + (off_t)off, SEEK_SET);
if (res1 < 0) {
int err = -errno;
LFS_FILEBD_TRACE("lfs_filebd_read -> %d", err);
LFS_TRACE("lfs_filebd_read -> %d", err);
return err;
}
ssize_t res2 = read(bd->fd, buffer, size);
if (res2 < 0) {
int err = -errno;
LFS_FILEBD_TRACE("lfs_filebd_read -> %d", err);
LFS_TRACE("lfs_filebd_read -> %d", err);
return err;
}
LFS_FILEBD_TRACE("lfs_filebd_read -> %d", 0);
LFS_TRACE("lfs_filebd_read -> %d", 0);
return 0;
}
int lfs_filebd_prog(const struct lfs_config *cfg, lfs_block_t block,
lfs_off_t off, const void *buffer, lfs_size_t size) {
LFS_FILEBD_TRACE("lfs_filebd_prog(%p, 0x%"PRIx32", %"PRIu32", %p, %"PRIu32")",
LFS_TRACE("lfs_filebd_prog(%p, 0x%"PRIx32", %"PRIu32", %p, %"PRIu32")",
(void*)cfg, block, off, buffer, size);
lfs_filebd_t *bd = cfg->context;
@@ -122,7 +121,7 @@ int lfs_filebd_prog(const struct lfs_config *cfg, lfs_block_t block,
(off_t)block*cfg->block_size + (off_t)off, SEEK_SET);
if (res1 < 0) {
int err = -errno;
LFS_FILEBD_TRACE("lfs_filebd_prog -> %d", err);
LFS_TRACE("lfs_filebd_prog -> %d", err);
return err;
}
@@ -131,7 +130,7 @@ int lfs_filebd_prog(const struct lfs_config *cfg, lfs_block_t block,
ssize_t res2 = read(bd->fd, &c, 1);
if (res2 < 0) {
int err = -errno;
LFS_FILEBD_TRACE("lfs_filebd_prog -> %d", err);
LFS_TRACE("lfs_filebd_prog -> %d", err);
return err;
}
@@ -144,23 +143,23 @@ int lfs_filebd_prog(const struct lfs_config *cfg, lfs_block_t block,
(off_t)block*cfg->block_size + (off_t)off, SEEK_SET);
if (res1 < 0) {
int err = -errno;
LFS_FILEBD_TRACE("lfs_filebd_prog -> %d", err);
LFS_TRACE("lfs_filebd_prog -> %d", err);
return err;
}
ssize_t res2 = write(bd->fd, buffer, size);
if (res2 < 0) {
int err = -errno;
LFS_FILEBD_TRACE("lfs_filebd_prog -> %d", err);
LFS_TRACE("lfs_filebd_prog -> %d", err);
return err;
}
LFS_FILEBD_TRACE("lfs_filebd_prog -> %d", 0);
LFS_TRACE("lfs_filebd_prog -> %d", 0);
return 0;
}
int lfs_filebd_erase(const struct lfs_config *cfg, lfs_block_t block) {
LFS_FILEBD_TRACE("lfs_filebd_erase(%p, 0x%"PRIx32")", (void*)cfg, block);
LFS_TRACE("lfs_filebd_erase(%p, 0x%"PRIx32")", (void*)cfg, block);
lfs_filebd_t *bd = cfg->context;
// check if erase is valid
@@ -171,7 +170,7 @@ int lfs_filebd_erase(const struct lfs_config *cfg, lfs_block_t block) {
off_t res1 = lseek(bd->fd, (off_t)block*cfg->block_size, SEEK_SET);
if (res1 < 0) {
int err = -errno;
LFS_FILEBD_TRACE("lfs_filebd_erase -> %d", err);
LFS_TRACE("lfs_filebd_erase -> %d", err);
return err;
}
@@ -179,27 +178,27 @@ int lfs_filebd_erase(const struct lfs_config *cfg, lfs_block_t block) {
ssize_t res2 = write(bd->fd, &(uint8_t){bd->cfg->erase_value}, 1);
if (res2 < 0) {
int err = -errno;
LFS_FILEBD_TRACE("lfs_filebd_erase -> %d", err);
LFS_TRACE("lfs_filebd_erase -> %d", err);
return err;
}
}
}
LFS_FILEBD_TRACE("lfs_filebd_erase -> %d", 0);
LFS_TRACE("lfs_filebd_erase -> %d", 0);
return 0;
}
int lfs_filebd_sync(const struct lfs_config *cfg) {
LFS_FILEBD_TRACE("lfs_filebd_sync(%p)", (void*)cfg);
LFS_TRACE("lfs_filebd_sync(%p)", (void*)cfg);
// file sync
lfs_filebd_t *bd = cfg->context;
int err = fsync(bd->fd);
if (err) {
err = -errno;
LFS_FILEBD_TRACE("lfs_filebd_sync -> %d", 0);
LFS_TRACE("lfs_filebd_sync -> %d", 0);
return err;
}
LFS_FILEBD_TRACE("lfs_filebd_sync -> %d", 0);
LFS_TRACE("lfs_filebd_sync -> %d", 0);
return 0;
}

View File

@@ -15,14 +15,6 @@ extern "C"
{
#endif
// Block device specific tracing
#ifdef LFS_FILEBD_YES_TRACE
#define LFS_FILEBD_TRACE(...) LFS_TRACE(__VA_ARGS__)
#else
#define LFS_FILEBD_TRACE(...)
#endif
// filebd config (optional)
struct lfs_filebd_config {
// 8-bit erase value to use for simulating erases. -1 does not simulate

View File

@@ -8,7 +8,7 @@
int lfs_rambd_createcfg(const struct lfs_config *cfg,
const struct lfs_rambd_config *bdcfg) {
LFS_RAMBD_TRACE("lfs_rambd_createcfg(%p {.context=%p, "
LFS_TRACE("lfs_rambd_createcfg(%p {.context=%p, "
".read=%p, .prog=%p, .erase=%p, .sync=%p, "
".read_size=%"PRIu32", .prog_size=%"PRIu32", "
".block_size=%"PRIu32", .block_count=%"PRIu32"}, "
@@ -27,7 +27,7 @@ int lfs_rambd_createcfg(const struct lfs_config *cfg,
} else {
bd->buffer = lfs_malloc(cfg->block_size * cfg->block_count);
if (!bd->buffer) {
LFS_RAMBD_TRACE("lfs_rambd_createcfg -> %d", LFS_ERR_NOMEM);
LFS_TRACE("lfs_rambd_createcfg -> %d", LFS_ERR_NOMEM);
return LFS_ERR_NOMEM;
}
}
@@ -38,12 +38,12 @@ int lfs_rambd_createcfg(const struct lfs_config *cfg,
cfg->block_size * cfg->block_count);
}
LFS_RAMBD_TRACE("lfs_rambd_createcfg -> %d", 0);
LFS_TRACE("lfs_rambd_createcfg -> %d", 0);
return 0;
}
int lfs_rambd_create(const struct lfs_config *cfg) {
LFS_RAMBD_TRACE("lfs_rambd_create(%p {.context=%p, "
LFS_TRACE("lfs_rambd_create(%p {.context=%p, "
".read=%p, .prog=%p, .erase=%p, .sync=%p, "
".read_size=%"PRIu32", .prog_size=%"PRIu32", "
".block_size=%"PRIu32", .block_count=%"PRIu32"})",
@@ -53,25 +53,24 @@ int lfs_rambd_create(const struct lfs_config *cfg) {
cfg->read_size, cfg->prog_size, cfg->block_size, cfg->block_count);
static const struct lfs_rambd_config defaults = {.erase_value=-1};
int err = lfs_rambd_createcfg(cfg, &defaults);
LFS_RAMBD_TRACE("lfs_rambd_create -> %d", err);
LFS_TRACE("lfs_rambd_create -> %d", err);
return err;
}
int lfs_rambd_destroy(const struct lfs_config *cfg) {
LFS_RAMBD_TRACE("lfs_rambd_destroy(%p)", (void*)cfg);
LFS_TRACE("lfs_rambd_destroy(%p)", (void*)cfg);
// clean up memory
lfs_rambd_t *bd = cfg->context;
if (!bd->cfg->buffer) {
lfs_free(bd->buffer);
}
LFS_RAMBD_TRACE("lfs_rambd_destroy -> %d", 0);
LFS_TRACE("lfs_rambd_destroy -> %d", 0);
return 0;
}
int lfs_rambd_read(const struct lfs_config *cfg, lfs_block_t block,
lfs_off_t off, void *buffer, lfs_size_t size) {
LFS_RAMBD_TRACE("lfs_rambd_read(%p, "
"0x%"PRIx32", %"PRIu32", %p, %"PRIu32")",
LFS_TRACE("lfs_rambd_read(%p, 0x%"PRIx32", %"PRIu32", %p, %"PRIu32")",
(void*)cfg, block, off, buffer, size);
lfs_rambd_t *bd = cfg->context;
@@ -83,14 +82,13 @@ int lfs_rambd_read(const struct lfs_config *cfg, lfs_block_t block,
// read data
memcpy(buffer, &bd->buffer[block*cfg->block_size + off], size);
LFS_RAMBD_TRACE("lfs_rambd_read -> %d", 0);
LFS_TRACE("lfs_rambd_read -> %d", 0);
return 0;
}
int lfs_rambd_prog(const struct lfs_config *cfg, lfs_block_t block,
lfs_off_t off, const void *buffer, lfs_size_t size) {
LFS_RAMBD_TRACE("lfs_rambd_prog(%p, "
"0x%"PRIx32", %"PRIu32", %p, %"PRIu32")",
LFS_TRACE("lfs_rambd_prog(%p, 0x%"PRIx32", %"PRIu32", %p, %"PRIu32")",
(void*)cfg, block, off, buffer, size);
lfs_rambd_t *bd = cfg->context;
@@ -110,12 +108,12 @@ int lfs_rambd_prog(const struct lfs_config *cfg, lfs_block_t block,
// program data
memcpy(&bd->buffer[block*cfg->block_size + off], buffer, size);
LFS_RAMBD_TRACE("lfs_rambd_prog -> %d", 0);
LFS_TRACE("lfs_rambd_prog -> %d", 0);
return 0;
}
int lfs_rambd_erase(const struct lfs_config *cfg, lfs_block_t block) {
LFS_RAMBD_TRACE("lfs_rambd_erase(%p, 0x%"PRIx32")", (void*)cfg, block);
LFS_TRACE("lfs_rambd_erase(%p, 0x%"PRIx32")", (void*)cfg, block);
lfs_rambd_t *bd = cfg->context;
// check if erase is valid
@@ -127,14 +125,14 @@ int lfs_rambd_erase(const struct lfs_config *cfg, lfs_block_t block) {
bd->cfg->erase_value, cfg->block_size);
}
LFS_RAMBD_TRACE("lfs_rambd_erase -> %d", 0);
LFS_TRACE("lfs_rambd_erase -> %d", 0);
return 0;
}
int lfs_rambd_sync(const struct lfs_config *cfg) {
LFS_RAMBD_TRACE("lfs_rambd_sync(%p)", (void*)cfg);
LFS_TRACE("lfs_rambd_sync(%p)", (void*)cfg);
// sync does nothing because we aren't backed by anything real
(void)cfg;
LFS_RAMBD_TRACE("lfs_rambd_sync -> %d", 0);
LFS_TRACE("lfs_rambd_sync -> %d", 0);
return 0;
}

View File

@@ -15,14 +15,6 @@ extern "C"
{
#endif
// Block device specific tracing
#ifdef LFS_RAMBD_YES_TRACE
#define LFS_RAMBD_TRACE(...) LFS_TRACE(__VA_ARGS__)
#else
#define LFS_RAMBD_TRACE(...)
#endif
// rambd config (optional)
struct lfs_rambd_config {
// 8-bit erase value to simulate erasing with. -1 indicates no erase

View File

@@ -12,7 +12,7 @@
int lfs_testbd_createcfg(const struct lfs_config *cfg, const char *path,
const struct lfs_testbd_config *bdcfg) {
LFS_TESTBD_TRACE("lfs_testbd_createcfg(%p {.context=%p, "
LFS_TRACE("lfs_testbd_createcfg(%p {.context=%p, "
".read=%p, .prog=%p, .erase=%p, .sync=%p, "
".read_size=%"PRIu32", .prog_size=%"PRIu32", "
".block_size=%"PRIu32", .block_count=%"PRIu32"}, "
@@ -38,9 +38,9 @@ int lfs_testbd_createcfg(const struct lfs_config *cfg, const char *path,
if (bd->cfg->wear_buffer) {
bd->wear = bd->cfg->wear_buffer;
} else {
bd->wear = lfs_malloc(sizeof(lfs_testbd_wear_t)*cfg->block_count);
bd->wear = lfs_malloc(sizeof(lfs_testbd_wear_t) * cfg->block_count);
if (!bd->wear) {
LFS_TESTBD_TRACE("lfs_testbd_createcfg -> %d", LFS_ERR_NOMEM);
LFS_TRACE("lfs_testbd_createcfg -> %d", LFS_ERR_NOMEM);
return LFS_ERR_NOMEM;
}
}
@@ -54,7 +54,7 @@ int lfs_testbd_createcfg(const struct lfs_config *cfg, const char *path,
.erase_value = bd->cfg->erase_value,
};
int err = lfs_filebd_createcfg(cfg, path, &bd->u.file.cfg);
LFS_TESTBD_TRACE("lfs_testbd_createcfg -> %d", err);
LFS_TRACE("lfs_testbd_createcfg -> %d", err);
return err;
} else {
bd->u.ram.cfg = (struct lfs_rambd_config){
@@ -62,13 +62,13 @@ int lfs_testbd_createcfg(const struct lfs_config *cfg, const char *path,
.buffer = bd->cfg->buffer,
};
int err = lfs_rambd_createcfg(cfg, &bd->u.ram.cfg);
LFS_TESTBD_TRACE("lfs_testbd_createcfg -> %d", err);
LFS_TRACE("lfs_testbd_createcfg -> %d", err);
return err;
}
}
int lfs_testbd_create(const struct lfs_config *cfg, const char *path) {
LFS_TESTBD_TRACE("lfs_testbd_create(%p {.context=%p, "
LFS_TRACE("lfs_testbd_create(%p {.context=%p, "
".read=%p, .prog=%p, .erase=%p, .sync=%p, "
".read_size=%"PRIu32", .prog_size=%"PRIu32", "
".block_size=%"PRIu32", .block_count=%"PRIu32"}, "
@@ -80,12 +80,12 @@ int lfs_testbd_create(const struct lfs_config *cfg, const char *path) {
path);
static const struct lfs_testbd_config defaults = {.erase_value=-1};
int err = lfs_testbd_createcfg(cfg, path, &defaults);
LFS_TESTBD_TRACE("lfs_testbd_create -> %d", err);
LFS_TRACE("lfs_testbd_create -> %d", err);
return err;
}
int lfs_testbd_destroy(const struct lfs_config *cfg) {
LFS_TESTBD_TRACE("lfs_testbd_destroy(%p)", (void*)cfg);
LFS_TRACE("lfs_testbd_destroy(%p)", (void*)cfg);
lfs_testbd_t *bd = cfg->context;
if (bd->cfg->erase_cycles && !bd->cfg->wear_buffer) {
lfs_free(bd->wear);
@@ -93,11 +93,11 @@ int lfs_testbd_destroy(const struct lfs_config *cfg) {
if (bd->persist) {
int err = lfs_filebd_destroy(cfg);
LFS_TESTBD_TRACE("lfs_testbd_destroy -> %d", err);
LFS_TRACE("lfs_testbd_destroy -> %d", err);
return err;
} else {
int err = lfs_rambd_destroy(cfg);
LFS_TESTBD_TRACE("lfs_testbd_destroy -> %d", err);
LFS_TRACE("lfs_testbd_destroy -> %d", err);
return err;
}
}
@@ -145,8 +145,7 @@ static int lfs_testbd_rawsync(const struct lfs_config *cfg) {
/// block device API ///
int lfs_testbd_read(const struct lfs_config *cfg, lfs_block_t block,
lfs_off_t off, void *buffer, lfs_size_t size) {
LFS_TESTBD_TRACE("lfs_testbd_read(%p, "
"0x%"PRIx32", %"PRIu32", %p, %"PRIu32")",
LFS_TRACE("lfs_testbd_read(%p, 0x%"PRIx32", %"PRIu32", %p, %"PRIu32")",
(void*)cfg, block, off, buffer, size);
lfs_testbd_t *bd = cfg->context;
@@ -158,20 +157,19 @@ int lfs_testbd_read(const struct lfs_config *cfg, lfs_block_t block,
// block bad?
if (bd->cfg->erase_cycles && bd->wear[block] >= bd->cfg->erase_cycles &&
bd->cfg->badblock_behavior == LFS_TESTBD_BADBLOCK_READERROR) {
LFS_TESTBD_TRACE("lfs_testbd_read -> %d", LFS_ERR_CORRUPT);
LFS_TRACE("lfs_testbd_read -> %d", LFS_ERR_CORRUPT);
return LFS_ERR_CORRUPT;
}
// read
int err = lfs_testbd_rawread(cfg, block, off, buffer, size);
LFS_TESTBD_TRACE("lfs_testbd_read -> %d", err);
LFS_TRACE("lfs_testbd_read -> %d", err);
return err;
}
int lfs_testbd_prog(const struct lfs_config *cfg, lfs_block_t block,
lfs_off_t off, const void *buffer, lfs_size_t size) {
LFS_TESTBD_TRACE("lfs_testbd_prog(%p, "
"0x%"PRIx32", %"PRIu32", %p, %"PRIu32")",
LFS_TRACE("lfs_testbd_prog(%p, 0x%"PRIx32", %"PRIu32", %p, %"PRIu32")",
(void*)cfg, block, off, buffer, size);
lfs_testbd_t *bd = cfg->context;
@@ -184,13 +182,13 @@ int lfs_testbd_prog(const struct lfs_config *cfg, lfs_block_t block,
if (bd->cfg->erase_cycles && bd->wear[block] >= bd->cfg->erase_cycles) {
if (bd->cfg->badblock_behavior ==
LFS_TESTBD_BADBLOCK_PROGERROR) {
LFS_TESTBD_TRACE("lfs_testbd_prog -> %d", LFS_ERR_CORRUPT);
LFS_TRACE("lfs_testbd_prog -> %d", LFS_ERR_CORRUPT);
return LFS_ERR_CORRUPT;
} else if (bd->cfg->badblock_behavior ==
LFS_TESTBD_BADBLOCK_PROGNOOP ||
bd->cfg->badblock_behavior ==
LFS_TESTBD_BADBLOCK_ERASENOOP) {
LFS_TESTBD_TRACE("lfs_testbd_prog -> %d", 0);
LFS_TRACE("lfs_testbd_prog -> %d", 0);
return 0;
}
}
@@ -198,7 +196,7 @@ int lfs_testbd_prog(const struct lfs_config *cfg, lfs_block_t block,
// prog
int err = lfs_testbd_rawprog(cfg, block, off, buffer, size);
if (err) {
LFS_TESTBD_TRACE("lfs_testbd_prog -> %d", err);
LFS_TRACE("lfs_testbd_prog -> %d", err);
return err;
}
@@ -213,12 +211,12 @@ int lfs_testbd_prog(const struct lfs_config *cfg, lfs_block_t block,
}
}
LFS_TESTBD_TRACE("lfs_testbd_prog -> %d", 0);
LFS_TRACE("lfs_testbd_prog -> %d", 0);
return 0;
}
int lfs_testbd_erase(const struct lfs_config *cfg, lfs_block_t block) {
LFS_TESTBD_TRACE("lfs_testbd_erase(%p, 0x%"PRIx32")", (void*)cfg, block);
LFS_TRACE("lfs_testbd_erase(%p, 0x%"PRIx32")", (void*)cfg, block);
lfs_testbd_t *bd = cfg->context;
// check if erase is valid
@@ -229,11 +227,11 @@ int lfs_testbd_erase(const struct lfs_config *cfg, lfs_block_t block) {
if (bd->wear[block] >= bd->cfg->erase_cycles) {
if (bd->cfg->badblock_behavior ==
LFS_TESTBD_BADBLOCK_ERASEERROR) {
LFS_TESTBD_TRACE("lfs_testbd_erase -> %d", LFS_ERR_CORRUPT);
LFS_TRACE("lfs_testbd_erase -> %d", LFS_ERR_CORRUPT);
return LFS_ERR_CORRUPT;
} else if (bd->cfg->badblock_behavior ==
LFS_TESTBD_BADBLOCK_ERASENOOP) {
LFS_TESTBD_TRACE("lfs_testbd_erase -> %d", 0);
LFS_TRACE("lfs_testbd_erase -> %d", 0);
return 0;
}
} else {
@@ -245,7 +243,7 @@ int lfs_testbd_erase(const struct lfs_config *cfg, lfs_block_t block) {
// erase
int err = lfs_testbd_rawerase(cfg, block);
if (err) {
LFS_TESTBD_TRACE("lfs_testbd_erase -> %d", err);
LFS_TRACE("lfs_testbd_erase -> %d", err);
return err;
}
@@ -260,14 +258,14 @@ int lfs_testbd_erase(const struct lfs_config *cfg, lfs_block_t block) {
}
}
LFS_TESTBD_TRACE("lfs_testbd_prog -> %d", 0);
LFS_TRACE("lfs_testbd_prog -> %d", 0);
return 0;
}
int lfs_testbd_sync(const struct lfs_config *cfg) {
LFS_TESTBD_TRACE("lfs_testbd_sync(%p)", (void*)cfg);
LFS_TRACE("lfs_testbd_sync(%p)", (void*)cfg);
int err = lfs_testbd_rawsync(cfg);
LFS_TESTBD_TRACE("lfs_testbd_sync -> %d", err);
LFS_TRACE("lfs_testbd_sync -> %d", err);
return err;
}
@@ -275,20 +273,20 @@ int lfs_testbd_sync(const struct lfs_config *cfg) {
/// simulated wear operations ///
lfs_testbd_swear_t lfs_testbd_getwear(const struct lfs_config *cfg,
lfs_block_t block) {
LFS_TESTBD_TRACE("lfs_testbd_getwear(%p, %"PRIu32")", (void*)cfg, block);
LFS_TRACE("lfs_testbd_getwear(%p, %"PRIu32")", (void*)cfg, block);
lfs_testbd_t *bd = cfg->context;
// check if block is valid
LFS_ASSERT(bd->cfg->erase_cycles);
LFS_ASSERT(block < cfg->block_count);
LFS_TESTBD_TRACE("lfs_testbd_getwear -> %"PRIu32, bd->wear[block]);
LFS_TRACE("lfs_testbd_getwear -> %"PRIu32, bd->wear[block]);
return bd->wear[block];
}
int lfs_testbd_setwear(const struct lfs_config *cfg,
lfs_block_t block, lfs_testbd_wear_t wear) {
LFS_TESTBD_TRACE("lfs_testbd_setwear(%p, %"PRIu32")", (void*)cfg, block);
LFS_TRACE("lfs_testbd_setwear(%p, %"PRIu32")", (void*)cfg, block);
lfs_testbd_t *bd = cfg->context;
// check if block is valid
@@ -297,6 +295,6 @@ int lfs_testbd_setwear(const struct lfs_config *cfg,
bd->wear[block] = wear;
LFS_TESTBD_TRACE("lfs_testbd_setwear -> %d", 0);
LFS_TRACE("lfs_testbd_setwear -> %d", 0);
return 0;
}

View File

@@ -19,13 +19,6 @@ extern "C"
#endif
// Block device specific tracing
#ifdef LFS_TESTBD_YES_TRACE
#define LFS_TESTBD_TRACE(...) LFS_TRACE(__VA_ARGS__)
#else
#define LFS_TESTBD_TRACE(...)
#endif
// Mode determining how "bad blocks" behave during testing. This simulates
// some real-world circumstances such as progs not sticking (prog-noop),
// a readonly disk (erase-noop), and ECC failures (read-error).

1253
lfs.c

File diff suppressed because it is too large Load Diff

12
lfs.h
View File

@@ -21,7 +21,7 @@ extern "C"
// Software library version
// Major (top-nibble), incremented on backwards incompatible changes
// Minor (bottom-nibble), incremented on feature additions
#define LFS_VERSION 0x00020002
#define LFS_VERSION 0x00020001
#define LFS_VERSION_MAJOR (0xffff & (LFS_VERSION >> 16))
#define LFS_VERSION_MINOR (0xffff & (LFS_VERSION >> 0))
@@ -111,12 +111,14 @@ enum lfs_type {
LFS_TYPE_INLINESTRUCT = 0x201,
LFS_TYPE_SOFTTAIL = 0x600,
LFS_TYPE_HARDTAIL = 0x601,
LFS_TYPE_BRANCH = 0x681,
LFS_TYPE_MOVESTATE = 0x7ff,
// internal chip sources
LFS_FROM_NOOP = 0x000,
LFS_FROM_MOVE = 0x101,
LFS_FROM_USERATTRS = 0x102,
LFS_FROM_DROP = 0x102,
LFS_FROM_USERATTRS = 0x103,
};
// File open flags
@@ -310,8 +312,11 @@ typedef struct lfs_mdir {
uint32_t etag;
uint16_t count;
bool erased;
bool first; // TODO come on
bool split;
bool mustrelocate; // TODO not great either
lfs_block_t tail[2];
lfs_block_t branch[2];
} lfs_mdir_t;
// littlefs directory type
@@ -366,6 +371,9 @@ typedef struct lfs {
lfs_cache_t pcache;
lfs_block_t root[2];
lfs_block_t relocate_tail[2];
lfs_block_t relocate_end[2];
bool relocate_do_hack; // TODO fixme
struct lfs_mlist {
struct lfs_mlist *next;
uint16_t id;

View File

@@ -50,35 +50,31 @@ extern "C"
// Logging functions
#ifdef LFS_YES_TRACE
#define LFS_TRACE_(fmt, ...) \
printf("%s:%d:trace: " fmt "%s\n", __FILE__, __LINE__, __VA_ARGS__)
#define LFS_TRACE(...) LFS_TRACE_(__VA_ARGS__, "")
#define LFS_TRACE(fmt, ...) \
printf("%s:%d:trace: " fmt "\n", __FILE__, __LINE__, __VA_ARGS__)
#else
#define LFS_TRACE(...)
#define LFS_TRACE(fmt, ...)
#endif
#ifndef LFS_NO_DEBUG
#define LFS_DEBUG_(fmt, ...) \
printf("%s:%d:debug: " fmt "%s\n", __FILE__, __LINE__, __VA_ARGS__)
#define LFS_DEBUG(...) LFS_DEBUG_(__VA_ARGS__, "")
#define LFS_DEBUG(fmt, ...) \
printf("%s:%d:debug: " fmt "\n", __FILE__, __LINE__, __VA_ARGS__)
#else
#define LFS_DEBUG(...)
#define LFS_DEBUG(fmt, ...)
#endif
#ifndef LFS_NO_WARN
#define LFS_WARN_(fmt, ...) \
printf("%s:%d:warn: " fmt "%s\n", __FILE__, __LINE__, __VA_ARGS__)
#define LFS_WARN(...) LFS_WARN_(__VA_ARGS__, "")
#define LFS_WARN(fmt, ...) \
printf("%s:%d:warn: " fmt "\n", __FILE__, __LINE__, __VA_ARGS__)
#else
#define LFS_WARN(...)
#define LFS_WARN(fmt, ...)
#endif
#ifndef LFS_NO_ERROR
#define LFS_ERROR_(fmt, ...) \
printf("%s:%d:error: " fmt "%s\n", __FILE__, __LINE__, __VA_ARGS__)
#define LFS_ERROR(...) LFS_ERROR_(__VA_ARGS__, "")
#define LFS_ERROR(fmt, ...) \
printf("%s:%d:error: " fmt "\n", __FILE__, __LINE__, __VA_ARGS__)
#else
#define LFS_ERROR(...)
#define LFS_ERROR(fmt, ...)
#endif
// Runtime assertions
@@ -111,7 +107,7 @@ static inline uint32_t lfs_alignup(uint32_t a, uint32_t alignment) {
return lfs_aligndown(a + alignment-1, alignment);
}
// Find the smallest power of 2 greater than or equal to a
// Find the next smallest power of 2 less than or equal to a
static inline uint32_t lfs_npw2(uint32_t a) {
#if !defined(LFS_NO_INTRINSICS) && (defined(__GNUC__) || defined(__CC_ARM))
return 32 - __builtin_clz(a-1);

View File

@@ -18,9 +18,10 @@ TAG_TYPES = {
'ctzstruct': (0x7ff, 0x202),
'inlinestruct': (0x7ff, 0x201),
'userattr': (0x700, 0x300),
'tail': (0x700, 0x600),
'tail': (0x700, 0x600), # TODO rename these?
'softtail': (0x7ff, 0x600),
'hardtail': (0x7ff, 0x601),
'branch': (0x7ff, 0x681),
'gstate': (0x700, 0x700),
'movestate': (0x7ff, 0x7ff),
'crc': (0x700, 0x500),
@@ -103,7 +104,7 @@ class Tag:
def mkmask(self):
return Tag(
0x700 if self.isunique else 0x7ff,
0x780 if self.is_('tail') else 0x700 if self.isunique else 0x7ff, # TODO best way?
0x3ff if self.isattr else 0,
0)
@@ -318,24 +319,14 @@ def main(args):
# find most recent pair
mdir = MetadataPair(blocks)
try:
mdir.tail = mdir[Tag('tail', 0, 0)]
if mdir.tail.size != 8 or mdir.tail.data == 8*b'\xff':
mdir.tail = None
except KeyError:
mdir.tail = None
print("mdir {%s} rev %d%s%s%s" % (
print("mdir {%s} rev %d%s%s" % (
', '.join('%#x' % b
for b in [args.block1, args.block2]
if b is not None),
mdir.rev,
' (was %s)' % ', '.join('%d' % m.rev for m in mdir.pair[1:])
if len(mdir.pair) > 1 else '',
' (corrupted!)' if not mdir else '',
' -> {%#x, %#x}' % struct.unpack('<II', mdir.tail.data)
if mdir.tail else ''))
' (corrupted!)' if not mdir else ''))
if args.all:
mdir.dump_all(truncate=not args.no_truncate)
elif args.log:

View File

@@ -5,24 +5,107 @@ import sys
import json
import io
import itertools as it
import collections as c
from readmdir import Tag, MetadataPair
def popc(x):
return bin(x).count('1')
def ctz(x):
return len(bin(x)) - len(bin(x).rstrip('0'))
def dumpentries(args, mdir, mdirs, f):
for k, id_ in enumerate(mdir.ids):
name = mdir[Tag('name', id_, 0)]
struct_ = mdir[Tag('struct', id_, 0)]
desc = "id %d %s %s" % (
id_, name.typerepr(),
json.dumps(name.data.decode('utf8')))
if struct_.is_('dirstruct'):
pair = struct.unpack('<II', struct_.data[:8].ljust(8, b'\xff'))
desc += " dir {%#x, %#x}%s" % (
pair[0], pair[1],
'?' if frozenset(pair) not in mdirs else '')
if struct_.is_('ctzstruct'):
desc += " ctz {%#x} size %d" % struct.unpack(
'<II', struct_.data[:8].ljust(8, b'\xff'))
if struct_.is_('inlinestruct'):
desc += " inline size %d" % struct_.size
data = None
if struct_.is_('inlinestruct'):
data = struct_.data
elif struct_.is_('ctzstruct'):
block, size = struct.unpack(
'<II', struct_.data[:8].ljust(8, b'\xff'))
data = []
i = 0 if size == 0 else (size-1) // (args.block_size - 8)
if i != 0:
i = ((size-1) - 4*popc(i-1)+2) // (args.block_size - 8)
with open(args.disk, 'rb') as f2:
while i >= 0:
f2.seek(block * args.block_size)
dat = f2.read(args.block_size)
data.append(dat[4*(ctz(i)+1) if i != 0 else 0:])
block, = struct.unpack('<I', dat[:4].ljust(4, b'\xff'))
i -= 1
data = bytes(it.islice(
it.chain.from_iterable(reversed(data)), size))
f.write("%-45s%s\n" % (desc,
"%-23s %-8s" % (
' '.join('%02x' % c for c in data[:8]),
''.join(c if c >= ' ' and c <= '~' else '.'
for c in map(chr, data[:8])))
if not args.no_truncate and len(desc) < 45
and data is not None else ""))
if name.is_('superblock') and struct_.is_('inlinestruct'):
f.write(
" block_size %d\n"
" block_count %d\n"
" name_max %d\n"
" file_max %d\n"
" attr_max %d\n" % struct.unpack(
'<IIIII', struct_.data[4:4+20].ljust(20, b'\xff')))
for tag in mdir.tags:
if tag.id==id_ and tag.is_('userattr'):
desc = "%s size %d" % (tag.typerepr(), tag.size)
f.write(" %-43s%s\n" % (desc,
"%-23s %-8s" % (
' '.join('%02x' % c for c in tag.data[:8]),
''.join(c if c >= ' ' and c <= '~' else '.'
for c in map(chr, tag.data[:8])))
if not args.no_truncate and len(desc) < 43 else ""))
if args.no_truncate:
for i in range(0, len(tag.data), 16):
f.write(" %08x: %-47s %-16s\n" % (
i, ' '.join('%02x' % c for c in tag.data[i:i+16]),
''.join(c if c >= ' ' and c <= '~' else '.'
for c in map(chr, tag.data[i:i+16]))))
if args.no_truncate and data is not None:
for i in range(0, len(data), 16):
f.write(" %08x: %-47s %-16s\n" % (
i, ' '.join('%02x' % c for c in data[i:i+16]),
''.join(c if c >= ' ' and c <= '~' else '.'
for c in map(chr, data[i:i+16]))))
def main(args):
superblock = None
gstate = b'\0\0\0\0\0\0\0\0\0\0\0\0'
dirs = []
mdirs = []
mdirs = c.OrderedDict()
corrupted = []
cycle = False
with open(args.disk, 'rb') as f:
tail = (args.block1, args.block2)
hard = False
while True:
for m in it.chain((m for d in dirs for m in d), mdirs):
if set(m.blocks) == set(tail):
while tail:
if frozenset(tail) in mdirs:
# cycle detected
cycle = m.blocks
if cycle:
cycle = tail
break
# load mdir
@@ -45,6 +128,13 @@ def main(args):
except KeyError:
mdir.tail = None
try:
mdir.branch = mdir[Tag('branch', 0, 0)]
if mdir.branch.size != 8 or mdir.branch.data == 8*b'\xff':
mdir.branch = None
except KeyError:
mdir.branch = None
# have superblock?
try:
nsuperblock = mdir[
@@ -65,39 +155,53 @@ def main(args):
if not mdir:
corrupted.append(mdir)
# add to directories
mdirs.append(mdir)
if mdir.tail is None or not mdir.tail.is_('hardtail'):
dirs.append(mdirs)
mdirs = []
# add to metadata-pairs
mdirs[frozenset(mdir.blocks)] = mdir
tail = (struct.unpack('<II', mdir.tail.data)
if mdir.tail else None)
if mdir.tail is None:
break
tail = struct.unpack('<II', mdir.tail.data)
hard = mdir.tail.is_('hardtail')
# find paths
dirtable = {}
for dir in dirs:
dirtable[frozenset(dir[0].blocks)] = dir
pending = [("/", dirs[0])]
# derive paths and build directories
dirs = {}
rogue = {}
pending = [('/', (args.block1, args.block2))]
while pending:
path, dir = pending.pop(0)
for mdir in dir:
path, branch = pending.pop(0)
dir = []
while branch and frozenset(branch) in mdirs:
mdir = mdirs[frozenset(branch)]
dir.append(mdir)
for tag in mdir.tags:
if tag.is_('dir'):
try:
npath = tag.data.decode('utf8')
npath = path + '/' + tag.data.decode('utf8')
npath = npath.replace('//', '/')
dirstruct = mdir[Tag('dirstruct', tag.id, 0)]
nblocks = struct.unpack('<II', dirstruct.data)
nmdir = dirtable[frozenset(nblocks)]
pending.append(((path + '/' + npath), nmdir))
npair = struct.unpack('<II', dirstruct.data)
pending.append((npath, npair))
except KeyError:
pass
dir[0].path = path.replace('//', '/')
branch = (struct.unpack('<II', mdir.branch.data)
if mdir.branch else None)
if not dir:
rogue[path] = branch
else:
dirs[path] = dir
# also find orphans
not_orphans = {frozenset(mdir.blocks)
for dir in dirs.values()
for mdir in dir}
orphans = []
for pair, mdir in mdirs.items():
if pair not in not_orphans:
if len(orphans) > 0 and (pair == frozenset(
struct.unpack('<II', orphans[-1][-1].tail.data))):
orphans[-1].append(mdir)
else:
orphans.append([mdir])
# print littlefs + version info
version = ('?', '?')
@@ -106,22 +210,28 @@ def main(args):
struct.unpack('<HH', superblock[1].data[0:4].ljust(4, b'\xff'))))
print("%-47s%s" % ("littlefs v%s.%s" % version,
"data (truncated, if it fits)"
if not any([args.no_truncate, args.log, args.all]) else ""))
if not any([args.no_truncate, args.tags, args.log, args.all]) else ""))
# print gstate
badgstate = None
print("gstate 0x%s" % ''.join('%02x' % c for c in gstate))
tag = Tag(struct.unpack('<I', gstate[0:4].ljust(4, b'\xff'))[0])
blocks = struct.unpack('<II', gstate[4:4+8].ljust(8, b'\xff'))
if tag.size or not tag.isvalid:
print(" orphans >=%d" % max(tag.size, 1))
if tag.type:
print(" move dir {%#x, %#x} id %d" % (
blocks[0], blocks[1], tag.id))
if frozenset(blocks) not in mdirs:
badgstate = gstate
print(" move dir {%#x, %#x}%s id %d" % (
blocks[0], blocks[1],
'?' if frozenset(blocks) not in mdirs else '',
tag.id))
# print mdir info
for i, dir in enumerate(dirs):
print("dir %s" % (json.dumps(dir[0].path)
if hasattr(dir[0], 'path') else '(orphan)'))
# print dir info
for path, dir in it.chain(
sorted(dirs.items()),
zip(it.repeat(None), orphans)):
print("dir %s" % json.dumps(path) if path else "orphaned")
for j, mdir in enumerate(dir):
print("mdir {%#x, %#x} rev %d (was %d)%s%s" % (
@@ -131,19 +241,21 @@ def main(args):
if mdir.tail else ''))
f = io.StringIO()
if args.log:
if args.tags:
mdir.dump_tags(f, truncate=not args.no_truncate)
elif args.log:
mdir.dump_log(f, truncate=not args.no_truncate)
elif args.all:
mdir.dump_all(f, truncate=not args.no_truncate)
else:
mdir.dump_tags(f, truncate=not args.no_truncate)
dumpentries(args, mdir, mdirs, f)
lines = list(filter(None, f.getvalue().split('\n')))
for k, line in enumerate(lines):
print("%s %s" % (
' ' if j == len(dir)-1 else
'v' if k == len(lines)-1 else
'|',
'|' if path else '.',
line))
errcode = 0
@@ -152,8 +264,18 @@ def main(args):
print("*** corrupted mdir {%#x, %#x}! ***" % (
mdir.blocks[0], mdir.blocks[1]))
if cycle:
for path, pair in rogue.items():
errcode = errcode or 2
print("*** couldn't find dir %s {%#x, %#x}! ***" % (
json.dumps(path), pair[0], pair[1]))
if badgstate:
errcode = errcode or 3
print("*** bad gstate 0x%s! ***" %
''.join('%02x' % c for c in gstate))
if cycle:
errcode = errcode or 4
print("*** cycle detected {%#x, %#x}! ***" % (
cycle[0], cycle[1]))
@@ -170,10 +292,12 @@ if __name__ == "__main__":
help="Size of a block in bytes.")
parser.add_argument('block1', nargs='?', default=0,
type=lambda x: int(x, 0),
help="Optional first block address for finding the superblock.")
help="Optional first block address for finding the root.")
parser.add_argument('block2', nargs='?', default=1,
type=lambda x: int(x, 0),
help="Optional second block address for finding the superblock.")
help="Optional second block address for finding the root.")
parser.add_argument('-t', '--tags', action='store_true',
help="Show metadata tags instead of reconstructing entries.")
parser.add_argument('-l', '--log', action='store_true',
help="Show tags in log.")
parser.add_argument('-a', '--all', action='store_true',

View File

@@ -231,7 +231,7 @@ class TestCase:
ncmd.extend(['-ex', 'r'])
if failure.assert_:
ncmd.extend(['-ex', 'up 2'])
elif gdb == 'main':
elif gdb == 'start' or isinstance(gdb, int):
ncmd.extend([
'-ex', 'b %s:%d' % (self.suite.path, self.code_lineno),
'-ex', 'r'])
@@ -329,7 +329,9 @@ class ReentrantTestCase(TestCase):
persist = 'noerase'
# exact cycle we should drop into debugger?
if gdb and failure and failure.cycleno == cycles:
if gdb and failure and (
failure.cycleno == cycles or
(isinstance(gdb, int) and gdb == cycles)):
return super().test(gdb=gdb, persist=persist, cycles=cycles,
failure=failure, **args)
@@ -760,7 +762,8 @@ if __name__ == "__main__":
help="Store disk image in a file.")
parser.add_argument('-b', '--build', action='store_true',
help="Only build the tests, do not execute.")
parser.add_argument('-g', '--gdb', choices=['init', 'main', 'assert'],
parser.add_argument('-g', '--gdb', metavar='{init,start,assert},CYCLE',
type=lambda n: n if n in {'init', 'start', 'assert'} else int(n, 0),
nargs='?', const='assert',
help="Drop into gdb on test failure.")
parser.add_argument('--no-internal', action='store_true',

View File

@@ -323,90 +323,6 @@ code = '''
lfs_unmount(&lfs) => 0;
'''
[[case]] # what if we have a bad block during an allocation scan?
in = "lfs.c"
define.LFS_ERASE_CYCLES = 0xffffffff
define.LFS_BADBLOCK_BEHAVIOR = 'LFS_TESTBD_BADBLOCK_READERROR'
code = '''
lfs_format(&lfs, &cfg) => 0;
lfs_mount(&lfs, &cfg) => 0;
// first fill to exhaustion to find available space
lfs_file_open(&lfs, &file, "pacman", LFS_O_WRONLY | LFS_O_CREAT) => 0;
strcpy((char*)buffer, "waka");
size = strlen("waka");
lfs_size_t filesize = 0;
while (true) {
lfs_ssize_t res = lfs_file_write(&lfs, &file, buffer, size);
assert(res == (lfs_ssize_t)size || res == LFS_ERR_NOSPC);
if (res == LFS_ERR_NOSPC) {
break;
}
filesize += size;
}
lfs_file_close(&lfs, &file) => 0;
// now fill all but a couple of blocks of the filesystem with data
filesize -= 3*LFS_BLOCK_SIZE;
lfs_file_open(&lfs, &file, "pacman", LFS_O_WRONLY | LFS_O_CREAT) => 0;
strcpy((char*)buffer, "waka");
size = strlen("waka");
for (lfs_size_t i = 0; i < filesize/size; i++) {
lfs_file_write(&lfs, &file, buffer, size) => size;
}
lfs_file_close(&lfs, &file) => 0;
// also save head of file so we can error during lookahead scan
lfs_block_t fileblock = file.ctz.head;
lfs_unmount(&lfs) => 0;
// remount to force an alloc scan
lfs_mount(&lfs, &cfg) => 0;
// but mark the head of our file as a "bad block", this is force our
// scan to bail early
lfs_testbd_setwear(&cfg, fileblock, 0xffffffff) => 0;
lfs_file_open(&lfs, &file, "ghost", LFS_O_WRONLY | LFS_O_CREAT) => 0;
strcpy((char*)buffer, "chomp");
size = strlen("chomp");
while (true) {
lfs_ssize_t res = lfs_file_write(&lfs, &file, buffer, size);
assert(res == (lfs_ssize_t)size || res == LFS_ERR_CORRUPT);
if (res == LFS_ERR_CORRUPT) {
break;
}
}
lfs_file_close(&lfs, &file) => 0;
// now reverse the "bad block" and try to write the file again until we
// run out of space
lfs_testbd_setwear(&cfg, fileblock, 0) => 0;
lfs_file_open(&lfs, &file, "ghost", LFS_O_WRONLY | LFS_O_CREAT) => 0;
strcpy((char*)buffer, "chomp");
size = strlen("chomp");
while (true) {
lfs_ssize_t res = lfs_file_write(&lfs, &file, buffer, size);
assert(res == (lfs_ssize_t)size || res == LFS_ERR_NOSPC);
if (res == LFS_ERR_NOSPC) {
break;
}
}
lfs_file_close(&lfs, &file) => 0;
lfs_unmount(&lfs) => 0;
// check that the disk isn't hurt
lfs_mount(&lfs, &cfg) => 0;
lfs_file_open(&lfs, &file, "pacman", LFS_O_RDONLY) => 0;
strcpy((char*)buffer, "waka");
size = strlen("waka");
for (lfs_size_t i = 0; i < filesize/size; i++) {
uint8_t rbuffer[4];
lfs_file_read(&lfs, &file, rbuffer, size) => size;
assert(memcmp(rbuffer, buffer, size) == 0);
}
lfs_file_close(&lfs, &file) => 0;
lfs_unmount(&lfs) => 0;
'''
# Below, I don't like these tests. They're fragile and depend _heavily_
# on the geometry of the block device. But they are valuable. Eventually they
# should be removed and replaced with generalized tests.

View File

@@ -246,6 +246,8 @@ code = '''
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
lfs_file_close(&lfs, &file) => 0;
}
// TODO rm me
lfs_mkdir(&lfs, "a") => 0;
lfs_unmount(&lfs) => 0;
lfs_mount(&lfs, &cfg) => 0;
@@ -256,6 +258,9 @@ code = '''
lfs_dir_read(&lfs, &dir, &info) => 1;
assert(info.type == LFS_TYPE_DIR);
assert(strcmp(info.name, "..") == 0);
lfs_dir_read(&lfs, &dir, &info) => 1;
assert(info.type == LFS_TYPE_DIR);
assert(strcmp(info.name, "a") == 0);
for (int i = 0; i < N; i++) {
sprintf(path, "file%03d", i);
lfs_dir_read(&lfs, &dir, &info) => 1;

View File

@@ -151,7 +151,6 @@ code = '''
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;
@@ -246,7 +245,6 @@ code = '''
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(
@@ -276,7 +274,6 @@ code = '''
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(

View File

@@ -148,7 +148,7 @@ code = '''
# almost every tree operation needs a relocation
reentrant = true
# TODO fix this case, caused by non-DAG trees
if = '!(DEPTH == 3 && LFS_CACHE_SIZE != 64)'
#if = '!(DEPTH == 3 && LFS_CACHE_SIZE != 64)'
define = [
{FILES=6, DEPTH=1, CYCLES=20, LFS_BLOCK_CYCLES=1},
{FILES=26, DEPTH=1, CYCLES=20, LFS_BLOCK_CYCLES=1},
@@ -210,7 +210,7 @@ code = '''
[[case]] # reentrant testing for relocations, but now with random renames!
reentrant = true
# TODO fix this case, caused by non-DAG trees
if = '!(DEPTH == 3 && LFS_CACHE_SIZE != 64)'
#if = '!(DEPTH == 3 && LFS_CACHE_SIZE != 64)'
define = [
{FILES=6, DEPTH=1, CYCLES=20, LFS_BLOCK_CYCLES=1},
{FILES=26, DEPTH=1, CYCLES=20, LFS_BLOCK_CYCLES=1},