From 843412cc79edef16739488ae5b9b539748d59c20 Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Mon, 8 Dec 2025 01:51:41 -0600 Subject: [PATCH] preerase: Implemented the gc side of preerase Allocating pre-erased blocks gets quite complicated due to our restricted flash model, but at least the actual pre-erasing is relatively straightforward: - We keep track of known preerased state in lfs3->gbmap.preeraser. - If LFS3_GC_PREERASE is provided during gc work, we increment the preeraser's known window by scanning the gbmap. - Any BMFREE ranges we find, we erase a block at a time, and store the resulting ecksum in a BMERASED range in the gbmap. - We keep track of how many blocks we erased, and stop early if this exceeds cfg.gc_preerase_count. This just lets users tune how many blocks to preerase in case something (?) prevents preerased blocks from being used. Some notes: - We don't really do anything with ranges in lfs3_alloc_preerase. In theory we could bulk in erase to minimize the number of commits to the gbmap, but we expect erase to dominate, so this probably isn't worth it. And if erase doesn't dominate, why would you bother pre-erasing blocks? - Preerasing isn't really a traversal operation, and is managed by a sort of secondary state machine in lfs3_fs_gc_. This also means lfs3_trv_read with LFS3_T_PREERASE does nothing, but I guess that is ok? It's tempting to try to make lfs3_trv_read also preerase, but it's unclear what block it should return -- it's probably the wrong API. - Introducing ecksums actually went quite a bit smoother than I expected. Though it helps ecksums are the only optional payload, no type punning or anything. Ecksums do muddy the gbmap's design a bit, unfortunately. The main issue being that we can only merge BMERASED ranges with equal ecksums. This makes BMERASED ranges less compressable than the others, and may be one reason to limit cfg.gc_preerase_count. However: 1. This is where I think it's useful to emphasize that the gbmap's responsibility is to track _free_ blocks, in-use blocks are secondary. When allocating, we're going to stop at the first BMFREE/BMERASED, but may need to skip over an unbounded number of BMINUSE/BMBAD blocks. So the compressability of BMFREE/BMERASED ranges should have less of an impact on block allocation. 2. In practice, most flash uses consistent erase values, so the resulting ecksums will probably be compressable. The exceptions are noop-erases (SD/eMMC, RAM, NVRAM, etc), and encryption with block address permutation? Though noop-erases are a pretty big exception. Code changes: code stack ctx before: 35116 2136 660 after: 35116 (+0.0%) 2136 (+0.0%) 660 (+0.0%) code stack ctx gbmap+np before: 38040 2136 776 gbmap+np after: 38188 (+0.4%) 2144 (+0.4%) 776 (+0.0%) code stack ctx gbmap+yp before: 38040 2136 776 gbmap+yp after: 38608 (+1.5%) 2144 (+0.4%) 796 (+2.6%) --- lfs3.c | 500 +++++++++++++++++++++++--------- lfs3.h | 69 +++++ lfs3_util.h | 6 + runners/bench_runner.h | 9 + runners/test_runner.h | 9 + scripts/dbgflags.py | 16 +- tests/test_gbmap.toml | 641 +++++++++++++++++++++++++++++++++++++---- 7 files changed, 1049 insertions(+), 201 deletions(-) diff --git a/lfs3.c b/lfs3.c index f49e6936..3ece7320 100644 --- a/lfs3.c +++ b/lfs3.c @@ -1915,10 +1915,9 @@ enum lfs3_from { LFS3_FROM_LEB128 = 5, LFS3_FROM_LLEB128 = 6, LFS3_FROM_NAME = 7, + LFS3_FROM_BRANCH = 8, - LFS3_FROM_ECKSUM = 8, - LFS3_FROM_BRANCH = 9, - + LFS3_FROM_ECKSUM = 9, LFS3_FROM_BPTR = 10, LFS3_FROM_BTREE = 11, LFS3_FROM_SHRUB = 12, @@ -2474,13 +2473,50 @@ static int lfs3_bptr_ck(lfs3_t *lfs3, const lfs3_bptr_t *bptr) { /// Erased-state checksum stuff /// -// erased-state checksum #ifndef LFS3_RDONLY -typedef struct lfs3_ecksum { - // cksize=-1 indicates no ecksum - lfs3_ssize_t cksize; - uint32_t cksum; -} lfs3_ecksum_t; +static int lfs3_ecksum_eck(lfs3_t *lfs3, lfs3_ecksum_t *ecksum, + lfs3_block_t block, lfs3_off_t off) { + // keep track of prog size + ecksum->cksize = lfs3->cfg->prog_size; + // checksum erased state + ecksum->cksum = 0; + return lfs3_bd_cksum(lfs3, + block, off, 0, + lfs3->cfg->prog_size, + &ecksum->cksum); +} +#endif + +#ifndef LFS3_RDONLY +static int lfs3_ecksum_ck(lfs3_t *lfs3, const lfs3_ecksum_t *ecksum, + lfs3_block_t block, lfs3_off_t off) { + uint32_t cksum_ = 0; + int err = lfs3_bd_cksum(lfs3, + block, off, 0, + ecksum->cksize, + &cksum_); + if (err) { + return err; + } + + return (cksum_ != ecksum->cksum) ? LFS3_ERR_CORRUPT : 0; +} +#endif + +#ifndef LFS3_RDONLY +static inline int lfs3_ecksum_cmp( + const lfs3_ecksum_t *a, + const lfs3_ecksum_t *b) { + // accept NULL as cksize=-1 + if (((a) ? a->cksize : -1) != ((b) ? b->cksize : -1)) { + return ((a) ? a->cksize : -1) - ((b) ? b->cksize : -1); + // only cmp cksum if cksize!=-1 + } else if (((a) ? a->cksize : -1) != -1) { + return a->cksum - b->cksum; + } else { + return 0; + } +} #endif // erased-state checksum on-disk encoding @@ -2623,17 +2659,8 @@ static int lfs3_rbyd_ckecksum(lfs3_t *lfs3, const lfs3_rbyd_t *rbyd, // check that erased-state matches our checksum, if this fails // most likely a write was interrupted - uint32_t ecksum_ = 0; - err = lfs3_bd_cksum(lfs3, - rbyd->blocks[0], lfs3_rbyd_eoff(rbyd), 0, - ecksum->cksize, - &ecksum_); - if (err) { - return err; - } - - // found erased-state? - return (ecksum_ == ecksum->cksum) ? 0 : LFS3_ERR_CORRUPT; + return lfs3_ecksum_ck(lfs3, ecksum, + rbyd->blocks[0], lfs3_rbyd_eoff(rbyd)); } #endif @@ -3323,16 +3350,15 @@ static int lfs3_rbyd_appendrattr_(lfs3_t *lfs3, lfs3_rbyd_t *rbyd, lfs3_data_t datas[2]; uint8_t buf[LFS3_LEB128_DSIZE]; } name; - - struct { - lfs3_data_t data; - uint8_t buf[LFS3_ECKSUM_DSIZE]; - } ecksum; struct { lfs3_data_t data; uint8_t buf[LFS3_BRANCH_DSIZE]; } branch; + struct { + lfs3_data_t data; + uint8_t buf[LFS3_ECKSUM_DSIZE]; + } ecksum; struct { lfs3_data_t data; uint8_t buf[LFS3_BPTR_DSIZE]; @@ -3418,16 +3444,6 @@ static int lfs3_rbyd_appendrattr_(lfs3_t *lfs3, lfs3_rbyd_t *rbyd, datas = ctx.u.name.datas; data_count = 2; - // ecksum? - } else if (from == LFS3_FROM_ECKSUM) { - ctx.u.ecksum.data = lfs3_data_fromecksum( - &(const lfs3_ecksum_t){ - .cksize = args[0], - .cksum = args[1]}, - ctx.u.ecksum.buf); - datas = &ctx.u.ecksum.data; - data_count = 1; - // branch? } else if (from == LFS3_FROM_BRANCH) { ctx.u.branch.data = lfs3_data_frombranch( @@ -3439,6 +3455,14 @@ static int lfs3_rbyd_appendrattr_(lfs3_t *lfs3, lfs3_rbyd_t *rbyd, datas = &ctx.u.branch.data; data_count = 1; + // ecksum? + } else if (from == LFS3_FROM_ECKSUM) { + ctx.u.ecksum.data = lfs3_data_fromecksum( + (const lfs3_ecksum_t*)args[0], + ctx.u.ecksum.buf); + datas = &ctx.u.ecksum.data; + data_count = 1; + // bptr? } else if (from == LFS3_FROM_BPTR) { ctx.u.bptr.data = lfs3_data_frombptr( @@ -4351,11 +4375,9 @@ static int lfs3_rbyd_appendcksum_(lfs3_t *lfs3, lfs3_rbyd_t *rbyd, perturb = ((e >> 7) == lfs3_parity(cksum)); // calculate the erased-state checksum - uint32_t ecksum = 0; - err = lfs3_bd_cksum(lfs3, - rbyd->blocks[0], off_, lfs3->cfg->prog_size, - lfs3->cfg->prog_size, - &ecksum); + lfs3_ecksum_t ecksum; + err = lfs3_ecksum_eck(lfs3, &ecksum, + rbyd->blocks[0], off_); if (err && err != LFS3_ERR_CORRUPT) { return err; } @@ -4363,8 +4385,7 @@ static int lfs3_rbyd_appendcksum_(lfs3_t *lfs3, lfs3_rbyd_t *rbyd, err = lfs3_rbyd_appendrattr_(lfs3, rbyd, LFS3_TAG_ECKSUM, 0, LFS3_FROM_ECKSUM, 0, LFS3_RATTRS( - LFS3_RATTR_ARG(lfs3->cfg->prog_size), - LFS3_RATTR_ARG(ecksum))); + LFS3_RATTR_ARG(&ecksum))); if (err) { return err; } @@ -7226,6 +7247,17 @@ static inline bool lfs3_t_islookahead(uint32_t flags) { #endif } +static inline bool lfs3_t_ispreerase(uint32_t flags) { + (void)flags; + #if !defined(LFS3_RDONLY) \ + && defined(LFS3_GBMAP) \ + && !defined(LFS3_NO_PREERASE) + return flags & LFS3_T_PREERASE; + #else + return false; + #endif +} + static inline bool lfs3_t_compact(uint32_t flags) { (void)flags; #ifndef LFS3_RDONLY @@ -10481,7 +10513,14 @@ eot:; static void lfs3_gbmap_init(lfs3_gbmap_t *gbmap) { gbmap->window = 0; gbmap->known = 0; + #ifndef LFS3_RDONLY gbmap->free = 0; + #endif + #if !defined(LFS3_RDONLY) && !defined(LFS3_NO_PREERASE) + gbmap->ecksum.cksize = -1; + gbmap->preeraser.known = 0; + gbmap->preeraser.count = 0; + #endif lfs3_btree_init(&gbmap->b); lfs3_btree_init(&gbmap->b_p); } @@ -10554,9 +10593,26 @@ static int lfs3_data_readgbmap(lfs3_t *lfs3, lfs3_data_t *data, #ifdef LFS3_GBMAP static lfs3_stag_t lfs3_gbmap_lookupnext(lfs3_t *lfs3, lfs3_btree_t *gbmap, lfs3_bid_t bid, - lfs3_bid_t *bid_, lfs3_bid_t *weight_) { - return lfs3_btree_lookupnext(lfs3, gbmap, bid, - bid_, weight_, NULL); + lfs3_bid_t *bid_, lfs3_bid_t *weight_, lfs3_ecksum_t *ecksum_) { + lfs3_data_t data; + lfs3_stag_t tag = lfs3_btree_lookupnext(lfs3, gbmap, bid, + bid_, weight_, &data); + if (tag < 0) { + return tag; + } + + if (ecksum_) { + if (tag == LFS3_TAG_BMERASED && lfs3_data_size(&data) > 0) { + int err = lfs3_data_readecksum(lfs3, &data, + ecksum_); + if (err) { + return err; + } + } else { + ecksum_->cksize = -1; + } + } + return tag; } #endif @@ -10574,19 +10630,21 @@ static int lfs3_gbmap_commit(lfs3_t *lfs3, lfs3_btree_t *gbmap, // the purpose of weight is really just to provide a shortcut for bulk // clearing ranges in lfs3_alloc_lookgbmap static int lfs3_gbmap_mark_(lfs3_t *lfs3, lfs3_btree_t *gbmap, - lfs3_block_t block, lfs3_block_t weight, lfs3_tag_t tag) { + lfs3_block_t block, lfs3_block_t weight, + lfs3_tag_t tag, const lfs3_ecksum_t *ecksum) { // lookup gbmap range lfs3_bid_t bid__; lfs3_bid_t weight__; + lfs3_ecksum_t ecksum__; lfs3_stag_t tag__ = lfs3_gbmap_lookupnext(lfs3, gbmap, block, - &bid__, &weight__); + &bid__, &weight__, &ecksum__); if (tag__ < 0) { LFS3_ASSERT(tag__ != LFS3_ERR_NOENT); return tag__; } // wait, already set to expected type? guess we're done - if (tag__ == tag) { + if (tag__ == tag && lfs3_ecksum_cmp(&ecksum__, ecksum) == 0) { return 0; } @@ -10607,15 +10665,16 @@ static int lfs3_gbmap_mark_(lfs3_t *lfs3, lfs3_btree_t *gbmap, && block < lfs3->block_count-1) { lfs3_bid_t r_bid; lfs3_bid_t r_weight; + lfs3_ecksum_t r_ecksum; lfs3_stag_t r_tag = lfs3_gbmap_lookupnext(lfs3, gbmap, block+1, - &r_bid, &r_weight); + &r_bid, &r_weight, &r_ecksum); if (r_tag < 0) { LFS3_ASSERT(r_tag != LFS3_ERR_NOENT); return r_tag; } LFS3_ASSERT(r_weight == r_bid - block); - if (r_tag == tag) { + if (r_tag == tag && lfs3_ecksum_cmp(&r_ecksum, ecksum) == 0) { // delete to prepare merge int err = lfs3_gbmap_commit(lfs3, &gbmap_, r_bid, LFS3_RATTRS( LFS3_RATTR(2, LFS3_tag_RM, -2), @@ -10638,15 +10697,16 @@ static int lfs3_gbmap_mark_(lfs3_t *lfs3, lfs3_btree_t *gbmap, && block-(weight-1) > 0) { lfs3_bid_t l_bid; lfs3_bid_t l_weight; + lfs3_ecksum_t l_ecksum; lfs3_stag_t l_tag = lfs3_gbmap_lookupnext(lfs3, gbmap, block-weight, - &l_bid, &l_weight); + &l_bid, &l_weight, &l_ecksum); if (l_tag < 0) { LFS3_ASSERT(l_tag != LFS3_ERR_NOENT); return l_tag; } LFS3_ASSERT(l_bid == block-weight); - if (l_tag == tag) { + if (l_tag == tag && lfs3_ecksum_cmp(&l_ecksum, ecksum) == 0) { // delete to prepare merge int err = lfs3_gbmap_commit(lfs3, &gbmap_, l_bid, LFS3_RATTRS( LFS3_RATTR(2, LFS3_tag_RM, -2), @@ -10676,12 +10736,18 @@ static int lfs3_gbmap_mark_(lfs3_t *lfs3, lfs3_btree_t *gbmap, ? LFS3_RATTR(2, LFS3_tag_GROW, -2) : LFS3_RATTR(2, LFS3_tag_RM, -2), LFS3_RATTR_WEIGHT(-((bid__+1) - (block-(weight-1)))), - LFS3_RATTR(2, tag, -2), + (ecksum && ecksum->cksize != -1) + ? LFS3_RATTR(3, tag, -2, LFS3_FROM_ECKSUM) + : LFS3_RATTR(3, tag, -2), LFS3_RATTR_WEIGHT(+weight_), + LFS3_RATTR_ARG(ecksum), (bid__ > block) - ? LFS3_RATTR(2, tag__, -2) - : LFS3_RATTR(2, LFS3_TAG_NULL, 0), + ? ((ecksum__.cksize != -1) + ? LFS3_RATTR(3, tag__, -2, LFS3_FROM_ECKSUM) + : LFS3_RATTR(3, tag__, -2)) + : LFS3_RATTR(3, LFS3_TAG_NULL, 0), LFS3_RATTR_WEIGHT(+(bid__ - block)), + LFS3_RATTR_ARG(&ecksum__), LFS3_RATTR_NULL)); if (err) { return err; @@ -10695,8 +10761,8 @@ static int lfs3_gbmap_mark_(lfs3_t *lfs3, lfs3_btree_t *gbmap, #if !defined(LFS3_RDONLY) && defined(LFS3_GBMAP) static int lfs3_gbmap_mark(lfs3_t *lfs3, lfs3_btree_t *gbmap, - lfs3_block_t block, lfs3_tag_t tag) { - return lfs3_gbmap_mark_(lfs3, gbmap, block, 1, tag); + lfs3_block_t block, lfs3_tag_t tag, const lfs3_ecksum_t *ecksum) { + return lfs3_gbmap_mark_(lfs3, gbmap, block, 1, tag, ecksum); } #endif @@ -10725,7 +10791,7 @@ static int lfs3_gbmap_markbptr(lfs3_t *lfs3, lfs3_btree_t *gbmap, } for (lfs3_size_t i = 0; i < block_count; i++) { - int err = lfs3_gbmap_mark(lfs3, gbmap, blocks[i], tag_); + int err = lfs3_gbmap_mark(lfs3, gbmap, blocks[i], tag_, NULL); if (err) { return err; } @@ -10743,7 +10809,7 @@ static int lfs3_gbmap_zero(lfs3_t *lfs3, lfs3_btree_t *gbmap) { while (true) { lfs3_block_t weight__; lfs3_stag_t tag__ = lfs3_gbmap_lookupnext(lfs3, gbmap, block__+1, - &block__, &weight__); + &block__, &weight__, NULL); if (tag__ < 0) { if (tag__ == LFS3_ERR_NOENT) { break; @@ -10753,8 +10819,8 @@ static int lfs3_gbmap_zero(lfs3_t *lfs3, lfs3_btree_t *gbmap) { // mark in-use/erased ranges as free if (tag__ == LFS3_TAG_BMINUSE || tag__ == LFS3_TAG_BMERASED) { - int err = lfs3_gbmap_mark_(lfs3, gbmap, block__, weight__, - LFS3_TAG_BMFREE); + int err = lfs3_gbmap_mark_(lfs3, gbmap, + block__, weight__, LFS3_TAG_BMFREE, NULL); if (err) { return err; } @@ -10826,8 +10892,8 @@ static inline int lfs3_alloc_ckpoint(lfs3_t *lfs3) { #endif // can we repopulate the lookahead buffer? -#ifndef LFS3_RDONLY static inline bool lfs3_alloc_canlookahead(const lfs3_t *lfs3) { + #ifndef LFS3_RDONLY // below gc_lookahead_thresh? return lfs3_max( lfs3->lookahead.known, @@ -10844,13 +10910,16 @@ static inline bool lfs3_alloc_canlookahead(const lfs3_t *lfs3) { lfs3_min( 8*lfs3->cfg->lookahead_size-1, lfs3->block_count-1)); + #else + // TODO adopt this localized void in flag functions? + (void)lfs3; + return false; + #endif } -#endif // can we repopulate the gbmap? -#ifndef LFS3_RDONLY static inline bool lfs3_alloc_canlookgbmap(const lfs3_t *lfs3) { - #ifdef LFS3_GBMAP + #if !defined(LFS3_RDONLY) && defined(LFS3_GBMAP) // do we even have a gbmap? return lfs3_f_isgbmap(lfs3->flags) // below gc_lookgbmap_thresh? @@ -10866,7 +10935,26 @@ static inline bool lfs3_alloc_canlookgbmap(const lfs3_t *lfs3) { return false; #endif } -#endif + +// can we pre-erase? +static inline bool lfs3_alloc_canpreerase(const lfs3_t *lfs3) { + #if !defined(LFS3_RDONLY) \ + && defined(LFS3_GBMAP) \ + && !defined(LFS3_NO_PREERASE) + // do we even have a gbmap? + return lfs3_f_isgbmap(lfs3->flags) + // have we pre-erased enough blocks? + && lfs3->gbmap.preeraser.count + < lfs3->cfg->gc_preerase_count + // are there any more blocks in our known window? + && lfs3->gbmap.preeraser.known + < lfs3->gbmap.known; + #else + // TODO adopt this localized void in flag functions? + (void)lfs3; + return false; + #endif +} // discard any lookahead/gbmap windows, this is necessary if block_count // changes @@ -11030,7 +11118,7 @@ static lfs3_sblock_t lfs3_alloc_findfree(lfs3_t *lfs3) { lfs3_block_t block; lfs3_stag_t tag = lfs3_gbmap_lookupnext(lfs3, &lfs3->gbmap.b, lfs3->gbmap.window, - &block, NULL); + &block, NULL, NULL); if (tag < 0) { return tag; } @@ -11172,6 +11260,7 @@ static lfs3_sblock_t lfs3_alloc(lfs3_t *lfs3, uint32_t flags) { } #endif +// rebuild the gbmap #if !defined(LFS3_RDONLY) && defined(LFS3_GBMAP) static int lfs3_alloc_lookgbmap(lfs3_t *lfs3) { LFS3_INFO("Repopulating gbmap (gbmap %"PRId32"/%"PRId32")", @@ -11225,6 +11314,66 @@ static int lfs3_alloc_lookgbmap(lfs3_t *lfs3) { } #endif +// try to pre-erase _one_ block +#if !defined(LFS3_RDONLY) && defined(LFS3_GBMAP) && !defined(LFS3_NO_PREERASE) +static int lfs3_alloc_preerase(lfs3_t *lfs3) { + while (lfs3->gbmap.preeraser.known < lfs3->gbmap.known) { + // lookup next known block + lfs3_block_t block = (lfs3->gbmap.window + lfs3->gbmap.preeraser.known) + % lfs3->cfg->block_count; + lfs3_bid_t block__; + lfs3_stag_t tag__ = lfs3_gbmap_lookupnext(lfs3, &lfs3->gbmap.b, block, + &block__, NULL, NULL); + if (tag__ < 0) { + LFS3_ASSERT(tag__ != LFS3_ERR_NOENT); + return tag__; + } + + // not free? + if (tag__ != LFS3_TAG_BMFREE) { + lfs3_sblock_t d = lfs3_min( + block__+1 - block, + lfs3->gbmap.known - lfs3->gbmap.preeraser.known); + // wait, already erased? + if (tag__ == LFS3_TAG_BMERASED) { + lfs3->gbmap.preeraser.count += d; + } + lfs3->gbmap.preeraser.known += d; + continue; + } + + // erase! + int err = lfs3_bd_erase(lfs3, block); + if (err) { + return err; + } + + // calculate erased-state checksum + lfs3_ecksum_t ecksum; + err = lfs3_ecksum_eck(lfs3, &ecksum, block, 0); + if (err) { + return err; + } + + // commit into gbmap + // + // this relies on lfs3_gbmap_commit being atomic + err = lfs3_gbmap_mark(lfs3, &lfs3->gbmap.b, + block, LFS3_TAG_BMERASED, &ecksum); + if (err) { + return err; + } + + // successful pre-erase + lfs3->gbmap.preeraser.count += 1; + lfs3->gbmap.preeraser.known += 1; + return 0; + } + + return LFS3_ERR_NOENT; +} +#endif + @@ -14870,6 +15019,9 @@ static int lfs3_init(lfs3_t *lfs3, uint32_t flags, LFS3_ASSERT((lfs3->cfg->gc_flags & ~( LFS3_IFDEF_RDONLY(0, LFS3_GC_MKCONSISTENT) | LFS3_IFDEF_RDONLY(0, LFS3_GC_LOOKAHEAD) + | LFS3_IFDEF_RDONLY(0, + LFS3_IFDEF_GBMAP( + LFS3_IFDEF_PREERASE(LFS3_GC_PREERASE, 0), 0)) | LFS3_IFDEF_RDONLY(0, LFS3_GC_COMPACT) | LFS3_GC_CKMETA | LFS3_GC_CKDATA)) == 0); @@ -14898,8 +15050,9 @@ static int lfs3_init(lfs3_t *lfs3, uint32_t flags, | LFS3_IFDEF_RDONLY(0, LFS3_I_MKCONSISTENT) // default to lookaheadable | LFS3_IFDEF_RDONLY(0, LFS3_I_LOOKAHEAD) - // default to assuming we need compaction somewhere, worst case - // this just makes lfs3_fs_gc read more than is strictly needed + // default to assuming we need compaction somewhere, worst + // case this just makes lfs3_fs_gc read more than is + // strictly needed | LFS3_IFDEF_RDONLY(0, LFS3_I_COMPACT) // default to needing a ckmeta/ckdata scan | LFS3_I_CKMETA @@ -15728,6 +15881,9 @@ int lfs3_mount(lfs3_t *lfs3, uint32_t flags, #ifdef LFS3_YES_LOOKAHEAD flags |= LFS3_M_LOOKAHEAD; #endif + #ifdef LFS3_YES_PREERASE + flags |= LFS3_M_PREERASE; + #endif #ifdef LFS3_YES_COMPACT flags |= LFS3_M_COMPACT; #endif @@ -15752,12 +15908,16 @@ int lfs3_mount(lfs3_t *lfs3, uint32_t flags, | LFS3_IFDEF_CKDATACKSUMS(LFS3_M_CKDATACKSUMS, 0) | LFS3_IFDEF_RDONLY(0, LFS3_M_MKCONSISTENT) | LFS3_IFDEF_RDONLY(0, LFS3_M_LOOKAHEAD) + | LFS3_IFDEF_RDONLY(0, + LFS3_IFDEF_GBMAP( + LFS3_IFDEF_PREERASE(LFS3_M_PREERASE, 0), 0)) | LFS3_IFDEF_RDONLY(0, LFS3_M_COMPACT) | LFS3_M_CKMETA | LFS3_M_CKDATA)) == 0); // these flags require a writable filesystem LFS3_ASSERT(!lfs3_m_isrdonly(flags) || !lfs3_t_ismkconsistent(flags)); LFS3_ASSERT(!lfs3_m_isrdonly(flags) || !lfs3_t_islookahead(flags)); + LFS3_ASSERT(!lfs3_m_isrdonly(flags) || !lfs3_t_ispreerase(flags)); LFS3_ASSERT(!lfs3_m_isrdonly(flags) || !lfs3_t_compact(flags)); int err = lfs3_init(lfs3, @@ -15786,12 +15946,18 @@ int lfs3_mount(lfs3_t *lfs3, uint32_t flags, if (flags & ( LFS3_IFDEF_RDONLY(0, LFS3_GC_MKCONSISTENT) | LFS3_IFDEF_RDONLY(0, LFS3_GC_LOOKAHEAD) + | LFS3_IFDEF_RDONLY(0, + LFS3_IFDEF_GBMAP( + LFS3_IFDEF_PREERASE(LFS3_GC_PREERASE, 0), 0)) | LFS3_IFDEF_RDONLY(0, LFS3_GC_COMPACT) | LFS3_GC_CKMETA | LFS3_GC_CKDATA)) { err = lfs3_fs_ck(lfs3, flags & ( LFS3_IFDEF_RDONLY(0, LFS3_GC_MKCONSISTENT) | LFS3_IFDEF_RDONLY(0, LFS3_GC_LOOKAHEAD) + | LFS3_IFDEF_RDONLY(0, + LFS3_IFDEF_GBMAP( + LFS3_IFDEF_PREERASE(LFS3_GC_PREERASE, 0), 0)) | LFS3_IFDEF_RDONLY(0, LFS3_GC_COMPACT) | LFS3_GC_CKMETA | LFS3_GC_CKDATA)); @@ -16036,6 +16202,9 @@ int lfs3_format(lfs3_t *lfs3, uint32_t flags, #ifdef LFS3_YES_LOOKAHEAD flags |= LFS3_F_LOOKAHEAD; #endif + #ifdef LFS3_YES_PREERASE + flags |= LFS3_F_PREERASE; + #endif #ifdef LFS3_YES_COMPACT flags |= LFS3_F_COMPACT; #endif @@ -16058,6 +16227,9 @@ int lfs3_format(lfs3_t *lfs3, uint32_t flags, | LFS3_IFDEF_CKDATACKSUMS(LFS3_F_CKDATACKSUMS, 0) | LFS3_F_MKCONSISTENT | LFS3_F_LOOKAHEAD + | LFS3_IFDEF_RDONLY(0, + LFS3_IFDEF_GBMAP( + LFS3_IFDEF_PREERASE(LFS3_F_PREERASE, 0), 0)) | LFS3_F_COMPACT | LFS3_F_CKMETA | LFS3_F_CKDATA)) == 0); @@ -16098,12 +16270,18 @@ int lfs3_format(lfs3_t *lfs3, uint32_t flags, if (flags & ( LFS3_IFDEF_RDONLY(0, LFS3_GC_MKCONSISTENT) | LFS3_IFDEF_RDONLY(0, LFS3_GC_LOOKAHEAD) + | LFS3_IFDEF_RDONLY(0, + LFS3_IFDEF_GBMAP( + LFS3_IFDEF_PREERASE(LFS3_GC_PREERASE, 0), 0)) | LFS3_IFDEF_RDONLY(0, LFS3_GC_COMPACT) | LFS3_GC_CKMETA | LFS3_GC_CKDATA)) { err = lfs3_fs_ck(lfs3, flags & ( LFS3_IFDEF_RDONLY(0, LFS3_GC_MKCONSISTENT) | LFS3_IFDEF_RDONLY(0, LFS3_GC_LOOKAHEAD) + | LFS3_IFDEF_RDONLY(0, + LFS3_IFDEF_GBMAP( + LFS3_IFDEF_PREERASE(LFS3_GC_PREERASE, 0), 0)) | LFS3_IFDEF_RDONLY(0, LFS3_GC_COMPACT) | LFS3_GC_CKMETA | LFS3_GC_CKDATA)); @@ -16150,7 +16328,16 @@ int lfs3_fs_stat(lfs3_t *lfs3, struct lfs3_fsinfo *fsinfo) { | LFS3_IFDEF_RDONLY(0, (lfs3_grm_count(lfs3) > 0) ? LFS3_I_MKCONSISTENT - : 0); + : 0) + // LFS3_I_PREERASE we're just lazy about, since it depends + // on both gc_preerase_count and preerase vs free known + // windows + | LFS3_IFDEF_RDONLY(0, + LFS3_IFDEF_GBMAP( + LFS3_IFDEF_PREERASE( + (lfs3_alloc_canpreerase(lfs3)) + ? LFS3_I_PREERASE + : 0, 0), 0)); // return filesystem config, this may come from disk fsinfo->block_size = lfs3->cfg->block_size; @@ -16354,7 +16541,7 @@ int lfs3_fs_mkconsistent(lfs3_t *lfs3) { static int lfs3_fs_gc_(lfs3_t *lfs3, lfs3_mgc_t *mgc, uint32_t flags, lfs3_soff_t steps) { while ((lfs3_off_t)steps > 0) { - // do we have any pending work? + // do we have any pending gc work? uint32_t pending = flags & ( (lfs3->flags & ( LFS3_IFDEF_RDONLY(0, LFS3_GC_MKCONSISTENT) @@ -16367,73 +16554,95 @@ static int lfs3_fs_gc_(lfs3_t *lfs3, lfs3_mgc_t *mgc, (lfs3_grm_count(lfs3) > 0) ? LFS3_GC_MKCONSISTENT : 0)); - if (!pending) { + if (pending) { + // prioritize lookahead/gbmap before any work that may need + // to allocate + #ifndef LFS3_RDONLY + if (lfs3_t_islookahead(pending)) { + pending &= ~( + LFS3_GC_MKCONSISTENT + | LFS3_GC_COMPACT); + } + #endif + + // start a new traversal? + if (!lfs3_handle_isopen(lfs3, &mgc->t.h)) { + lfs3_mgc_init(mgc, pending); + lfs3_handle_open(lfs3, &mgc->t.h); + } + // mask out any flags that changed + // + // note that even though our current API prevents flags from + // changing mid-traversal, lfs3->flags can be updated by + // other operations + mgc->t.h.flags &= ~(pending ^ ( + LFS3_IFDEF_RDONLY(0, LFS3_GC_MKCONSISTENT) + | LFS3_IFDEF_RDONLY(0, LFS3_GC_LOOKAHEAD) + | LFS3_IFDEF_RDONLY(0, LFS3_GC_COMPACT) + | LFS3_GC_CKMETA + | LFS3_GC_CKDATA)); + + // will this traversal still make progress? no? start over + if (!(mgc->t.h.flags + & (LFS3_IFDEF_RDONLY(0, LFS3_GC_MKCONSISTENT) + | LFS3_IFDEF_RDONLY(0, LFS3_GC_LOOKAHEAD) + | LFS3_IFDEF_RDONLY(0, LFS3_GC_COMPACT) + | LFS3_GC_CKMETA + | LFS3_GC_CKDATA) + // don't bother with lookahead/gbmap if we've + // ckpointed + & ~LFS3_IFDEF_RDONLY( + 0, + (lfs3_t_isckpointed(mgc->t.h.flags)) + ? LFS3_GC_LOOKAHEAD + : 0))) { + lfs3_handle_close(lfs3, &mgc->t.h); + continue; + } + + // do we really need a full traversal? + if (!(mgc->t.h.flags & ( + LFS3_IFDEF_RDONLY(0, LFS3_GC_LOOKAHEAD) + | LFS3_GC_CKMETA + | LFS3_GC_CKDATA))) { + mgc->t.h.flags |= LFS3_T_MTREEONLY; + } + + // progress gc + lfs3_bptr_t bptr; + lfs3_stag_t tag = lfs3_mtree_gc(lfs3, mgc, + &bptr); + if (tag < 0 && tag != LFS3_ERR_NOENT) { + lfs3_handle_close(lfs3, &mgc->t.h); + return tag; + } + + // end of traversal? + if (tag == LFS3_ERR_NOENT) { + lfs3_handle_close(lfs3, &mgc->t.h); + } + + // if we have no pending gc work, can we preerase blocks? + } else if (lfs3_alloc_canpreerase(lfs3)) { + #if !defined(LFS3_RDONLY) \ + && defined(LFS3_GBMAP) \ + && !defined(LFS3_NO_PREERASE) + int err = lfs3_alloc_preerase(lfs3); + if (err && err != LFS3_ERR_NOENT) { + return err; + } + #endif + + // TODO do this + // // if we have nothing else to do, try to commit the gbmap to + // // disk so it's recoverable if we lose power + // } else if (lfs3_btree_cmp(&lfs3->gbmap.b, &lfs3->gbmap.b_p) != 0) { + // // TODO + + // nothing to do at all? guess we're done + } else { break; } - // prioritize lookahead/gbmap before any work that may need to - // allocate - #ifndef LFS3_RDONLY - if (lfs3_t_islookahead(pending)) { - pending &= ~( - LFS3_GC_MKCONSISTENT - | LFS3_GC_COMPACT); - } - #endif - - // start a new traversal? - if (!lfs3_handle_isopen(lfs3, &mgc->t.h)) { - lfs3_mgc_init(mgc, pending); - lfs3_handle_open(lfs3, &mgc->t.h); - } - // mask out any flags that changed - // - // note that even though our current API prevents flags from - // changing mid-traversal, lfs3->flags can be updated by other - // operations - mgc->t.h.flags &= ~(pending ^ ( - LFS3_IFDEF_RDONLY(0, LFS3_GC_MKCONSISTENT) - | LFS3_IFDEF_RDONLY(0, LFS3_GC_LOOKAHEAD) - | LFS3_IFDEF_RDONLY(0, LFS3_GC_COMPACT) - | LFS3_GC_CKMETA - | LFS3_GC_CKDATA)); - - // will this traversal still make progress? no? start over - if (!(mgc->t.h.flags - & (LFS3_IFDEF_RDONLY(0, LFS3_GC_MKCONSISTENT) - | LFS3_IFDEF_RDONLY(0, LFS3_GC_LOOKAHEAD) - | LFS3_IFDEF_RDONLY(0, LFS3_GC_COMPACT) - | LFS3_GC_CKMETA - | LFS3_GC_CKDATA) - // don't bother with lookahead/gbmap if we've ckpointed - & ~LFS3_IFDEF_RDONLY( - 0, - (lfs3_t_isckpointed(mgc->t.h.flags)) - ? LFS3_GC_LOOKAHEAD - : 0))) { - lfs3_handle_close(lfs3, &mgc->t.h); - continue; - } - - // do we really need a full traversal? - if (!(mgc->t.h.flags & ( - LFS3_IFDEF_RDONLY(0, LFS3_GC_LOOKAHEAD) - | LFS3_GC_CKMETA - | LFS3_GC_CKDATA))) { - mgc->t.h.flags |= LFS3_T_MTREEONLY; - } - - // progress gc - lfs3_bptr_t bptr; - lfs3_stag_t tag = lfs3_mtree_gc(lfs3, mgc, - &bptr); - if (tag < 0 && tag != LFS3_ERR_NOENT) { - lfs3_handle_close(lfs3, &mgc->t.h); - return tag; - } - // end of traversal? - if (tag == LFS3_ERR_NOENT) { - lfs3_handle_close(lfs3, &mgc->t.h); - } // decrement steps if (steps > 0) { @@ -16452,6 +16661,9 @@ int lfs3_fs_ck(lfs3_t *lfs3, uint32_t flags) { LFS3_ASSERT((flags & ~( LFS3_IFDEF_RDONLY(0, LFS3_CK_MKCONSISTENT) | LFS3_IFDEF_RDONLY(0, LFS3_CK_LOOKAHEAD) + | LFS3_IFDEF_RDONLY(0, + LFS3_IFDEF_GBMAP( + LFS3_IFDEF_PREERASE(LFS3_CK_PREERASE, 0), 0)) | LFS3_IFDEF_RDONLY(0, LFS3_CK_COMPACT) | LFS3_CK_CKMETA | LFS3_CK_CKDATA)) == 0); @@ -16460,6 +16672,8 @@ int lfs3_fs_ck(lfs3_t *lfs3, uint32_t flags) { || !lfs3_t_ismkconsistent(flags)); LFS3_ASSERT(!lfs3_m_isrdonly(lfs3->flags) || !lfs3_t_islookahead(flags)); + LFS3_ASSERT(!lfs3_m_isrdonly(lfs3->flags) + || !lfs3_t_ispreerase(flags)); LFS3_ASSERT(!lfs3_m_isrdonly(lfs3->flags) || !lfs3_t_compact(flags)); @@ -16481,6 +16695,9 @@ int lfs3_fs_gc(lfs3_t *lfs3) { LFS3_ASSERT((lfs3->cfg->gc_flags & ~( LFS3_IFDEF_RDONLY(0, LFS3_GC_MKCONSISTENT) | LFS3_IFDEF_RDONLY(0, LFS3_GC_LOOKAHEAD) + | LFS3_IFDEF_RDONLY(0, + LFS3_IFDEF_GBMAP( + LFS3_IFDEF_PREERASE(LFS3_GC_PREERASE, 0), 0)) | LFS3_IFDEF_RDONLY(0, LFS3_GC_COMPACT) | LFS3_GC_CKMETA | LFS3_GC_CKDATA)) == 0); @@ -16489,6 +16706,8 @@ int lfs3_fs_gc(lfs3_t *lfs3) { || !lfs3_t_ismkconsistent(lfs3->cfg->gc_flags)); LFS3_ASSERT(!lfs3_m_isrdonly(lfs3->flags) || !lfs3_t_islookahead(lfs3->cfg->gc_flags)); + LFS3_ASSERT(!lfs3_m_isrdonly(lfs3->flags) + || !lfs3_t_ispreerase(lfs3->cfg->gc_flags)); LFS3_ASSERT(!lfs3_m_isrdonly(lfs3->flags) || !lfs3_t_compact(lfs3->cfg->gc_flags)); @@ -16507,6 +16726,9 @@ int lfs3_fs_unck(lfs3_t *lfs3, uint32_t flags) { LFS3_ASSERT((flags & ~( LFS3_IFDEF_RDONLY(0, LFS3_GC_MKCONSISTENT) | LFS3_IFDEF_RDONLY(0, LFS3_GC_LOOKAHEAD) + | LFS3_IFDEF_RDONLY(0, + LFS3_IFDEF_GBMAP( + LFS3_IFDEF_PREERASE(LFS3_GC_PREERASE, 0), 0)) | LFS3_IFDEF_RDONLY(0, LFS3_GC_COMPACT) | LFS3_GC_CKMETA | LFS3_GC_CKDATA)) == 0); @@ -16571,7 +16793,7 @@ int lfs3_fs_grow(lfs3_t *lfs3, lfs3_size_t block_count_) { // need a new range lfs3_stag_t tag = lfs3_gbmap_lookupnext(lfs3, &lfs3->gbmap.b, block_count-1, - NULL, NULL); + NULL, NULL, NULL); if (tag < 0) { LFS3_ASSERT(tag != LFS3_ERR_NOENT); err = tag; @@ -16745,6 +16967,9 @@ int lfs3_trv_open(lfs3_t *lfs3, lfs3_trv_t *trv, uint32_t flags) { | LFS3_T_EXCL | LFS3_IFDEF_RDONLY(0, LFS3_T_MKCONSISTENT) | LFS3_IFDEF_RDONLY(0, LFS3_T_LOOKAHEAD) + | LFS3_IFDEF_RDONLY(0, + LFS3_IFDEF_GBMAP( + LFS3_IFDEF_PREERASE(LFS3_T_PREERASE, 0), 0)) | LFS3_IFDEF_RDONLY(0, LFS3_T_COMPACT) | LFS3_T_CKMETA | LFS3_T_CKDATA)) == 0); @@ -16753,6 +16978,7 @@ int lfs3_trv_open(lfs3_t *lfs3, lfs3_trv_t *trv, uint32_t flags) { // these flags require a writable traversal LFS3_ASSERT(!lfs3_t_isrdonly(flags) || !lfs3_t_ismkconsistent(flags)); LFS3_ASSERT(!lfs3_t_isrdonly(flags) || !lfs3_t_islookahead(flags)); + LFS3_ASSERT(!lfs3_t_isrdonly(flags) || !lfs3_t_ispreerase(flags)); LFS3_ASSERT(!lfs3_t_isrdonly(flags) || !lfs3_t_compact(flags)); // some flags don't make sense when only traversing the mtree LFS3_ASSERT(!lfs3_t_ismtreeonly(flags) || !lfs3_t_islookahead(flags)); diff --git a/lfs3.h b/lfs3.h index 27980b41..4f807f84 100644 --- a/lfs3.h +++ b/lfs3.h @@ -211,6 +211,9 @@ enum lfs3_type { #define LFS3_F_LOOKAHEAD \ 0x00000200 // Repopulate lookahead/gbmap #endif +#if !defined(LFS3_RDONLY) && defined(LFS3_GBMAP) && !defined(LFS3_NO_PREERASE) +#define LFS3_F_PREERASE 0x00000400 // Try to pre-erase free blocks +#endif #ifndef LFS3_RDONLY #define LFS3_F_COMPACT 0x00000800 // Compact metadata logs #endif @@ -228,6 +231,9 @@ enum lfs3_type { #define LFS3_F_GC ( \ LFS3_IFDEF_RDONLY(0, LFS3_F_MKCONSISTENT) \ | LFS3_IFDEF_RDONLY(0, LFS3_F_LOOKAHEAD) \ + | LFS3_IFDEF_RDONLY(0, \ + LFS3_IFDEF_GBMAP( \ + LFS3_IFDEF_PREERASE(LFS3_F_PREERASE, 0), 0)) \ | LFS3_IFDEF_RDONLY(0, LFS3_F_COMPACT) \ | LFS3_F_CKMETA \ | LFS3_F_CKDATA) @@ -269,6 +275,9 @@ enum lfs3_type { #define LFS3_M_LOOKAHEAD \ 0x00000200 // Repopulate lookahead/gbmap #endif +#if !defined(LFS3_RDONLY) && defined(LFS3_GBMAP) && !defined(LFS3_NO_PREERASE) +#define LFS3_M_PREERASE 0x00000400 // Try to pre-erase free blocks +#endif #ifndef LFS3_RDONLY #define LFS3_M_COMPACT 0x00000800 // Compact metadata logs #endif @@ -282,6 +291,9 @@ enum lfs3_type { #define LFS3_M_GC ( \ LFS3_IFDEF_RDONLY(0, LFS3_M_MKCONSISTENT) \ | LFS3_IFDEF_RDONLY(0, LFS3_M_LOOKAHEAD) \ + | LFS3_IFDEF_RDONLY(0, \ + LFS3_IFDEF_GBMAP( \ + LFS3_IFDEF_PREERASE(LFS3_M_PREERASE, 0), 0)) \ | LFS3_IFDEF_RDONLY(0, LFS3_M_COMPACT) \ | LFS3_M_CKMETA \ | LFS3_M_CKDATA) @@ -322,6 +334,9 @@ enum lfs3_type { #define LFS3_I_LOOKAHEAD \ 0x00000200 // Lookahead/gbmap is not full #endif +#if !defined(LFS3_RDONLY) && defined(LFS3_GBMAP) && !defined(LFS3_NO_PREERASE) +#define LFS3_I_PREERASE 0x00000400 // Blocks can be pre-erased +#endif #ifndef LFS3_RDONLY #define LFS3_I_COMPACT 0x00000800 // Filesystem may have uncompacted metadata #endif @@ -352,6 +367,9 @@ enum lfs3_btype { #define LFS3_T_LOOKAHEAD \ 0x00000200 // Repopulate lookahead/gbmap #endif +#if !defined(LFS3_RDONLY) && defined(LFS3_GBMAP) && !defined(LFS3_NO_PREERASE) +#define LFS3_T_PREERASE 0x00000400 // Try to pre-erase free blocks +#endif #ifndef LFS3_RDONLY #define LFS3_T_COMPACT 0x00000800 // Compact metadata logs #endif @@ -374,6 +392,9 @@ enum lfs3_btype { #define LFS3_T_GC ( \ LFS3_IFDEF_RDONLY(0, LFS3_T_MKCONSISTENT) \ | LFS3_IFDEF_RDONLY(0, LFS3_T_LOOKAHEAD) \ + | LFS3_IFDEF_RDONLY(0, \ + LFS3_IFDEF_GBMAP( \ + LFS3_IFDEF_PREERASE(LFS3_T_PREERASE, 0), 0)) \ | LFS3_IFDEF_RDONLY(0, LFS3_T_COMPACT) \ | LFS3_T_CKMETA \ | LFS3_T_CKDATA) @@ -387,6 +408,10 @@ enum lfs3_btype { #define LFS3_CK_LOOKAHEAD \ 0x00000200 // Repopulate lookahead/gbmap #endif +#if !defined(LFS3_RDONLY) && defined(LFS3_GBMAP) && !defined(LFS3_NO_PREERASE) +#define LFS3_CK_PREERASE \ + 0x00000400 // Try to pre-erase free blocks +#endif #ifndef LFS3_RDONLY #define LFS3_CK_COMPACT 0x00000800 // Compact metadata logs #endif @@ -400,6 +425,9 @@ enum lfs3_btype { #define LFS3_CK_GC ( \ LFS3_IFDEF_RDONLY(0, LFS3_CK_MKCONSISTENT) \ | LFS3_IFDEF_RDONLY(0, LFS3_CK_LOOKAHEAD) \ + | LFS3_IFDEF_RDONLY(0, \ + LFS3_IFDEF_GBMAP( \ + LFS3_IFDEF_PREERASE(LFS3_CK_PREERASE, 0), 0)) \ | LFS3_IFDEF_RDONLY(0, LFS3_CK_COMPACT) \ | LFS3_CK_CKMETA \ | LFS3_CK_CKDATA) @@ -413,6 +441,10 @@ enum lfs3_btype { #define LFS3_GC_LOOKAHEAD \ 0x00000200 // Repopulate lookahead/gbmap #endif +#if !defined(LFS3_RDONLY) && defined(LFS3_GBMAP) && !defined(LFS3_NO_PREERASE) +#define LFS3_GC_PREERASE \ + 0x00000400 // Try to pre-erase free blocks +#endif #ifndef LFS3_RDONLY #define LFS3_GC_COMPACT 0x00000800 // Compact metadata logs #endif @@ -426,6 +458,9 @@ enum lfs3_btype { #define LFS3_GC_GC ( \ LFS3_IFDEF_RDONLY(0, LFS3_GC_MKCONSISTENT) \ | LFS3_IFDEF_RDONLY(0, LFS3_GC_LOOKAHEAD) \ + | LFS3_IFDEF_RDONLY(0, \ + LFS3_IFDEF_GBMAP( \ + LFS3_IFDEF_PREERASE(LFS3_GC_PREERASE, 0), 0)) \ | LFS3_IFDEF_RDONLY(0, LFS3_GC_COMPACT) \ | LFS3_GC_CKMETA \ | LFS3_GC_CKDATA) @@ -577,6 +612,22 @@ struct lfs3_cfg { lfs3_block_t gc_lookgbmap_thresh; #endif + // Number of blocks to try to pre-erase during gc. When erase is + // expensive (flash), pre-erasing blocks can help reduce the latency + // of block allocation. + // + // Requires the gbmap to track pre-erased blocks. + // + // 0 only erases blocks immediately before prog, while -1 or any + // value >= block_count attempts to pre-erase all known free blocks + // during gc. + // + #if !defined(LFS3_RDONLY) \ + && defined(LFS3_GBMAP) \ + && !defined(LFS3_NO_PREERASE) + lfs3_block_t gc_preerase_count; + #endif + // Threshold for metadata compaction during gc in bytes. // // Metadata logs that exceed this threshold will be compacted during @@ -1083,6 +1134,15 @@ typedef struct lfs3_bptr { #endif } lfs3_bptr_t; +// erased-state checksum +#ifndef LFS3_RDONLY +typedef struct lfs3_ecksum { + // cksize=-1 indicates no ecksum + lfs3_ssize_t cksize; + uint32_t cksum; +} lfs3_ecksum_t; +#endif + // littlefs's core metadata log type typedef struct lfs3_rbyd { lfs3_rid_t weight; @@ -1317,7 +1377,16 @@ typedef struct lfs3 { struct lfs3_gbmap { lfs3_block_t window; lfs3_block_t known; + #if !defined(LFS3_RDONLY) lfs3_sblock_t free; + #endif + #if !defined(LFS3_RDONLY) && !defined(LFS3_NO_PREERASE) + lfs3_ecksum_t ecksum; + struct lfs3_preeraser { + lfs3_block_t known; + lfs3_block_t count; + } preeraser; + #endif lfs3_btree_t b; lfs3_btree_t b_p; } gbmap; diff --git a/lfs3_util.h b/lfs3_util.h index 068dc8d0..f9621121 100644 --- a/lfs3_util.h +++ b/lfs3_util.h @@ -276,6 +276,12 @@ #define LFS3_IFDEF_YES_GBMAP(a, b) (b) #endif +#ifndef LFS3_NO_PREERASE +#define LFS3_IFDEF_PREERASE(a, b) (a) +#else +#define LFS3_IFDEF_PREERASE(a, b) (b) +#endif + #ifdef LFS3_BLEAFCACHE #define LFS3_IFDEF_BLEAFCACHE(a, b) (a) #else diff --git a/runners/bench_runner.h b/runners/bench_runner.h index 075b6cd6..dba06bbb 100644 --- a/runners/bench_runner.h +++ b/runners/bench_runner.h @@ -118,6 +118,7 @@ void bench_permutation(size_t i, uint32_t *buffer, size_t size); BENCH_DEFINE(GC_STEPS, 0 ) \ BENCH_DEFINE(GC_LOOKAHEAD_THRESH, -1 ) \ BENCH_DEFINE(GC_LOOKGBMAP_THRESH, -1 ) \ + BENCH_DEFINE(GC_PREERASE_COUNT, -1 ) \ BENCH_DEFINE(GC_COMPACT_THRESH, 0 ) \ BENCH_DEFINE(SHRUB_SIZE, BLOCK_SIZE/4 ) \ BENCH_DEFINE(FRAGMENT_SIZE, LFS3_MIN(BLOCK_SIZE/8, 512) ) \ @@ -148,6 +149,7 @@ void bench_permutation(size_t i, uint32_t *buffer, size_t size); .fcache_size = FCACHE_SIZE, \ .lookahead_size = LOOKAHEAD_SIZE, \ BENCH_GBMAP_CFG \ + BENCH_PREERASE_CFG \ BENCH_GC_CFG \ .gc_lookahead_thresh = GC_LOOKAHEAD_THRESH, \ .gc_compact_thresh = GC_COMPACT_THRESH, \ @@ -163,6 +165,13 @@ void bench_permutation(size_t i, uint32_t *buffer, size_t size); #define BENCH_GBMAP_CFG #endif +#if defined(LFS3_GBMAP) && !defined(LFS3_NO_PREERASE) +#define BENCH_PREERASE_CFG \ + .gc_preerase_count = GC_PREERASE_COUNT, +#else +#define BENCH_PREERASE_CFG +#endif + #ifdef LFS3_GC #define BENCH_GC_CFG \ .gc_flags = GC_FLAGS, \ diff --git a/runners/test_runner.h b/runners/test_runner.h index db48f4ba..488abb06 100644 --- a/runners/test_runner.h +++ b/runners/test_runner.h @@ -109,6 +109,7 @@ void test_permutation(size_t i, uint32_t *buffer, size_t size); TEST_DEFINE(GC_STEPS, 0 ) \ TEST_DEFINE(GC_LOOKAHEAD_THRESH, -1 ) \ TEST_DEFINE(GC_LOOKGBMAP_THRESH, -1 ) \ + TEST_DEFINE(GC_PREERASE_COUNT, -1 ) \ TEST_DEFINE(GC_COMPACT_THRESH, 0 ) \ TEST_DEFINE(SHRUB_SIZE, BLOCK_SIZE/4 ) \ TEST_DEFINE(FRAGMENT_SIZE, LFS3_MIN(BLOCK_SIZE/8, 512) ) \ @@ -139,6 +140,7 @@ void test_permutation(size_t i, uint32_t *buffer, size_t size); .fcache_size = FCACHE_SIZE, \ .lookahead_size = LOOKAHEAD_SIZE, \ TEST_GBMAP_CFG \ + TEST_PREERASE_CFG \ TEST_GC_CFG \ .gc_lookahead_thresh = GC_LOOKAHEAD_THRESH, \ .gc_compact_thresh = GC_COMPACT_THRESH, \ @@ -154,6 +156,13 @@ void test_permutation(size_t i, uint32_t *buffer, size_t size); #define TEST_GBMAP_CFG #endif +#if defined(LFS3_GBMAP) && !defined(LFS3_NO_PREERASE) +#define TEST_PREERASE_CFG \ + .gc_preerase_count = GC_PREERASE_COUNT, +#else +#define TEST_PREERASE_CFG +#endif + #ifdef LFS3_GC #define TEST_GC_CFG \ .gc_flags = GC_FLAGS, \ diff --git a/scripts/dbgflags.py b/scripts/dbgflags.py index 19172cbf..db0490bd 100755 --- a/scripts/dbgflags.py +++ b/scripts/dbgflags.py @@ -84,11 +84,12 @@ F_CKDATACKSUMS = 0x01000000 # y- Check data checksums on reads F_MKCONSISTENT = 0x00000100 # y- Make the filesystem consistent F_LOOKAHEAD = 0x00000200 # y- Repopulate lookahead buffer +F_PREERASE = 0x00000400 # y- Try to pre-erase free blocks F_COMPACT = 0x00000800 # y- Compact metadata logs F_CKMETA = 0x00001000 # y- Check metadata checksums F_CKDATA = 0x00002000 # y- Check metadata + data checksums F_CK = 0x00003000 # a- Alias for all check work -F_GC = 0x00003b00 # a- Alias for all gc work +F_GC = 0x00003f00 # a- Alias for all gc work # Filesystem mount flags M_MODE = 1 # -m Mount's access mode @@ -105,29 +106,32 @@ M_CKDATACKSUMS = 0x01000000 # y- Check data checksums on reads M_MKCONSISTENT = 0x00000100 # y- Make the filesystem consistent M_LOOKAHEAD = 0x00000200 # y- Repopulate lookahead buffer +M_PREERASE = 0x00000400 # y- Try to pre-erase free blocks M_COMPACT = 0x00000800 # y- Compact metadata logs M_CKMETA = 0x00001000 # y- Check metadata checksums M_CKDATA = 0x00002000 # y- Check metadata + data checksums M_CK = 0x00003000 # a- Alias for all check work -M_GC = 0x00003b00 # a- Alias for all gc work +M_GC = 0x00003f00 # a- Alias for all gc work # File/filesystem check flags CK_MKCONSISTENT = 0x00000100 # -- Make the filesystem consistent CK_LOOKAHEAD = 0x00000200 # -- Repopulate lookahead buffer +CK_PREERASE = 0x00000400 # -- Try to pre-erase free blocks CK_COMPACT = 0x00000800 # -- Compact metadata logs CK_CKMETA = 0x00001000 # -- Check metadata checksums CK_CKDATA = 0x00002000 # -- Check metadata + data checksums CK_CK = 0x00003000 # a- Alias for all check work -CK_GC = 0x00003b00 # a- Alias for all gc work +CK_GC = 0x00003f00 # a- Alias for all gc work # GC flags GC_MKCONSISTENT = 0x00000100 # -- Make the filesystem consistent GC_LOOKAHEAD = 0x00000200 # -- Repopulate lookahead buffer +GC_PREERASE = 0x00000400 # -- Try to pre-erase free blocks GC_COMPACT = 0x00000800 # -- Compact metadata logs GC_CKMETA = 0x00001000 # -- Check metadata checksums GC_CKDATA = 0x00002000 # -- Check metadata + data checksums GC_CK = 0x00003000 # a- Alias for all check work -GC_GC = 0x00003b00 # a- Alias for all gc work +GC_GC = 0x00003f00 # a- Alias for all gc work # Filesystem info flags I_RDONLY = 0x00000001 # -- Mounted read only @@ -144,6 +148,7 @@ I_CKDATACKSUMS = 0x01000000 # -- Mounted with LFS3_M_CKDATACKSUMS I_MKCONSISTENT = 0x00000100 # -- Filesystem needs mkconsistent to write I_LOOKAHEAD = 0x00000200 # -- Lookahead buffer is not full +I_PREERASE = 0x00000400 # -- Blocks can be pre-erased I_COMPACT = 0x00000800 # -- Filesystem may have uncompacted metadata I_CKMETA = 0x00001000 # -- Metadata checksums not checked recently I_CKDATA = 0x00002000 # -- Data checksums not checked recently @@ -156,11 +161,12 @@ T_MTREEONLY = 0x00000002 # -- Only traverse the mtree T_EXCL = 0x00000008 # -- Error if filesystem modified T_MKCONSISTENT = 0x00000100 # -- Make the filesystem consistent T_LOOKAHEAD = 0x00000200 # -- Repopulate lookahead buffer +T_PREERASE = 0x00000400 # -- Try to pre-erase free blocks T_COMPACT = 0x00000800 # -- Compact metadata logs T_CKMETA = 0x00001000 # -- Check metadata checksums T_CKDATA = 0x00002000 # -- Check metadata + data checksums T_CK = 0x00003000 # a- Alias for all check work -T_GC = 0x00003b00 # a- Alias for all gc work +T_GC = 0x00003f00 # a- Alias for all gc work t_TYPE = 0xf0000000 # im The traversal's type t_REG = 0x10000000 # i^ Type = regular-file diff --git a/tests/test_gbmap.toml b/tests/test_gbmap.toml index 1c42b13f..f3565159 100644 --- a/tests/test_gbmap.toml +++ b/tests/test_gbmap.toml @@ -29,9 +29,9 @@ code = ''' LFS3_RATTR_NULL)) => 0; // set some blocks as in-use, avoid any weird merges for now - lfs3_gbmap_mark(&lfs3, &gbmap, 6, LFS3_TAG_BMINUSE) => 0; - lfs3_gbmap_mark(&lfs3, &gbmap, 8, LFS3_TAG_BMINUSE) => 0; - lfs3_gbmap_mark(&lfs3, &gbmap, 10, LFS3_TAG_BMINUSE) => 0; + lfs3_gbmap_mark(&lfs3, &gbmap, 6, LFS3_TAG_BMINUSE, NULL) => 0; + lfs3_gbmap_mark(&lfs3, &gbmap, 8, LFS3_TAG_BMINUSE, NULL) => 0; + lfs3_gbmap_mark(&lfs3, &gbmap, 10, LFS3_TAG_BMINUSE, NULL) => 0; printf("gbmap: w%d 0x%x.%x\n", gbmap.r.weight, gbmap.r.blocks[0], @@ -44,31 +44,31 @@ code = ''' lfs3_bid_t bid_; lfs3_bid_t weight_; lfs3_gbmap_lookupnext(&lfs3, &gbmap, 0, - &bid_, &weight_) => LFS3_TAG_BMFREE; + &bid_, &weight_, NULL) => LFS3_TAG_BMFREE; assert(bid_ == 5); assert(weight_ == 6); lfs3_gbmap_lookupnext(&lfs3, &gbmap, 6, - &bid_, &weight_) => LFS3_TAG_BMINUSE; + &bid_, &weight_, NULL) => LFS3_TAG_BMINUSE; assert(bid_ == 6); assert(weight_ == 1); lfs3_gbmap_lookupnext(&lfs3, &gbmap, 7, - &bid_, &weight_) => LFS3_TAG_BMFREE; + &bid_, &weight_, NULL) => LFS3_TAG_BMFREE; assert(bid_ == 7); assert(weight_ == 1); lfs3_gbmap_lookupnext(&lfs3, &gbmap, 8, - &bid_, &weight_) => LFS3_TAG_BMINUSE; + &bid_, &weight_, NULL) => LFS3_TAG_BMINUSE; assert(bid_ == 8); assert(weight_ == 1); lfs3_gbmap_lookupnext(&lfs3, &gbmap, 9, - &bid_, &weight_) => LFS3_TAG_BMFREE; + &bid_, &weight_, NULL) => LFS3_TAG_BMFREE; assert(bid_ == 9); assert(weight_ == 1); lfs3_gbmap_lookupnext(&lfs3, &gbmap, 10, - &bid_, &weight_) => LFS3_TAG_BMINUSE; + &bid_, &weight_, NULL) => LFS3_TAG_BMINUSE; assert(bid_ == 10); assert(weight_ == 1); lfs3_gbmap_lookupnext(&lfs3, &gbmap, 11, - &bid_, &weight_) => LFS3_TAG_BMFREE; + &bid_, &weight_, NULL) => LFS3_TAG_BMFREE; assert(bid_ == BLOCK_COUNT-1); assert(weight_ == BLOCK_COUNT-11); @@ -92,13 +92,13 @@ code = ''' LFS3_RATTR_NULL)) => 0; // set some blocks as in-use, avoid any weird merges for now - lfs3_gbmap_mark(&lfs3, &gbmap, 6, LFS3_TAG_BMINUSE) => 0; - lfs3_gbmap_mark(&lfs3, &gbmap, 8, LFS3_TAG_BMINUSE) => 0; - lfs3_gbmap_mark(&lfs3, &gbmap, 10, LFS3_TAG_BMINUSE) => 0; + lfs3_gbmap_mark(&lfs3, &gbmap, 6, LFS3_TAG_BMINUSE, NULL) => 0; + lfs3_gbmap_mark(&lfs3, &gbmap, 8, LFS3_TAG_BMINUSE, NULL) => 0; + lfs3_gbmap_mark(&lfs3, &gbmap, 10, LFS3_TAG_BMINUSE, NULL) => 0; // replace those blocks as bad, this tests deleting ranges - lfs3_gbmap_mark(&lfs3, &gbmap, 6, LFS3_TAG_BMBAD) => 0; - lfs3_gbmap_mark(&lfs3, &gbmap, 8, LFS3_TAG_BMBAD) => 0; - lfs3_gbmap_mark(&lfs3, &gbmap, 10, LFS3_TAG_BMBAD) => 0; + lfs3_gbmap_mark(&lfs3, &gbmap, 6, LFS3_TAG_BMBAD, NULL) => 0; + lfs3_gbmap_mark(&lfs3, &gbmap, 8, LFS3_TAG_BMBAD, NULL) => 0; + lfs3_gbmap_mark(&lfs3, &gbmap, 10, LFS3_TAG_BMBAD, NULL) => 0; printf("gbmap: w%d 0x%x.%x\n", gbmap.r.weight, gbmap.r.blocks[0], @@ -111,31 +111,31 @@ code = ''' lfs3_bid_t bid_; lfs3_bid_t weight_; lfs3_gbmap_lookupnext(&lfs3, &gbmap, 0, - &bid_, &weight_) => LFS3_TAG_BMFREE; + &bid_, &weight_, NULL) => LFS3_TAG_BMFREE; assert(bid_ == 5); assert(weight_ == 6); lfs3_gbmap_lookupnext(&lfs3, &gbmap, 6, - &bid_, &weight_) => LFS3_TAG_BMBAD; + &bid_, &weight_, NULL) => LFS3_TAG_BMBAD; assert(bid_ == 6); assert(weight_ == 1); lfs3_gbmap_lookupnext(&lfs3, &gbmap, 7, - &bid_, &weight_) => LFS3_TAG_BMFREE; + &bid_, &weight_, NULL) => LFS3_TAG_BMFREE; assert(bid_ == 7); assert(weight_ == 1); lfs3_gbmap_lookupnext(&lfs3, &gbmap, 8, - &bid_, &weight_) => LFS3_TAG_BMBAD; + &bid_, &weight_, NULL) => LFS3_TAG_BMBAD; assert(bid_ == 8); assert(weight_ == 1); lfs3_gbmap_lookupnext(&lfs3, &gbmap, 9, - &bid_, &weight_) => LFS3_TAG_BMFREE; + &bid_, &weight_, NULL) => LFS3_TAG_BMFREE; assert(bid_ == 9); assert(weight_ == 1); lfs3_gbmap_lookupnext(&lfs3, &gbmap, 10, - &bid_, &weight_) => LFS3_TAG_BMBAD; + &bid_, &weight_, NULL) => LFS3_TAG_BMBAD; assert(bid_ == 10); assert(weight_ == 1); lfs3_gbmap_lookupnext(&lfs3, &gbmap, 11, - &bid_, &weight_) => LFS3_TAG_BMFREE; + &bid_, &weight_, NULL) => LFS3_TAG_BMFREE; assert(bid_ == BLOCK_COUNT-1); assert(weight_ == BLOCK_COUNT-11); @@ -159,14 +159,14 @@ code = ''' LFS3_RATTR_NULL)) => 0; // set some blocks as in-use, avoid any weird merges for now - lfs3_gbmap_mark(&lfs3, &gbmap, 6, LFS3_TAG_BMINUSE) => 0; - lfs3_gbmap_mark(&lfs3, &gbmap, 8, LFS3_TAG_BMINUSE) => 0; - lfs3_gbmap_mark(&lfs3, &gbmap, 10, LFS3_TAG_BMINUSE) => 0; - lfs3_gbmap_mark(&lfs3, &gbmap, 12, LFS3_TAG_BMINUSE) => 0; + lfs3_gbmap_mark(&lfs3, &gbmap, 6, LFS3_TAG_BMINUSE, NULL) => 0; + lfs3_gbmap_mark(&lfs3, &gbmap, 8, LFS3_TAG_BMINUSE, NULL) => 0; + lfs3_gbmap_mark(&lfs3, &gbmap, 10, LFS3_TAG_BMINUSE, NULL) => 0; + lfs3_gbmap_mark(&lfs3, &gbmap, 12, LFS3_TAG_BMINUSE, NULL) => 0; // set neighboring blocks as in-use, triggering merges - lfs3_gbmap_mark(&lfs3, &gbmap, 5, LFS3_TAG_BMINUSE) => 0; - lfs3_gbmap_mark(&lfs3, &gbmap, 9, LFS3_TAG_BMINUSE) => 0; - lfs3_gbmap_mark(&lfs3, &gbmap, 13, LFS3_TAG_BMINUSE) => 0; + lfs3_gbmap_mark(&lfs3, &gbmap, 5, LFS3_TAG_BMINUSE, NULL) => 0; + lfs3_gbmap_mark(&lfs3, &gbmap, 9, LFS3_TAG_BMINUSE, NULL) => 0; + lfs3_gbmap_mark(&lfs3, &gbmap, 13, LFS3_TAG_BMINUSE, NULL) => 0; printf("gbmap: w%d 0x%x.%x\n", gbmap.r.weight, gbmap.r.blocks[0], @@ -179,31 +179,31 @@ code = ''' lfs3_bid_t bid_; lfs3_bid_t weight_; lfs3_gbmap_lookupnext(&lfs3, &gbmap, 0, - &bid_, &weight_) => LFS3_TAG_BMFREE; + &bid_, &weight_, NULL) => LFS3_TAG_BMFREE; assert(bid_ == 4); assert(weight_ == 5); lfs3_gbmap_lookupnext(&lfs3, &gbmap, 5, - &bid_, &weight_) => LFS3_TAG_BMINUSE; + &bid_, &weight_, NULL) => LFS3_TAG_BMINUSE; assert(bid_ == 6); assert(weight_ == 2); lfs3_gbmap_lookupnext(&lfs3, &gbmap, 7, - &bid_, &weight_) => LFS3_TAG_BMFREE; + &bid_, &weight_, NULL) => LFS3_TAG_BMFREE; assert(bid_ == 7); assert(weight_ == 1); lfs3_gbmap_lookupnext(&lfs3, &gbmap, 8, - &bid_, &weight_) => LFS3_TAG_BMINUSE; + &bid_, &weight_, NULL) => LFS3_TAG_BMINUSE; assert(bid_ == 10); assert(weight_ == 3); lfs3_gbmap_lookupnext(&lfs3, &gbmap, 11, - &bid_, &weight_) => LFS3_TAG_BMFREE; + &bid_, &weight_, NULL) => LFS3_TAG_BMFREE; assert(bid_ == 11); assert(weight_ == 1); lfs3_gbmap_lookupnext(&lfs3, &gbmap, 12, - &bid_, &weight_) => LFS3_TAG_BMINUSE; + &bid_, &weight_, NULL) => LFS3_TAG_BMINUSE; assert(bid_ == 13); assert(weight_ == 2); lfs3_gbmap_lookupnext(&lfs3, &gbmap, 14, - &bid_, &weight_) => LFS3_TAG_BMFREE; + &bid_, &weight_, NULL) => LFS3_TAG_BMFREE; assert(bid_ == BLOCK_COUNT-1); assert(weight_ == BLOCK_COUNT-14); @@ -227,15 +227,15 @@ code = ''' LFS3_RATTR_NULL)) => 0; // set some blocks as in-use, avoid any weird merges for now - lfs3_gbmap_mark(&lfs3, &gbmap, 6, LFS3_TAG_BMINUSE) => 0; - lfs3_gbmap_mark(&lfs3, &gbmap, 8, LFS3_TAG_BMINUSE) => 0; - lfs3_gbmap_mark(&lfs3, &gbmap, 10, LFS3_TAG_BMINUSE) => 0; + lfs3_gbmap_mark(&lfs3, &gbmap, 6, LFS3_TAG_BMINUSE, NULL) => 0; + lfs3_gbmap_mark(&lfs3, &gbmap, 8, LFS3_TAG_BMINUSE, NULL) => 0; + lfs3_gbmap_mark(&lfs3, &gbmap, 10, LFS3_TAG_BMINUSE, NULL) => 0; // test a bunch of noops - lfs3_gbmap_mark(&lfs3, &gbmap, 6, LFS3_TAG_BMINUSE) => 0; - lfs3_gbmap_mark(&lfs3, &gbmap, 7, LFS3_TAG_BMFREE) => 0; - lfs3_gbmap_mark(&lfs3, &gbmap, 8, LFS3_TAG_BMINUSE) => 0; - lfs3_gbmap_mark(&lfs3, &gbmap, 9, LFS3_TAG_BMFREE) => 0; - lfs3_gbmap_mark(&lfs3, &gbmap, 10, LFS3_TAG_BMINUSE) => 0; + lfs3_gbmap_mark(&lfs3, &gbmap, 6, LFS3_TAG_BMINUSE, NULL) => 0; + lfs3_gbmap_mark(&lfs3, &gbmap, 7, LFS3_TAG_BMFREE, NULL) => 0; + lfs3_gbmap_mark(&lfs3, &gbmap, 8, LFS3_TAG_BMINUSE, NULL) => 0; + lfs3_gbmap_mark(&lfs3, &gbmap, 9, LFS3_TAG_BMFREE, NULL) => 0; + lfs3_gbmap_mark(&lfs3, &gbmap, 10, LFS3_TAG_BMINUSE, NULL) => 0; printf("gbmap: w%d 0x%x.%x\n", gbmap.r.weight, gbmap.r.blocks[0], @@ -248,31 +248,31 @@ code = ''' lfs3_bid_t bid_; lfs3_bid_t weight_; lfs3_gbmap_lookupnext(&lfs3, &gbmap, 0, - &bid_, &weight_) => LFS3_TAG_BMFREE; + &bid_, &weight_, NULL) => LFS3_TAG_BMFREE; assert(bid_ == 5); assert(weight_ == 6); lfs3_gbmap_lookupnext(&lfs3, &gbmap, 6, - &bid_, &weight_) => LFS3_TAG_BMINUSE; + &bid_, &weight_, NULL) => LFS3_TAG_BMINUSE; assert(bid_ == 6); assert(weight_ == 1); lfs3_gbmap_lookupnext(&lfs3, &gbmap, 7, - &bid_, &weight_) => LFS3_TAG_BMFREE; + &bid_, &weight_, NULL) => LFS3_TAG_BMFREE; assert(bid_ == 7); assert(weight_ == 1); lfs3_gbmap_lookupnext(&lfs3, &gbmap, 8, - &bid_, &weight_) => LFS3_TAG_BMINUSE; + &bid_, &weight_, NULL) => LFS3_TAG_BMINUSE; assert(bid_ == 8); assert(weight_ == 1); lfs3_gbmap_lookupnext(&lfs3, &gbmap, 9, - &bid_, &weight_) => LFS3_TAG_BMFREE; + &bid_, &weight_, NULL) => LFS3_TAG_BMFREE; assert(bid_ == 9); assert(weight_ == 1); lfs3_gbmap_lookupnext(&lfs3, &gbmap, 10, - &bid_, &weight_) => LFS3_TAG_BMINUSE; + &bid_, &weight_, NULL) => LFS3_TAG_BMINUSE; assert(bid_ == 10); assert(weight_ == 1); lfs3_gbmap_lookupnext(&lfs3, &gbmap, 11, - &bid_, &weight_) => LFS3_TAG_BMFREE; + &bid_, &weight_, NULL) => LFS3_TAG_BMFREE; assert(bid_ == BLOCK_COUNT-1); assert(weight_ == BLOCK_COUNT-11); @@ -298,8 +298,8 @@ code = ''' // test that setting the first and last blocks don't break anything // // though in theory blocks 0x{0,1} are immutable... - lfs3_gbmap_mark(&lfs3, &gbmap, 0, LFS3_TAG_BMINUSE) => 0; - lfs3_gbmap_mark(&lfs3, &gbmap, BLOCK_COUNT-1, LFS3_TAG_BMINUSE) => 0; + lfs3_gbmap_mark(&lfs3, &gbmap, 0, LFS3_TAG_BMINUSE, NULL) => 0; + lfs3_gbmap_mark(&lfs3, &gbmap, BLOCK_COUNT-1, LFS3_TAG_BMINUSE, NULL) => 0; printf("gbmap: w%d 0x%x.%x\n", gbmap.r.weight, gbmap.r.blocks[0], @@ -312,15 +312,15 @@ code = ''' lfs3_bid_t bid_; lfs3_bid_t weight_; lfs3_gbmap_lookupnext(&lfs3, &gbmap, 0, - &bid_, &weight_) => LFS3_TAG_BMINUSE; + &bid_, &weight_, NULL) => LFS3_TAG_BMINUSE; assert(bid_ == 0); assert(weight_ == 1); lfs3_gbmap_lookupnext(&lfs3, &gbmap, 1, - &bid_, &weight_) => LFS3_TAG_BMFREE; + &bid_, &weight_, NULL) => LFS3_TAG_BMFREE; assert(bid_ == BLOCK_COUNT-2); assert(weight_ == BLOCK_COUNT-2); lfs3_gbmap_lookupnext(&lfs3, &gbmap, BLOCK_COUNT-1, - &bid_, &weight_) => LFS3_TAG_BMINUSE; + &bid_, &weight_, NULL) => LFS3_TAG_BMINUSE; assert(bid_ == BLOCK_COUNT-1); assert(weight_ == 1); @@ -361,7 +361,7 @@ code = ''' lfs3_tag_t tag = LFS3_TAG_BMRANGE + (TEST_PRNG(&prng) % TYPES); // set in gbmap - lfs3_gbmap_mark(&lfs3, &gbmap, block, tag) => 0; + lfs3_gbmap_mark(&lfs3, &gbmap, block, tag, NULL) => 0; // and set in sim sim[block] = tag; @@ -386,7 +386,7 @@ code = ''' lfs3_bid_t bid_; lfs3_bid_t weight_; lfs3_gbmap_lookupnext(&lfs3, &gbmap, i, - &bid_, &weight_) => tag; + &bid_, &weight_, NULL) => tag; assert(bid_ == i+(weight_-1)); assert(weight_ == d); @@ -398,6 +398,413 @@ code = ''' ''' +# set operations with ecksums are a bit more complicated +[cases.test_gbmap_mark_ecksum_split] +in = 'lfs3.c' +code = ''' + lfs3_t lfs3; + lfs3_format(&lfs3, LFS3_F_RDWR, CFG) => 0; + lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0; + lfs3_alloc_ckpoint(&lfs3); + + // create an initial gbmap + lfs3_btree_t gbmap; + lfs3_btree_init(&gbmap); + lfs3_gbmap_commit(&lfs3, &gbmap, 0, LFS3_RATTRS( + LFS3_RATTR(2, LFS3_TAG_BMFREE, -2), + LFS3_RATTR_WEIGHT(+BLOCK_COUNT), + LFS3_RATTR_NULL)) => 0; + + // set some blocks as erased with ecksums, avoid any weird merges + // for now + lfs3_ecksum_t ecksum = {.cksize=CFG->prog_size, .cksum=0x12345678}; + lfs3_gbmap_mark(&lfs3, &gbmap, 6, LFS3_TAG_BMERASED, &ecksum) => 0; + lfs3_gbmap_mark(&lfs3, &gbmap, 8, LFS3_TAG_BMERASED, &ecksum) => 0; + lfs3_gbmap_mark(&lfs3, &gbmap, 10, LFS3_TAG_BMERASED, &ecksum) => 0; + printf("gbmap: w%d 0x%x.%x\n", + gbmap.r.weight, + gbmap.r.blocks[0], + gbmap.r.trunk); + + // weight should stay the same + assert(gbmap.r.weight == BLOCK_COUNT); + + // check ranges + lfs3_bid_t bid_; + lfs3_bid_t weight_; + lfs3_ecksum_t ecksum_; + lfs3_gbmap_lookupnext(&lfs3, &gbmap, 0, + &bid_, &weight_, &ecksum_) => LFS3_TAG_BMFREE; + assert(bid_ == 5); + assert(weight_ == 6); + assert(lfs3_ecksum_cmp(&ecksum_, NULL) == 0); + lfs3_gbmap_lookupnext(&lfs3, &gbmap, 6, + &bid_, &weight_, &ecksum_) => LFS3_TAG_BMERASED; + assert(bid_ == 6); + assert(weight_ == 1); + assert(lfs3_ecksum_cmp(&ecksum_, &ecksum) == 0); + lfs3_gbmap_lookupnext(&lfs3, &gbmap, 7, + &bid_, &weight_, &ecksum_) => LFS3_TAG_BMFREE; + assert(bid_ == 7); + assert(weight_ == 1); + assert(lfs3_ecksum_cmp(&ecksum_, NULL) == 0); + lfs3_gbmap_lookupnext(&lfs3, &gbmap, 8, + &bid_, &weight_, &ecksum_) => LFS3_TAG_BMERASED; + assert(bid_ == 8); + assert(weight_ == 1); + assert(lfs3_ecksum_cmp(&ecksum_, &ecksum) == 0); + lfs3_gbmap_lookupnext(&lfs3, &gbmap, 9, + &bid_, &weight_, &ecksum_) => LFS3_TAG_BMFREE; + assert(bid_ == 9); + assert(weight_ == 1); + assert(lfs3_ecksum_cmp(&ecksum_, NULL) == 0); + lfs3_gbmap_lookupnext(&lfs3, &gbmap, 10, + &bid_, &weight_, &ecksum_) => LFS3_TAG_BMERASED; + assert(bid_ == 10); + assert(weight_ == 1); + assert(lfs3_ecksum_cmp(&ecksum_, &ecksum) == 0); + lfs3_gbmap_lookupnext(&lfs3, &gbmap, 11, + &bid_, &weight_, &ecksum_) => LFS3_TAG_BMFREE; + assert(bid_ == BLOCK_COUNT-1); + assert(weight_ == BLOCK_COUNT-11); + assert(lfs3_ecksum_cmp(&ecksum_, NULL) == 0); + + lfs3_unmount(&lfs3) => 0; +''' + +[cases.test_gbmap_mark_ecksum_replace] +in = 'lfs3.c' +code = ''' + lfs3_t lfs3; + lfs3_format(&lfs3, LFS3_F_RDWR, CFG) => 0; + lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0; + lfs3_alloc_ckpoint(&lfs3); + + // create an initial gbmap + lfs3_btree_t gbmap; + lfs3_btree_init(&gbmap); + lfs3_gbmap_commit(&lfs3, &gbmap, 0, LFS3_RATTRS( + LFS3_RATTR(2, LFS3_TAG_BMFREE, -2), + LFS3_RATTR_WEIGHT(+BLOCK_COUNT), + LFS3_RATTR_NULL)) => 0; + + // set some blocks as erased with ecksums, avoid any weird merges + // for now + lfs3_ecksum_t ecksum_a = {.cksize=CFG->prog_size, .cksum=0x12345678}; + lfs3_gbmap_mark(&lfs3, &gbmap, 6, LFS3_TAG_BMERASED, &ecksum_a) => 0; + lfs3_gbmap_mark(&lfs3, &gbmap, 8, LFS3_TAG_BMERASED, &ecksum_a) => 0; + lfs3_gbmap_mark(&lfs3, &gbmap, 10, LFS3_TAG_BMERASED, &ecksum_a) => 0; + // replace those blocks with different ecksums + lfs3_ecksum_t ecksum_b = {.cksize=CFG->prog_size, .cksum=0x9abcdef0}; + lfs3_gbmap_mark(&lfs3, &gbmap, 6, LFS3_TAG_BMERASED, &ecksum_b) => 0; + lfs3_gbmap_mark(&lfs3, &gbmap, 8, LFS3_TAG_BMERASED, &ecksum_b) => 0; + lfs3_gbmap_mark(&lfs3, &gbmap, 10, LFS3_TAG_BMERASED, &ecksum_b) => 0; + printf("gbmap: w%d 0x%x.%x\n", + gbmap.r.weight, + gbmap.r.blocks[0], + gbmap.r.trunk); + + // weight should stay the same + assert(gbmap.r.weight == BLOCK_COUNT); + + // check ranges + lfs3_bid_t bid_; + lfs3_bid_t weight_; + lfs3_ecksum_t ecksum_; + lfs3_gbmap_lookupnext(&lfs3, &gbmap, 0, + &bid_, &weight_, &ecksum_) => LFS3_TAG_BMFREE; + assert(bid_ == 5); + assert(weight_ == 6); + assert(lfs3_ecksum_cmp(&ecksum_, NULL) == 0); + lfs3_gbmap_lookupnext(&lfs3, &gbmap, 6, + &bid_, &weight_, &ecksum_) => LFS3_TAG_BMERASED; + assert(bid_ == 6); + assert(weight_ == 1); + assert(lfs3_ecksum_cmp(&ecksum_, &ecksum_b) == 0); + lfs3_gbmap_lookupnext(&lfs3, &gbmap, 7, + &bid_, &weight_, &ecksum_) => LFS3_TAG_BMFREE; + assert(bid_ == 7); + assert(weight_ == 1); + assert(lfs3_ecksum_cmp(&ecksum_, NULL) == 0); + lfs3_gbmap_lookupnext(&lfs3, &gbmap, 8, + &bid_, &weight_, &ecksum_) => LFS3_TAG_BMERASED; + assert(bid_ == 8); + assert(weight_ == 1); + assert(lfs3_ecksum_cmp(&ecksum_, &ecksum_b) == 0); + lfs3_gbmap_lookupnext(&lfs3, &gbmap, 9, + &bid_, &weight_, &ecksum_) => LFS3_TAG_BMFREE; + assert(bid_ == 9); + assert(weight_ == 1); + assert(lfs3_ecksum_cmp(&ecksum_, NULL) == 0); + lfs3_gbmap_lookupnext(&lfs3, &gbmap, 10, + &bid_, &weight_, &ecksum_) => LFS3_TAG_BMERASED; + assert(bid_ == 10); + assert(weight_ == 1); + assert(lfs3_ecksum_cmp(&ecksum_, &ecksum_b) == 0); + lfs3_gbmap_lookupnext(&lfs3, &gbmap, 11, + &bid_, &weight_, &ecksum_) => LFS3_TAG_BMFREE; + assert(bid_ == BLOCK_COUNT-1); + assert(weight_ == BLOCK_COUNT-11); + assert(lfs3_ecksum_cmp(&ecksum_, NULL) == 0); + + lfs3_unmount(&lfs3) => 0; +''' + +[cases.test_gbmap_mark_ecksum_merge] +in = 'lfs3.c' +code = ''' + lfs3_t lfs3; + lfs3_format(&lfs3, LFS3_F_RDWR, CFG) => 0; + lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0; + lfs3_alloc_ckpoint(&lfs3); + + // create an initial gbmap + lfs3_btree_t gbmap; + lfs3_btree_init(&gbmap); + lfs3_gbmap_commit(&lfs3, &gbmap, 0, LFS3_RATTRS( + LFS3_RATTR(2, LFS3_TAG_BMFREE, -2), + LFS3_RATTR_WEIGHT(+BLOCK_COUNT), + LFS3_RATTR_NULL)) => 0; + + // set some blocks as erased with ecksums, avoid any weird merges + // for now + lfs3_ecksum_t ecksum = {.cksize=CFG->prog_size, .cksum=0x12345678}; + lfs3_gbmap_mark(&lfs3, &gbmap, 6, LFS3_TAG_BMERASED, &ecksum) => 0; + lfs3_gbmap_mark(&lfs3, &gbmap, 8, LFS3_TAG_BMERASED, &ecksum) => 0; + lfs3_gbmap_mark(&lfs3, &gbmap, 10, LFS3_TAG_BMERASED, &ecksum) => 0; + lfs3_gbmap_mark(&lfs3, &gbmap, 12, LFS3_TAG_BMERASED, &ecksum) => 0; + // set neighboring blocks as erased with ecksums, triggering merges + lfs3_gbmap_mark(&lfs3, &gbmap, 5, LFS3_TAG_BMERASED, &ecksum) => 0; + lfs3_gbmap_mark(&lfs3, &gbmap, 9, LFS3_TAG_BMERASED, &ecksum) => 0; + lfs3_gbmap_mark(&lfs3, &gbmap, 13, LFS3_TAG_BMERASED, &ecksum) => 0; + printf("gbmap: w%d 0x%x.%x\n", + gbmap.r.weight, + gbmap.r.blocks[0], + gbmap.r.trunk); + + // weight should stay the same + assert(gbmap.r.weight == BLOCK_COUNT); + + // check ranges + lfs3_bid_t bid_; + lfs3_bid_t weight_; + lfs3_ecksum_t ecksum_; + lfs3_gbmap_lookupnext(&lfs3, &gbmap, 0, + &bid_, &weight_, &ecksum_) => LFS3_TAG_BMFREE; + assert(bid_ == 4); + assert(weight_ == 5); + assert(lfs3_ecksum_cmp(&ecksum_, NULL) == 0); + lfs3_gbmap_lookupnext(&lfs3, &gbmap, 5, + &bid_, &weight_, &ecksum_) => LFS3_TAG_BMERASED; + assert(bid_ == 6); + assert(weight_ == 2); + assert(lfs3_ecksum_cmp(&ecksum_, &ecksum) == 0); + lfs3_gbmap_lookupnext(&lfs3, &gbmap, 7, + &bid_, &weight_, &ecksum_) => LFS3_TAG_BMFREE; + assert(bid_ == 7); + assert(weight_ == 1); + assert(lfs3_ecksum_cmp(&ecksum_, NULL) == 0); + lfs3_gbmap_lookupnext(&lfs3, &gbmap, 8, + &bid_, &weight_, &ecksum_) => LFS3_TAG_BMERASED; + assert(bid_ == 10); + assert(weight_ == 3); + assert(lfs3_ecksum_cmp(&ecksum_, &ecksum) == 0); + lfs3_gbmap_lookupnext(&lfs3, &gbmap, 11, + &bid_, &weight_, &ecksum_) => LFS3_TAG_BMFREE; + assert(bid_ == 11); + assert(weight_ == 1); + assert(lfs3_ecksum_cmp(&ecksum_, NULL) == 0); + lfs3_gbmap_lookupnext(&lfs3, &gbmap, 12, + &bid_, &weight_, &ecksum_) => LFS3_TAG_BMERASED; + assert(bid_ == 13); + assert(weight_ == 2); + assert(lfs3_ecksum_cmp(&ecksum_, &ecksum) == 0); + lfs3_gbmap_lookupnext(&lfs3, &gbmap, 14, + &bid_, &weight_, &ecksum_) => LFS3_TAG_BMFREE; + assert(bid_ == BLOCK_COUNT-1); + assert(weight_ == BLOCK_COUNT-14); + assert(lfs3_ecksum_cmp(&ecksum_, NULL) == 0); + + lfs3_unmount(&lfs3) => 0; +''' + +[cases.test_gbmap_mark_ecksum_nomerge] +in = 'lfs3.c' +code = ''' + lfs3_t lfs3; + lfs3_format(&lfs3, LFS3_F_RDWR, CFG) => 0; + lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0; + lfs3_alloc_ckpoint(&lfs3); + + // create an initial gbmap + lfs3_btree_t gbmap; + lfs3_btree_init(&gbmap); + lfs3_gbmap_commit(&lfs3, &gbmap, 0, LFS3_RATTRS( + LFS3_RATTR(2, LFS3_TAG_BMFREE, -2), + LFS3_RATTR_WEIGHT(+BLOCK_COUNT), + LFS3_RATTR_NULL)) => 0; + + // set some blocks as erased with ecksums, avoid any weird merges + // for now + lfs3_ecksum_t ecksum_a = {.cksize=CFG->prog_size, .cksum=0x12345678}; + lfs3_gbmap_mark(&lfs3, &gbmap, 6, LFS3_TAG_BMERASED, &ecksum_a) => 0; + lfs3_gbmap_mark(&lfs3, &gbmap, 8, LFS3_TAG_BMERASED, &ecksum_a) => 0; + lfs3_gbmap_mark(&lfs3, &gbmap, 10, LFS3_TAG_BMERASED, &ecksum_a) => 0; + lfs3_gbmap_mark(&lfs3, &gbmap, 12, LFS3_TAG_BMERASED, &ecksum_a) => 0; + // set neighboring blocks as erased with _different_ ecksums, this + // should _not_ trigger a merge! + lfs3_ecksum_t ecksum_b = {.cksize=CFG->prog_size, .cksum=0x9abcdef0}; + lfs3_gbmap_mark(&lfs3, &gbmap, 5, LFS3_TAG_BMERASED, &ecksum_b) => 0; + lfs3_gbmap_mark(&lfs3, &gbmap, 9, LFS3_TAG_BMERASED, &ecksum_b) => 0; + lfs3_gbmap_mark(&lfs3, &gbmap, 13, LFS3_TAG_BMERASED, &ecksum_b) => 0; + printf("gbmap: w%d 0x%x.%x\n", + gbmap.r.weight, + gbmap.r.blocks[0], + gbmap.r.trunk); + + // weight should stay the same + assert(gbmap.r.weight == BLOCK_COUNT); + + // check ranges + lfs3_bid_t bid_; + lfs3_bid_t weight_; + lfs3_ecksum_t ecksum_; + lfs3_gbmap_lookupnext(&lfs3, &gbmap, 0, + &bid_, &weight_, &ecksum_) => LFS3_TAG_BMFREE; + assert(bid_ == 4); + assert(weight_ == 5); + assert(lfs3_ecksum_cmp(&ecksum_, NULL) == 0); + lfs3_gbmap_lookupnext(&lfs3, &gbmap, 5, + &bid_, &weight_, &ecksum_) => LFS3_TAG_BMERASED; + assert(bid_ == 5); + assert(weight_ == 1); + assert(lfs3_ecksum_cmp(&ecksum_, &ecksum_b) == 0); + lfs3_gbmap_lookupnext(&lfs3, &gbmap, 6, + &bid_, &weight_, &ecksum_) => LFS3_TAG_BMERASED; + assert(bid_ == 6); + assert(weight_ == 1); + assert(lfs3_ecksum_cmp(&ecksum_, &ecksum_a) == 0); + lfs3_gbmap_lookupnext(&lfs3, &gbmap, 7, + &bid_, &weight_, &ecksum_) => LFS3_TAG_BMFREE; + assert(bid_ == 7); + assert(weight_ == 1); + assert(lfs3_ecksum_cmp(&ecksum_, NULL) == 0); + lfs3_gbmap_lookupnext(&lfs3, &gbmap, 8, + &bid_, &weight_, &ecksum_) => LFS3_TAG_BMERASED; + assert(bid_ == 8); + assert(weight_ == 1); + assert(lfs3_ecksum_cmp(&ecksum_, &ecksum_a) == 0); + lfs3_gbmap_lookupnext(&lfs3, &gbmap, 9, + &bid_, &weight_, &ecksum_) => LFS3_TAG_BMERASED; + assert(bid_ == 9); + assert(weight_ == 1); + assert(lfs3_ecksum_cmp(&ecksum_, &ecksum_b) == 0); + lfs3_gbmap_lookupnext(&lfs3, &gbmap, 10, + &bid_, &weight_, &ecksum_) => LFS3_TAG_BMERASED; + assert(bid_ == 10); + assert(weight_ == 1); + assert(lfs3_ecksum_cmp(&ecksum_, &ecksum_a) == 0); + lfs3_gbmap_lookupnext(&lfs3, &gbmap, 11, + &bid_, &weight_, &ecksum_) => LFS3_TAG_BMFREE; + assert(bid_ == 11); + assert(weight_ == 1); + assert(lfs3_ecksum_cmp(&ecksum_, NULL) == 0); + lfs3_gbmap_lookupnext(&lfs3, &gbmap, 12, + &bid_, &weight_, &ecksum_) => LFS3_TAG_BMERASED; + assert(bid_ == 12); + assert(weight_ == 1); + assert(lfs3_ecksum_cmp(&ecksum_, &ecksum_a) == 0); + lfs3_gbmap_lookupnext(&lfs3, &gbmap, 13, + &bid_, &weight_, &ecksum_) => LFS3_TAG_BMERASED; + assert(bid_ == 13); + assert(weight_ == 1); + assert(lfs3_ecksum_cmp(&ecksum_, &ecksum_b) == 0); + lfs3_gbmap_lookupnext(&lfs3, &gbmap, 14, + &bid_, &weight_, &ecksum_) => LFS3_TAG_BMFREE; + assert(bid_ == BLOCK_COUNT-1); + assert(weight_ == BLOCK_COUNT-14); + assert(lfs3_ecksum_cmp(&ecksum_, NULL) == 0); + + lfs3_unmount(&lfs3) => 0; +''' + +[cases.test_gbmap_mark_ecksum_fuzz] +defines.N = [1, 2, 4, 8, 16, 32, 64, 128, 256, 512] +defines.MASK = [0x1, 0x3, 0xf, 0xffff] +defines.SEED = 'range(20)' +fuzz = 'SEED' +in = 'lfs3.c' +code = ''' + lfs3_t lfs3; + lfs3_format(&lfs3, LFS3_F_RDWR, CFG) => 0; + lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0; + lfs3_alloc_ckpoint(&lfs3); + + // create an initial gbmap + lfs3_ecksum_t ecksum = {.cksize=CFG->prog_size, .cksum=0x00000000}; + lfs3_btree_t gbmap; + lfs3_btree_init(&gbmap); + lfs3_gbmap_commit(&lfs3, &gbmap, 0, LFS3_RATTRS( + LFS3_RATTR(3, LFS3_TAG_BMERASED, -2, LFS3_FROM_ECKSUM), + LFS3_RATTR_WEIGHT(+BLOCK_COUNT), + LFS3_RATTR_ARG(&ecksum), + LFS3_RATTR_NULL)) => 0; + + // create a simulation to compare against + lfs3_tag_t *sim = malloc(BLOCK_COUNT*sizeof(uint32_t)); + for (lfs3_size_t i = 0; i < BLOCK_COUNT; i++) { + sim[i] = 0x00000000; + } + + uint32_t prng = SEED; + for (lfs3_size_t i = 0; i < N; i++) { + // choose a pseudo-random block + lfs3_block_t block = TEST_PRNG(&prng) % BLOCK_COUNT; + // and pseudo-random ecksum + ecksum.cksum = TEST_PRNG(&prng) & MASK; + + // set in gbmap + lfs3_gbmap_mark(&lfs3, &gbmap, + block, LFS3_TAG_BMERASED, &ecksum) => 0; + + // and set in sim + sim[block] = ecksum.cksum; + } + printf("gbmap: w%d 0x%x.%x\n", + gbmap.r.weight, + gbmap.r.blocks[0], + gbmap.r.trunk); + + // check if gbmap matches sim + lfs3_size_t i = 0; + while (i < BLOCK_COUNT) { + // we need to convert our sim's raw blocks to compressed ranges, + // in theory our gbmap is optimal + uint32_t cksum = sim[i]; + lfs3_size_t d = 1; + while (i+d < BLOCK_COUNT && sim[i+d] == cksum) { + d += 1; + } + + // does our gbmap contain the optimal range? + lfs3_bid_t bid_; + lfs3_bid_t weight_; + lfs3_ecksum_t ecksum_; + lfs3_gbmap_lookupnext(&lfs3, &gbmap, i, + &bid_, &weight_, &ecksum_) => LFS3_TAG_BMERASED; + assert(bid_ == i+(weight_-1)); + assert(weight_ == d); + assert(ecksum_.cksize == CFG->prog_size); + assert(ecksum_.cksum == cksum); + + i += d; + } + + free(sim); + lfs3_unmount(&lfs3) => 0; +''' + + # test high-level gbmap operations @@ -499,6 +906,122 @@ code = ''' lfs3_unmount(&lfs3) => 0; ''' +# test that the gbmap with gc generally works +[cases.test_gbmap_gc_files] +defines.COUNT = [ + 'BLOCK_COUNT', + 'BLOCK_COUNT-1', + 'BLOCK_COUNT/2', + 'BLOCK_COUNT/4', + '5', + '3', +] +defines.SIZE = [ + 'BLOCK_SIZE', + '2*BLOCK_SIZE', + '8*BLOCK_SIZE', +] +defines.N = ['5', 'BLOCK_COUNT/2'] +defines.WRAPAROUND = 3 +defines.PREERASE = [false, true] +defines.GC_FLAGS = ''' + LFS3_GC_LOOKAHEAD + | ((PREERASE) ? LFS3_GC_PREERASE : 0) +''' +defines.GC_STEPS = -1 +defines.GC_PREERASE_COUNT = ['0', '4', 'COUNT/2', 'COUNT-4', '-1'] +ifdef = 'LFS3_GC' +if = [ + 'COUNT >= 3', + 'COUNT >= 2*N*SIZE/BLOCK_SIZE', + 'LFS3_IFDEF_PREERASE(true, !PREERASE)', + # this is just to reduce useless permutations + 'PREERASE || GC_PREERASE_COUNT == 0', +] +code = ''' + // test various block counts + struct lfs3_cfg cfg = *CFG; + cfg.block_count = COUNT; + lfs3_t lfs3; + // note the gbmap flag + lfs3_format(&lfs3, LFS3_F_RDWR | LFS3_F_GBMAP, &cfg) => 0; + lfs3_mount(&lfs3, LFS3_M_RDWR, &cfg) => 0; + + // check that we were formatted with the gbmap + struct lfs3_fsinfo fsinfo; + lfs3_fs_stat(&lfs3, &fsinfo) => 0; + assert(fsinfo.flags & LFS3_I_GBMAP); + + // run gc to prebuild gbmap, preerase blocks, etc + lfs3_fs_gc(&lfs3) => 0; + + // create n files repeatedly until we're sure we've wrapped around + // a few times + uint32_t prng = 42; + uint32_t prng_ = prng; + for (lfs3_size_t i = 0; + i < (WRAPAROUND*COUNT) + / (N*(SIZE/BLOCK_SIZE)); + i++) { + prng = prng_; + for (lfs3_size_t n = 0; n < N; n++) { + char name[256]; + sprintf(name, "file%08d", n); + + uint8_t wbuf[SIZE]; + for (lfs3_size_t j = 0; j < SIZE; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&prng_) % 26); + } + + lfs3_file_t file; + lfs3_file_open(&lfs3, &file, name, + LFS3_O_WRONLY | LFS3_O_CREAT | LFS3_O_TRUNC) => 0; + lfs3_file_write(&lfs3, &file, wbuf, SIZE) => SIZE; + lfs3_file_close(&lfs3, &file) => 0; + } + } + + for (int remount = 0; remount < 2; remount++) { + // remount? + if (remount) { + lfs3_unmount(&lfs3) => 0; + lfs3_mount(&lfs3, LFS3_M_RDWR, &cfg) => 0; + } + + // check that our file writes worked + prng_ = prng; + for (lfs3_size_t i = 0; i < N; i++) { + // check with stat + char name[256]; + sprintf(name, "file%08d", i); + struct lfs3_info info; + lfs3_stat(&lfs3, name, &info) => 0; + assert(strcmp(info.name, name) == 0); + assert(info.type == LFS3_TYPE_REG); + assert(info.size == SIZE); + + // try reading the file, note we reset prng above + uint8_t wbuf[SIZE]; + for (lfs3_size_t j = 0; j < SIZE; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&prng_) % 26); + } + + lfs3_file_t file; + uint8_t rbuf[SIZE]; + lfs3_file_open(&lfs3, &file, name, LFS3_O_RDONLY) => 0; + lfs3_file_read(&lfs3, &file, rbuf, SIZE) => SIZE; + assert(memcmp(rbuf, wbuf, SIZE) == 0); + lfs3_file_close(&lfs3, &file) => 0; + } + } + + lfs3_unmount(&lfs3) => 0; +''' + + + +# test high-high-level gbmap operations + # test that we can remove the gbmap and things still work [cases.test_gbmap_rmgbmap] ifndef = 'LFS3_YES_GBMAP'