From ca710b5a2953f59aa279721ee7353293b838ed50 Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Sat, 31 Dec 2022 00:52:12 -0600 Subject: [PATCH] Initial, very, very rough implementation of rbyd range deletion Tree deletion is such a pain. It always seems like an easy addition to the core algorithm but always comes with problems. The initial plan for deletes was to iterate through all tags, tombstone, and then adjust weights as needed. This accomplishes deletes with little change to the rbyd algorithm, but adds a complex traversal inside the commit logic. Doable in one commit, but complex. It also risks weird unintuitive corner cases since the cost of deletion grows with the number of tags being deleted (O(m log n)). But this rbyd data structure is a tree, so in theory it's possible to delete a whole range of tags in a single O(log n) operation. --- This is a proof-of-concept range deletion algorithm for rbyd trees. Note, this does not preserve rbyd's balancing properties! But it is no worse than tombstoning. This is acceptable for littlefs as any unbalanced trees will be rebalanced during compaction. The idea is to follow the same underlying dhara algorithm, where we follow a search path and save any alt pointers not taken, but we follow both search paths that form the outside of the range, and only keep outside edges. For example, a tree: .-------o-------. | | .---o---. .---o---. | | | | .-o-. .-o-. .-o-. .-o-. | | | | | | | | a b c d e f g h To delete the range d-e, we would search for d, and search for e: ********o******** * * .---***** *****---. | * * | .-o-. .-*** ***-. .-o-. | | | * * | | | a b c d e f g h And keep the outside edges: .--- ---. | | .-o-. .- -. .-o-. | | | | | | a b c f g h But how do we combine the outside edges? The simpler option is to do both searches seperately, one after the other. This would end up with a tree like this: .---------o | | .-o-. .---o | | | | a b c o---------. | | o---. .-o-. | | | | _ f g h But this horribly throws off the balance of our tree! It's worse than tombstoning, and gets worse with more tags. An alternative strategy, which is used here, is to alternate edges as we descend down the tree. This unfortunately is more complex, and requires ~2x the RAM, but better preserves the balance of our tree. It isn't perfect, because we lose color information, but we can leave that up to compaction: .---------o | | .-o-. o---------. | | | | a b .---o .-o-. | | | | c o---. g h | | _ f I also hope this can be merged into lfs_rbyd_append, deduplicating the entire core rbyd append algorithm. --- lfs.c | 317 +++++++++++++++++++++++++++++++-- scripts/dbgrbyd.py | 74 ++++---- tests/test_rbyd.toml | 406 ++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 751 insertions(+), 46 deletions(-) diff --git a/lfs.c b/lfs.c index ce50d3ae..e9a3a68a 100644 --- a/lfs.c +++ b/lfs.c @@ -415,7 +415,7 @@ enum lfs_rtag_type1 { LFS_TYPE1_CREATE = 0x0040, LFS_TYPE1_CREATEREG = 0x00c0, LFS_TYPE1_CREATEDIR = 0x0140, - LFS_TYPE1_DELETE = 0x0048, + LFS_TYPE1_DELETE = 0x0041, LFS_TYPE1_STRUCT = 0x0050, LFS_TYPE1_UATTR = 0x0060, @@ -1126,7 +1126,12 @@ static int lfs_rbyd_fetch(lfs_t *lfs, // found a create? increase count of ids if (lfs_rtag_type1(tag) == LFS_TYPE1_CREATE) { + LFS_ASSERT(count < 0xffff); count += 1; + // found a delete? decrease count of ids + } else if (lfs_rtag_type1(tag) == LFS_TYPE1_DELETE) { + LFS_ASSERT(count > 0); + count -= 1; // found an fcrc? save for later } else if (lfs_rtag_type1(tag) == LFS_TYPE1_FCRC) { err = lfs_bd_read(lfs, @@ -1250,12 +1255,6 @@ tryagain:; // update the tag id lfs_rtag_t tag_ = lfs_rtag_setid(alt, lfs_rtag_id(tag)); - // was removed? go on to the next tag - if (lfs_rtag_isrm(tag_)) { - tag = lfs_rtag_inc(tag_); - goto tryagain; - } - printf("lt, gt = (%x, %x)\n", lt, gt); printf("lookup %08x => %08x (raw %08x)\n", tag, tag_, alt); @@ -1264,6 +1263,12 @@ tryagain:; return LFS_ERR_NOENT; } + // was removed? go on to the next tag + if (lfs_rtag_isrm(tag_)) { + tag = lfs_rtag_inc(tag_); + goto tryagain; + } + // save what we found *off = branch + delta; *size = jump; @@ -1511,6 +1516,7 @@ static int lfs_rbyd_append(lfs_t *lfs, lfs_rbyd_t *rbyd_, if (lfs_rtag_weight(alt) >= lt+gt+1) { printf("prune!\n"); LFS_ASSERT(p_alts[0]); + LFS_ASSERT(lfs_rtag_isred(p_alts[0])); alt = lfs_rtag_black(p_alts[0]); branch_ = jump; @@ -1546,6 +1552,7 @@ static int lfs_rbyd_append(lfs_t *lfs, lfs_rbyd_t *rbyd_, } else { printf("ysplit nofollow\n"); + LFS_ASSERT(graft != 0); p_alts[0] = lfs_rtag_black( lfs_rtag_merge(alt, p_alts[0])); p_jumps[0] = graft; @@ -1553,7 +1560,7 @@ static int lfs_rbyd_append(lfs_t *lfs, lfs_rbyd_t *rbyd_, lfs_rtag_trim(alt, <, >); lfs_rbyd_p_red(p_alts, p_jumps); - graft = branch; + graft = 0; branch = branch_; continue; } @@ -1741,6 +1748,282 @@ leaf:; return 0; } +static int lfs_rbyd_delete(lfs_t *lfs, lfs_rbyd_t *rbyd_, + lfs_rtag_t tag, const void *buffer, lfs_size_t size) { + printf("delete()\n"); + LFS_ASSERT(lfs_rtag_id(tag) <= rbyd_->count+1); + + // no trunk yet? + if (!rbyd_->trunk) { + goto leaf; + } + + // assume we'll update our trunk + lfs_off_t lower_branch = rbyd_->trunk; + lfs_off_t upper_branch = rbyd_->trunk; + rbyd_->trunk = rbyd_->off; + + // weights for pruning + bool lower = true; + bool cut = false; + bool other_done = false; + lfs_srtag_t lower_lt = lfs_rtag_weight_lt(tag & ~0x7fff, rbyd_->count+1); + lfs_srtag_t lower_gt = lfs_rtag_weight_gt(tag & ~0x7fff, rbyd_->count+1); + lfs_srtag_t upper_lt = lfs_rtag_weight_lt((tag | 0x7fff)+1, rbyd_->count+1); + lfs_srtag_t upper_gt = lfs_rtag_weight_gt((tag | 0x7fff)+1, rbyd_->count+1); + + printf("lower, upper = (%x, %x), (%x, %x)\n", lower_lt, lower_gt, upper_lt, upper_gt); + + // queue of pending alts we can emulate rotations with + lfs_rtag_t p_alts[3] = {0, 0, 0}; + lfs_off_t p_jumps[3] = {0, 0, 0}; + lfs_off_t graft = 0; + + // descend down tree, building alt pointers + while (true) { + lfs_off_t branch = lower ? lower_branch : upper_branch; + lfs_srtag_t lt = lower ? lower_lt : upper_lt; + lfs_srtag_t gt = lower ? lower_gt : upper_gt; + + lfs_rtag_t alt; + lfs_off_t jump; + lfs_ssize_t delta = lfs_rbyd_readtag(lfs, + &lfs->pcache, &lfs->rcache, lfs->cfg->block_size, + rbyd_->block, branch, &alt, &jump, NULL); + if (delta < 0) { + return delta; + } + + // found an alt? + if (lfs_rtag_isalt(alt)) { + // make jump absolute + jump = branch - jump; + lfs_rtag_t branch_ = branch + delta; + + // do bounds want to take different paths? begin cutting + if (!cut && lfs_rtag_follow(alt, lower_lt, lower_gt) + != lfs_rtag_follow(alt, upper_lt, upper_gt)) { + printf("beginning cut\n"); + cut = true; + } + + // prune? + if (lfs_rtag_weight(alt) >= lt+gt+1) { + printf("prune!\n"); + LFS_ASSERT(p_alts[0]); + LFS_ASSERT(lfs_rtag_isred(p_alts[0])); + + alt = lfs_rtag_black(p_alts[0]); + branch_ = jump; + jump = p_jumps[0]; + lfs_rbyd_p_pop(p_alts, p_jumps); + + lfs_rtag_untrim(alt, <, >); + } + + // two reds makes a yellow, split? + if (lfs_rtag_isred(alt) + && p_alts[0] + && lfs_rtag_isred(p_alts[0])) { + LFS_ASSERT(lfs_rtag_isparallel(alt, p_alts[0])); + + // if we take the red or yellow alt we can just point + // to the black alt, otherwise we need to point to the + // yellow alt and prune later + if (lfs_rtag_follow(alt, lt, gt)) { + printf("ysplit follow\n"); + lfs_rtag_t alt_ = p_alts[0]; + lfs_off_t jump_ = p_jumps[0]; + p_alts[0] = lfs_rtag_black( + lfs_rtag_flip(alt, lt, gt)); + p_jumps[0] = branch_; + alt = lfs_rtag_black(alt_); + branch_ = jump; + jump = jump_; + + lfs_rtag_untrim(alt, <, >); + lfs_rtag_trim(p_alts[0], <, >); + lfs_rbyd_p_red(p_alts, p_jumps); + + } else { + printf("ysplit nofollow\n"); + LFS_ASSERT(graft != 0); + p_alts[0] = lfs_rtag_black( + lfs_rtag_merge(alt, p_alts[0])); + p_jumps[0] = graft; + + lfs_rtag_trim(alt, <, >); + lfs_rbyd_p_red(p_alts, p_jumps); + + graft = 0; + branch = branch_; + goto next; + } + } + + // should've taken red alt? needs a flip + if (lt < 0 || gt < 0) { + LFS_ASSERT(lfs_rtag_isblack(alt)); + LFS_ASSERT(p_alts[0]); + LFS_ASSERT(lfs_rtag_isred(p_alts[0])); + + printf("rflip %s (%x,%x)\n", + lfs_rtag_isparallel(alt, p_alts[0]) ? "parallel" : "perpendicular", + lt, gt); + + // if black alt would've been taken, it also needs a flip + if (lfs_rtag_isparallel(alt, p_alts[0])) { + alt = lfs_rtag_flip(alt, lt, gt); + lfs_off_t jump_ = jump; + jump = branch_; + branch_ = jump_; + } + + lfs_rtag_t alt_ = p_alts[0]; + lfs_off_t jump_ = p_jumps[0]; + p_alts[0] = lfs_rtag_red(alt); + p_jumps[0] = jump; + alt = lfs_rtag_black(alt_); + jump = jump_; + + lfs_rtag_untrim(alt, <, >); + lfs_rtag_trim(p_alts[0], <, >); + } + + // take black alt? needs a flip + if (lfs_rtag_isblack(alt) && lfs_rtag_follow(alt, lt, gt)) { + printf("bflip\n"); + alt = lfs_rtag_flip(alt, lt, gt); + lfs_off_t jump_ = jump; + jump = branch_; + branch_ = jump_; + } + + // TODO can this be combined with prune? maybe not? + // cut? + if (cut && ((lower && lfs_rtag_isgt(alt)) + || (!lower && lfs_rtag_islt(alt)))) { + if (p_alts[0]) { + p_alts[0] = lfs_rtag_black(p_alts[0]); + + if ((lower && lfs_rtag_isgt(alt)) + || (!lower && lfs_rtag_islt(alt))) { + lfs_rbyd_p_pop(p_alts, p_jumps); + } + } + goto dont_push; + } + + // push alt onto queue + int err = lfs_rbyd_p_push(lfs, rbyd_, + p_alts, p_jumps, + alt, jump); + if (err) { + return err; + } + + dont_push:; + // continue to next alt + lfs_rtag_trim(alt, <, >); + graft = branch; + branch = branch_; + + next:; + if (!cut || lower) { + lower_branch = branch; + lower_lt = lt; + lower_gt = gt; + } + if (!cut || !lower) { + upper_branch = branch; + upper_lt = lt; + upper_gt = gt; + } + + // switch bounds we are following? + if (cut && lfs_rtag_isblack(alt) && !other_done) { + printf("switch bounds\n"); + lower = !lower; + } + + // found end of tree? + // TODO just break? the gotos above are smelly + } else { + // update the tag id + lfs_rtag_t tag_ = lfs_rtag_setid(alt, lfs_rtag_id(tag)); + + printf("found %x (%x, %x, lower=%d)\n", tag_, lt, gt, lower); + printf("(%x, %x), (%x, %x)\n", lower_lt, lower_gt, upper_lt, upper_gt); + + // note, when deleting we should always find some tag with the + // expected id + // TODO note this only applies with deletes + LFS_ASSERT(lfs_rtag_id(tag_) == lfs_rtag_id(tag)); + + if (cut && !lower) { + // TODO deduplicate this with append? + // if we're on the upper bound, create a new alt + alt = LFS_MKRALT(B, GT, (1 << 12) + gt+1); + + int err = lfs_rbyd_p_push(lfs, rbyd_, + p_alts, p_jumps, + alt, branch); + if (err) { + return err; + } + + lfs_rbyd_p_red(p_alts, p_jumps); + } else { + // increase weight to make this path unreachable + if (p_alts[0] && lfs_rtag_islt(p_alts[0])) { + // TODO function for this + p_alts[0] += (lt << 3); + } + } + + // TODO this should be restructured, it's a bit of mess + if (cut && !other_done) { + other_done = true; + lower = !lower; + continue; + } + + // flush any pending alts + int err = lfs_rbyd_p_flush(lfs, rbyd_, + p_alts, p_jumps, 3); + if (err) { + return err; + } + + // done! lets get out of here + goto leaf; + } + } + +leaf:; + // write the tag + int err = lfs_rbyd_progtag(lfs, rbyd_, tag, size, &rbyd_->crc); + if (err) { + return err; + } + + // don't forget the actual data! + err = lfs_rbyd_prog(lfs, rbyd_, buffer, size, &rbyd_->crc); + if (err) { + return err; + } + + // if we're deleting, decrease the id count, indirectly shifting + // all ids >= over one + // + // TODO handle an empty tree + if (lfs_rtag_type1(tag) == LFS_TYPE1_DELETE) { + rbyd_->count -= 1; + } + + return 0; +} + static int lfs_rbyd_commit(lfs_t *lfs, lfs_rbyd_t *rbyd, const struct lfs_rattr *attrs) { printf("commit()\n"); @@ -1765,10 +2048,20 @@ static int lfs_rbyd_commit(lfs_t *lfs, lfs_rbyd_t *rbyd, // append each tag to the tree for (const struct lfs_rattr *attr = attrs; attr; attr = attr->next) { - int err = lfs_rbyd_append(lfs, &rbyd_, - attr->tag, attr->buffer, attr->size); - if (err) { - return err; + if (lfs_rtag_type1(attr->tag) == LFS_TYPE1_DELETE) { + // deletes require range-deletion, this is a bit more complicated + // than normal tags + int err = lfs_rbyd_delete(lfs, &rbyd_, + attr->tag, attr->buffer, attr->size); + if (err) { + return err; + } + } else { + int err = lfs_rbyd_append(lfs, &rbyd_, + attr->tag, attr->buffer, attr->size); + if (err) { + return err; + } } } diff --git a/scripts/dbgrbyd.py b/scripts/dbgrbyd.py index 945849c7..665a71ba 100755 --- a/scripts/dbgrbyd.py +++ b/scripts/dbgrbyd.py @@ -54,16 +54,15 @@ def tagrepr(tag, size, off=None): type2 = (tag >> 7) & 0xff id = (tag >> 15) & 0xffff - if (type1 & 0x7e) == 0x40: - return '%screate x%02x id%d%s' % ( - '~' if type1 & 0x1 else '', - type2, + if (type1 & 0x7f) == 0x40: + return 'create%s id%d%s' % ( + 'reg' if type2 == 1 + else ' x%02x' % type2, id, ' %d' % size if not type1 & 0x1 else '') - elif (type1 & 0x7e) == 0x48: - return '%sdelete x%02x id%d%s' % ( - '~' if type1 & 0x1 else '', - type2, + elif (type1 & 0x7f) == 0x41: + return 'delete%s id%d%s' % ( + ' x%02x' % type2 if type2 else '', id, ' %d' % size if not type1 & 0x1 else '') elif (type1 & 0x7e) == 0x50: @@ -245,10 +244,11 @@ def main(disk, block_size, block1, block2=None, *, # print tags if args.get('rbyd'): alts = [] - if args.get('ids'): + if args.get('lifetimes'): count = 0 ids = [] ids_i = 0 + deleted_id = '' j = 4 while j < (block_size if args.get('all') else off): notes = [] @@ -264,15 +264,15 @@ def main(disk, block_size, block1, block2=None, *, if (tag & 0x7e) != 0x2: crc = crc32c(data[j:j+size], crc) # adjust count - if args.get('ids'): + if args.get('lifetimes'): if (tag & 0x7f) == 0x40: count += 1 ids.insert(((tag >> 15) & 0xffff)-1, COLORS[ids_i % len(COLORS)]) ids_i += 1 - elif (tag & 0x7f) == 0x48: + elif (tag & 0x7f) == 0x41: count -= 1 - ids.pop(((tag >> 15) & 0xffff)-1) + deleted_id = ids.pop(((tag >> 15) & 0xffff)-1) # found a crc? else: crc_, = struct.unpack('= off else '')) - if not args.get('in_tree') or (tag & 0x7) == 0: + if not args.get('in_tree') or (tag & 0x6) == 0: # show human-readable tag representation print('%s%08x: %-57s%s%s' % ( '\x1b[90m' if color and j_ >= off else '', @@ -315,23 +315,35 @@ def main(disk, block_size, block1, block2=None, *, if args.get('rbyd') and (tag & 0x7) == 0 else ' %s' % jumprepr(j_) if args.get('jumps') - else ' %s' % ''.join( - '%s%s%s' % ( - '\x1b[%sm' % ids[id] if color else '', - '.' if (tag & 0x7f) == 0x40 - and id == ((tag >> 15) & 0xffff)-1 - else '\'' if (tag & 0x7f) == 0x48 - and id == ((tag >> 15) & 0xffff)-1 - else '* ' if not tag & 0x4 - and id == ((tag >> 15) & 0xffff)-1 - else '\ ' if (tag & 0x7f) == 0x40 - and id > ((tag >> 15) & 0xffff)-1 - else '/ ' if (tag & 0x7f) == 0x48 - and id > ((tag >> 15) & 0xffff)-1 - else '| ', + else ' %s%s' % ( + ''.join( + '%s%s%s%s' % ( + '%s\'%s' % ( + '\x1b[%sm' % deleted_id if color else '', + '\x1b[m' if color else '') + if (tag & 0x7f) == 0x41 + and id == ((tag >> 15) & 0xffff)-1 + else '', + '\x1b[%sm' % ids[id] if color else '', + '.' if (tag & 0x7f) == 0x40 + and id == ((tag >> 15) & 0xffff)-1 + else '* ' if not tag & 0x4 + and not (tag & 0x7f) == 0x41 + and id == ((tag >> 15) & 0xffff)-1 + else '\ ' if (tag & 0x7f) == 0x40 + and id > ((tag >> 15) & 0xffff)-1 + else '/ ' if (tag & 0x7f) == 0x41 + and id >= ((tag >> 15) & 0xffff)-1 + else '| ', + '\x1b[m' if color else '') + for id in range(count)), + '%s\'%s' % ( + '\x1b[%sm' % deleted_id if color else '', '\x1b[m' if color else '') - for id in range(count)) - if args.get('ids') + if (tag & 0x7f) == 0x41 + and count == ((tag >> 15) & 0xffff)-1 + else '') + if args.get('lifetimes') else '')) # show in-device representation, including some extra @@ -403,7 +415,7 @@ if __name__ == "__main__": action='store_true', help="Don't stop parsing on bad commits.") parser.add_argument( - '-t', '--in-tree', + '-i', '--in-tree', action='store_true', help="Only show tags in the tree.") parser.add_argument( @@ -427,7 +439,7 @@ if __name__ == "__main__": action='store_true', help="Show alt pointer jumps in the margin.") parser.add_argument( - '-i', '--ids', + '-g', '--lifetimes', action='store_true', help="Show inserts/deletes of ids in the margin.") parser.add_argument( diff --git a/tests/test_rbyd.toml b/tests/test_rbyd.toml index c1d1e317..2da72815 100644 --- a/tests/test_rbyd.toml +++ b/tests/test_rbyd.toml @@ -1778,7 +1778,6 @@ code = ''' lfs_bd_erase(&lfs, rbyd.block) => 0; lfs_rbyd_commit(&lfs, &rbyd, LFS_MKRATTR(UATTR, 1, 0, &(uint32_t){0xaaaaaaaa}, 4, NULL)) => 0; - lfs_rbyd_commit(&lfs, &rbyd, LFS_MKRRMATTR(UATTR, 1, 0, NULL)) => 0; @@ -1799,7 +1798,6 @@ code = ''' lfs_rbyd_commit(&lfs, &rbyd, LFS_MKRATTR(UATTR, 1, 0, &(uint32_t){0xaaaaaaaa}, 4, LFS_MKRATTR(UATTR, 2, 0, &(uint32_t){0xbbbbbbbb}, 4, NULL))) => 0; - lfs_rbyd_commit(&lfs, &rbyd, LFS_MKRRMATTR(UATTR, 1, 0, NULL)) => 0; @@ -1824,7 +1822,6 @@ code = ''' lfs_rbyd_commit(&lfs, &rbyd, LFS_MKRATTR(UATTR, 1, 0, &(uint32_t){0xaaaaaaaa}, 4, LFS_MKRATTR(UATTR, 2, 0, &(uint32_t){0xbbbbbbbb}, 4, NULL))) => 0; - lfs_rbyd_commit(&lfs, &rbyd, LFS_MKRRMATTR(UATTR, 2, 0, NULL)) => 0; @@ -2720,3 +2717,406 @@ code = ''' => LFS_MKRTAG(CREATEREG, 0, x+1); } ''' + + +### Deletion testing ### + +[cases.test_rbyd_delete] +in = 'lfs.c' +code = ''' + lfs_t lfs; + lfs_init(&lfs, cfg) => 0; + + lfs_rbyd_t init_rbyd = { + .block = 0, + .trunk = 0, + .off = 0, + .rev = 1, + .crc = 0, + .count = 0, + .erased = true, + }; + lfs_rbyd_t rbyd; + uint8_t buffer[4]; + + // try to delete one id + rbyd = init_rbyd; + lfs_bd_erase(&lfs, rbyd.block) => 0; + lfs_rbyd_commit(&lfs, &rbyd, + LFS_MKRATTR(CREATEREG, 0, 1, "\xaa\xaa\xaa\xaa", 4, + LFS_MKRATTR(CREATEREG, 0, 2, "\xbb\xbb\xbb\xbb", 4, NULL))) => 0; + lfs_rbyd_commit(&lfs, &rbyd, + LFS_MKRATTR(DELETE, 0, 2, NULL, 0, NULL)) => 0; + + assert(rbyd.count == 1); + lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(CREATEREG, 0, 1), buffer, 4) + => 4; + assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); +// TODO +// lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(CREATEREG, 0, 2), buffer, 4) +// => LFS_ERR_NOENT; + + lfs_rbyd_fetch(&lfs, &rbyd, rbyd.block, NULL) => 0; + assert(rbyd.count == 1); + lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(CREATEREG, 0, 1), buffer, 4) + => 4; + assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); +// TODO +// lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(CREATEREG, 0, 2), buffer, 4) +// => LFS_ERR_NOENT; + + // try to delete the other id + rbyd = init_rbyd; + lfs_bd_erase(&lfs, rbyd.block) => 0; + lfs_rbyd_commit(&lfs, &rbyd, + LFS_MKRATTR(CREATEREG, 0, 1, "\xaa\xaa\xaa\xaa", 4, + LFS_MKRATTR(CREATEREG, 0, 2, "\xbb\xbb\xbb\xbb", 4, NULL))) => 0; + lfs_rbyd_commit(&lfs, &rbyd, + LFS_MKRATTR(DELETE, 0, 1, NULL, 0, NULL)) => 0; + + assert(rbyd.count == 1); + lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(CREATEREG, 0, 1), buffer, 4) + => 4; + assert(memcmp(buffer, "\xbb\xbb\xbb\xbb", 4) == 0); +// TODO +// lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(CREATEREG, 0, 2), buffer, 4) +// => LFS_ERR_NOENT; + + lfs_rbyd_fetch(&lfs, &rbyd, rbyd.block, NULL) => 0; + assert(rbyd.count == 1); + lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(CREATEREG, 0, 1), buffer, 4) + => 4; + assert(memcmp(buffer, "\xbb\xbb\xbb\xbb", 4) == 0); +// TODO +// lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(CREATEREG, 0, 2), buffer, 4) +// => LFS_ERR_NOENT; + + // try to delete the largest of three + rbyd = init_rbyd; + lfs_bd_erase(&lfs, rbyd.block) => 0; + lfs_rbyd_commit(&lfs, &rbyd, + LFS_MKRATTR(CREATEREG, 0, 1, "\xaa\xaa\xaa\xaa", 4, + LFS_MKRATTR(CREATEREG, 0, 2, "\xbb\xbb\xbb\xbb", 4, + LFS_MKRATTR(CREATEREG, 0, 3, "\xcc\xcc\xcc\xcc", 4, NULL)))) => 0; + lfs_rbyd_commit(&lfs, &rbyd, + LFS_MKRATTR(DELETE, 0, 3, NULL, 0, NULL)) => 0; + + assert(rbyd.count == 2); + lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(CREATEREG, 0, 1), buffer, 4) + => 4; + assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); + lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(CREATEREG, 0, 2), buffer, 4) + => 4; + assert(memcmp(buffer, "\xbb\xbb\xbb\xbb", 4) == 0); +// TODO +// lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(CREATEREG, 0, 3), buffer, 4) +// => LFS_ERR_NOENT; + + lfs_rbyd_fetch(&lfs, &rbyd, rbyd.block, NULL) => 0; + assert(rbyd.count == 2); + lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(CREATEREG, 0, 1), buffer, 4) + => 4; + assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); + lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(CREATEREG, 0, 2), buffer, 4) + => 4; + assert(memcmp(buffer, "\xbb\xbb\xbb\xbb", 4) == 0); +// TODO +// lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(CREATEREG, 0, 3), buffer, 4) +// => LFS_ERR_NOENT; + + // try to delete the smallest of three + rbyd = init_rbyd; + lfs_bd_erase(&lfs, rbyd.block) => 0; + lfs_rbyd_commit(&lfs, &rbyd, + LFS_MKRATTR(CREATEREG, 0, 1, "\xaa\xaa\xaa\xaa", 4, + LFS_MKRATTR(CREATEREG, 0, 2, "\xbb\xbb\xbb\xbb", 4, + LFS_MKRATTR(CREATEREG, 0, 3, "\xcc\xcc\xcc\xcc", 4, NULL)))) => 0; + lfs_rbyd_commit(&lfs, &rbyd, + LFS_MKRATTR(DELETE, 0, 1, NULL, 0, NULL)) => 0; + + assert(rbyd.count == 2); + lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(CREATEREG, 0, 1), buffer, 4) + => 4; + assert(memcmp(buffer, "\xbb\xbb\xbb\xbb", 4) == 0); + lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(CREATEREG, 0, 2), buffer, 4) + => 4; + assert(memcmp(buffer, "\xcc\xcc\xcc\xcc", 4) == 0); +// TODO +// lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(CREATEREG, 0, 3), buffer, 4) +// => LFS_ERR_NOENT; + + lfs_rbyd_fetch(&lfs, &rbyd, rbyd.block, NULL) => 0; + assert(rbyd.count == 2); + lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(CREATEREG, 0, 1), buffer, 4) + => 4; + assert(memcmp(buffer, "\xbb\xbb\xbb\xbb", 4) == 0); + lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(CREATEREG, 0, 2), buffer, 4) + => 4; + assert(memcmp(buffer, "\xcc\xcc\xcc\xcc", 4) == 0); +// TODO +// lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(CREATEREG, 0, 3), buffer, 4) +// => LFS_ERR_NOENT; + + // try to delete the middle + rbyd = init_rbyd; + lfs_bd_erase(&lfs, rbyd.block) => 0; + lfs_rbyd_commit(&lfs, &rbyd, + LFS_MKRATTR(CREATEREG, 0, 1, "\xaa\xaa\xaa\xaa", 4, + LFS_MKRATTR(CREATEREG, 0, 2, "\xbb\xbb\xbb\xbb", 4, + LFS_MKRATTR(CREATEREG, 0, 3, "\xcc\xcc\xcc\xcc", 4, NULL)))) => 0; + lfs_rbyd_commit(&lfs, &rbyd, + LFS_MKRATTR(DELETE, 0, 2, NULL, 0, NULL)) => 0; + + assert(rbyd.count == 2); + lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(CREATEREG, 0, 1), buffer, 4) + => 4; + assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); + lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(CREATEREG, 0, 2), buffer, 4) + => 4; + assert(memcmp(buffer, "\xcc\xcc\xcc\xcc", 4) == 0); +// TODO +// lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(CREATEREG, 0, 3), buffer, 4) +// => LFS_ERR_NOENT; + + lfs_rbyd_fetch(&lfs, &rbyd, rbyd.block, NULL) => 0; + assert(rbyd.count == 2); + lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(CREATEREG, 0, 1), buffer, 4) + => 4; + assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); + lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(CREATEREG, 0, 2), buffer, 4) + => 4; + assert(memcmp(buffer, "\xcc\xcc\xcc\xcc", 4) == 0); +// TODO +// lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(CREATEREG, 0, 3), buffer, 4) +// => LFS_ERR_NOENT; +''' + +[cases.test_rbyd_delete_range] +in = 'lfs.c' +code = ''' + lfs_t lfs; + lfs_init(&lfs, cfg) => 0; + + lfs_rbyd_t init_rbyd = { + .block = 0, + .trunk = 0, + .off = 0, + .rev = 1, + .crc = 0, + .count = 0, + .erased = true, + }; + lfs_rbyd_t rbyd; + uint8_t buffer[4]; + + // try to delete one id + rbyd = init_rbyd; + lfs_bd_erase(&lfs, rbyd.block) => 0; + lfs_rbyd_commit(&lfs, &rbyd, + LFS_MKRATTR(CREATEREG, 0, 1, "\xaa\xaa\xaa\xaa", 4, + LFS_MKRATTR(UATTR, 0, 1, "\xaa\xaa\xaa\xaa", 4, + LFS_MKRATTR(CREATEREG, 0, 2, "\xbb\xbb\xbb\xbb", 4, + LFS_MKRATTR(UATTR, 0, 2, "\xbb\xbb\xbb\xbb", 4, NULL))))) => 0; + lfs_rbyd_commit(&lfs, &rbyd, + LFS_MKRATTR(DELETE, 0, 2, NULL, 0, NULL)) => 0; + + assert(rbyd.count == 1); + lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(CREATEREG, 0, 1), buffer, 4) + => 4; + assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); + lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(UATTR, 0, 1), buffer, 4) + => 4; + assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); +// TODO +// lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(CREATEREG, 0, 2), buffer, 4) +// => LFS_ERR_NOENT; + + lfs_rbyd_fetch(&lfs, &rbyd, rbyd.block, NULL) => 0; + assert(rbyd.count == 1); + lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(CREATEREG, 0, 1), buffer, 4) + => 4; + lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(UATTR, 0, 1), buffer, 4) + => 4; + assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); +// TODO +// lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(CREATEREG, 0, 2), buffer, 4) +// => LFS_ERR_NOENT; + + // try to delete the other id + rbyd = init_rbyd; + lfs_bd_erase(&lfs, rbyd.block) => 0; + lfs_rbyd_commit(&lfs, &rbyd, + LFS_MKRATTR(CREATEREG, 0, 1, "\xaa\xaa\xaa\xaa", 4, + LFS_MKRATTR(UATTR, 0, 1, "\xaa\xaa\xaa\xaa", 4, + LFS_MKRATTR(CREATEREG, 0, 2, "\xbb\xbb\xbb\xbb", 4, + LFS_MKRATTR(UATTR, 0, 2, "\xbb\xbb\xbb\xbb", 4, NULL))))) => 0; + lfs_rbyd_commit(&lfs, &rbyd, + LFS_MKRATTR(DELETE, 0, 1, NULL, 0, NULL)) => 0; + + assert(rbyd.count == 1); + lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(CREATEREG, 0, 1), buffer, 4) + => 4; + assert(memcmp(buffer, "\xbb\xbb\xbb\xbb", 4) == 0); + lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(UATTR, 0, 1), buffer, 4) + => 4; + assert(memcmp(buffer, "\xbb\xbb\xbb\xbb", 4) == 0); +// TODO +// lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(CREATEREG, 0, 2), buffer, 4) +// => LFS_ERR_NOENT; + + lfs_rbyd_fetch(&lfs, &rbyd, rbyd.block, NULL) => 0; + assert(rbyd.count == 1); + lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(CREATEREG, 0, 1), buffer, 4) + => 4; + assert(memcmp(buffer, "\xbb\xbb\xbb\xbb", 4) == 0); + lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(UATTR, 0, 1), buffer, 4) + => 4; + assert(memcmp(buffer, "\xbb\xbb\xbb\xbb", 4) == 0); +// TODO +// lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(CREATEREG, 0, 2), buffer, 4) +// => LFS_ERR_NOENT; + + // try to delete the largest of three + rbyd = init_rbyd; + lfs_bd_erase(&lfs, rbyd.block) => 0; + lfs_rbyd_commit(&lfs, &rbyd, + LFS_MKRATTR(CREATEREG, 0, 1, "\xaa\xaa\xaa\xaa", 4, + LFS_MKRATTR(UATTR, 0, 1, "\xaa\xaa\xaa\xaa", 4, + LFS_MKRATTR(CREATEREG, 0, 2, "\xbb\xbb\xbb\xbb", 4, + LFS_MKRATTR(UATTR, 0, 2, "\xbb\xbb\xbb\xbb", 4, + LFS_MKRATTR(CREATEREG, 0, 3, "\xcc\xcc\xcc\xcc", 4, + LFS_MKRATTR(UATTR, 0, 3, "\xcc\xcc\xcc\xcc", 4, NULL))))))) => 0; + lfs_rbyd_commit(&lfs, &rbyd, + LFS_MKRATTR(DELETE, 0, 3, NULL, 0, NULL)) => 0; + + assert(rbyd.count == 2); + lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(CREATEREG, 0, 1), buffer, 4) + => 4; + assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); + lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(UATTR, 0, 1), buffer, 4) + => 4; + assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); + lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(CREATEREG, 0, 2), buffer, 4) + => 4; + assert(memcmp(buffer, "\xbb\xbb\xbb\xbb", 4) == 0); + lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(UATTR, 0, 2), buffer, 4) + => 4; + assert(memcmp(buffer, "\xbb\xbb\xbb\xbb", 4) == 0); +// TODO +// lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(CREATEREG, 0, 3), buffer, 4) +// => LFS_ERR_NOENT; + + lfs_rbyd_fetch(&lfs, &rbyd, rbyd.block, NULL) => 0; + assert(rbyd.count == 2); + lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(CREATEREG, 0, 1), buffer, 4) + => 4; + assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); + lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(UATTR, 0, 1), buffer, 4) + => 4; + assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); + lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(CREATEREG, 0, 2), buffer, 4) + => 4; + assert(memcmp(buffer, "\xbb\xbb\xbb\xbb", 4) == 0); + lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(UATTR, 0, 2), buffer, 4) + => 4; + assert(memcmp(buffer, "\xbb\xbb\xbb\xbb", 4) == 0); +// TODO +// lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(CREATEREG, 0, 3), buffer, 4) +// => LFS_ERR_NOENT; + +// // try to delete the smallest of three +// rbyd = init_rbyd; +// lfs_bd_erase(&lfs, rbyd.block) => 0; +// lfs_rbyd_commit(&lfs, &rbyd, +// LFS_MKRATTR(CREATEREG, 0, 1, "\xaa\xaa\xaa\xaa", 4, +// LFS_MKRATTR(UATTR, 0, 1, "\xaa\xaa\xaa\xaa", 4, +// LFS_MKRATTR(CREATEREG, 0, 2, "\xbb\xbb\xbb\xbb", 4, +// LFS_MKRATTR(UATTR, 0, 2, "\xbb\xbb\xbb\xbb", 4, +// LFS_MKRATTR(CREATEREG, 0, 3, "\xcc\xcc\xcc\xcc", 4, +// LFS_MKRATTR(UATTR, 0, 3, "\xcc\xcc\xcc\xcc", 4, NULL))))))) => 0; +// lfs_rbyd_commit(&lfs, &rbyd, +// LFS_MKRATTR(DELETE, 0, 1, NULL, 0, NULL)) => 0; +// +// assert(rbyd.count == 2); +// lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(CREATEREG, 0, 1), buffer, 4) +// => 4; +// assert(memcmp(buffer, "\xbb\xbb\xbb\xbb", 4) == 0); +// lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(UATTR, 0, 1), buffer, 4) +// => 4; +// assert(memcmp(buffer, "\xbb\xbb\xbb\xbb", 4) == 0); +// lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(CREATEREG, 0, 2), buffer, 4) +// => 4; +// assert(memcmp(buffer, "\xcc\xcc\xcc\xcc", 4) == 0); +// lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(UATTR, 0, 2), buffer, 4) +// => 4; +// assert(memcmp(buffer, "\xcc\xcc\xcc\xcc", 4) == 0); +//// TODO +//// lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(CREATEREG, 0, 3), buffer, 4) +//// => LFS_ERR_NOENT; +// +// lfs_rbyd_fetch(&lfs, &rbyd, rbyd.block, NULL) => 0; +// assert(rbyd.count == 2); +// lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(CREATEREG, 0, 1), buffer, 4) +// => 4; +// assert(memcmp(buffer, "\xbb\xbb\xbb\xbb", 4) == 0); +// lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(UATTR, 0, 1), buffer, 4) +// => 4; +// assert(memcmp(buffer, "\xbb\xbb\xbb\xbb", 4) == 0); +// lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(CREATEREG, 0, 2), buffer, 4) +// => 4; +// assert(memcmp(buffer, "\xcc\xcc\xcc\xcc", 4) == 0); +// lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(UATTR, 0, 2), buffer, 4) +// => 4; +// assert(memcmp(buffer, "\xcc\xcc\xcc\xcc", 4) == 0); +//// TODO +//// lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(CREATEREG, 0, 3), buffer, 4) +//// => LFS_ERR_NOENT; + + // try to delete the middle + rbyd = init_rbyd; + lfs_bd_erase(&lfs, rbyd.block) => 0; + lfs_rbyd_commit(&lfs, &rbyd, + LFS_MKRATTR(CREATEREG, 0, 1, "\xaa\xaa\xaa\xaa", 4, + LFS_MKRATTR(UATTR, 0, 1, "\xaa\xaa\xaa\xaa", 4, + LFS_MKRATTR(CREATEREG, 0, 2, "\xbb\xbb\xbb\xbb", 4, + LFS_MKRATTR(UATTR, 0, 2, "\xbb\xbb\xbb\xbb", 4, + LFS_MKRATTR(CREATEREG, 0, 3, "\xcc\xcc\xcc\xcc", 4, + LFS_MKRATTR(UATTR, 0, 3, "\xcc\xcc\xcc\xcc", 4, NULL))))))) => 0; + lfs_rbyd_commit(&lfs, &rbyd, + LFS_MKRATTR(DELETE, 0, 2, NULL, 0, NULL)) => 0; + + assert(rbyd.count == 2); + lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(CREATEREG, 0, 1), buffer, 4) + => 4; + assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); + lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(UATTR, 0, 1), buffer, 4) + => 4; + assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); + lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(CREATEREG, 0, 2), buffer, 4) + => 4; + assert(memcmp(buffer, "\xcc\xcc\xcc\xcc", 4) == 0); + lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(UATTR, 0, 2), buffer, 4) + => 4; + assert(memcmp(buffer, "\xcc\xcc\xcc\xcc", 4) == 0); +// TODO +// lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(CREATEREG, 0, 3), buffer, 4) +// => LFS_ERR_NOENT; + + lfs_rbyd_fetch(&lfs, &rbyd, rbyd.block, NULL) => 0; + assert(rbyd.count == 2); + lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(CREATEREG, 0, 1), buffer, 4) + => 4; + assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); + lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(UATTR, 0, 1), buffer, 4) + => 4; + assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); + lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(CREATEREG, 0, 2), buffer, 4) + => 4; + assert(memcmp(buffer, "\xcc\xcc\xcc\xcc", 4) == 0); + lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(UATTR, 0, 2), buffer, 4) + => 4; + assert(memcmp(buffer, "\xcc\xcc\xcc\xcc", 4) == 0); +// TODO +// lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(CREATEREG, 0, 3), buffer, 4) +// => LFS_ERR_NOENT; +'''