From 73015909a1d6e204b28695961fb8aaba0e2e84b3 Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Tue, 13 Aug 2024 16:31:27 -0500 Subject: [PATCH] Added high-level every-block error tests to test_ck These are basically the same as our test_badblock tests, except we accept LFS_ERR_CORRUPT. This lets us test more checking modes that may not enable recovery (ckreads, ckfetches, etc). Well, in theory, at least. The lack of rollback protection gets in the way of both ckreads and ckfetches, so we're currently only testing ckprogs, which isn't much of an improvement. At least this gets the scaffolding in place... This also inverts the test_ck -> test_badblocks dependency. Now that these both have exhaustive tests, we might as well limit test_badblocks to simple erroring erases/progs and let test_ck check the ck checks. --- bd/lfs_emubd.c | 33 +- bd/lfs_emubd.h | 5 +- tests/test_badblocks.toml | 101 +- tests/test_ck.toml | 1960 ++++++++++++++++++++++++++++++++++++- 4 files changed, 2002 insertions(+), 97 deletions(-) diff --git a/bd/lfs_emubd.c b/bd/lfs_emubd.c index c7d1b515..1fdb4235 100644 --- a/bd/lfs_emubd.c +++ b/bd/lfs_emubd.c @@ -92,7 +92,7 @@ static lfs_emubd_block_t *lfs_emubd_mutblock( // prng used for some emulation things -static uint32_t lfs_emubd_prng(uint32_t *state) { +static uint32_t lfs_emubd_prng_(uint32_t *state) { // A simple xorshift32 generator, easily reproducible. Keep in mind // determinism is much more important than actual randomness here. uint32_t x = *state; @@ -331,7 +331,7 @@ int lfs_emubd_read(const struct lfs_config *cfg, lfs_block_t block, lfs_size_t bit = b->bad_bit & 0x7fffffff; if (bit/8 >= off && bit/8 < off+size - && (lfs_emubd_prng(&bd->prng) & 1)) { + && (lfs_emubd_prng_(&bd->prng) & 1)) { ((uint8_t*)buffer)[(bit/8) - off] ^= 1 << (bit%8); } } @@ -401,7 +401,7 @@ int lfs_emubd_prog(const struct lfs_config *cfg, lfs_block_t block, bd->blocks[block] = b; // flip bit - lfs_size_t bit = lfs_emubd_prng(&bd->prng) + lfs_size_t bit = lfs_emubd_prng_(&bd->prng) % (cfg->prog_size*8); b->data[off + (bit/8)] ^= 1 << (bit%8); @@ -441,7 +441,7 @@ int lfs_emubd_prog(const struct lfs_config *cfg, lfs_block_t block, memcpy(&b->data[off], buffer, size); // flip bit - lfs_size_t bit = lfs_emubd_prng(&bd->prng) + lfs_size_t bit = lfs_emubd_prng_(&bd->prng) % (cfg->prog_size*8); b->data[off + (bit/8)] ^= 1 << (bit%8); @@ -519,7 +519,7 @@ int lfs_emubd_prog(const struct lfs_config *cfg, lfs_block_t block, // choose a new bad bit unless overridden if (!(0x80000000 & b->bad_bit)) { - b->bad_bit = lfs_emubd_prng(&bd->prng) + b->bad_bit = lfs_emubd_prng_(&bd->prng) % (cfg->block_size*8); } @@ -696,7 +696,7 @@ int lfs_emubd_erase(const struct lfs_config *cfg, lfs_block_t block) { bd->blocks[block] = b; // flip bit - lfs_size_t bit = lfs_emubd_prng(&bd->prng) + lfs_size_t bit = lfs_emubd_prng_(&bd->prng) % (cfg->block_size*8); b->data[(bit/8)] ^= 1 << (bit%8); @@ -739,7 +739,7 @@ int lfs_emubd_erase(const struct lfs_config *cfg, lfs_block_t block) { } // flip bit - lfs_size_t bit = lfs_emubd_prng(&bd->prng) + lfs_size_t bit = lfs_emubd_prng_(&bd->prng) % (cfg->block_size*8); b->data[(bit/8)] ^= 1 << (bit%8); @@ -817,7 +817,7 @@ int lfs_emubd_erase(const struct lfs_config *cfg, lfs_block_t block) { // choose a new bad bit unless overridden if (!(0x80000000 & b->bad_bit)) { - b->bad_bit = lfs_emubd_prng(&bd->prng) + b->bad_bit = lfs_emubd_prng_(&bd->prng) % (cfg->block_size*8); } @@ -917,7 +917,7 @@ int lfs_emubd_erase(const struct lfs_config *cfg, lfs_block_t block) { || bd->cfg->badblock_behavior == LFS_EMUBD_BADBLOCK_READFLIP) { if (!(0x80000000 & b->bad_bit)) { - b->bad_bit = lfs_emubd_prng(&bd->prng) + b->bad_bit = lfs_emubd_prng_(&bd->prng) % (cfg->block_size*8); } } @@ -988,15 +988,24 @@ int lfs_emubd_sync(const struct lfs_config *cfg) { /// Additional extended API for driving test features /// -int lfs_emubd_seed(const struct lfs_config *cfg, uint32_t seed) { +void lfs_emubd_seed(const struct lfs_config *cfg, uint32_t seed) { LFS_EMUBD_TRACE("lfs_emubd_seed(%p, 0x%08"PRIx32")", (void*)cfg, seed); lfs_emubd_t *bd = cfg->context; bd->prng = seed; - LFS_EMUBD_TRACE("lfs_emubd_seed -> %d", 0); - return 0; + LFS_EMUBD_TRACE("lfs_emubd_seed -> _"); +} + +uint32_t lfs_emubd_prng(const struct lfs_config *cfg) { + LFS_EMUBD_TRACE("lfs_emubd_prng(%p)", (void*)cfg); + lfs_emubd_t *bd = cfg->context; + + uint32_t x = lfs_emubd_prng_(&bd->prng); + + LFS_EMUBD_TRACE("lfs_emubd_prng -> 0x%08"PRIx32, x); + return x; } lfs_emubd_sio_t lfs_emubd_readed(const struct lfs_config *cfg) { diff --git a/bd/lfs_emubd.h b/bd/lfs_emubd.h index 326590d5..c6a319ed 100644 --- a/bd/lfs_emubd.h +++ b/bd/lfs_emubd.h @@ -191,7 +191,10 @@ int lfs_emubd_sync(const struct lfs_config *cfg); /// Additional extended API for driving test features /// // Set the current prng state -int lfs_emubd_seed(const struct lfs_config *cfg, uint32_t seed); +void lfs_emubd_seed(const struct lfs_config *cfg, uint32_t seed); + +// Get a pseudo-random number from emubd's internal prng +uint32_t lfs_emubd_prng(const struct lfs_config *cfg); // Get total amount of bytes read lfs_emubd_sio_t lfs_emubd_readed(const struct lfs_config *cfg); diff --git a/tests/test_badblocks.toml b/tests/test_badblocks.toml index 6678a982..bbcf97ae 100644 --- a/tests/test_badblocks.toml +++ b/tests/test_badblocks.toml @@ -7,18 +7,17 @@ after = [ 'test_traversal', 'test_gc', 'test_mount', - 'test_ck', 'test_compat', ] -## Single badblock tests +## Single-block badblock tests # # first test with every possible single badblock # B-tree's ridiculous branching factor is great for performance, but it makes # them a bit of a pain to test, here we test them explicitly -[cases.test_badblocks_single_btree_many] +[cases.test_badblocks_every_btree_many] defines.BADBLOCK = -1 defines.BADBLOCK_BEHAVIOR = [ 'LFS_EMUBD_BADBLOCK_PROGERROR', @@ -26,7 +25,6 @@ defines.BADBLOCK_BEHAVIOR = [ 'LFS_EMUBD_BADBLOCK_READERROR', 'LFS_EMUBD_BADBLOCK_PROGNOOP', 'LFS_EMUBD_BADBLOCK_ERASENOOP', - 'LFS_EMUBD_BADBLOCK_PROGFLIP', ] # we need prog checking to detect read errors defines.CKPROGS = 'BADBLOCK_BEHAVIOR >= LFS_EMUBD_BADBLOCK_READERROR' @@ -43,9 +41,9 @@ code = ''' i < ((BADBLOCK == -1) ? BLOCK_COUNT : 1); i++) { lfs_size_t badblock = (BADBLOCK == -1) ? i : BADBLOCK; + printf("--- badblock: 0x%x ---\n", badblock); // mark our badblock as bad lfs_emubd_markbad(CFG, badblock) => 0; - printf("--- badblock: 0x%x ---\n", badblock); // test creating a btree lfs_t lfs; @@ -129,8 +127,8 @@ code = ''' } ''' -# with dirs -[cases.test_badblocks_single_dir_many] +# badblocks with dirs +[cases.test_badblocks_every_dir_many] defines.BADBLOCK = -1 defines.BADBLOCK_BEHAVIOR = [ 'LFS_EMUBD_BADBLOCK_PROGERROR', @@ -138,7 +136,6 @@ defines.BADBLOCK_BEHAVIOR = [ 'LFS_EMUBD_BADBLOCK_READERROR', 'LFS_EMUBD_BADBLOCK_PROGNOOP', 'LFS_EMUBD_BADBLOCK_ERASENOOP', - 'LFS_EMUBD_BADBLOCK_PROGFLIP', ] # we need prog checking to detect read errors defines.CKPROGS = 'BADBLOCK_BEHAVIOR >= LFS_EMUBD_BADBLOCK_READERROR' @@ -150,9 +147,9 @@ code = ''' i < ((BADBLOCK == -1) ? BLOCK_COUNT : 1); i++) { lfs_size_t badblock = (BADBLOCK == -1) ? i : BADBLOCK; + printf("--- badblock: 0x%x ---\n", badblock); // mark our badblock as bad lfs_emubd_markbad(CFG, badblock) => 0; - printf("--- badblock: 0x%x ---\n", badblock); // test creating directories lfs_t lfs; @@ -245,8 +242,8 @@ code = ''' } ''' -# fuzz dirs -[cases.test_badblocks_single_dir_fuzz] +# badblocks with fuzz dirs +[cases.test_badblocks_every_dir_fuzz] defines.BADBLOCK = -1 defines.BADBLOCK_BEHAVIOR = [ 'LFS_EMUBD_BADBLOCK_PROGERROR', @@ -254,7 +251,6 @@ defines.BADBLOCK_BEHAVIOR = [ 'LFS_EMUBD_BADBLOCK_READERROR', 'LFS_EMUBD_BADBLOCK_PROGNOOP', 'LFS_EMUBD_BADBLOCK_ERASENOOP', - 'LFS_EMUBD_BADBLOCK_PROGFLIP', ] # we need prog checking to detect read errors defines.CKPROGS = 'BADBLOCK_BEHAVIOR >= LFS_EMUBD_BADBLOCK_READERROR' @@ -269,9 +265,9 @@ code = ''' i < ((BADBLOCK == -1) ? BLOCK_COUNT : 1); i++) { lfs_size_t badblock = (BADBLOCK == -1) ? i : BADBLOCK; + printf("--- badblock: 0x%x ---\n", badblock); // mark our badblock as bad lfs_emubd_markbad(CFG, badblock) => 0; - printf("--- badblock: 0x%x ---\n", badblock); // test fuzz with dirs lfs_t lfs; @@ -432,8 +428,8 @@ code = ''' } ''' -# with files -[cases.test_badblocks_single_file_many] +# badblocks with files +[cases.test_badblocks_every_file_many] defines.BADBLOCK = -1 defines.BADBLOCK_BEHAVIOR = [ 'LFS_EMUBD_BADBLOCK_PROGERROR', @@ -441,7 +437,6 @@ defines.BADBLOCK_BEHAVIOR = [ 'LFS_EMUBD_BADBLOCK_READERROR', 'LFS_EMUBD_BADBLOCK_PROGNOOP', 'LFS_EMUBD_BADBLOCK_ERASENOOP', - 'LFS_EMUBD_BADBLOCK_PROGFLIP', ] # we need prog checking to detect read errors defines.CKPROGS = 'BADBLOCK_BEHAVIOR >= LFS_EMUBD_BADBLOCK_READERROR' @@ -465,9 +460,9 @@ code = ''' i < ((BADBLOCK == -1) ? BLOCK_COUNT : 1); i++) { lfs_size_t badblock = (BADBLOCK == -1) ? i : BADBLOCK; + printf("--- badblock: 0x%x ---\n", badblock); // mark our badblock as bad lfs_emubd_markbad(CFG, badblock) => 0; - printf("--- badblock: 0x%x ---\n", badblock); // test creating files lfs_t lfs; @@ -544,8 +539,8 @@ code = ''' } ''' -# fuzz files -[cases.test_badblocks_single_file_fuzz] +# badblocks with fuzz files +[cases.test_badblocks_every_file_fuzz] defines.BADBLOCK = -1 defines.BADBLOCK_BEHAVIOR = [ 'LFS_EMUBD_BADBLOCK_PROGERROR', @@ -553,7 +548,6 @@ defines.BADBLOCK_BEHAVIOR = [ 'LFS_EMUBD_BADBLOCK_READERROR', 'LFS_EMUBD_BADBLOCK_PROGNOOP', 'LFS_EMUBD_BADBLOCK_ERASENOOP', - 'LFS_EMUBD_BADBLOCK_PROGFLIP', ] # we need prog checking to detect read errors defines.CKPROGS = 'BADBLOCK_BEHAVIOR >= LFS_EMUBD_BADBLOCK_READERROR' @@ -580,9 +574,9 @@ code = ''' i < ((BADBLOCK == -1) ? BLOCK_COUNT : 1); i++) { lfs_size_t badblock = (BADBLOCK == -1) ? i : BADBLOCK; + printf("--- badblock: 0x%x ---\n", badblock); // mark our badblock as bad lfs_emubd_markbad(CFG, badblock) => 0; - printf("--- badblock: 0x%x ---\n", badblock); // test fuzz with files lfs_t lfs; @@ -795,8 +789,8 @@ code = ''' } ''' -# with more complex file writes -[cases.test_badblocks_single_fwrite_fuzz] +# badblocks with more complex file writes +[cases.test_badblocks_every_fwrite_fuzz] defines.BADBLOCK = -1 defines.BADBLOCK_BEHAVIOR = [ 'LFS_EMUBD_BADBLOCK_PROGERROR', @@ -804,7 +798,6 @@ defines.BADBLOCK_BEHAVIOR = [ 'LFS_EMUBD_BADBLOCK_READERROR', 'LFS_EMUBD_BADBLOCK_PROGNOOP', 'LFS_EMUBD_BADBLOCK_ERASENOOP', - 'LFS_EMUBD_BADBLOCK_PROGFLIP', ] # we need prog checking to detect read errors defines.CKPROGS = 'BADBLOCK_BEHAVIOR >= LFS_EMUBD_BADBLOCK_READERROR' @@ -838,9 +831,9 @@ code = ''' i < ((BADBLOCK == -1) ? BLOCK_COUNT : 1); i++) { lfs_size_t badblock = (BADBLOCK == -1) ? i : BADBLOCK; + printf("--- badblock: 0x%x ---\n", badblock); // mark our badblock as bad lfs_emubd_markbad(CFG, badblock) => 0; - printf("--- badblock: 0x%x ---\n", badblock); // test with complex file writes lfs_t lfs; @@ -966,8 +959,8 @@ code = ''' } ''' -# with orphans, zombies, etc -[cases.test_badblocks_single_orphanzombie_fuzz] +# badblocks with orphans, zombies, etc +[cases.test_badblocks_every_orphanzombie_fuzz] defines.BADBLOCK = -1 defines.BADBLOCK_BEHAVIOR = [ 'LFS_EMUBD_BADBLOCK_PROGERROR', @@ -975,7 +968,6 @@ defines.BADBLOCK_BEHAVIOR = [ 'LFS_EMUBD_BADBLOCK_READERROR', 'LFS_EMUBD_BADBLOCK_PROGNOOP', 'LFS_EMUBD_BADBLOCK_ERASENOOP', - 'LFS_EMUBD_BADBLOCK_PROGFLIP', ] # we need prog checking to detect read errors defines.CKPROGS = 'BADBLOCK_BEHAVIOR >= LFS_EMUBD_BADBLOCK_READERROR' @@ -1002,9 +994,9 @@ code = ''' i < ((BADBLOCK == -1) ? BLOCK_COUNT : 1); i++) { lfs_size_t badblock = (BADBLOCK == -1) ? i : BADBLOCK; + printf("--- badblock: 0x%x ---\n", badblock); // mark our badblock as bad lfs_emubd_markbad(CFG, badblock) => 0; - printf("--- badblock: 0x%x ---\n", badblock); // test with orphans, zombies, etc lfs_t lfs; @@ -1338,8 +1330,8 @@ code = ''' } ''' -# with orphans, zombies, dirs, etc -[cases.test_badblocks_single_orphanzombiedir_fuzz] +# badblocks with orphans, zombies, dirs, etc +[cases.test_badblocks_every_orphanzombiedir_fuzz] defines.BADBLOCK = -1 defines.BADBLOCK_BEHAVIOR = [ 'LFS_EMUBD_BADBLOCK_PROGERROR', @@ -1347,7 +1339,6 @@ defines.BADBLOCK_BEHAVIOR = [ 'LFS_EMUBD_BADBLOCK_READERROR', 'LFS_EMUBD_BADBLOCK_PROGNOOP', 'LFS_EMUBD_BADBLOCK_ERASENOOP', - 'LFS_EMUBD_BADBLOCK_PROGFLIP', ] # we need prog checking to detect read errors defines.CKPROGS = 'BADBLOCK_BEHAVIOR >= LFS_EMUBD_BADBLOCK_READERROR' @@ -1374,9 +1365,9 @@ code = ''' i < ((BADBLOCK == -1) ? BLOCK_COUNT : 1); i++) { lfs_size_t badblock = (BADBLOCK == -1) ? i : BADBLOCK; + printf("--- badblock: 0x%x ---\n", badblock); // mark our badblock as bad lfs_emubd_markbad(CFG, badblock) => 0; - printf("--- badblock: 0x%x ---\n", badblock); // test with orphans, zombies, dirs, etc lfs_t lfs; @@ -1806,7 +1797,6 @@ defines.BADBLOCK_BEHAVIOR = [ 'LFS_EMUBD_BADBLOCK_READERROR', 'LFS_EMUBD_BADBLOCK_PROGNOOP', 'LFS_EMUBD_BADBLOCK_ERASENOOP', - 'LFS_EMUBD_BADBLOCK_PROGFLIP', ] # we need prog checking to detect read errors defines.CKPROGS = 'BADBLOCK_BEHAVIOR >= LFS_EMUBD_BADBLOCK_READERROR' @@ -1907,7 +1897,7 @@ code = ''' lfs_deinit(&lfs) => 0; ''' -# with dirs +# badblocks with dirs [cases.test_badblocks_region_dir_many] defines.BADBLOCK_BEHAVIOR = [ 'LFS_EMUBD_BADBLOCK_PROGERROR', @@ -1915,7 +1905,6 @@ defines.BADBLOCK_BEHAVIOR = [ 'LFS_EMUBD_BADBLOCK_READERROR', 'LFS_EMUBD_BADBLOCK_PROGNOOP', 'LFS_EMUBD_BADBLOCK_ERASENOOP', - 'LFS_EMUBD_BADBLOCK_PROGFLIP', ] # we need prog checking to detect read errors defines.CKPROGS = 'BADBLOCK_BEHAVIOR >= LFS_EMUBD_BADBLOCK_READERROR' @@ -2024,7 +2013,7 @@ code = ''' lfsr_unmount(&lfs) => 0; ''' -# fuzz dirs +# badblocks with fuzz dirs [cases.test_badblocks_region_dir_fuzz] defines.BADBLOCK_BEHAVIOR = [ 'LFS_EMUBD_BADBLOCK_PROGERROR', @@ -2032,7 +2021,6 @@ defines.BADBLOCK_BEHAVIOR = [ 'LFS_EMUBD_BADBLOCK_READERROR', 'LFS_EMUBD_BADBLOCK_PROGNOOP', 'LFS_EMUBD_BADBLOCK_ERASENOOP', - 'LFS_EMUBD_BADBLOCK_PROGFLIP', ] # we need prog checking to detect read errors defines.CKPROGS = 'BADBLOCK_BEHAVIOR >= LFS_EMUBD_BADBLOCK_READERROR' @@ -2212,7 +2200,7 @@ code = ''' lfsr_unmount(&lfs) => 0; ''' -# with files +# badblocks with files [cases.test_badblocks_region_file_many] defines.BADBLOCK_BEHAVIOR = [ 'LFS_EMUBD_BADBLOCK_PROGERROR', @@ -2220,7 +2208,6 @@ defines.BADBLOCK_BEHAVIOR = [ 'LFS_EMUBD_BADBLOCK_READERROR', 'LFS_EMUBD_BADBLOCK_PROGNOOP', 'LFS_EMUBD_BADBLOCK_ERASENOOP', - 'LFS_EMUBD_BADBLOCK_PROGFLIP', ] # we need prog checking to detect read errors defines.CKPROGS = 'BADBLOCK_BEHAVIOR >= LFS_EMUBD_BADBLOCK_READERROR' @@ -2325,7 +2312,7 @@ code = ''' lfsr_unmount(&lfs) => 0; ''' -# fuzz files +# badblocks with fuzz files [cases.test_badblocks_region_file_fuzz] defines.BADBLOCK_BEHAVIOR = [ 'LFS_EMUBD_BADBLOCK_PROGERROR', @@ -2333,7 +2320,6 @@ defines.BADBLOCK_BEHAVIOR = [ 'LFS_EMUBD_BADBLOCK_READERROR', 'LFS_EMUBD_BADBLOCK_PROGNOOP', 'LFS_EMUBD_BADBLOCK_ERASENOOP', - 'LFS_EMUBD_BADBLOCK_PROGFLIP', ] # we need prog checking to detect read errors defines.CKPROGS = 'BADBLOCK_BEHAVIOR >= LFS_EMUBD_BADBLOCK_READERROR' @@ -2577,7 +2563,7 @@ code = ''' lfsr_unmount(&lfs) => 0; ''' -# with more complex file writes +# badblocks with more complex file writes [cases.test_badblocks_region_fwrite_fuzz] defines.BADBLOCK_BEHAVIOR = [ 'LFS_EMUBD_BADBLOCK_PROGERROR', @@ -2585,7 +2571,6 @@ defines.BADBLOCK_BEHAVIOR = [ 'LFS_EMUBD_BADBLOCK_READERROR', 'LFS_EMUBD_BADBLOCK_PROGNOOP', 'LFS_EMUBD_BADBLOCK_ERASENOOP', - 'LFS_EMUBD_BADBLOCK_PROGFLIP', ] # we need prog checking to detect read errors defines.CKPROGS = 'BADBLOCK_BEHAVIOR >= LFS_EMUBD_BADBLOCK_READERROR' @@ -2749,7 +2734,7 @@ code = ''' lfsr_unmount(&lfs) => 0; ''' -# with orphans, zombies, etc +# badblocks with orphans, zombies, etc [cases.test_badblocks_region_orphanzombie_fuzz] defines.BADBLOCK_BEHAVIOR = [ 'LFS_EMUBD_BADBLOCK_PROGERROR', @@ -2757,7 +2742,6 @@ defines.BADBLOCK_BEHAVIOR = [ 'LFS_EMUBD_BADBLOCK_READERROR', 'LFS_EMUBD_BADBLOCK_PROGNOOP', 'LFS_EMUBD_BADBLOCK_ERASENOOP', - 'LFS_EMUBD_BADBLOCK_PROGFLIP', ] # we need prog checking to detect read errors defines.CKPROGS = 'BADBLOCK_BEHAVIOR >= LFS_EMUBD_BADBLOCK_READERROR' @@ -3122,7 +3106,7 @@ code = ''' lfsr_unmount(&lfs) => 0; ''' -# with orphans, zombies, dirs, etc +# badblocks with orphans, zombies, dirs, etc [cases.test_badblocks_region_orphanzombiedir_fuzz] defines.BADBLOCK_BEHAVIOR = [ 'LFS_EMUBD_BADBLOCK_PROGERROR', @@ -3130,7 +3114,6 @@ defines.BADBLOCK_BEHAVIOR = [ 'LFS_EMUBD_BADBLOCK_READERROR', 'LFS_EMUBD_BADBLOCK_PROGNOOP', 'LFS_EMUBD_BADBLOCK_ERASENOOP', - 'LFS_EMUBD_BADBLOCK_PROGFLIP', ] # we need prog checking to detect read errors defines.CKPROGS = 'BADBLOCK_BEHAVIOR >= LFS_EMUBD_BADBLOCK_READERROR' @@ -3588,7 +3571,6 @@ defines.BADBLOCK_BEHAVIOR = [ 'LFS_EMUBD_BADBLOCK_READERROR', 'LFS_EMUBD_BADBLOCK_PROGNOOP', 'LFS_EMUBD_BADBLOCK_ERASENOOP', - 'LFS_EMUBD_BADBLOCK_PROGFLIP', ] # we need prog checking to detect read errors defines.CKPROGS = 'BADBLOCK_BEHAVIOR >= LFS_EMUBD_BADBLOCK_READERROR' @@ -3689,7 +3671,7 @@ code = ''' lfs_deinit(&lfs) => 0; ''' -# with dirs +# badblocks with dirs [cases.test_badblocks_alternating_dir_many] defines.BADBLOCK_BEHAVIOR = [ 'LFS_EMUBD_BADBLOCK_PROGERROR', @@ -3697,7 +3679,6 @@ defines.BADBLOCK_BEHAVIOR = [ 'LFS_EMUBD_BADBLOCK_READERROR', 'LFS_EMUBD_BADBLOCK_PROGNOOP', 'LFS_EMUBD_BADBLOCK_ERASENOOP', - 'LFS_EMUBD_BADBLOCK_PROGFLIP', ] # we need prog checking to detect read errors defines.CKPROGS = 'BADBLOCK_BEHAVIOR >= LFS_EMUBD_BADBLOCK_READERROR' @@ -3806,7 +3787,7 @@ code = ''' lfsr_unmount(&lfs) => 0; ''' -# fuzz dirs +# badblocks with fuzz dirs [cases.test_badblocks_alternating_dir_fuzz] defines.BADBLOCK_BEHAVIOR = [ 'LFS_EMUBD_BADBLOCK_PROGERROR', @@ -3814,7 +3795,6 @@ defines.BADBLOCK_BEHAVIOR = [ 'LFS_EMUBD_BADBLOCK_READERROR', 'LFS_EMUBD_BADBLOCK_PROGNOOP', 'LFS_EMUBD_BADBLOCK_ERASENOOP', - 'LFS_EMUBD_BADBLOCK_PROGFLIP', ] # we need prog checking to detect read errors defines.CKPROGS = 'BADBLOCK_BEHAVIOR >= LFS_EMUBD_BADBLOCK_READERROR' @@ -3994,7 +3974,7 @@ code = ''' lfsr_unmount(&lfs) => 0; ''' -# with files +# badblocks with files [cases.test_badblocks_alternating_file_many] defines.BADBLOCK_BEHAVIOR = [ 'LFS_EMUBD_BADBLOCK_PROGERROR', @@ -4002,7 +3982,6 @@ defines.BADBLOCK_BEHAVIOR = [ 'LFS_EMUBD_BADBLOCK_READERROR', 'LFS_EMUBD_BADBLOCK_PROGNOOP', 'LFS_EMUBD_BADBLOCK_ERASENOOP', - 'LFS_EMUBD_BADBLOCK_PROGFLIP', ] # we need prog checking to detect read errors defines.CKPROGS = 'BADBLOCK_BEHAVIOR >= LFS_EMUBD_BADBLOCK_READERROR' @@ -4107,7 +4086,7 @@ code = ''' lfsr_unmount(&lfs) => 0; ''' -# fuzz files +# badblocks with fuzz files [cases.test_badblocks_alternating_file_fuzz] defines.BADBLOCK_BEHAVIOR = [ 'LFS_EMUBD_BADBLOCK_PROGERROR', @@ -4115,7 +4094,6 @@ defines.BADBLOCK_BEHAVIOR = [ 'LFS_EMUBD_BADBLOCK_READERROR', 'LFS_EMUBD_BADBLOCK_PROGNOOP', 'LFS_EMUBD_BADBLOCK_ERASENOOP', - 'LFS_EMUBD_BADBLOCK_PROGFLIP', ] # we need prog checking to detect read errors defines.CKPROGS = 'BADBLOCK_BEHAVIOR >= LFS_EMUBD_BADBLOCK_READERROR' @@ -4359,7 +4337,7 @@ code = ''' lfsr_unmount(&lfs) => 0; ''' -# with more complex file writes +# badblocks with more complex file writes [cases.test_badblocks_alternating_fwrite_fuzz] defines.BADBLOCK_BEHAVIOR = [ 'LFS_EMUBD_BADBLOCK_PROGERROR', @@ -4367,7 +4345,6 @@ defines.BADBLOCK_BEHAVIOR = [ 'LFS_EMUBD_BADBLOCK_READERROR', 'LFS_EMUBD_BADBLOCK_PROGNOOP', 'LFS_EMUBD_BADBLOCK_ERASENOOP', - 'LFS_EMUBD_BADBLOCK_PROGFLIP', ] # we need prog checking to detect read errors defines.CKPROGS = 'BADBLOCK_BEHAVIOR >= LFS_EMUBD_BADBLOCK_READERROR' @@ -4529,7 +4506,7 @@ code = ''' lfsr_unmount(&lfs) => 0; ''' -# with orphans, zombies, etc +# badblocks with orphans, zombies, etc [cases.test_badblocks_alternating_orphanzombie_fuzz] defines.BADBLOCK_BEHAVIOR = [ 'LFS_EMUBD_BADBLOCK_PROGERROR', @@ -4537,7 +4514,6 @@ defines.BADBLOCK_BEHAVIOR = [ 'LFS_EMUBD_BADBLOCK_READERROR', 'LFS_EMUBD_BADBLOCK_PROGNOOP', 'LFS_EMUBD_BADBLOCK_ERASENOOP', - 'LFS_EMUBD_BADBLOCK_PROGFLIP', ] # we need prog checking to detect read errors defines.CKPROGS = 'BADBLOCK_BEHAVIOR >= LFS_EMUBD_BADBLOCK_READERROR' @@ -4902,7 +4878,7 @@ code = ''' lfsr_unmount(&lfs) => 0; ''' -# with orphans, zombies, dirs, etc +# badblocks with orphans, zombies, dirs, etc [cases.test_badblocks_alternating_orphanzombiedir_fuzz] defines.BADBLOCK_BEHAVIOR = [ 'LFS_EMUBD_BADBLOCK_PROGERROR', @@ -4910,7 +4886,6 @@ defines.BADBLOCK_BEHAVIOR = [ 'LFS_EMUBD_BADBLOCK_READERROR', 'LFS_EMUBD_BADBLOCK_PROGNOOP', 'LFS_EMUBD_BADBLOCK_ERASENOOP', - 'LFS_EMUBD_BADBLOCK_PROGFLIP', ] # we need prog checking to detect read errors defines.CKPROGS = 'BADBLOCK_BEHAVIOR >= LFS_EMUBD_BADBLOCK_READERROR' diff --git a/tests/test_ck.toml b/tests/test_ck.toml index 303349da..fc2ffd7d 100644 --- a/tests/test_ck.toml +++ b/tests/test_ck.toml @@ -1,5 +1,10 @@ # Test checksum validation things -after = ['test_traversal', 'test_gc', 'test_mount'] +after = [ + 'test_traversal', + 'test_gc', + 'test_mount', + 'test_badblocks', +] @@ -467,8 +472,6 @@ done:; # Some simple ckprog tests -# -# We test these much more aggressively in test_badblocks # test every single-bit error in block 0/1 [cases.test_ck_ckprogs_mroot] @@ -484,11 +487,11 @@ code = ''' i < ((BADBIT == -1) ? 8*BLOCK_SIZE : 1); i++) { lfs_size_t badbit = (BADBIT == -1) ? i : BADBIT; + printf("--- badblock: 0x%x.%x, badbit: 0x%x (0x%x+%x) ---\n", + (lfs_size_t)BADBLOCK, badbit/8, badbit, badbit/8, badbit%8); // mark our badbit as bad lfs_emubd_markbadbit(CFG, BADBLOCK, badbit) => 0; - printf("--- badblock: 0x%x.%x, badbit: 0x%x (0x%x+%x) ---\n", - (lfs_size_t)BADBLOCK, badbit/8, badbit, badbit/8, badbit%8); // formatting the filesystem may already find the bit error lfs_t lfs; @@ -600,11 +603,11 @@ code = ''' i < ((BADBIT == -1) ? 8*BLOCK_SIZE : 1); i++) { lfs_size_t badbit = (BADBIT == -1) ? i : BADBIT; + printf("--- badblock: 0x%x.%x, badbit: 0x%x (0x%x+%x) ---\n", + badblock, badbit/8, badbit, badbit/8, badbit%8); // mark our badbit as bad lfs_emubd_markbadbit(CFG, badblock, badbit) => 0; - printf("--- badblock: 0x%x.%x, badbit: 0x%x (0x%x+%x) ---\n", - badblock, badbit/8, badbit, badbit/8, badbit%8); // format lfs_t lfs; @@ -710,11 +713,11 @@ code = ''' i < ((BADBIT == -1) ? 8*BLOCK_SIZE : 1); i++) { lfs_size_t badbit = (BADBIT == -1) ? i : BADBIT; + printf("--- badblock: 0x%x.%x, badbit: 0x%x (0x%x+%x) ---\n", + badblock, badbit/8, badbit, badbit/8, badbit%8); // mark our badbit as bad lfs_emubd_markbadbit(CFG, badblock, badbit) => 0; - printf("--- badblock: 0x%x.%x, badbit: 0x%x (0x%x+%x) ---\n", - badblock, badbit/8, badbit, badbit/8, badbit%8); // format lfs_t lfs; @@ -817,12 +820,12 @@ code = ''' lfs_size_t badbit = (BADBIT == -1) ? i : BADBIT; // reset the bd prng every run for reproducibility - lfs_emubd_seed(CFG, 42) => 0; + lfs_emubd_seed(CFG, 42); + printf("--- badblock: 0x%x.%x, badbit: 0x%x (0x%x+%x) ---\n", + (lfs_size_t)BADBLOCK, badbit/8, badbit, badbit/8, badbit%8); // mark our badbit as bad lfs_emubd_markbadbit(CFG, BADBLOCK, badbit) => 0; - printf("--- badblock: 0x%x.%x, badbit: 0x%x (0x%x+%x) ---\n", - (lfs_size_t)BADBLOCK, badbit/8, badbit, badbit/8, badbit%8); // With metastability, basically any filesystem operation can // return LFS_ERR_CORRUPT. This is ok, what we're really testing @@ -964,12 +967,12 @@ code = ''' lfs_size_t badbit = (BADBIT == -1) ? i : BADBIT; // reset the bd prng every run for reproducibility - lfs_emubd_seed(CFG, 42) => 0; + lfs_emubd_seed(CFG, 42); + printf("--- badblock: 0x%x.%x, badbit: 0x%x (0x%x+%x) ---\n", + badblock, badbit/8, badbit, badbit/8, badbit%8); // mark our badbit as bad lfs_emubd_markbadbit(CFG, badblock, badbit) => 0; - printf("--- badblock: 0x%x.%x, badbit: 0x%x (0x%x+%x) ---\n", - badblock, badbit/8, badbit, badbit/8, badbit%8); // With metastability, basically any filesystem operation can // return LFS_ERR_CORRUPT. This is ok, what we're really testing @@ -1100,12 +1103,12 @@ code = ''' lfs_size_t badbit = (BADBIT == -1) ? i : BADBIT; // reset the bd prng every run for reproducibility - lfs_emubd_seed(CFG, 42) => 0; + lfs_emubd_seed(CFG, 42); + printf("--- badblock: 0x%x.%x, badbit: 0x%x (0x%x+%x) ---\n", + badblock, badbit/8, badbit, badbit/8, badbit%8); // mark our badbit as bad lfs_emubd_markbadbit(CFG, badblock, badbit) => 0; - printf("--- badblock: 0x%x.%x, badbit: 0x%x (0x%x+%x) ---\n", - badblock, badbit/8, badbit, badbit/8, badbit%8); // With metastability, basically any filesystem operation can // return LFS_ERR_CORRUPT. This is ok, what we're really testing @@ -1193,7 +1196,6 @@ code = ''' i < ((BADBIT == -1) ? 8*BLOCK_SIZE : 1); i++) { lfs_size_t badbit = (BADBIT == -1) ? i : BADBIT; - printf("--- badblock: 0x%x.%x, badbit: 0x%x (0x%x+%x) ---\n", (lfs_size_t)BADBLOCK, badbit/8, badbit, badbit/8, badbit%8); @@ -1308,7 +1310,6 @@ code = ''' i < ((BADBIT == -1) ? 8*BLOCK_SIZE : 1); i++) { lfs_size_t badbit = (BADBIT == -1) ? i : BADBIT; - printf("--- badblock: 0x%x.%x, badbit: 0x%x (0x%x+%x) ---\n", badblock, badbit/8, badbit, badbit/8, badbit%8); @@ -1420,7 +1421,6 @@ code = ''' i < ((BADBIT == -1) ? 8*BLOCK_SIZE : 1); i++) { lfs_size_t badbit = (BADBIT == -1) ? i : BADBIT; - printf("--- badblock: 0x%x.%x, badbit: 0x%x (0x%x+%x) ---\n", badblock, badbit/8, badbit, badbit/8, badbit%8); @@ -1479,3 +1479,1921 @@ code = ''' lfs_emubd_markgood(CFG, badblock) => 0; } ''' + + + +## High-level tests with every possible single badblock +# +# these are basically the same as our test_badblock tests, except we +# accept LFS_ERR_CORRUPT here + +# badblocks with dirs +[cases.test_ck_every_dir_many] +defines.BADBLOCK = -1 +# TODO enable other methods once rollback protection is in place +# our different methods detect different types of errors, so we implement +# errors for each one a bit differently +# METHOD=0 => ckprogs +# METHOD=1 => ckreads +# METHOD=2 => ckfetches +defines.METHOD = [0] +defines.BADBLOCK_BEHAVIOR = 'LFS_EMUBD_BADBLOCK_PROGFLIP' +defines.CKPROGS = 'METHOD == 0' +defines.CKREADS = 'METHOD == 1' +defines.CKFETCHES = 'METHOD == 2' +defines.N = [1, 2, 4, 8, 16, 32, 64, 128, 256, 512] +if = [ + 'LFS_IFDEF_CKPROGS(true, !CKPROGS)', + 'LFS_IFDEF_CKREADS(true, !CKREADS)', + 'LFS_IFDEF_CKFETCHES(true, !CKFETCHES)', +] +code = ''' + // test all possible bad blocks + for (lfs_size_t i = 0; + i < ((BADBLOCK == -1) ? BLOCK_COUNT : 1); + i++) { + lfs_size_t badblock = (BADBLOCK == -1) ? i : BADBLOCK; + printf("--- badblock: 0x%x ---\n", badblock); + + // reset the bd prng every run for reproducibility + lfs_emubd_seed(CFG, 42); + + // ckprogs? ckreads? + if (METHOD == 0 || METHOD == 1) { + // mark our badblock as bad + lfs_emubd_markbad(CFG, badblock) => 0; + + // ckreads? + if (METHOD == 1) { + // limit to first 4-bytes to avoid escaping parity bits + lfs_emubd_setbadbit(CFG, badblock, + lfs_emubd_prng(CFG) % (4*8)) => 0; + } + } + + // test creating directories + lfs_t lfs; + lfsr_format(&lfs, + LFS_F_RDWR + | ((CKPROGS) ? LFS_IFDEF_CKPROGS(LFS_F_CKPROGS, -1) : 0) + | ((CKREADS) ? LFS_IFDEF_CKREADS(LFS_F_CKREADS, -1) : 0) + | ((CKFETCHES) + ? LFS_IFDEF_CKFETCHES(LFS_F_CKFETCHES, -1) + : 0), + CFG) => 0; + lfsr_mount(&lfs, + LFS_M_RDWR + | ((CKPROGS) ? LFS_IFDEF_CKPROGS(LFS_M_CKPROGS, -1) : 0) + | ((CKREADS) ? LFS_IFDEF_CKREADS(LFS_M_CKREADS, -1) : 0) + | ((CKFETCHES) + ? LFS_IFDEF_CKFETCHES(LFS_M_CKFETCHES, -1) + : 0), + CFG) => 0; + + // make this many directories + for (lfs_size_t i = 0; i < N; i++) { + char name[256]; + sprintf(name, "dir%03x", i); + int err = lfsr_mkdir(&lfs, name); + assert(!err || (TEST_PLS && err == LFS_ERR_EXIST)); + } + + for (int remount = 0; remount < 2; remount++) { + // remount? + if (remount) { + lfsr_unmount(&lfs) => 0; + + // ckfetches? + if (METHOD == 2) { + // flip a bit + lfs_emubd_flipbit(CFG, badblock, + lfs_emubd_prng(CFG) % (BLOCK_SIZE*8)) => 0; + } + + lfsr_mount(&lfs, + LFS_M_RDWR + | ((CKPROGS) + ? LFS_IFDEF_CKPROGS(LFS_M_CKPROGS, -1) + : 0) + | ((CKREADS) + ? LFS_IFDEF_CKREADS(LFS_M_CKREADS, -1) + : 0) + | ((CKFETCHES) + ? LFS_IFDEF_CKFETCHES(LFS_M_CKFETCHES, -1) + : 0), + CFG) => 0; + } + + // grm should be zero here + assert(lfs.grm_p[0] == 0); + + // check that our mkdir worked + for (lfs_size_t i = 0; i < N; i++) { + char name[256]; + sprintf(name, "dir%03x", i); + struct lfs_info info; + lfsr_stat(&lfs, name, &info) => 0; + assert(strcmp(info.name, name) == 0); + assert(info.type == LFS_TYPE_DIR); + assert(info.size == 0); + } + + lfsr_dir_t dir; + lfsr_dir_open(&lfs, &dir, "/") => 0; + struct lfs_info info; + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, ".") == 0); + assert(info.type == LFS_TYPE_DIR); + assert(info.size == 0); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "..") == 0); + assert(info.type == LFS_TYPE_DIR); + assert(info.size == 0); + for (lfs_size_t i = 0; i < N; i++) { + char name[256]; + sprintf(name, "dir%03x", i); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, name) == 0); + assert(info.type == LFS_TYPE_DIR); + assert(info.size == 0); + } + lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; + lfsr_dir_close(&lfs, &dir) => 0; + + for (lfs_size_t i = 0; i < N; i++) { + char name[256]; + sprintf(name, "dir%03x", i); + lfsr_dir_open(&lfs, &dir, name) => 0; + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, ".") == 0); + assert(info.type == LFS_TYPE_DIR); + assert(info.size == 0); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "..") == 0); + assert(info.type == LFS_TYPE_DIR); + assert(info.size == 0); + lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; + lfsr_dir_close(&lfs, &dir) => 0; + } + } + + lfsr_unmount(&lfs) => 0; + + // reset badblock + lfs_emubd_markgood(CFG, badblock) => 0; + } +''' + +# badblocks with fuzz dirs +[cases.test_ck_every_dir_fuzz] +defines.BADBLOCK = -1 +# TODO enable other methods once rollback protection is in place +# our different methods detect different types of errors, so we implement +# errors for each one a bit differently +# METHOD=0 => ckprogs +# METHOD=1 => ckreads +# METHOD=2 => ckfetches +defines.METHOD = [0] +defines.BADBLOCK_BEHAVIOR = 'LFS_EMUBD_BADBLOCK_PROGFLIP' +defines.CKPROGS = 'METHOD == 0' +defines.CKREADS = 'METHOD == 1' +defines.CKFETCHES = 'METHOD == 2' +defines.N = [1, 2, 4, 8, 16, 32, 64, 128, 256] +defines.OPS = '2*N' +defines.SEED = 42 +fuzz = 'SEED' +if = [ + 'LFS_IFDEF_CKPROGS(true, !CKPROGS)', + 'LFS_IFDEF_CKREADS(true, !CKREADS)', + 'LFS_IFDEF_CKFETCHES(true, !CKFETCHES)', +] +code = ''' + // test all possible bad blocks + for (lfs_size_t i = 0; + i < ((BADBLOCK == -1) ? BLOCK_COUNT : 1); + i++) { + lfs_size_t badblock = (BADBLOCK == -1) ? i : BADBLOCK; + printf("--- badblock: 0x%x ---\n", badblock); + + // reset the bd prng every run for reproducibility + lfs_emubd_seed(CFG, 42); + + // ckprogs? ckreads? + if (METHOD == 0 || METHOD == 1) { + // mark our badblock as bad + lfs_emubd_markbad(CFG, badblock) => 0; + + // ckreads? + if (METHOD == 1) { + // limit to first 4-bytes to avoid escaping parity bits + lfs_emubd_setbadbit(CFG, badblock, + lfs_emubd_prng(CFG) % (4*8)) => 0; + } + } + + // test fuzz with dirs + lfs_t lfs; + lfsr_format(&lfs, + LFS_F_RDWR + | ((CKPROGS) ? LFS_IFDEF_CKPROGS(LFS_F_CKPROGS, -1) : 0) + | ((CKREADS) ? LFS_IFDEF_CKREADS(LFS_F_CKREADS, -1) : 0) + | ((CKFETCHES) + ? LFS_IFDEF_CKFETCHES(LFS_F_CKFETCHES, -1) + : 0), + CFG) => 0; + lfsr_mount(&lfs, + LFS_M_RDWR + | ((CKPROGS) ? LFS_IFDEF_CKPROGS(LFS_M_CKPROGS, -1) : 0) + | ((CKREADS) ? LFS_IFDEF_CKREADS(LFS_M_CKREADS, -1) : 0) + | ((CKFETCHES) + ? LFS_IFDEF_CKFETCHES(LFS_M_CKFETCHES, -1) + : 0), + CFG) => 0; + + // set up a simulation to compare against + lfs_size_t *sim = malloc(N*sizeof(lfs_size_t)); + lfs_size_t sim_size = 0; + + uint32_t prng = SEED; + for (lfs_size_t i = 0; i < OPS; i++) { + // choose a pseudo-random op, either mkdir, remove, or rename + uint8_t op = TEST_PRNG(&prng) % 3; + + if (op == 0 || sim_size == 0) { + // choose a pseudo-random number, truncate to 3 hexadecimals + lfs_size_t x = TEST_PRNG(&prng) % N; + // insert into our sim + for (lfs_size_t j = 0;; j++) { + if (j >= sim_size || sim[j] >= x) { + // already seen? + if (j < sim_size && sim[j] == x) { + // do nothing + } else { + // insert + memmove(&sim[j+1], &sim[j], + (sim_size-j)*sizeof(lfs_size_t)); + sim_size += 1; + sim[j] = x; + } + break; + } + } + + // create a directory here + char name[256]; + sprintf(name, "dir%03x", x); + int err = lfsr_mkdir(&lfs, name); + assert(!err || err == LFS_ERR_EXIST); + + } else if (op == 1) { + // choose a pseudo-random entry to delete + lfs_size_t j = TEST_PRNG(&prng) % sim_size; + lfs_size_t x = sim[j]; + // delete from our sim + memmove(&sim[j], &sim[j+1], + (sim_size-(j+1))*sizeof(lfs_size_t)); + sim_size -= 1; + + // remove this directory + char name[256]; + sprintf(name, "dir%03x", x); + lfsr_remove(&lfs, name) => 0; + + } else { + // choose a pseudo-random entry to rename, and a pseudo-random + // number to rename to + lfs_size_t j = TEST_PRNG(&prng) % sim_size; + lfs_size_t x = sim[j]; + lfs_size_t y = TEST_PRNG(&prng) % N; + for (lfs_size_t k = 0;; k++) { + if (k >= sim_size || sim[k] >= y) { + // already seen and not a noop? + if (k < sim_size && sim[k] == y && x != y) { + // just delete the original entry + memmove(&sim[j], &sim[j+1], + (sim_size-(j+1))*sizeof(lfs_size_t)); + sim_size -= 1; + } else { + // first delete + memmove(&sim[j], &sim[j+1], + (sim_size-(j+1))*sizeof(lfs_size_t)); + if (k > j) { + k -= 1; + } + // then insert + memmove(&sim[k+1], &sim[k], + (sim_size-k)*sizeof(lfs_size_t)); + sim[k] = y; + } + break; + } + } + + // rename this directory + char old_name[256]; + sprintf(old_name, "dir%03x", x); + char new_name[256]; + sprintf(new_name, "dir%03x", y); + lfsr_rename(&lfs, old_name, new_name) => 0; + } + } + + for (int remount = 0; remount < 2; remount++) { + // remount? + if (remount) { + lfsr_unmount(&lfs) => 0; + + // ckfetches? + if (METHOD == 2) { + // flip a bit + lfs_emubd_flipbit(CFG, badblock, + lfs_emubd_prng(CFG) % (BLOCK_SIZE*8)) => 0; + } + + lfsr_mount(&lfs, + LFS_M_RDWR + | ((CKPROGS) + ? LFS_IFDEF_CKPROGS(LFS_M_CKPROGS, -1) + : 0) + | ((CKREADS) + ? LFS_IFDEF_CKREADS(LFS_M_CKREADS, -1) + : 0) + | ((CKFETCHES) + ? LFS_IFDEF_CKFETCHES(LFS_M_CKFETCHES, -1) + : 0), + CFG) => 0; + } + + // grm should be zero here + assert(lfs.grm_p[0] == 0); + + // test that our directories match our simulation + for (lfs_size_t j = 0; j < sim_size; j++) { + char name[256]; + sprintf(name, "dir%03x", sim[j]); + struct lfs_info info; + lfsr_stat(&lfs, name, &info) => 0; + char name2[256]; + sprintf(name2, "dir%03x", sim[j]); + assert(strcmp(info.name, name2) == 0); + assert(info.type == LFS_TYPE_DIR); + assert(info.size == 0); + } + + lfsr_dir_t dir; + lfsr_dir_open(&lfs, &dir, "/") => 0; + struct lfs_info info; + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, ".") == 0); + assert(info.type == LFS_TYPE_DIR); + assert(info.size == 0); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "..") == 0); + assert(info.type == LFS_TYPE_DIR); + assert(info.size == 0); + for (lfs_size_t j = 0; j < sim_size; j++) { + char name[256]; + sprintf(name, "dir%03x", sim[j]); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, name) == 0); + assert(info.type == LFS_TYPE_DIR); + assert(info.size == 0); + } + lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; + lfsr_dir_close(&lfs, &dir) => 0; + } + + // clean up sim/lfs + free(sim); + lfsr_unmount(&lfs) => 0; + + // reset badblock + lfs_emubd_markgood(CFG, badblock) => 0; + } +''' + +# badblocks with files +[cases.test_ck_every_file_many] +defines.BADBLOCK = -1 +# TODO enable other methods once rollback protection is in place +# our different methods detect different types of errors, so we implement +# errors for each one a bit differently +# METHOD=0 => ckprogs +# METHOD=1 => ckreads +# METHOD=2 => ckfetches +defines.METHOD = [0] +defines.BADBLOCK_BEHAVIOR = 'LFS_EMUBD_BADBLOCK_PROGFLIP' +defines.CKPROGS = 'METHOD == 0' +defines.CKREADS = 'METHOD == 1' +defines.CKFETCHES = 'METHOD == 2' +defines.N = [1, 2, 4, 8, 16, 32, 64] +defines.SIZE = [ + '0', + 'FILE_BUFFER_SIZE/2', + '2*FILE_BUFFER_SIZE', + 'BLOCK_SIZE/2', + 'BLOCK_SIZE', + '2*BLOCK_SIZE', + '4*BLOCK_SIZE', +] +if = [ + 'LFS_IFDEF_CKPROGS(true, !CKPROGS)', + 'LFS_IFDEF_CKREADS(true, !CKREADS)', + 'LFS_IFDEF_CKFETCHES(true, !CKFETCHES)', + '(SIZE*N)/BLOCK_SIZE <= 32', +] +code = ''' + // test all possible bad blocks + for (lfs_size_t i = 0; + i < ((BADBLOCK == -1) ? BLOCK_COUNT : 1); + i++) { + lfs_size_t badblock = (BADBLOCK == -1) ? i : BADBLOCK; + printf("--- badblock: 0x%x ---\n", badblock); + + // reset the bd prng every run for reproducibility + lfs_emubd_seed(CFG, 42); + + // ckprogs? ckreads? + if (METHOD == 0 || METHOD == 1) { + // mark our badblock as bad + lfs_emubd_markbad(CFG, badblock) => 0; + + // ckreads? + if (METHOD == 1) { + // limit to first 4-bytes to avoid escaping parity bits + lfs_emubd_setbadbit(CFG, badblock, + lfs_emubd_prng(CFG) % (4*8)) => 0; + } + } + + // test creating files + lfs_t lfs; + lfsr_format(&lfs, + LFS_F_RDWR + | ((CKPROGS) ? LFS_IFDEF_CKPROGS(LFS_F_CKPROGS, -1) : 0) + | ((CKREADS) ? LFS_IFDEF_CKREADS(LFS_F_CKREADS, -1) : 0) + | ((CKFETCHES) + ? LFS_IFDEF_CKFETCHES(LFS_F_CKFETCHES, -1) + : 0), + CFG) => 0; + lfsr_mount(&lfs, + LFS_M_RDWR + | ((CKPROGS) ? LFS_IFDEF_CKPROGS(LFS_M_CKPROGS, -1) : 0) + | ((CKREADS) ? LFS_IFDEF_CKREADS(LFS_M_CKREADS, -1) : 0) + | ((CKFETCHES) + ? LFS_IFDEF_CKFETCHES(LFS_M_CKFETCHES, -1) + : 0), + CFG) => 0; + + // create this many files + uint32_t prng = 42; + for (lfs_size_t i = 0; i < N; i++) { + char name[256]; + sprintf(name, "amethyst%03x", i); + + uint8_t wbuf[SIZE]; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + + lfsr_file_t file; + lfsr_file_open(&lfs, &file, name, + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE; + lfsr_file_close(&lfs, &file) => 0; + } + + for (int remount = 0; remount < 2; remount++) { + // remount? + if (remount) { + lfsr_unmount(&lfs) => 0; + + // ckfetches? + if (METHOD == 2) { + // flip a bit + lfs_emubd_flipbit(CFG, badblock, + lfs_emubd_prng(CFG) % (BLOCK_SIZE*8)) => 0; + } + + lfsr_mount(&lfs, + LFS_M_RDWR + | ((CKPROGS) + ? LFS_IFDEF_CKPROGS(LFS_M_CKPROGS, -1) + : 0) + | ((CKREADS) + ? LFS_IFDEF_CKREADS(LFS_M_CKREADS, -1) + : 0) + | ((CKFETCHES) + ? LFS_IFDEF_CKFETCHES(LFS_M_CKFETCHES, -1) + : 0), + CFG) => 0; + } + + // check that our writes worked + prng = 42; + for (lfs_size_t i = 0; i < N; i++) { + // check with stat + char name[256]; + sprintf(name, "amethyst%03x", i); + struct lfs_info info; + lfsr_stat(&lfs, name, &info) => 0; + assert(strcmp(info.name, name) == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == SIZE); + + // try reading the file, note we reset prng above + uint8_t wbuf[SIZE]; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + + lfsr_file_t file; + uint8_t rbuf[SIZE]; + lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => 0; + lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; + assert(memcmp(rbuf, wbuf, SIZE) == 0); + lfsr_file_close(&lfs, &file) => 0; + } + } + + lfsr_unmount(&lfs) => 0; + + // reset badblock + lfs_emubd_markgood(CFG, badblock) => 0; + } +''' + +# badblocks with fuzz files +[cases.test_ck_every_file_fuzz] +defines.BADBLOCK = -1 +# TODO enable other methods once rollback protection is in place +# our different methods detect different types of errors, so we implement +# errors for each one a bit differently +# METHOD=0 => ckprogs +# METHOD=1 => ckreads +# METHOD=2 => ckfetches +defines.METHOD = [0] +defines.BADBLOCK_BEHAVIOR = 'LFS_EMUBD_BADBLOCK_PROGFLIP' +defines.CKPROGS = 'METHOD == 0' +defines.CKREADS = 'METHOD == 1' +defines.CKFETCHES = 'METHOD == 2' +defines.N = [1, 2, 4, 8, 16, 32, 64] +defines.OPS = '2*N' +defines.SIZE = [ + '0', + 'FILE_BUFFER_SIZE/2', + '2*FILE_BUFFER_SIZE', + 'BLOCK_SIZE/2', + 'BLOCK_SIZE', + '2*BLOCK_SIZE', + '4*BLOCK_SIZE', +] +defines.SEED = 42 +fuzz = 'SEED' +if = [ + 'LFS_IFDEF_CKPROGS(true, !CKPROGS)', + 'LFS_IFDEF_CKREADS(true, !CKREADS)', + 'LFS_IFDEF_CKFETCHES(true, !CKFETCHES)', + '(SIZE*N)/BLOCK_SIZE <= 16', +] +code = ''' + // test all possible bad blocks + for (lfs_size_t i = 0; + i < ((BADBLOCK == -1) ? BLOCK_COUNT : 1); + i++) { + lfs_size_t badblock = (BADBLOCK == -1) ? i : BADBLOCK; + printf("--- badblock: 0x%x ---\n", badblock); + + // reset the bd prng every run for reproducibility + lfs_emubd_seed(CFG, 42); + + // ckprogs? ckreads? + if (METHOD == 0 || METHOD == 1) { + // mark our badblock as bad + lfs_emubd_markbad(CFG, badblock) => 0; + + // ckreads? + if (METHOD == 1) { + // limit to first 4-bytes to avoid escaping parity bits + lfs_emubd_setbadbit(CFG, badblock, + lfs_emubd_prng(CFG) % (4*8)) => 0; + } + } + + // test fuzz with files + lfs_t lfs; + lfsr_format(&lfs, + LFS_F_RDWR + | ((CKPROGS) ? LFS_IFDEF_CKPROGS(LFS_F_CKPROGS, -1) : 0) + | ((CKREADS) ? LFS_IFDEF_CKREADS(LFS_F_CKREADS, -1) : 0) + | ((CKFETCHES) + ? LFS_IFDEF_CKFETCHES(LFS_F_CKFETCHES, -1) + : 0), + CFG) => 0; + lfsr_mount(&lfs, + LFS_M_RDWR + | ((CKPROGS) ? LFS_IFDEF_CKPROGS(LFS_M_CKPROGS, -1) : 0) + | ((CKREADS) ? LFS_IFDEF_CKREADS(LFS_M_CKREADS, -1) : 0) + | ((CKFETCHES) + ? LFS_IFDEF_CKFETCHES(LFS_M_CKFETCHES, -1) + : 0), + CFG) => 0; + + // set up a simulation to compare against + lfs_size_t *sim = malloc(N*sizeof(lfs_size_t)); + uint32_t *sim_prngs = malloc(N*sizeof(uint32_t)); + lfs_size_t sim_size = 0; + + uint32_t prng = SEED; + for (lfs_size_t i = 0; i < OPS; i++) { + // choose which operation to do + uint8_t op = TEST_PRNG(&prng) % 3; + + // creating a new file? + if (op == 0 || sim_size == 0) { + // choose a pseudo-random number + lfs_size_t x = TEST_PRNG(&prng) % N; + // associate each file with a prng that generates its contents + uint32_t wprng = TEST_PRNG(&prng); + + // insert into our sim + for (lfs_size_t j = 0;; j++) { + if (j >= sim_size || sim[j] >= x) { + // already seen? + if (j < sim_size && sim[j] == x) { + // new prng + sim_prngs[j] = wprng; + } else { + // insert + memmove(&sim[j+1], &sim[j], + (sim_size-j)*sizeof(lfs_size_t)); + memmove(&sim_prngs[j+1], &sim_prngs[j], + (sim_size-j)*sizeof(uint32_t)); + sim_size += 1; + sim[j] = x; + sim_prngs[j] = wprng; + } + break; + } + } + + // create a file here + char name[256]; + sprintf(name, "amethyst%03x", x); + uint8_t wbuf[SIZE]; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26); + } + + lfsr_file_t file; + lfsr_file_open(&lfs, &file, name, + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0; + lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE; + lfsr_file_close(&lfs, &file) => 0; + + // deleting a file? + } else if (op == 1) { + // choose a random file to delete + lfs_size_t j = TEST_PRNG(&prng) % sim_size; + lfs_size_t x = sim[j]; + // delete from our sim + memmove(&sim[j], &sim[j+1], + (sim_size-(j+1))*sizeof(lfs_size_t)); + memmove(&sim_prngs[j], &sim_prngs[j+1], + (sim_size-(j+1))*sizeof(uint32_t)); + sim_size -= 1; + + // delete this file + char name[256]; + sprintf(name, "amethyst%03x", x); + lfsr_remove(&lfs, name) => 0; + + // renaming a file? + } else { + // choose a random file to rename, and a random number to + // rename to + lfs_size_t j = TEST_PRNG(&prng) % sim_size; + lfs_size_t x = sim[j]; + lfs_size_t y = TEST_PRNG(&prng) % N; + uint32_t wprng = sim_prngs[j]; + + // update our sim + for (lfs_size_t k = 0;; k++) { + if (k >= sim_size || sim[k] >= y) { + // renaming and replacing + if (k < sim_size && sim[k] == y && x != y) { + // delete the original entry + memmove(&sim[j], &sim[j+1], + (sim_size-(j+1))*sizeof(lfs_size_t)); + memmove(&sim_prngs[j], &sim_prngs[j+1], + (sim_size-(j+1))*sizeof(uint32_t)); + sim_size -= 1; + if (k > j) { + k -= 1; + } + // update the prng + sim_prngs[k] = wprng; + // just renaming + } else { + // first delete + memmove(&sim[j], &sim[j+1], + (sim_size-(j+1))*sizeof(lfs_size_t)); + memmove(&sim_prngs[j], &sim_prngs[j+1], + (sim_size-(j+1))*sizeof(uint32_t)); + if (k > j) { + k -= 1; + } + // then insert + memmove(&sim[k+1], &sim[k], + (sim_size-k)*sizeof(lfs_size_t)); + memmove(&sim_prngs[k+1], &sim_prngs[k], + (sim_size-k)*sizeof(uint32_t)); + sim[k] = y; + sim_prngs[k] = wprng; + } + break; + } + } + + // rename this file + char old_name[256]; + sprintf(old_name, "amethyst%03x", x); + char new_name[256]; + sprintf(new_name, "amethyst%03x", y); + lfsr_rename(&lfs, old_name, new_name) => 0; + } + } + + for (int remount = 0; remount < 2; remount++) { + // remount? + if (remount) { + lfsr_unmount(&lfs) => 0; + + // ckfetches? + if (METHOD == 2) { + // flip a bit + lfs_emubd_flipbit(CFG, badblock, + lfs_emubd_prng(CFG) % (BLOCK_SIZE*8)) => 0; + } + + lfsr_mount(&lfs, + LFS_M_RDWR + | ((CKPROGS) + ? LFS_IFDEF_CKPROGS(LFS_M_CKPROGS, -1) + : 0) + | ((CKREADS) + ? LFS_IFDEF_CKREADS(LFS_M_CKREADS, -1) + : 0) + | ((CKFETCHES) + ? LFS_IFDEF_CKFETCHES(LFS_M_CKFETCHES, -1) + : 0), + CFG) => 0; + } + + // check that our files match our simulation + for (lfs_size_t j = 0; j < sim_size; j++) { + char name[256]; + sprintf(name, "amethyst%03x", sim[j]); + struct lfs_info info; + lfsr_stat(&lfs, name, &info) => 0; + assert(strcmp(info.name, name) == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == SIZE); + } + + lfsr_dir_t dir; + lfsr_dir_open(&lfs, &dir, "/") => 0; + struct lfs_info info; + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, ".") == 0); + assert(info.type == LFS_TYPE_DIR); + assert(info.size == 0); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "..") == 0); + assert(info.type == LFS_TYPE_DIR); + assert(info.size == 0); + for (lfs_size_t j = 0; j < sim_size; j++) { + char name[256]; + sprintf(name, "amethyst%03x", sim[j]); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, name) == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == SIZE); + } + lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; + lfsr_dir_close(&lfs, &dir) => 0; + + // check the file contents + for (lfs_size_t j = 0; j < sim_size; j++) { + char name[256]; + sprintf(name, "amethyst%03x", sim[j]); + lfsr_file_t file; + lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => 0; + + uint32_t wprng = sim_prngs[j]; + uint8_t wbuf[SIZE]; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26); + } + + uint8_t rbuf[SIZE]; + lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; + assert(memcmp(rbuf, wbuf, SIZE) == 0); + lfsr_file_close(&lfs, &file) => 0; + } + } + + // clean up sim/lfs + free(sim); + free(sim_prngs); + lfsr_unmount(&lfs) => 0; + + // reset badblock + lfs_emubd_markgood(CFG, badblock) => 0; + } +''' + +# badblocks with more complex file writes +[cases.test_ck_every_fwrite_fuzz] +defines.BADBLOCK = -1 +# TODO enable other methods once rollback protection is in place +# our different methods detect different types of errors, so we implement +# errors for each one a bit differently +# METHOD=0 => ckprogs +# METHOD=1 => ckreads +# METHOD=2 => ckfetches +defines.METHOD = [0] +defines.BADBLOCK_BEHAVIOR = 'LFS_EMUBD_BADBLOCK_PROGFLIP' +defines.CKPROGS = 'METHOD == 0' +defines.CKREADS = 'METHOD == 1' +defines.CKFETCHES = 'METHOD == 2' +defines.OPS = 20 +defines.SIZE = [ + 'FILE_BUFFER_SIZE/2', + '2*FILE_BUFFER_SIZE', + 'BLOCK_SIZE/2', + 'BLOCK_SIZE', + '2*BLOCK_SIZE', + '4*BLOCK_SIZE', +] +# chunk is more an upper limit here +defines.CHUNK = 64 +# INIT=0 => no init +# INIT=1 => fill with data +# INIT=2 => truncate to size +defines.INIT = [0, 1, 2] +defines.SYNC = [false, true] +defines.SEED = 42 +fuzz = 'SEED' +if = [ + 'LFS_IFDEF_CKPROGS(true, !CKPROGS)', + 'LFS_IFDEF_CKREADS(true, !CKREADS)', + 'LFS_IFDEF_CKFETCHES(true, !CKFETCHES)', + 'CHUNK <= SIZE', + # this just saves testing time + 'SIZE <= 4*1024*FRAGMENT_SIZE', +] +code = ''' + // test all possible bad blocks + for (lfs_size_t i = 0; + i < ((BADBLOCK == -1) ? BLOCK_COUNT : 1); + i++) { + lfs_size_t badblock = (BADBLOCK == -1) ? i : BADBLOCK; + printf("--- badblock: 0x%x ---\n", badblock); + + // reset the bd prng every run for reproducibility + lfs_emubd_seed(CFG, 42); + + // ckprogs? ckreads? + if (METHOD == 0 || METHOD == 1) { + // mark our badblock as bad + lfs_emubd_markbad(CFG, badblock) => 0; + + // ckreads? + if (METHOD == 1) { + // limit to first 4-bytes to avoid escaping parity bits + lfs_emubd_setbadbit(CFG, badblock, + lfs_emubd_prng(CFG) % (4*8)) => 0; + } + } + + // test with complex file writes + lfs_t lfs; + lfsr_format(&lfs, + LFS_F_RDWR + | ((CKPROGS) ? LFS_IFDEF_CKPROGS(LFS_F_CKPROGS, -1) : 0) + | ((CKREADS) ? LFS_IFDEF_CKREADS(LFS_F_CKREADS, -1) : 0) + | ((CKFETCHES) + ? LFS_IFDEF_CKFETCHES(LFS_F_CKFETCHES, -1) + : 0), + CFG) => 0; + lfsr_mount(&lfs, + LFS_M_RDWR + | ((CKPROGS) ? LFS_IFDEF_CKPROGS(LFS_M_CKPROGS, -1) : 0) + | ((CKREADS) ? LFS_IFDEF_CKREADS(LFS_M_CKREADS, -1) : 0) + | ((CKFETCHES) + ? LFS_IFDEF_CKFETCHES(LFS_M_CKFETCHES, -1) + : 0), + CFG) => 0; + + // create a file + lfsr_file_t file; + lfsr_file_open(&lfs, &file, "hello", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + // simulate our file in ram + uint8_t sim[SIZE]; + lfs_off_t size; + uint32_t prng = SEED; + if (INIT == 0) { + memset(sim, 0, SIZE); + size = 0; + } else if (INIT == 1) { + for (lfs_size_t i = 0; i < SIZE; i++) { + sim[i] = 'a' + (TEST_PRNG(&prng) % 26); + } + lfsr_file_write(&lfs, &file, sim, SIZE) => SIZE; + size = SIZE; + } else { + memset(sim, 0, SIZE); + lfsr_file_truncate(&lfs, &file, SIZE) => 0; + size = SIZE; + } + + // sync? + if (SYNC) { + lfsr_file_sync(&lfs, &file) => 0; + } + + for (lfs_size_t i = 0; i < OPS; i++) { + // choose a random location + lfs_off_t off = TEST_PRNG(&prng) % SIZE; + // and a random size, up to the chunk size + lfs_size_t chunk = lfs_min( + TEST_PRNG(&prng) % CHUNK, + SIZE - off); + + // update sim + for (lfs_size_t j = 0; j < chunk; j++) { + sim[off+j] = 'a' + (TEST_PRNG(&prng) % 26); + } + if (chunk != 0) { + size = lfs_max(size, off+chunk); + } + + // update file + lfsr_file_seek(&lfs, &file, off, LFS_SEEK_SET) => off; + lfsr_file_write(&lfs, &file, &sim[off], chunk) => chunk; + + // sync? + if (SYNC) { + lfsr_file_sync(&lfs, &file) => 0; + } + } + + lfsr_file_close(&lfs, &file) => 0; + + for (int remount = 0; remount < 2; remount++) { + // remount? + if (remount) { + lfsr_unmount(&lfs) => 0; + + // ckfetches? + if (METHOD == 2) { + // flip a bit + lfs_emubd_flipbit(CFG, badblock, + lfs_emubd_prng(CFG) % (BLOCK_SIZE*8)) => 0; + } + + lfsr_mount(&lfs, + LFS_M_RDWR + | ((CKPROGS) + ? LFS_IFDEF_CKPROGS(LFS_M_CKPROGS, -1) + : 0) + | ((CKREADS) + ? LFS_IFDEF_CKREADS(LFS_M_CKREADS, -1) + : 0) + | ((CKFETCHES) + ? LFS_IFDEF_CKFETCHES(LFS_M_CKFETCHES, -1) + : 0), + CFG) => 0; + } + + // check our file with stat + struct lfs_info info; + lfsr_stat(&lfs, "hello", &info) => 0; + assert(strcmp(info.name, "hello") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == size); + + // and with dir read + lfsr_dir_t dir; + lfsr_dir_open(&lfs, &dir, "/") => 0; + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, ".") == 0); + assert(info.type == LFS_TYPE_DIR); + assert(info.size == 0); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "..") == 0); + assert(info.type == LFS_TYPE_DIR); + assert(info.size == 0); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "hello") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == size); + lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; + lfsr_dir_close(&lfs, &dir) => 0; + + // try reading our file + lfsr_file_open(&lfs, &file, "hello", LFS_O_RDONLY) => 0; + // is size correct? + lfsr_file_size(&lfs, &file) => size; + // try reading + uint8_t rbuf[2*SIZE]; + memset(rbuf, 0xaa, 2*SIZE); + lfsr_file_read(&lfs, &file, rbuf, 2*SIZE) => size; + // does our file match our simulation? + assert(memcmp(rbuf, sim, size) == 0); + lfsr_file_close(&lfs, &file) => 0; + } + + lfsr_unmount(&lfs) => 0; + + // reset badblock + lfs_emubd_markgood(CFG, badblock) => 0; + } +''' + +# badblocks with orphans, zombies, etc +[cases.test_ck_every_orphanzombie_fuzz] +defines.BADBLOCK = -1 +# TODO enable other methods once rollback protection is in place +# our different methods detect different types of errors, so we implement +# errors for each one a bit differently +# METHOD=0 => ckprogs +# METHOD=1 => ckreads +# METHOD=2 => ckfetches +defines.METHOD = [0] +defines.BADBLOCK_BEHAVIOR = 'LFS_EMUBD_BADBLOCK_PROGFLIP' +defines.CKPROGS = 'METHOD == 0' +defines.CKREADS = 'METHOD == 1' +defines.CKFETCHES = 'METHOD == 2' +defines.N = [1, 2, 4, 8, 16, 32, 64] +defines.OPS = '2*N' +defines.SIZE = [ + '0', + 'FILE_BUFFER_SIZE/2', + '2*FILE_BUFFER_SIZE', + 'BLOCK_SIZE/2', + 'BLOCK_SIZE', + '2*BLOCK_SIZE', + '4*BLOCK_SIZE', +] +defines.SEED = 42 +fuzz = 'SEED' +if = [ + 'LFS_IFDEF_CKPROGS(true, !CKPROGS)', + 'LFS_IFDEF_CKREADS(true, !CKREADS)', + 'LFS_IFDEF_CKFETCHES(true, !CKFETCHES)', + '(SIZE*N)/BLOCK_SIZE <= 16', +] +code = ''' + // test all possible bad blocks + for (lfs_size_t i = 0; + i < ((BADBLOCK == -1) ? BLOCK_COUNT : 1); + i++) { + lfs_size_t badblock = (BADBLOCK == -1) ? i : BADBLOCK; + printf("--- badblock: 0x%x ---\n", badblock); + + // reset the bd prng every run for reproducibility + lfs_emubd_seed(CFG, 42); + + // ckprogs? ckreads? + if (METHOD == 0 || METHOD == 1) { + // mark our badblock as bad + lfs_emubd_markbad(CFG, badblock) => 0; + + // ckreads? + if (METHOD == 1) { + // limit to first 4-bytes to avoid escaping parity bits + lfs_emubd_setbadbit(CFG, badblock, + lfs_emubd_prng(CFG) % (4*8)) => 0; + } + } + + // test with orphans, zombies, etc + lfs_t lfs; + lfsr_format(&lfs, + LFS_F_RDWR + | ((CKPROGS) ? LFS_IFDEF_CKPROGS(LFS_F_CKPROGS, -1) : 0) + | ((CKREADS) ? LFS_IFDEF_CKREADS(LFS_F_CKREADS, -1) : 0) + | ((CKFETCHES) + ? LFS_IFDEF_CKFETCHES(LFS_F_CKFETCHES, -1) + : 0), + CFG) => 0; + lfsr_mount(&lfs, + LFS_M_RDWR + | ((CKPROGS) ? LFS_IFDEF_CKPROGS(LFS_M_CKPROGS, -1) : 0) + | ((CKREADS) ? LFS_IFDEF_CKREADS(LFS_M_CKREADS, -1) : 0) + | ((CKFETCHES) + ? LFS_IFDEF_CKFETCHES(LFS_M_CKFETCHES, -1) + : 0), + CFG) => 0; + + // set up a simulation to compare against + lfs_size_t *sim = malloc(N*sizeof(lfs_size_t)); + uint32_t *sim_prngs = malloc(N*sizeof(uint32_t)); + lfs_size_t sim_size = 0; + + typedef struct sim_file { + lfs_size_t x; + bool orphan; + bool zombie; + uint32_t prng; + lfsr_file_t file; + } sim_file_t; + sim_file_t **sim_files = malloc(N*sizeof(sim_file_t*)); + lfs_size_t sim_file_count = 0; + + uint32_t prng = SEED; + for (lfs_size_t i = 0; i < OPS; i++) { + nonsense:; + // choose which operation to do + uint8_t op = TEST_PRNG(&prng) % 5; + + // open a new file? + if (op == 0) { + if (sim_file_count >= N) { + goto nonsense; + } + // choose a pseudo-random number + lfs_size_t x = TEST_PRNG(&prng) % N; + + // already exists? + bool orphan = true; + uint32_t wprng = 0; + for (lfs_size_t j = 0; j < sim_size; j++) { + if (sim[j] == x) { + orphan = false; + wprng = sim_prngs[j]; + break; + } + } + // choose a random seed if we don't exist + if (orphan) { + wprng = TEST_PRNG(&prng); + } + + // open in our sim + lfs_size_t j = sim_file_count; + sim_files[j] = malloc(sizeof(sim_file_t)); + sim_files[j]->x = x; + sim_files[j]->orphan = orphan; + sim_files[j]->zombie = false; + sim_files[j]->prng = wprng; + sim_file_count++; + + // open the actual file + char name[256]; + sprintf(name, "batman%03x", x); + lfsr_file_open(&lfs, &sim_files[j]->file, name, + LFS_O_RDWR | LFS_O_CREAT) => 0; + + // write some initial data if we don't exist + if (orphan) { + uint8_t wbuf[SIZE]; + for (lfs_size_t k = 0; k < SIZE; k++) { + wbuf[k] = 'a' + (TEST_PRNG(&wprng) % 26); + } + lfsr_file_write(&lfs, &sim_files[j]->file, wbuf, SIZE) + => SIZE; + } + + // write/rewrite a file? + } else if (op == 1) { + if (sim_file_count == 0) { + goto nonsense; + } + // choose a random file handle + lfs_size_t j = TEST_PRNG(&prng) % sim_file_count; + lfs_size_t x = sim_files[j]->x; + // choose a random seed + uint32_t wprng = TEST_PRNG(&prng); + + // update sim + sim_files[j]->prng = wprng; + if (!sim_files[j]->zombie) { + // insert into our sim + for (lfs_size_t k = 0;; k++) { + if (k >= sim_size || sim[k] >= x) { + // already seen? + if (k < sim_size && sim[k] == x) { + // new prng + sim_prngs[k] = wprng; + } else { + // insert + memmove(&sim[k+1], &sim[k], + (sim_size-k)*sizeof(lfs_size_t)); + memmove(&sim_prngs[k+1], &sim_prngs[k], + (sim_size-k)*sizeof(uint32_t)); + sim_size += 1; + sim[k] = x; + sim_prngs[k] = wprng; + } + break; + } + } + + // update related sim files + for (lfs_size_t k = 0; k < sim_file_count; k++) { + if (sim_files[k]->x == x && !sim_files[k]->zombie) { + sim_files[k]->orphan = false; + sim_files[k]->prng = wprng; + } + } + } + + // write to the file + lfsr_file_rewind(&lfs, &sim_files[j]->file) => 0; + uint8_t wbuf[SIZE]; + for (lfs_size_t k = 0; k < SIZE; k++) { + wbuf[k] = 'a' + (TEST_PRNG(&wprng) % 26); + } + lfsr_file_write(&lfs, &sim_files[j]->file, wbuf, SIZE) => SIZE; + lfsr_file_sync(&lfs, &sim_files[j]->file) + => (!sim_files[j]->zombie) ? 0 : LFS_ERR_NOENT; + + // close a file? + } else if (op == 2) { + if (sim_file_count == 0) { + goto nonsense; + } + // choose a random file handle + lfs_size_t j = TEST_PRNG(&prng) % sim_file_count; + + // this doesn't really test anything, but if we don't close + // files eventually everything will end up zombies + + // close the file without affected disk + lfsr_file_desync(&lfs, &sim_files[j]->file) => 0; + lfsr_file_close(&lfs, &sim_files[j]->file) => 0; + + // remove from list + free(sim_files[j]); + sim_files[j] = sim_files[sim_file_count-1]; + sim_file_count -= 1; + + // remove a file? + } else if (op == 3) { + if (sim_size == 0) { + goto nonsense; + } + // choose a random file to delete + lfs_size_t j = TEST_PRNG(&prng) % sim_size; + lfs_size_t x = sim[j]; + + // delete from our sim + memmove(&sim[j], &sim[j+1], + (sim_size-(j+1))*sizeof(lfs_size_t)); + memmove(&sim_prngs[j], &sim_prngs[j+1], + (sim_size-(j+1))*sizeof(uint32_t)); + sim_size -= 1; + + // mark any related sim files as zombied + for (lfs_size_t k = 0; k < sim_file_count; k++) { + if (sim_files[k]->x == x) { + sim_files[k]->zombie = true; + } + } + + // delete this file + char name[256]; + sprintf(name, "batman%03x", x); + lfsr_remove(&lfs, name) => 0; + + // rename a file? + } else if (op == 4) { + if (sim_size == 0) { + goto nonsense; + } + // choose a random file to rename, and a random number to + // rename to + lfs_size_t j = TEST_PRNG(&prng) % sim_size; + lfs_size_t x = sim[j]; + lfs_size_t y = TEST_PRNG(&prng) % N; + uint32_t wprng = sim_prngs[j]; + + // update our sim + for (lfs_size_t k = 0;; k++) { + if (k >= sim_size || sim[k] >= y) { + // renaming and replacing + if (k < sim_size && sim[k] == y && x != y) { + // delete the original entry + memmove(&sim[j], &sim[j+1], + (sim_size-(j+1))*sizeof(lfs_size_t)); + memmove(&sim_prngs[j], &sim_prngs[j+1], + (sim_size-(j+1))*sizeof(uint32_t)); + sim_size -= 1; + if (k > j) { + k -= 1; + } + // update the prng + sim_prngs[k] = wprng; + // just renaming + } else { + // first delete + memmove(&sim[j], &sim[j+1], + (sim_size-(j+1))*sizeof(lfs_size_t)); + memmove(&sim_prngs[j], &sim_prngs[j+1], + (sim_size-(j+1))*sizeof(uint32_t)); + if (k > j) { + k -= 1; + } + // then insert + memmove(&sim[k+1], &sim[k], + (sim_size-k)*sizeof(lfs_size_t)); + memmove(&sim_prngs[k+1], &sim_prngs[k], + (sim_size-k)*sizeof(uint32_t)); + sim[k] = y; + sim_prngs[k] = wprng; + } + break; + } + } + + // update any related sim files + for (lfs_size_t k = 0; k < sim_file_count; k++) { + // move source files + if (sim_files[k]->x == x) { + sim_files[k]->x = y; + + // mark target files as zombied + } else if (sim_files[k]->x == y) { + sim_files[k]->zombie = true; + } + } + + // rename this file + char old_name[256]; + sprintf(old_name, "batman%03x", x); + char new_name[256]; + sprintf(new_name, "batman%03x", y); + lfsr_rename(&lfs, old_name, new_name) => 0; + } + } + + // check that disk matches our simulation + for (lfs_size_t j = 0; j < sim_size; j++) { + char name[256]; + sprintf(name, "batman%03x", sim[j]); + struct lfs_info info; + lfsr_stat(&lfs, name, &info) => 0; + assert(strcmp(info.name, name) == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == SIZE); + } + + lfsr_dir_t dir; + lfsr_dir_open(&lfs, &dir, "/") => 0; + struct lfs_info info; + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, ".") == 0); + assert(info.type == LFS_TYPE_DIR); + assert(info.size == 0); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "..") == 0); + assert(info.type == LFS_TYPE_DIR); + assert(info.size == 0); + for (lfs_size_t j = 0; j < sim_size; j++) { + char name[256]; + sprintf(name, "batman%03x", sim[j]); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, name) == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == SIZE); + } + lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; + lfsr_dir_close(&lfs, &dir) => 0; + + for (lfs_size_t j = 0; j < sim_size; j++) { + char name[256]; + sprintf(name, "batman%03x", sim[j]); + lfsr_file_t file; + lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => 0; + + uint32_t wprng = sim_prngs[j]; + uint8_t wbuf[SIZE]; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26); + } + + uint8_t rbuf[SIZE]; + lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; + assert(memcmp(rbuf, wbuf, SIZE) == 0); + lfsr_file_close(&lfs, &file) => 0; + } + + // check that our file handles match our simulation + for (lfs_size_t j = 0; j < sim_file_count; j++) { + uint32_t wprng = sim_files[j]->prng; + uint8_t wbuf[SIZE]; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26); + } + + lfsr_file_rewind(&lfs, &sim_files[j]->file) => 0; + uint8_t rbuf[SIZE]; + lfsr_file_read(&lfs, &sim_files[j]->file, rbuf, SIZE) => SIZE; + assert(memcmp(rbuf, wbuf, SIZE) == 0); + } + + // clean up sim/lfs + free(sim); + free(sim_prngs); + for (lfs_size_t j = 0; j < sim_file_count; j++) { + lfsr_file_close(&lfs, &sim_files[j]->file) => 0; + free(sim_files[j]); + } + free(sim_files); + lfsr_unmount(&lfs) => 0; + // reset badblock + lfs_emubd_markgood(CFG, badblock) => 0; + } +''' + +# badblocks with orphans, zombies, dirs, etc +[cases.test_ck_every_orphanzombiedir_fuzz] +defines.BADBLOCK = -1 +# TODO enable other methods once rollback protection is in place +# our different methods detect different types of errors, so we implement +# errors for each one a bit differently +# METHOD=0 => ckprogs +# METHOD=1 => ckreads +# METHOD=2 => ckfetches +defines.METHOD = [0] +defines.BADBLOCK_BEHAVIOR = 'LFS_EMUBD_BADBLOCK_PROGFLIP' +defines.CKPROGS = 'METHOD == 0' +defines.CKREADS = 'METHOD == 1' +defines.CKFETCHES = 'METHOD == 2' +defines.N = [1, 2, 4, 8, 16, 32, 64] +defines.OPS = '2*N' +defines.SIZE = [ + '0', + 'FILE_BUFFER_SIZE/2', + '2*FILE_BUFFER_SIZE', + 'BLOCK_SIZE/2', + 'BLOCK_SIZE', + '2*BLOCK_SIZE', + '4*BLOCK_SIZE', +] +defines.SEED = 42 +fuzz = 'SEED' +if = [ + 'LFS_IFDEF_CKPROGS(true, !CKPROGS)', + 'LFS_IFDEF_CKREADS(true, !CKREADS)', + 'LFS_IFDEF_CKFETCHES(true, !CKFETCHES)', + 'LFS_IFDEF_CKPROGS(true, !CKPROGS)', + '(SIZE*N)/BLOCK_SIZE <= 16', +] +code = ''' + // test all possible bad blocks + for (lfs_size_t i = 0; + i < ((BADBLOCK == -1) ? BLOCK_COUNT : 1); + i++) { + lfs_size_t badblock = (BADBLOCK == -1) ? i : BADBLOCK; + printf("--- badblock: 0x%x ---\n", badblock); + + // reset the bd prng every run for reproducibility + lfs_emubd_seed(CFG, 42); + + // ckprogs? ckreads? + if (METHOD == 0 || METHOD == 1) { + // mark our badblock as bad + lfs_emubd_markbad(CFG, badblock) => 0; + + // ckreads? + if (METHOD == 1) { + // limit to first 4-bytes to avoid escaping parity bits + lfs_emubd_setbadbit(CFG, badblock, + lfs_emubd_prng(CFG) % (4*8)) => 0; + } + } + + // test with orphans, zombies, dirs, etc + lfs_t lfs; + lfsr_format(&lfs, + LFS_F_RDWR + | ((CKPROGS) ? LFS_IFDEF_CKPROGS(LFS_F_CKPROGS, -1) : 0) + | ((CKREADS) ? LFS_IFDEF_CKREADS(LFS_F_CKREADS, -1) : 0) + | ((CKFETCHES) + ? LFS_IFDEF_CKFETCHES(LFS_F_CKFETCHES, -1) + : 0), + CFG) => 0; + lfsr_mount(&lfs, + LFS_M_RDWR + | ((CKPROGS) ? LFS_IFDEF_CKPROGS(LFS_M_CKPROGS, -1) : 0) + | ((CKREADS) ? LFS_IFDEF_CKREADS(LFS_M_CKREADS, -1) : 0) + | ((CKFETCHES) + ? LFS_IFDEF_CKFETCHES(LFS_M_CKFETCHES, -1) + : 0), + CFG) => 0; + + // set up a simulation to compare against + lfs_size_t *sim = malloc(N*sizeof(lfs_size_t)); + uint32_t *sim_prngs = malloc(N*sizeof(uint32_t)); + bool *sim_isdirs = malloc(N*sizeof(bool)); + lfs_size_t sim_size = 0; + + typedef struct sim_file { + lfs_size_t x; + bool orphan; + bool zombie; + uint32_t prng; + lfsr_file_t file; + } sim_file_t; + sim_file_t **sim_files = malloc(N*sizeof(sim_file_t*)); + lfs_size_t sim_file_count = 0; + + uint32_t prng = SEED; + for (lfs_size_t i = 0; i < OPS; i++) { + nonsense:; + // choose which operation to do + uint8_t op = TEST_PRNG(&prng) % 8; + + // open a new file? + if (op == 0) { + if (sim_file_count >= N) { + goto nonsense; + } + // choose a pseudo-random number + lfs_size_t x = TEST_PRNG(&prng) % N; + + // already exists? + bool orphan = true; + uint32_t wprng = 0; + for (lfs_size_t j = 0; j < sim_size; j++) { + if (sim[j] == x) { + if (sim_isdirs[j]) { + goto nonsense; + } + orphan = false; + wprng = sim_prngs[j]; + break; + } + } + // choose a random seed if we don't exist + if (orphan) { + wprng = TEST_PRNG(&prng); + } + + // open in our sim + lfs_size_t j = sim_file_count; + sim_files[j] = malloc(sizeof(sim_file_t)); + sim_files[j]->x = x; + sim_files[j]->orphan = orphan; + sim_files[j]->zombie = false; + sim_files[j]->prng = wprng; + sim_file_count++; + + // open the actual file + char name[256]; + sprintf(name, "batman%03x", x); + lfsr_file_open(&lfs, &sim_files[j]->file, name, + LFS_O_RDWR | LFS_O_CREAT) => 0; + + // write some initial data if we don't exist + if (orphan) { + uint8_t wbuf[SIZE]; + for (lfs_size_t k = 0; k < SIZE; k++) { + wbuf[k] = 'a' + (TEST_PRNG(&wprng) % 26); + } + lfsr_file_write(&lfs, &sim_files[j]->file, wbuf, SIZE) + => SIZE; + } + + // write/rewrite a file? + } else if (op == 1) { + if (sim_file_count == 0) { + goto nonsense; + } + // choose a random file handle + lfs_size_t j = TEST_PRNG(&prng) % sim_file_count; + lfs_size_t x = sim_files[j]->x; + // choose a random seed + uint32_t wprng = TEST_PRNG(&prng); + + // update sim + sim_files[j]->prng = wprng; + if (!sim_files[j]->zombie) { + // insert into our sim + for (lfs_size_t k = 0;; k++) { + if (k >= sim_size || sim[k] >= x) { + // already seen? + if (k < sim_size && sim[k] == x) { + // new prng + sim_prngs[k] = wprng; + } else { + // insert + memmove(&sim[k+1], &sim[k], + (sim_size-k)*sizeof(lfs_size_t)); + memmove(&sim_prngs[k+1], &sim_prngs[k], + (sim_size-k)*sizeof(uint32_t)); + memmove(&sim_isdirs[k+1], &sim_isdirs[k], + (sim_size-k)*sizeof(bool)); + sim_size += 1; + sim[k] = x; + sim_prngs[k] = wprng; + sim_isdirs[k] = false; + } + break; + } + } + + // update related sim files + for (lfs_size_t k = 0; k < sim_file_count; k++) { + if (sim_files[k]->x == x && !sim_files[k]->zombie) { + sim_files[k]->orphan = false; + sim_files[k]->prng = wprng; + } + } + } + + // write to the file + lfsr_file_rewind(&lfs, &sim_files[j]->file) => 0; + uint8_t wbuf[SIZE]; + for (lfs_size_t k = 0; k < SIZE; k++) { + wbuf[k] = 'a' + (TEST_PRNG(&wprng) % 26); + } + lfsr_file_write(&lfs, &sim_files[j]->file, wbuf, SIZE) => SIZE; + lfsr_file_sync(&lfs, &sim_files[j]->file) + => (!sim_files[j]->zombie) ? 0 : LFS_ERR_NOENT; + + // close a file? + } else if (op == 2) { + if (sim_file_count == 0) { + goto nonsense; + } + // choose a random file handle + lfs_size_t j = TEST_PRNG(&prng) % sim_file_count; + + // this doesn't really test anything, but if we don't close + // files eventually everything will end up zombies + + // close the file without affected disk + lfsr_file_desync(&lfs, &sim_files[j]->file) => 0; + lfsr_file_close(&lfs, &sim_files[j]->file) => 0; + + // remove from list + free(sim_files[j]); + sim_files[j] = sim_files[sim_file_count-1]; + sim_file_count -= 1; + + // remove a file? + } else if (op == 3) { + if (sim_size == 0) { + goto nonsense; + } + // choose a random file to delete + lfs_size_t j = TEST_PRNG(&prng) % sim_size; + lfs_size_t x = sim[j]; + + // delete from our sim + memmove(&sim[j], &sim[j+1], + (sim_size-(j+1))*sizeof(lfs_size_t)); + memmove(&sim_prngs[j], &sim_prngs[j+1], + (sim_size-(j+1))*sizeof(uint32_t)); + memmove(&sim_isdirs[j], &sim_isdirs[j+1], + (sim_size-(j+1))*sizeof(bool)); + sim_size -= 1; + + // mark any related sim files as zombied + for (lfs_size_t k = 0; k < sim_file_count; k++) { + if (sim_files[k]->x == x) { + sim_files[k]->zombie = true; + } + } + + // delete this file + char name[256]; + sprintf(name, "batman%03x", x); + lfsr_remove(&lfs, name) => 0; + + // rename a file? + } else if (op == 4) { + if (sim_size == 0) { + goto nonsense; + } + // choose a random file to rename, and a random number to + // rename to + lfs_size_t j = TEST_PRNG(&prng) % sim_size; + lfs_size_t x = sim[j]; + lfs_size_t y = TEST_PRNG(&prng) % N; + uint32_t wprng = sim_prngs[j]; + bool isdir = sim_isdirs[j]; + + // update our sim + for (lfs_size_t k = 0;; k++) { + if (k >= sim_size || sim[k] >= y) { + // renaming and replacing + if (k < sim_size && sim[k] == y && x != y) { + // type mismatch? + if (sim_isdirs[k] != isdir) { + goto nonsense; + } + + // delete the original entry + memmove(&sim[j], &sim[j+1], + (sim_size-(j+1))*sizeof(lfs_size_t)); + memmove(&sim_prngs[j], &sim_prngs[j+1], + (sim_size-(j+1))*sizeof(uint32_t)); + memmove(&sim_isdirs[j], &sim_isdirs[j+1], + (sim_size-(j+1))*sizeof(bool)); + sim_size -= 1; + if (k > j) { + k -= 1; + } + // update the prng + sim_prngs[k] = wprng; + // just renaming + } else { + // first delete + memmove(&sim[j], &sim[j+1], + (sim_size-(j+1))*sizeof(lfs_size_t)); + memmove(&sim_prngs[j], &sim_prngs[j+1], + (sim_size-(j+1))*sizeof(uint32_t)); + memmove(&sim_isdirs[j], &sim_isdirs[j+1], + (sim_size-(j+1))*sizeof(bool)); + if (k > j) { + k -= 1; + } + // then insert + memmove(&sim[k+1], &sim[k], + (sim_size-k)*sizeof(lfs_size_t)); + memmove(&sim_prngs[k+1], &sim_prngs[k], + (sim_size-k)*sizeof(uint32_t)); + memmove(&sim_isdirs[k+1], &sim_isdirs[k], + (sim_size-k)*sizeof(bool)); + sim[k] = y; + sim_prngs[k] = wprng; + sim_isdirs[k] = isdir; + } + break; + } + } + + // update any related sim files + for (lfs_size_t k = 0; k < sim_file_count; k++) { + // move source files + if (sim_files[k]->x == x) { + sim_files[k]->x = y; + + // mark target files as zombied + } else if (sim_files[k]->x == y) { + sim_files[k]->zombie = true; + } + } + + // rename this file + char old_name[256]; + sprintf(old_name, "batman%03x", x); + char new_name[256]; + sprintf(new_name, "batman%03x", y); + lfsr_rename(&lfs, old_name, new_name) => 0; + + // toss a directory into the mix + } else if (op == 5) { + // choose a pseudo-random number + lfs_size_t x = TEST_PRNG(&prng) % N; + + // insert into our sim, use negative numbers for dirs + for (lfs_size_t k = 0;; k++) { + if (k >= sim_size || sim[k] >= x) { + // already seen? + if (k < sim_size && sim[k] == x) { + goto nonsense; + } else { + // insert + memmove(&sim[k+1], &sim[k], + (sim_size-k)*sizeof(lfs_size_t)); + memmove(&sim_prngs[k+1], &sim_prngs[k], + (sim_size-k)*sizeof(uint32_t)); + memmove(&sim_isdirs[k+1], &sim_isdirs[k], + (sim_size-k)*sizeof(bool)); + sim_size += 1; + sim[k] = x; + sim_prngs[k] = 0; + sim_isdirs[k] = true; + } + break; + } + } + + // mark any related sim files as zombied + for (lfs_size_t k = 0; k < sim_file_count; k++) { + if (sim_files[k]->x == x) { + sim_files[k]->zombie = true; + } + } + + // make the directory + char name[256]; + sprintf(name, "batman%03x", x); + lfsr_mkdir(&lfs, name) => 0; + } + } + + // check that disk matches our simulation + for (lfs_size_t j = 0; j < sim_size; j++) { + char name[256]; + sprintf(name, "batman%03x", sim[j]); + struct lfs_info info; + lfsr_stat(&lfs, name, &info) => 0; + assert(strcmp(info.name, name) == 0); + if (sim_isdirs[j]) { + assert(info.type == LFS_TYPE_DIR); + } else { + assert(info.type == LFS_TYPE_REG); + assert(info.size == SIZE); + } + } + + lfsr_dir_t dir; + lfsr_dir_open(&lfs, &dir, "/") => 0; + struct lfs_info info; + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, ".") == 0); + assert(info.type == LFS_TYPE_DIR); + assert(info.size == 0); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "..") == 0); + assert(info.type == LFS_TYPE_DIR); + assert(info.size == 0); + for (lfs_size_t j = 0; j < sim_size; j++) { + char name[256]; + sprintf(name, "batman%03x", sim[j]); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, name) == 0); + if (sim_isdirs[j]) { + assert(info.type == LFS_TYPE_DIR); + } else { + assert(info.type == LFS_TYPE_REG); + assert(info.size == SIZE); + } + } + lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; + lfsr_dir_close(&lfs, &dir) => 0; + + for (lfs_size_t j = 0; j < sim_size; j++) { + if (sim_isdirs[j]) { + char name[256]; + sprintf(name, "batman%03x", sim[j]); + lfsr_file_t file; + lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) + => LFS_ERR_ISDIR; + + } else { + char name[256]; + sprintf(name, "batman%03x", sim[j]); + lfsr_file_t file; + lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => 0; + + uint32_t wprng = sim_prngs[j]; + uint8_t wbuf[SIZE]; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26); + } + + uint8_t rbuf[SIZE]; + lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; + assert(memcmp(rbuf, wbuf, SIZE) == 0); + lfsr_file_close(&lfs, &file) => 0; + } + } + + // check that our file handles match our simulation + for (lfs_size_t j = 0; j < sim_file_count; j++) { + uint32_t wprng = sim_files[j]->prng; + uint8_t wbuf[SIZE]; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26); + } + + lfsr_file_rewind(&lfs, &sim_files[j]->file) => 0; + uint8_t rbuf[SIZE]; + lfsr_file_read(&lfs, &sim_files[j]->file, rbuf, SIZE) => SIZE; + assert(memcmp(rbuf, wbuf, SIZE) == 0); + } + + // clean up sim/lfs + free(sim); + free(sim_prngs); + for (lfs_size_t j = 0; j < sim_file_count; j++) { + lfsr_file_close(&lfs, &sim_files[j]->file) => 0; + free(sim_files[j]); + } + free(sim_files); + lfsr_unmount(&lfs) => 0; + + // reset badblock + lfs_emubd_markgood(CFG, badblock) => 0; + } +'''