diff --git a/lfs.c b/lfs.c index b4886efc..957318d6 100644 --- a/lfs.c +++ b/lfs.c @@ -2568,11 +2568,220 @@ static int lfsr_rbyd_appendall(lfs_t *lfs, lfsr_rbyd_t *rbyd, static int lfsr_rbyd_compact(lfs_t *lfs, lfsr_rbyd_t *rbyd, lfs_ssize_t start_id, lfs_ssize_t end_id, bool drop_vestigial, const lfsr_rbyd_t *source) { +#ifndef LFSR_NO_REBALANCE + // must fetch before mutating! + LFS_ASSERT(lfsr_rbyd_isfetched(rbyd)); + + // we can't do anything if we're not erased + int err; + if (rbyd->off >= lfs->cfg->block_size) { + err = LFS_ERR_RANGE; + goto failed; + } + + // make sure every rbyd starts with a revision count + if (rbyd->off == 0) { + err = lfsr_rbyd_appendrev(lfs, rbyd, 0); + if (err) { + goto failed; + } + } + // optionally drop the first name in our rbyd, a so-called "vestigial" // name, see lfsr_btree_commit for why we need to do this lfs_ssize_t id = start_id; - lfsr_tag_t tag = (drop_vestigial ? lfsr_tag_next(LFSR_TAG_BRANCH) : 0); - + // TODO vestigial names? + //lfsr_tag_t tag = (drop_vestigial ? lfsr_tag_next(LFSR_TAG_BRANCH) : 0); + lfsr_tag_t tag = 0; + + // first copy over raw tags, note this doesn't create a tree + lfs_size_t trunk_count = 0; + lfs_size_t trunk_w = 0; + lfs_off_t layer_start = rbyd->off; + while (true) { + lfs_size_t w; + lfsr_data_t data; + int err = lfsr_rbyd_lookupnext(lfs, source, id, lfsr_tag_next(tag), + &id, &tag, &w, &data); + if (err && err != LFS_ERR_NOENT) { + return err; + } + if (err == LFS_ERR_NOENT || (end_id >= 0 && id >= end_id)) { + break; + } + + // keep track of the layer weight + trunk_w += w; + + // write the tag + lfs_ssize_t d = lfsr_bd_progtag(lfs, rbyd->block, rbyd->off, + tag, w, lfsr_data_size(data), + &rbyd->crc); + if (d < 0) { + err = d; + goto failed; + } + rbyd->off += d; + + // and the data + err = lfsr_bd_progdata(lfs, rbyd->block, rbyd->off, data, + &rbyd->crc); + if (err) { + goto failed; + } + rbyd->off += lfsr_data_size(data); + + trunk_count += 1; + } + lfs_off_t layer_end = rbyd->off; + + // build each layer of a perfectly balanced tree upwards + while (trunk_count > 1) { + lfs_off_t off = layer_start; + trunk_count = 0; + trunk_w = 0; + layer_start = rbyd->off; + while (off < layer_end) { + // read two trunks + lfs_off_t loff = off; + lfsr_tag_t ltag = 0; + lfs_size_t lw = 0; + while (true) { + lfsr_tag_t tag; + lfs_size_t w; + lfs_size_t size; + lfs_ssize_t d = lfsr_bd_readtag(lfs, rbyd->block, off, + layer_end-off, + &tag, &w, &size, NULL); + if (d < 0) { + err = d; + goto failed; + } + off += d; + lw += w; + trunk_w += w; + + // keep track of last non-null tag + if (tag) { + ltag = tag; + } + + // skip the data + if (!lfsr_tag_isalt(tag)) { + off += size; + } + + // read all tags in the trunk + if (!lfsr_tag_isalt(tag)) { + break; + } + } + + lfs_off_t roff = off; + lfsr_tag_t rtag = 0; + lfs_size_t rw = 0; + if (off >= layer_end) { + roff = loff; + rtag = ltag; + rw = lw; + } else { + while (true) { + lfsr_tag_t tag; + lfs_size_t w; + lfs_size_t size; + lfs_ssize_t d = lfsr_bd_readtag(lfs, rbyd->block, off, + layer_end-off, + &tag, &w, &size, NULL); + if (d < 0) { + err = d; + goto failed; + } + off += d; + rw += w; + trunk_w += w; + + // keep track of last non-null tag + if (tag) { + rtag = tag; + } + + // skip the data + if (!lfsr_tag_isalt(tag)) { + off += size; + } + + // read all tags in the trunk + if (!lfsr_tag_isalt(tag)) { + break; + } + } + + // connect ltag with an altle + lfs_ssize_t d = lfsr_bd_progtag(lfs, rbyd->block, rbyd->off, + LFSR_TAG_ALTLE(false, lfsr_tag_key(ltag)), + lw, + rbyd->off - loff, + &rbyd->crc); + if (d < 0) { + err = d; + goto failed; + } + rbyd->off += d; + + trunk_w = lw + rw; + } + + lfs_ssize_t d = lfsr_bd_progtag(lfs, rbyd->block, rbyd->off, + LFSR_TAG_ALTLE(false, lfsr_tag_key(rtag)), + rw, + rbyd->off - roff, + &rbyd->crc); + if (d < 0) { + err = d; + goto failed; + } + rbyd->off += d; + + // terminate with a null tag + d = lfsr_bd_progtag(lfs, rbyd->block, rbyd->off, + LFSR_TAG_NULL, 0, 0, + &rbyd->crc); + if (d < 0) { + err = d; + goto failed; + } + rbyd->off += d; + + trunk_count += 1; + } + layer_end = rbyd->off; + } + + // TODO if we use trunk=0 special this way, should fetch + // also use trunk=0 for its "no-mdir" test? + + // done! just need to update our trunk/weight + if (trunk_count >= 1) { + rbyd->trunk = layer_start; + } + rbyd->weight = trunk_w; + + return 0; + +failed:; + // if we fail mark the rbyd as unerased and release the pcache + lfs_cache_zero(lfs, &lfs->pcache); + rbyd->off = lfs->cfg->block_size; + return err; + +#else + // optionally drop the first name in our rbyd, a so-called "vestigial" + // name, see lfsr_btree_commit for why we need to do this + lfs_ssize_t id = start_id; + // TODO vestigial names? + //lfsr_tag_t tag = (drop_vestigial ? lfsr_tag_next(LFSR_TAG_BRANCH) : 0); + lfsr_tag_t tag = 0; + // try to copy over tags while (true) { lfsr_data_t data; @@ -2598,6 +2807,7 @@ static int lfsr_rbyd_compact(lfs_t *lfs, lfsr_rbyd_t *rbyd, return err; } } +#endif } static int lfsr_rbyd_commit(lfs_t *lfs, lfsr_rbyd_t *rbyd, diff --git a/scripts/dbgbtree.py b/scripts/dbgbtree.py index 7eb54708..fd488977 100755 --- a/scripts/dbgbtree.py +++ b/scripts/dbgbtree.py @@ -87,6 +87,7 @@ def fromleb128(data): return word, len(data) def fromtag(data): + data = data.ljust(4, b'\0') tag = (data[0] << 8) | data[1] weight, d = fromleb128(data[2:]) size, d_ = fromleb128(data[2+d:]) diff --git a/scripts/dbgmtree.py b/scripts/dbgmtree.py index 9715c627..df052039 100755 --- a/scripts/dbgmtree.py +++ b/scripts/dbgmtree.py @@ -87,6 +87,7 @@ def fromleb128(data): return word, len(data) def fromtag(data): + data = data.ljust(4, b'\0') tag = (data[0] << 8) | data[1] weight, d = fromleb128(data[2:]) size, d_ = fromleb128(data[2+d:]) diff --git a/scripts/dbgrbyd.py b/scripts/dbgrbyd.py index d682e5f5..3d6a2804 100755 --- a/scripts/dbgrbyd.py +++ b/scripts/dbgrbyd.py @@ -96,6 +96,7 @@ def fromleb128(data): return word, len(data) def fromtag(data): + data = data.ljust(4, b'\0') tag = (data[0] << 8) | data[1] weight, d = fromleb128(data[2:]) size, d_ = fromleb128(data[2+d:]) diff --git a/tests/test_mtree.toml b/tests/test_mtree.toml index 0cabb7b4..cff356dd 100644 --- a/tests/test_mtree.toml +++ b/tests/test_mtree.toml @@ -3,7 +3,7 @@ defines.LOOKAHEAD_SIZE = 'BLOCK_COUNT / 8' # test a single mroot -[cases.test_mtree_one_mroot] +[cases.test_mtree_mroot] code = ''' lfs_t lfs; lfsr_format(&lfs, cfg) => 0; @@ -11,21 +11,27 @@ code = ''' lfsr_unmount(&lfs) => 0; ''' -# test a single mroot with a custom attribute -[cases.test_mtree_one_mroot_attr] +# test a single mroot with attributes +[cases.test_mtree_mroot_attrs] +defines.N = [1, 3] in = 'lfs.c' code = ''' + const char *alphas = "abcdefghijklmnopqrstuvwxyz"; lfs_t lfs; lfsr_format(&lfs, cfg) => 0; lfsr_mount(&lfs, cfg) => 0; - lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS( - LFSR_ATTR(-1, UATTR(1), 0, "ardvark", 7))) => 0; + for (lfs_size_t i = 0; i < N; i++) { + lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS( + LFSR_ATTR(-1, UATTR(i), 0, &alphas[i % 26], 1))) => 0; + } - uint8_t buffer[7]; - lfsr_mdir_get(&lfs, &lfs.mroot, - -1, LFSR_TAG_UATTR(1), buffer, 7) => 7; - assert(memcmp(buffer, "ardvark", 7) == 0); + for (lfs_size_t i = 0; i < N; i++) { + uint8_t buffer[1]; + lfsr_mdir_get(&lfs, &lfs.mroot, + -1, LFSR_TAG_UATTR(i), buffer, 1) => 1; + assert(memcmp(buffer, &alphas[i % 26], 1) == 0); + } lfsr_unmount(&lfs) => 0; @@ -33,15 +39,59 @@ code = ''' // check things stay sane after remount lfsr_mount(&lfs, cfg) => 0; - lfsr_mdir_get(&lfs, &lfs.mroot, - -1, LFSR_TAG_UATTR(1), buffer, 7) => 7; - assert(memcmp(buffer, "ardvark", 7) == 0); + for (lfs_size_t i = 0; i < N; i++) { + uint8_t buffer[1]; + lfsr_mdir_get(&lfs, &lfs.mroot, + -1, LFSR_TAG_UATTR(i), buffer, 1) => 1; + assert(memcmp(buffer, &alphas[i % 26], 1) == 0); + } + + lfsr_unmount(&lfs) => 0; +''' + +# test a single mroot with forced compaction +[cases.test_mtree_mroot_compact] +defines.N = [1, 3] +in = 'lfs.c' +code = ''' + const char *alphas = "abcdefghijklmnopqrstuvwxyz"; + lfs_t lfs; + lfsr_format(&lfs, cfg) => 0; + lfsr_mount(&lfs, cfg) => 0; + + for (lfs_size_t i = 0; i < N; i++) { + // force mroot to compact + lfs.mroot.rbyd.off = BLOCK_SIZE; + + lfsr_mdir_commit(&lfs, &lfs.mroot, &(lfs_ssize_t){-1}, LFSR_ATTRS( + LFSR_ATTR(-1, UATTR(i), 0, &alphas[i % 26], 1))) => 0; + } + + for (lfs_size_t i = 0; i < N; i++) { + uint8_t buffer[1]; + lfsr_mdir_get(&lfs, &lfs.mroot, + -1, LFSR_TAG_UATTR(i), buffer, 1) => 1; + assert(memcmp(buffer, &alphas[i % 26], 1) == 0); + } + + lfsr_unmount(&lfs) => 0; + + + // check things stay sane after remount + lfsr_mount(&lfs, cfg) => 0; + + for (lfs_size_t i = 0; i < N; i++) { + uint8_t buffer[1]; + lfsr_mdir_get(&lfs, &lfs.mroot, + -1, LFSR_TAG_UATTR(i), buffer, 1) => 1; + assert(memcmp(buffer, &alphas[i % 26], 1) == 0); + } lfsr_unmount(&lfs) => 0; ''' # test a single mroot with many commits -[cases.test_mtree_one_mroot_many_commits] +[cases.test_mtree_mroot_many_commits] defines.N = [5, 5000] in = 'lfs.c' code = '''