diff --git a/lfs.c b/lfs.c index c92db4cf..97bea3ea 100644 --- a/lfs.c +++ b/lfs.c @@ -4846,41 +4846,40 @@ static int lfsr_mdir_compact_(lfs_t *lfs, lfsr_mdir_t *mdir, // - mid = manchor => never alloc (mroot anchor) // - mid = wl => only alloc if mdir is tired (wear-leveling) // - otherwise => always alloc, use this mid (new mdir) + + // first thing we need to do is read our current revision count uint32_t rev; - if (mid != LFSR_MID_MANCHOR) { - // first thing we need to do is read our current revision count - int err = lfsr_bd_read(lfs, source->rbyd.block, 0, sizeof(uint32_t), + int err = lfsr_bd_read(lfs, source->rbyd.block, 0, sizeof(uint32_t), + &rev, sizeof(uint32_t)); + if (err && err != LFS_ERR_CORRUPT) { + return err; + } + // note we allow corrupt errors here, as long as they are consistent + rev = (err != LFS_ERR_CORRUPT ? lfs_fromle32_(&rev) : 0); + + // decide if we need to relocate + if (mid != LFSR_MID_MANCHOR && (mid != LFSR_MID_WL || ( + lfs->cfg->block_cycles > 0 + // TODO rev things + && (rev + 1) % lfs->cfg->block_cycles == 0))) { + // allocate a new mdir for relocation + err = lfsr_mdir_alloc(lfs, mdir, + (mid != LFSR_MID_WL ? mid : mdir->mid)); + if (err) { + return err; + } + + // read the new revision count + // + // we use whatever is on-disk to avoid needing to rewrite the + // redund block + err = lfsr_bd_read(lfs, mdir->rbyd.block, 0, sizeof(uint32_t), &rev, sizeof(uint32_t)); if (err && err != LFS_ERR_CORRUPT) { return err; } // note we allow corrupt errors here, as long as they are consistent rev = (err != LFS_ERR_CORRUPT ? lfs_fromle32_(&rev) : 0); - - // decide if we need to relocate - if (mid != LFSR_MID_WL || ( - lfs->cfg->block_cycles > 0 - // TODO rev things - && (rev + 1) % lfs->cfg->block_cycles == 0)) { - // allocate a new mdir for relocation - err = lfsr_mdir_alloc(lfs, mdir, - (mid != LFSR_MID_WL ? mid : mdir->mid)); - if (err) { - return err; - } - - // read the new revision count - // - // we use whatever is on-disk to avoid needing to rewrite the - // redund block - err = lfsr_bd_read(lfs, mdir->rbyd.block, 0, sizeof(uint32_t), - &rev, sizeof(uint32_t)); - if (err && err != LFS_ERR_CORRUPT) { - return err; - } - // note we allow corrupt errors here, as long as they are consistent - rev = (err != LFS_ERR_CORRUPT ? lfs_fromle32_(&rev) : 0); - } } // swap our rbyds @@ -4892,7 +4891,7 @@ static int lfsr_mdir_compact_(lfs_t *lfs, lfsr_mdir_t *mdir, mdir->rbyd.crc = 0; // erase, preparing for compact - int err = lfsr_bd_erase(lfs, mdir->rbyd.block); + err = lfsr_bd_erase(lfs, mdir->rbyd.block); if (err) { return err; } diff --git a/tests/test_mtree.toml b/tests/test_mtree.toml index 3ce105f5..0cabb7b4 100644 --- a/tests/test_mtree.toml +++ b/tests/test_mtree.toml @@ -1787,6 +1787,64 @@ code = ''' lfsr_unmount(&lfs) => 0; ''' +[cases.test_mtree_extend_twice] +# this should be set so only one entry can fit in a metadata block +defines.SIZE = 'BLOCK_SIZE / 4' +# make it so blocks relocate every two compacts +defines.BLOCK_CYCLES = 2 +# force our block to compact by setting prog_size=block_size, we don't have +# any way to indirectly force the intermediary mroots to compact otherwise +defines.PROG_SIZE = 'BLOCK_SIZE' +in = 'lfs.c' +code = ''' + const char *alphas = "abcdefghijklmnopqrstuvwxyz"; + lfs_t lfs; + lfsr_format(&lfs, cfg) => 0; + lfsr_mount(&lfs, cfg) => 0; + + // prepare mroot with an attr + uint8_t buffer[SIZE]; + memset(buffer, alphas[0 % 26], SIZE); + lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS( + LFSR_ATTR(-1, UATTR(1), 0, buffer, SIZE))) => 0; + + // force mroot to compact 2x2 times, this should extend the mroot twice + lfsr_mdir_t old_mroot = lfs.mroot; + + for (int i = 0; i < 4; i++) { + lfs.mroot.rbyd.off = BLOCK_SIZE; + lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, NULL, 0) => 0; + } + lfs.mroot.rbyd.off = BLOCK_SIZE; + memset(buffer, alphas[1 % 26], SIZE); + lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS( + LFSR_ATTR(-1, UATTR(1), 0, buffer, SIZE))) => 0; + + // assert we relocated + assert(!lfsr_mdir_eq(&old_mroot, &lfs.mroot)); + + // assert that our attr is still in the mroot + lfsr_mdir_get(&lfs, &lfs.mroot, -1, LFSR_TAG_UATTR(1), + buffer, SIZE) => SIZE; + assert(memcmp(buffer, &alphas[1 % 26], 1) == 0); + + lfsr_unmount(&lfs) => 0; + + + // check things stay sane after remount + lfsr_mount(&lfs, cfg) => 0; + + // assert we relocated + assert(!lfsr_mdir_eq(&old_mroot, &lfs.mroot)); + + // assert that our attr is still in the mroot + lfsr_mdir_get(&lfs, &lfs.mroot, -1, LFSR_TAG_UATTR(1), + buffer, SIZE) => SIZE; + assert(memcmp(buffer, &alphas[1 % 26], 1) == 0); + + lfsr_unmount(&lfs) => 0; +''' + [cases.test_mtree_relocate_mroot] # this should be set so only one entry can fit in a metadata block defines.SIZE = 'BLOCK_SIZE / 4' @@ -3802,3 +3860,133 @@ code = ''' lfsr_unmount(&lfs) => 0; ''' + +## Magic consistency ## + +# make sure our magic string ("littlefs") shows up in the same place (off=8) +[cases.test_mtree_magic] +# this should be set so only one entry can fit in a metadata block +defines.SIZE = 'BLOCK_SIZE / 4' +code = ''' + lfs_t lfs; + lfsr_format(&lfs, cfg) => 0; + + // check our magic string + // + // note if we lose power we may not have the magic string in both blocks! + // but we don't lose power in this test so we can assert the magic string + // is present in both + uint8_t magic[lfs_max(16, READ_SIZE)]; + cfg->read(cfg, 0, 0, magic, lfs_max(16, READ_SIZE)) => 0; + assert(memcmp(&magic[8], "littlefs", 8) == 0); + cfg->read(cfg, 1, 0, magic, lfs_max(16, READ_SIZE)) => 0; + assert(memcmp(&magic[8], "littlefs", 8) == 0); +''' + +[cases.test_mtree_magic_extend] +# this should be set so only one entry can fit in a metadata block +defines.SIZE = 'BLOCK_SIZE / 4' +# make it so blocks relocate every two compacts +defines.BLOCK_CYCLES = 2 +in = 'lfs.c' +code = ''' + const char *alphas = "abcdefghijklmnopqrstuvwxyz"; + lfs_t lfs; + lfsr_format(&lfs, cfg) => 0; + lfsr_mount(&lfs, cfg) => 0; + + // prepare mroot with an attr + uint8_t buffer[SIZE]; + memset(buffer, alphas[0 % 26], SIZE); + lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS( + LFSR_ATTR(-1, UATTR(1), 0, buffer, SIZE))) => 0; + + // force mroot to compact twice, this should extend the mroot + lfsr_mdir_t old_mroot = lfs.mroot; + + lfs.mroot.rbyd.off = BLOCK_SIZE; + lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, NULL, 0) => 0; + lfs.mroot.rbyd.off = BLOCK_SIZE; + memset(buffer, alphas[1 % 26], SIZE); + lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS( + LFSR_ATTR(-1, UATTR(1), 0, buffer, SIZE))) => 0; + + // assert we relocated + assert(!lfsr_mdir_eq(&old_mroot, &lfs.mroot)); + + // assert that our attr is still in the mroot + lfsr_mdir_get(&lfs, &lfs.mroot, -1, LFSR_TAG_UATTR(1), + buffer, SIZE) => SIZE; + assert(memcmp(buffer, &alphas[1 % 26], 1) == 0); + + lfsr_unmount(&lfs) => 0; + + + // check our magic string + // + // note if we lose power we may not have the magic string in both blocks! + // but we don't lose power in this test so we can assert the magic string + // is present in both + uint8_t magic[lfs_max(16, READ_SIZE)]; + cfg->read(cfg, 0, 0, magic, lfs_max(16, READ_SIZE)) => 0; + assert(memcmp(&magic[8], "littlefs", 8) == 0); + cfg->read(cfg, 1, 0, magic, lfs_max(16, READ_SIZE)) => 0; + assert(memcmp(&magic[8], "littlefs", 8) == 0); +''' + +[cases.test_mtree_magic_extend_twice] +# this should be set so only one entry can fit in a metadata block +defines.SIZE = 'BLOCK_SIZE / 4' +# make it so blocks relocate every two compacts +defines.BLOCK_CYCLES = 2 +# force our block to compact by setting prog_size=block_size, we don't have +# any way to indirectly force the intermediary mroots to compact otherwise +defines.PROG_SIZE = 'BLOCK_SIZE' +in = 'lfs.c' +code = ''' + const char *alphas = "abcdefghijklmnopqrstuvwxyz"; + lfs_t lfs; + lfsr_format(&lfs, cfg) => 0; + lfsr_mount(&lfs, cfg) => 0; + + // prepare mroot with an attr + uint8_t buffer[SIZE]; + memset(buffer, alphas[0 % 26], SIZE); + lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS( + LFSR_ATTR(-1, UATTR(1), 0, buffer, SIZE))) => 0; + + // force mroot to compact 2x2 times, this should extend the mroot twice + lfsr_mdir_t old_mroot = lfs.mroot; + + for (int i = 0; i < 4; i++) { + lfs.mroot.rbyd.off = BLOCK_SIZE; + lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, NULL, 0) => 0; + } + lfs.mroot.rbyd.off = BLOCK_SIZE; + memset(buffer, alphas[1 % 26], SIZE); + lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS( + LFSR_ATTR(-1, UATTR(1), 0, buffer, SIZE))) => 0; + + // assert we relocated + assert(!lfsr_mdir_eq(&old_mroot, &lfs.mroot)); + + // assert that our attr is still in the mroot + lfsr_mdir_get(&lfs, &lfs.mroot, -1, LFSR_TAG_UATTR(1), + buffer, SIZE) => SIZE; + assert(memcmp(buffer, &alphas[1 % 26], 1) == 0); + + lfsr_unmount(&lfs) => 0; + + + // check our magic string + // + // note if we lose power we may not have the magic string in both blocks! + // but we don't lose power in this test so we can assert the magic string + // is present in both + uint8_t magic[lfs_max(16, READ_SIZE)]; + cfg->read(cfg, 0, 0, magic, lfs_max(16, READ_SIZE)) => 0; + assert(memcmp(&magic[8], "littlefs", 8) == 0); + cfg->read(cfg, 1, 0, magic, lfs_max(16, READ_SIZE)) => 0; + assert(memcmp(&magic[8], "littlefs", 8) == 0); +''' +