From b5a94f3397599a2fe59f526ed333035c467ff329 Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Sat, 11 Oct 2025 13:34:30 -0500 Subject: [PATCH] gbmap: Added mkgbmap and rmgbmap for enabling/disabling the gbmap These two functions allow changing whether or not the gbmap is in use after format: // Enable the global on-disk block-map // // Returns a negative error code on failure. Does nothing if a gbmap // already exists. int lfs3_fs_mkgbmap(lfs3_t *lfs3); // Disable the global on-disk block-map // // Returns a negative error code on failure. Does nothing if no gbmap // is found. int lfs3_fs_rmgbmap(lfs3_t *lfs3); rmgbmap was easy enough, but implementing mkgbmap turned out to be surprisingly tricky due to how gstate permeates the system: - Even if we zero gstate when we removing the gbmap, mounting the image on a driver that doesn't understand the gbmap results in garbage gstate over time as mdir compacts drop unknown gdeltas. I think this sort of implicit gdelta cleanup is a good thing, but the possibility of garbage gstate is a bit annoying. Example A: the dbg scripts are currently printing a bunch of warnings for corrupt gstate that can be safely ignored. To support recovering from garbage gstate in mkgbmap, I changed lfs3_fs_commitgdelta to _always_ track p state even when disabled. We already needed to do this in lfs3_fs_flush/consumegdelta anyways, since we don't know if the gbmap is used until parsing wcompat flags. - The commit that enables the gbmap is tricky. We need the gbmap enabled to calculate the new gdelta, but we also need it disabled so we don't traverse the existing gbmap_p (which may be garbage). As a workaround I added gbmap.b_p, which is in theory redundant with gbmap_p, but (1) avoids needing to decode gbmap_p during traversals, and (2) allows the two to temporarily fall out-of-sync in mkgbmap. This means we potentially have 5 (!) snaphots flying around when rebuilding the gbmap, which is starting to get a bit silly. But this was also motivated by gbmap_p decoding adding roughly the same amount of RAM to lfs3_mtree_traverse_, so the total RAM usage should in theory be roughly the same. There might be a better solution, but this at least gets mkgbmap working. The gbmap builds are not our most RAM senstive configurations anyways. --- Also added a couple more tests in test_gbmap to test these: - test_gbmap_files - test_gbmap_rmgbmap - test_gbmap_mkgbmap - test_gbmap_rmmkgbmap - test_gbmap_mkrmgbmap And an explicit wraparound test to test_alloc. This was loosely implied by the nospc tests, but it's probably better to have an explicit test. The only downside is this implementation is limited to files: - test_alloc_wraparound_files --- Note we are currently dealing with three different configurations: no-gbmap (the default), yes-gbmap (LFS3_YES_GBMAP), and maybe-gbmap (LFS3_GBMAP + LFS3_F_GBMAP at runtime). It only makes sense to include these in maybe-gbmap mode, so this is the only mode with a notable code increase. However these functions are relatively cheap. The stack/ctx changes also affect yes-gbmap, but should mostly cancel out, see above: code stack ctx no-gbmap before: 37168 2352 684 no-gbmap after: 37168 (+0.0%) 2352 (+0.0%) 684 (+0.0%) code stack ctx maybe-gbmap before: 39292 2456 800 maybe-gbmap after: 39688 (+1.0%) 2392 (-2.6%) 852 (+6.5%) code stack ctx yes-gbmap before: 39116 2456 800 yes-gbmap after: 39156 (+0.1%) 2392 (-2.6%) 852 (+6.5%) --- lfs3.c | 151 ++++++-- lfs3.h | 17 + tests/test_alloc.toml | 106 +++++- tests/test_gbmap.toml | 785 +++++++++++++++++++++++++++++++++++++++++- 4 files changed, 1027 insertions(+), 32 deletions(-) diff --git a/lfs3.c b/lfs3.c index f189ee7b..62570f51 100644 --- a/lfs3.c +++ b/lfs3.c @@ -7786,10 +7786,18 @@ static void lfs3_fs_commitgdelta(lfs3_t *lfs3) { // keep track of the on-disk grm lfs3_data_fromgrm(&lfs3->grm, lfs3->grm_p); - // keep track of the on-disk gbmap #ifdef LFS3_GBMAP + // keep track of the on-disk gbmap if (lfs3_f_isgbmap(lfs3->flags)) { + // keep track of both the committed gstate and btree for + // traversals + lfs3->gbmap.b_p = lfs3->gbmap.b; lfs3_data_fromgbmap(&lfs3->gbmap, lfs3->gbmap_p); + + // if disabled, we still want to keep track of the on-disk gstate + // in case the user wants to re-enable the gbmap + } else { + lfs3_memxor(lfs3->gbmap_p, lfs3->gbmap_d, LFS3_GBMAP_DSIZE); } #endif } @@ -10387,25 +10395,12 @@ static lfs3_stag_t lfs3_mtree_traverse_(lfs3_t *lfs3, lfs3_trv_t *trv, == LFS3_TSTATE_GBMAP, false)) { #ifdef LFS3_GBMAP - // decode the on-disk gbmap - // - // TODO this adds 64 bytes of mostly unused stack - // to the stack hot-path, can we avoid this somehow? - // do we care in gbmap mode? - // - lfs3_gbmap_t gbmap_p; - err = lfs3_data_readgbmap(lfs3, - &LFS3_DATA_BUF(lfs3->gbmap_p, - LFS3_GBMAP_DSIZE), - &gbmap_p); - if (err) { - LFS3_UNREACHABLE(); - } - // if on-disk gbmap does not match the active gbmap, // transition to traversing the on-disk gbmap - if (lfs3_btree_cmp(&gbmap_p.b, &lfs3->gbmap.b) != 0) { - trv->b.shrub = gbmap_p.b; + if (lfs3_btree_cmp( + &lfs3->gbmap.b_p, + &lfs3->gbmap.b) != 0) { + trv->b.shrub = lfs3->gbmap.b_p; trv->bid = -2; lfs3_t_settstate(&trv->b.h.flags, LFS3_TSTATE_GBMAP_P); @@ -10723,6 +10718,15 @@ eot:; /// Optional on-disk block map /// +#if !defined(LFS3_RDONLY) && !defined(LFS3_2BONLY) && defined(LFS3_GBMAP) +static void lfs3_gbmap_init(lfs3_gbmap_t *gbmap) { + gbmap->window = 0; + gbmap->known = 0; + lfs3_btree_init(&gbmap->b); + lfs3_btree_init(&gbmap->b_p); +} +#endif + #if !defined(LFS3_RDONLY) && !defined(LFS3_2BONLY) && defined(LFS3_GBMAP) static lfs3_data_t lfs3_data_fromgbmap(const lfs3_gbmap_t *gbmap, uint8_t buffer[static LFS3_GBMAP_DSIZE]) { @@ -10775,6 +10779,8 @@ static int lfs3_data_readgbmap(lfs3_t *lfs3, lfs3_data_t *data, // make sure to zero btree leaf lfs3_btree_discardleaf(&gbmap->b); + // and keep track of the committed gbmap for traversals + gbmap->b_p = gbmap->b; return 0; } #endif @@ -11310,8 +11316,6 @@ static lfs3_sblock_t lfs3_alloc(lfs3_t *lfs3, uint32_t flags) { #if !defined(LFS3_RDONLY) && defined(LFS3_GBMAP) static int lfs3_alloc_rebuildgbmap(lfs3_t *lfs3) { - // we should ckpoint before calling this - LFS3_ASSERT(lfs3->lookahead.ckpoint == lfs3->block_count); LFS3_INFO("Rebuilding gbmap " "(gbmap %"PRId32"/%"PRId32")", lfs3->lookahead.gbmapped, @@ -15544,6 +15548,7 @@ static int lfs3_init(lfs3_t *lfs3, uint32_t flags, // TODO are these zeros accomplished by flushgdelta in mountinited? // should the flushgdelta be dropped? + // TODO should we just call flushgdelta here? // zero gstate lfs3->gcksum = 0; @@ -15560,11 +15565,10 @@ static int lfs3_init(lfs3_t *lfs3, uint32_t flags, #endif // setup other global gbmap state - // TODO is this actually needed? #ifdef LFS3_GBMAP - lfs3_btree_init(&lfs3->gbmap.b); - lfs3->gbmap.window = 0; - lfs3->gbmap.known = 0; + lfs3_gbmap_init(&lfs3->gbmap); + // TODO should this be in the gbmap struct? + lfs3->lookahead.gbmapped = 0; lfs3_memset(lfs3->gbmap_p, 0, LFS3_GBMAP_DSIZE); lfs3_memset(lfs3->gbmap_d, 0, LFS3_GBMAP_DSIZE); #endif @@ -17112,6 +17116,105 @@ failed:; } #endif +// enable the global on-disk block-map +#if !defined(LFs3_RDONLY) && defined(LFS3_GBMAP) && !defined(LFS3_YES_GBMAP) +int lfs3_fs_mkgbmap(lfs3_t *lfs3) { + // do nothing if we already have a gbmap + if (lfs3_f_isgbmap(lfs3->flags)) { + return 0; + } + + // prepare our filesystem for writing + int err = lfs3_fs_mkconsistent(lfs3); + if (err) { + return err; + } + + // checkpoint the allocator + // TODO, should lfs3_fs_mkconsistent also checkpoint the allocator? + err = lfs3_alloc_ckpoint(lfs3); + if (err) { + return err; + } + + // create an empty gbmap + lfs3_gbmap_init(&lfs3->gbmap); + // TODO should this be in the gbmap struct? + lfs3->lookahead.gbmapped = 0; + + // start with everything free, rebuilding the gbmap will populate it + err = lfs3_gbmap_commit(lfs3, &lfs3->gbmap.b, 0, LFS3_RATTRS( + LFS3_RATTR(LFS3_TAG_BMFREE, +lfs3->block_count))); + if (err) { + goto failed; + } + + // go ahead and mark gbmap as in-use internally + lfs3->flags |= LFS3_F_GBMAP; + + // checkpoint the allocator, this should trigger a rebuild + err = lfs3_alloc_ckpoint(lfs3); + if (err) { + goto failed; + } + + // mark the gbmap as in-use on-disk while atomically committing the + // gbmap into gstate + lfs3_wcompat_t wcompat_ = lfs3_wcompat(lfs3); + wcompat_ |= LFS3_WCOMPAT_GBMAP; + + err = lfs3_mdir_commit(lfs3, &lfs3->mroot, LFS3_RATTRS( + LFS3_RATTR_LE32(LFS3_TAG_WCOMPAT, 0, wcompat_))); + if (err) { + goto failed; + } + + return 0; + +failed:; + // if we failed clear the gbmap bit and reset the gbmap to be safe + lfs3->flags &= ~LFS3_F_GBMAP; + lfs3_gbmap_init(&lfs3->gbmap); + // TODO should this be in the gbmap struct? + lfs3->lookahead.gbmapped = 0; + return err; +} +#endif + +// disable the global on-disk block-map +#if !defined(LFs3_RDONLY) && defined(LFS3_GBMAP) && !defined(LFS3_YES_GBMAP) +int lfs3_fs_rmgbmap(lfs3_t *lfs3) { + // do nothing if we already don't have a gbmap + if (!lfs3_f_isgbmap(lfs3->flags)) { + return 0; + } + + // prepare our filesystem for writing + int err = lfs3_fs_mkconsistent(lfs3); + if (err) { + return err; + } + + // removing the gbmap is relatively easy, we just need to mark the + // gbmap as not in use + // + // this leaves garbage gdeltas around, but these should be cleaned + // up implicitly as mdirs are compacted + lfs3_wcompat_t wcompat_ = lfs3_wcompat(lfs3); + wcompat_ &= ~LFS3_WCOMPAT_GBMAP; + + err = lfs3_mdir_commit(lfs3, &lfs3->mroot, LFS3_RATTRS( + LFS3_RATTR_LE32(LFS3_TAG_WCOMPAT, 0, wcompat_))); + if (err) { + return err; + } + + // on success mark gbmap as not-in-use internally + lfs3->flags &= ~LFS3_F_GBMAP; + return 0; +} +#endif + /// High-level filesystem traversal /// diff --git a/lfs3.h b/lfs3.h index f2912840..f4fbec29 100644 --- a/lfs3.h +++ b/lfs3.h @@ -849,6 +849,7 @@ typedef struct lfs3_gbmap { lfs3_block_t window; lfs3_block_t known; lfs3_btree_t b; + lfs3_btree_t b_p; } lfs3_gbmap_t; @@ -1413,5 +1414,21 @@ int lfs3_fs_unck(lfs3_t *lfs3, uint32_t flags); int lfs3_fs_grow(lfs3_t *lfs3, lfs3_size_t block_count); #endif +// Enable the global on-disk block-map +// +// Returns a negative error code on failure. Does nothing if a gbmap +// already exists. +#if !defined(LFs3_RDONLY) && defined(LFS3_GBMAP) && !defined(LFS3_YES_GBMAP) +int lfs3_fs_mkgbmap(lfs3_t *lfs3); +#endif + +// Disable the global on-disk block-map +// +// Returns a negative error code on failure. Does nothing if no gbmap +// is found. +#if !defined(LFs3_RDONLY) && defined(LFS3_GBMAP) && !defined(LFS3_YES_GBMAP) +int lfs3_fs_rmgbmap(lfs3_t *lfs3); +#endif + #endif diff --git a/tests/test_alloc.toml b/tests/test_alloc.toml index 20ed2b55..15470369 100644 --- a/tests/test_alloc.toml +++ b/tests/test_alloc.toml @@ -20,7 +20,7 @@ defines.COUNT = [ 'BLOCK_COUNT/2', 'BLOCK_COUNT/4', '5', - '2', + 'FORMAT_BLOCK_COUNT', ] defines.ERASE = [false, true] if = 'COUNT >= FORMAT_BLOCK_COUNT' @@ -67,7 +67,7 @@ defines.COUNT = [ 'BLOCK_COUNT/2', 'BLOCK_COUNT/4', '5', - '2', + 'FORMAT_BLOCK_COUNT', ] defines.ERASE = [false, true] if = 'COUNT >= FORMAT_BLOCK_COUNT' @@ -643,10 +643,102 @@ code = ''' ''' -# TODO more nospc tests (opened files? other?) -# nospc tests mostly test that things still work when block allocation -# wraparound occurs +# test that alloc can wrap around the disk +[cases.test_alloc_wraparound_files] +defines.COUNT = [ + 'BLOCK_COUNT', + 'BLOCK_COUNT-1', + 'BLOCK_COUNT/2', + 'BLOCK_COUNT/4', + '5', + 'FORMAT_BLOCK_COUNT', +] +defines.SIZE = [ + 'BLOCK_SIZE', + '2*BLOCK_SIZE', + '8*BLOCK_SIZE', +] +defines.N = ['5', 'BLOCK_COUNT/2'] +defines.WRAPAROUND = 3 +if = [ + 'COUNT >= FORMAT_BLOCK_COUNT', + 'COUNT >= 2*N*SIZE/BLOCK_SIZE', +] +code = ''' + // test various block counts + struct lfs3_cfg cfg = *CFG; + cfg.block_count = COUNT; + lfs3_t lfs3; + lfs3_format(&lfs3, LFS3_F_RDWR, &cfg) => 0; + lfs3_mount(&lfs3, LFS3_M_RDWR, &cfg) => 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 that alloc works up until nospc [cases.test_alloc_nospc_dirs] defines.COUNT = [ 'BLOCK_COUNT', @@ -654,7 +746,7 @@ defines.COUNT = [ 'BLOCK_COUNT/2', 'BLOCK_COUNT/4', '5', - '2', + 'FORMAT_BLOCK_COUNT', ] if = 'COUNT >= FORMAT_BLOCK_COUNT' code = ''' @@ -728,7 +820,7 @@ defines.COUNT = [ 'BLOCK_COUNT/2', 'BLOCK_COUNT/4', '5', - '2', + 'FORMAT_BLOCK_COUNT', ] defines.SIZE = [ '0', diff --git a/tests/test_gbmap.toml b/tests/test_gbmap.toml index 58bb3032..cddb7cf4 100644 --- a/tests/test_gbmap.toml +++ b/tests/test_gbmap.toml @@ -9,7 +9,7 @@ after = ['test_btree', 'test_mtree'] ifdef = 'LFS3_GBMAP' -# test gbmap operations +# test low-level gbmap operations # test simple set operations [cases.test_gbmap_set_split] @@ -384,3 +384,786 @@ code = ''' free(sim); lfs3_unmount(&lfs3) => 0; ''' + + + +# test high-level gbmap operations + +# test that the gbmap generally works +[cases.test_gbmap_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 +if = [ + 'COUNT >= 3', + 'COUNT >= 2*N*SIZE/BLOCK_SIZE', +] +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); + + // 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 that we can remove the gbmap and things still work +[cases.test_gbmap_rmgbmap] +ifndef = 'LFS3_YES_GBMAP' +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 +# REMOUNT=0 => don't remount +# REMOUNT=1 => remount after +# REMOUNT=2 => remount before +# REMOUNT=3 => remount both after and before +defines.REMOUNT = [0, 1, 2, 3] +if = [ + 'COUNT >= 3', + 'COUNT >= 2*N*SIZE/BLOCK_SIZE', +] +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); + + // 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; + } + } + + // remount before? + if (REMOUNT & 2) { + lfs3_unmount(&lfs3) => 0; + lfs3_mount(&lfs3, LFS3_M_RDWR, &cfg) => 0; + } + + // now remove the gbmap + lfs3_fs_rmgbmap(&lfs3) => 0; + + // remount after? + if (REMOUNT & 1) { + lfs3_unmount(&lfs3) => 0; + lfs3_mount(&lfs3, LFS3_M_RDWR, &cfg) => 0; + } + + // we should no longer have a gbmap + lfs3_fs_stat(&lfs3, &fsinfo) => 0; + assert(!(fsinfo.flags & LFS3_I_GBMAP)); + + // create n files repeatedly until we're sure we've wrapped around + // a few times + 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 that we can make the gbmap and things still work +[cases.test_gbmap_mkgbmap] +ifndef = 'LFS3_YES_GBMAP' +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 +# REMOUNT=0 => don't remount +# REMOUNT=1 => remount after +# REMOUNT=2 => remount before +# REMOUNT=3 => remount both after and before +defines.REMOUNT = [0, 1, 2, 3] +if = [ + 'COUNT >= 3', + 'COUNT >= 2*N*SIZE/BLOCK_SIZE', +] +code = ''' + // test various block counts + struct lfs3_cfg cfg = *CFG; + cfg.block_count = COUNT; + lfs3_t lfs3; + // note the lack of gbmap flag + lfs3_format(&lfs3, LFS3_F_RDWR, &cfg) => 0; + lfs3_mount(&lfs3, LFS3_M_RDWR, &cfg) => 0; + + // check that we were formatted without the gbmap + struct lfs3_fsinfo fsinfo; + lfs3_fs_stat(&lfs3, &fsinfo) => 0; + assert(!(fsinfo.flags & LFS3_I_GBMAP)); + + // 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; + } + } + + // remount before? + if (REMOUNT & 2) { + lfs3_unmount(&lfs3) => 0; + lfs3_mount(&lfs3, LFS3_M_RDWR, &cfg) => 0; + } + + // now make the gbmap + lfs3_fs_mkgbmap(&lfs3) => 0; + + // remount after? + if (REMOUNT & 1) { + lfs3_unmount(&lfs3) => 0; + lfs3_mount(&lfs3, LFS3_M_RDWR, &cfg) => 0; + } + + // we should now have a gbmap + lfs3_fs_stat(&lfs3, &fsinfo) => 0; + assert(fsinfo.flags & LFS3_I_GBMAP); + + // create n files repeatedly until we're sure we've wrapped around + // a few times + 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 that we can remove and then remake the gbmap and things still +# work +# +# this leaves the disk with garbage gstate that mkgbmap needs to clean +# up +[cases.test_gbmap_rmmkgbmap] +ifndef = 'LFS3_YES_GBMAP' +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 +# REMOUNT=0x1 => remount after +# REMOUNT=0x2 => remount before make +# REMOUNT=0x4 => remount after remove +# REMOUNT=0x8 => remount before +defines.REMOUNT = 'range(0x10)' +if = [ + 'COUNT >= 3', + 'COUNT >= 2*N*SIZE/BLOCK_SIZE', +] +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); + + // 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; + } + } + + // remount before? + if (REMOUNT & 8) { + lfs3_unmount(&lfs3) => 0; + lfs3_mount(&lfs3, LFS3_M_RDWR, &cfg) => 0; + } + + // now remove the gbmap + lfs3_fs_rmgbmap(&lfs3) => 0; + + // remount after remove? + if (REMOUNT & 4) { + lfs3_unmount(&lfs3) => 0; + lfs3_mount(&lfs3, LFS3_M_RDWR, &cfg) => 0; + } + + // we should no longer have a gbmap + lfs3_fs_stat(&lfs3, &fsinfo) => 0; + assert(!(fsinfo.flags & LFS3_I_GBMAP)); + + // create n files repeatedly until we're sure we've wrapped around + // a few times + 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; + } + } + + // remount before make? + if (REMOUNT & 2) { + lfs3_unmount(&lfs3) => 0; + lfs3_mount(&lfs3, LFS3_M_RDWR, &cfg) => 0; + } + + // now remake the gbmap + lfs3_fs_mkgbmap(&lfs3) => 0; + + // remount after? + if (REMOUNT & 1) { + lfs3_unmount(&lfs3) => 0; + lfs3_mount(&lfs3, LFS3_M_RDWR, &cfg) => 0; + } + + // we should now have a gbmap + lfs3_fs_stat(&lfs3, &fsinfo) => 0; + assert(fsinfo.flags & LFS3_I_GBMAP); + + // create n files repeatedly until we're sure we've wrapped around + // a few times + 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 that we can make and then remove the gbmap and things still +# work +# +# not exactly sure what this tests, but including for completeness +[cases.test_gbmap_mkrmgbmap] +ifndef = 'LFS3_YES_GBMAP' +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 +# REMOUNT=0x1 => remount after +# REMOUNT=0x2 => remount before remove +# REMOUNT=0x4 => remount after make +# REMOUNT=0x8 => remount before +defines.REMOUNT = 'range(0x10)' +if = [ + 'COUNT >= 3', + 'COUNT >= 2*N*SIZE/BLOCK_SIZE', +] +code = ''' + // test various block counts + struct lfs3_cfg cfg = *CFG; + cfg.block_count = COUNT; + lfs3_t lfs3; + // note the lack of gbmap flag + lfs3_format(&lfs3, LFS3_F_RDWR, &cfg) => 0; + lfs3_mount(&lfs3, LFS3_M_RDWR, &cfg) => 0; + + // check that we were formatted without the gbmap + struct lfs3_fsinfo fsinfo; + lfs3_fs_stat(&lfs3, &fsinfo) => 0; + assert(!(fsinfo.flags & LFS3_I_GBMAP)); + + // 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; + } + } + + // remount before? + if (REMOUNT & 8) { + lfs3_unmount(&lfs3) => 0; + lfs3_mount(&lfs3, LFS3_M_RDWR, &cfg) => 0; + } + + // now make the gbmap + lfs3_fs_mkgbmap(&lfs3) => 0; + + // remount after make? + if (REMOUNT & 4) { + lfs3_unmount(&lfs3) => 0; + lfs3_mount(&lfs3, LFS3_M_RDWR, &cfg) => 0; + } + + // we should now have a gbmap + lfs3_fs_stat(&lfs3, &fsinfo) => 0; + assert(fsinfo.flags & LFS3_I_GBMAP); + + // create n files repeatedly until we're sure we've wrapped around + // a few times + 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; + } + } + + // remount before remove? + if (REMOUNT & 2) { + lfs3_unmount(&lfs3) => 0; + lfs3_mount(&lfs3, LFS3_M_RDWR, &cfg) => 0; + } + + // now remove the gbmap + lfs3_fs_rmgbmap(&lfs3) => 0; + + // remount after? + if (REMOUNT & 1) { + lfs3_unmount(&lfs3) => 0; + lfs3_mount(&lfs3, LFS3_M_RDWR, &cfg) => 0; + } + + // we should no longer have a gbmap + lfs3_fs_stat(&lfs3, &fsinfo) => 0; + assert(!(fsinfo.flags & LFS3_I_GBMAP)); + + // create n files repeatedly until we're sure we've wrapped around + // a few times + 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; +'''