From 9f0160556ffc47856730963277992257024feda8 Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Wed, 20 Sep 2023 23:31:56 -0500 Subject: [PATCH] Made significant progress around inlined-file state during mdir commits The main improvement is moving the special inlined-file compaction logic up into lfsr_mdir_compact__. We only need this logic for files stored in mdirs, and thanks to its recursive nature, we weren't getting any benefit from handling this at a lower level anyways. This is a nice logical restructuring that probably saves a bit of code cost in the end. Another significant improvement is moving the staging copy of the inlined tree's state up into the file struct itself. This solves the problem of needed N copies of temporary inlined state when you have N open files. It also provides a central place to stage changes when compacting inlined trees, which happens across several different places in the mdir commit logic. Though some may see this as more a hack than a feature. Also note-worthy, but minor: these changes required an additional opened-mdir linked-list to know when the mdir is a file and may contain an inlined tree. --- lfs.c | 418 ++++++++++++++++++++++++++++-------------- lfs.h | 50 ++--- scripts/dbglfs.py | 20 ++ tests/test_mtree.toml | 80 ++++---- 4 files changed, 366 insertions(+), 202 deletions(-) diff --git a/lfs.c b/lfs.c index 9c409770..be176b3d 100644 --- a/lfs.c +++ b/lfs.c @@ -724,10 +724,6 @@ static inline bool lfsr_tag_istrunk(lfsr_tag_t tag) { return (tag & 0x6000) != 0x2000; } -static inline uint8_t lfsr_tag_filetype(lfsr_tag_t tag) { - return tag - LFSR_TAG_REG; -} - static inline bool lfsr_tag_isinternal(lfsr_tag_t tag) { return tag & LFSR_TAG_INTERNAL; } @@ -3017,11 +3013,6 @@ static int lfsr_rbyd_appendcompactattr(lfs_t *lfs, lfsr_rbyd_t *rbyd, return 0; } -// lfsr_rbyd_appendcompactrbyd actually calls lfsr_rbyd_compact if it -// encounters an inlined tree -static int lfsr_rbyd_compact(lfs_t *lfs, lfsr_rbyd_t *rbyd, - bool deferred, lfs_off_t off); - static int lfsr_rbyd_appendcompactrbyd(lfs_t *lfs, lfsr_rbyd_t *rbyd_, lfsr_srid_t start_rid, lfsr_srid_t end_rid, const lfsr_rbyd_t *rbyd) { @@ -3047,69 +3038,11 @@ static int lfsr_rbyd_appendcompactrbyd(lfs_t *lfs, lfsr_rbyd_t *rbyd_, break; } - // found an inlined tree? we need to compact the tree as well to bring - // it along with us - if (tag == LFSR_TAG_TRUNK) { - lfsr_rbyd_t inlined_rbyd; - err = lfsr_data_readtrunk(lfs, &data, &inlined_rbyd); - if (err) { - return err; - } - inlined_rbyd.block = rbyd->block; - - // keep track of the start of our new tree - lfs_size_t off = rbyd_->eoff; - - // what a great use case for recursion! too bad we can't use - // recursion here - lfsr_srid_t rid_ = -1; - lfsr_tag_t tag_ = 0; - while (true) { - lfsr_rid_t weight_; - lfsr_data_t data_; - err = lfsr_rbyd_lookupnext(lfs, &inlined_rbyd, - rid_, tag_+1, - &rid_, &tag_, &weight_, &data_); - if (err) { - if (err == LFS_ERR_NOENT) { - break; - } - return err; - } - - // write the tag_ - err = lfsr_rbyd_appendcompactattr(lfs, rbyd_, - tag_, weight_, data_); - if (err) { - LFS_ASSERT(err != LFS_ERR_RANGE); - return err; - } - } - - // compact our inlined tree - err = lfsr_rbyd_compact(lfs, rbyd_, true, off); - if (err) { - LFS_ASSERT(err != LFS_ERR_RANGE); - return err; - } - - // write the new inlined tree tag - uint8_t trunk_buf[LFSR_TRUNK_DSIZE]; - err = lfsr_rbyd_appendcompactattr(lfs, rbyd_, - tag, weight, lfsr_data_fromtrunk(lfs, - rbyd_, trunk_buf)); - if (err) { - LFS_ASSERT(err != LFS_ERR_RANGE); - return err; - } - - } else { - // write the tag - err = lfsr_rbyd_appendcompactattr(lfs, rbyd_, tag, weight, data); - if (err) { - LFS_ASSERT(err != LFS_ERR_RANGE); - return err; - } + // write the tag + err = lfsr_rbyd_appendcompactattr(lfs, rbyd_, tag, weight, data); + if (err) { + LFS_ASSERT(err != LFS_ERR_RANGE); + return err; } } @@ -3120,8 +3053,9 @@ static int lfsr_rbyd_compact(lfs_t *lfs, lfsr_rbyd_t *rbyd, bool deferred, lfs_size_t off) { // must fetch before mutating! LFS_ASSERT(lfsr_rbyd_isfetched(rbyd)); + // offset must be after the revision count + LFS_ASSERT(off >= sizeof(uint32_t)); - // TODO still need this? // ignore empty rbyds, we can't really compact these, so leave it up to // upper layers to deal with this if (rbyd->eoff <= sizeof(uint32_t)) { @@ -4719,13 +4653,15 @@ static int lfsr_data_readmblocks(lfs_t *lfs, lfsr_data_t *data, // track opened mdirs that may need to by updated static void lfsr_mdir_addopened(lfs_t *lfs, int type, lfsr_openedmdir_t *opened) { - opened->next = lfs->opened[type]; - lfs->opened[type] = opened; + opened->next = lfs->opened[type-LFS_TYPE_REG]; + lfs->opened[type-LFS_TYPE_REG] = opened; } static void lfsr_mdir_removeopened(lfs_t *lfs, int type, lfsr_openedmdir_t *opened) { - for (lfsr_openedmdir_t **p = &lfs->opened[type]; *p; p = &(*p)->next) { + for (lfsr_openedmdir_t **p = &lfs->opened[type-LFS_TYPE_REG]; + *p; + p = &(*p)->next) { if (*p == opened) { *p = (*p)->next; break; @@ -4735,7 +4671,9 @@ static void lfsr_mdir_removeopened(lfs_t *lfs, int type, static bool lfsr_mdir_isopened(lfs_t *lfs, int type, const lfsr_openedmdir_t *opened) { - for (lfsr_openedmdir_t *p = lfs->opened[type]; p; p = p->next) { + for (lfsr_openedmdir_t *p = lfs->opened[type-LFS_TYPE_REG]; + p; + p = p->next) { if (p == opened) { return true; } @@ -5148,13 +5086,14 @@ static int lfsr_mdir_commit__(lfs_t *lfs, lfsr_mdir_t *mdir, } else if (attrs[i].tag == LFSR_TAG_DEFER) { const lfsr_defer_t *defer = (const lfsr_defer_t*)attrs[i].data.u.b.buffer; + // swap out our trunk/weight temporarily, note we're operating // on a copy so if this fails not _too_ many things will get // messed up // // it is important that these rbyds share eoff/cksum/etc - mdir_.u.m.weight = defer->rbyd->weight; mdir_.u.m.trunk = defer->rbyd->trunk; + mdir_.u.m.weight = defer->rbyd->weight; // append any deferred attributes int err = lfsr_rbyd_appendattrs(lfs, &mdir_.u.r.rbyd, -1, -1, @@ -5163,10 +5102,11 @@ static int lfsr_mdir_commit__(lfs_t *lfs, lfsr_mdir_t *mdir, return err; } - defer->rbyd->weight = mdir_.u.m.weight; + // revert to mdir trunk/weight defer->rbyd->trunk = mdir_.u.m.trunk; - mdir_.u.m.weight = mdir->u.m.weight; + defer->rbyd->weight = mdir_.u.m.weight; mdir_.u.m.trunk = mdir->u.m.trunk; + mdir_.u.m.weight = mdir->u.m.weight; // write out normal tags normally } else { @@ -5228,18 +5168,207 @@ static int lfsr_mdir_commit__(lfs_t *lfs, lfsr_mdir_t *mdir, return 0; } +// needed by lfsr_mdir_compact__ +static bool lfsr_file_isinlineddata(const lfsr_file_t *file); +static bool lfsr_file_isinlinedtree(const lfsr_file_t *file); + static int lfsr_mdir_compact__(lfs_t *lfs, lfsr_mdir_t *mdir_, lfsr_srid_t start_rid, lfsr_srid_t end_rid, const lfsr_mdir_t *mdir) { - int err = lfsr_rbyd_appendcompactrbyd(lfs, &mdir_->u.r.rbyd, - start_rid, end_rid, &mdir->u.r.rbyd); + // this is basically the same as lfsr_rbyd_appendcompactrbyd + + // lfsr_rbyd_compact, but with special handling for inlined trees. + // + // it's really tempting to deduplicate this via recursion! but we can't + // do that here + + // copy over tags in the rbyd in order + lfsr_srid_t rid = start_rid; + lfsr_tag_t tag = 0; + while (true) { + lfsr_rid_t weight; + lfsr_data_t data; + int err = lfsr_rbyd_lookupnext(lfs, &mdir->u.r.rbyd, + rid, tag+1, + &rid, &tag, &weight, &data); + if (err) { + if (err == LFS_ERR_NOENT) { + break; + } + return err; + } + // end of range? note the use of rid+1 and unsigned comparison here to + // treat end_rid=-1 as "unbounded" in such a way that rid=-1 is still + // included + if ((lfs_size_t)(rid + 1) > (lfs_size_t)end_rid) { + break; + } + + // found an inlined data? we can just copy this like normal but + // we need to update any opened inlined files + if (tag == LFSR_TAG_INLINED) { + // write the tag + err = lfsr_rbyd_appendcompactattr(lfs, &mdir_->u.r.rbyd, + tag, weight, data); + if (err) { + LFS_ASSERT(err != LFS_ERR_RANGE); + return err; + } + + // stage any opened deferred trees with their new location so + // we can update these later if our commit is a success + for (lfsr_openedmdir_t *opened = lfs->opened[ + LFS_TYPE_REG-LFS_TYPE_REG]; + opened; + opened = opened->next) { + lfsr_file_t *file = (lfsr_file_t*)opened; + if (lfsr_file_isinlineddata(file) + && file->inlined.u.data.u.d.block == data.u.d.block + && file->inlined.u.data.u.d.off == data.u.d.off) { + // this is a bit tricky see we don't know the tag size, + // but we have enough enough info + file->inlined_.u.data = LFSR_DATA_DISK( + mdir_->u.r.rbyd.block, + mdir_->u.r.rbyd.eoff + - lfsr_data_size(&file->inlined.u.data), + lfsr_data_size(&file->inlined.u.data)); + } + } + + // found an inlined tree? we need to compact the tree as well to bring + // it along with us + } else if (tag == LFSR_TAG_TRUNK) { + lfsr_rbyd_t inlined_rbyd; + err = lfsr_data_readtrunk(lfs, &data, &inlined_rbyd); + if (err) { + return err; + } + inlined_rbyd.block = mdir->u.r.rbyd.block; + + // keep track of the start of our new tree + lfs_size_t off = mdir_->u.r.rbyd.eoff; + + // compact our inlined tree + err = lfsr_rbyd_appendcompactrbyd(lfs, &mdir_->u.r.rbyd, -1, -1, + &inlined_rbyd); + if (err) { + LFS_ASSERT(err != LFS_ERR_RANGE); + return err; + } + + err = lfsr_rbyd_compact(lfs, &mdir_->u.r.rbyd, true, off); + if (err) { + LFS_ASSERT(err != LFS_ERR_RANGE); + return err; + } + + // write the new inlined tree tag + uint8_t trunk_buf[LFSR_TRUNK_DSIZE]; + err = lfsr_rbyd_appendcompactattr(lfs, &mdir_->u.r.rbyd, + tag, weight, lfsr_data_fromtrunk(lfs, + &mdir_->u.r.rbyd, trunk_buf)); + if (err) { + LFS_ASSERT(err != LFS_ERR_RANGE); + return err; + } + + // stage any opened deferred trees with their new location so + // we can update these later if our commit is a success + for (lfsr_openedmdir_t *opened = lfs->opened[ + LFS_TYPE_REG-LFS_TYPE_REG]; + opened; + opened = opened->next) { + lfsr_file_t *file = (lfsr_file_t*)opened; + if (lfsr_file_isinlinedtree(file) + && file->inlined.u.rbyd.block == mdir->u.r.rbyd.block + && file->inlined.u.rbyd.trunk == inlined_rbyd.trunk) { + file->inlined_.u.rbyd.block = mdir_->u.r.rbyd.block; + file->inlined_.u.rbyd.trunk = mdir_->u.r.rbyd.trunk; + file->inlined_.u.rbyd.weight = mdir_->u.r.rbyd.weight; + file->inlined_.u.deferred.overhead + = file->inlined.u.deferred.overhead; + } + } + + } else { + // write the tag + err = lfsr_rbyd_appendcompactattr(lfs, &mdir_->u.r.rbyd, + tag, weight, data); + if (err) { + LFS_ASSERT(err != LFS_ERR_RANGE); + return err; + } + } + } + + int err = lfsr_rbyd_compact(lfs, &mdir_->u.r.rbyd, false, sizeof(uint32_t)); if (err) { + LFS_ASSERT(err != LFS_ERR_RANGE); return err; } - err = lfsr_rbyd_compact(lfs, &mdir_->u.r.rbyd, false, sizeof(uint32_t)); - if (err) { - return err; + // we're not quite done! we also need to bring over any opened, + // unsynced files + // + // TODO note for this to fully work we need to mark opened readonly + // files as unsynced if their entry is updated + for (lfsr_openedmdir_t *opened = lfs->opened[ + LFS_TYPE_REG-LFS_TYPE_REG]; + opened; + opened = opened->next) { + lfsr_file_t *file = (lfsr_file_t*)opened; + // inlined data? + if (lfsr_file_isinlineddata(file) + && (file->flags & LFS_F_UNSYNCED) + && file->inlined.u.data.u.d.block == mdir->u.r.rbyd.block) { + // write the data as a deferred tag + err = lfsr_rbyd_appendcompactattr(lfs, &mdir_->u.r.rbyd, + LFSR_TAG_DEFERRED(INLINED), 0, file->inlined.u.data); + if (err) { + LFS_ASSERT(err != LFS_ERR_RANGE); + return err; + } + + // this is a bit tricky see we don't know the tag size, + // but we have enough enough info + file->inlined_.u.data = LFSR_DATA_DISK( + mdir_->u.r.rbyd.block, + mdir_->u.r.rbyd.eoff + - lfsr_data_size(&file->inlined.u.data), + lfsr_data_size(&file->inlined.u.data)); + + // inlined tree? + } else if (lfsr_file_isinlinedtree(file) + && (file->flags & LFS_F_UNSYNCED) + && file->inlined.u.rbyd.block == mdir->u.r.rbyd.block) { + // save our current off/trunk/weight + lfs_size_t off = mdir_->u.r.rbyd.eoff; + lfs_size_t trunk = mdir_->u.r.rbyd.trunk; + lfsr_srid_t weight = mdir_->u.r.rbyd.weight; + + // compact our inlined tree + err = lfsr_rbyd_appendcompactrbyd(lfs, &mdir_->u.r.rbyd, -1, -1, + &file->inlined.u.rbyd); + if (err) { + LFS_ASSERT(err != LFS_ERR_RANGE); + return err; + } + + err = lfsr_rbyd_compact(lfs, &mdir_->u.r.rbyd, true, off); + if (err) { + LFS_ASSERT(err != LFS_ERR_RANGE); + return err; + } + + // stage our new trunk and revert to mdir trunk/weight + file->inlined_.u.rbyd.block = mdir_->u.r.rbyd.block; + file->inlined_.u.rbyd.trunk = mdir_->u.r.rbyd.trunk; + file->inlined_.u.rbyd.weight = mdir_->u.r.rbyd.weight; + file->inlined_.u.deferred.overhead + = file->inlined.u.deferred.overhead; + + mdir_->u.r.rbyd.trunk = trunk; + mdir_->u.r.rbyd.weight = weight; + } } return 0; @@ -5347,8 +5476,8 @@ static int lfsr_mdir_commit(lfs_t *lfs, lfsr_mdir_t *mdir, if (mid == -1 || lfsr_mtree_isinlined(lfs)) { lfsr_mdir_unerase(&lfs->mroot); } - for (int type = 0; type < 2; type++) { - for (lfsr_openedmdir_t *opened = lfs->opened[type]; + for (int type = LFS_TYPE_REG; type < LFS_TYPE_REG+3; type++) { + for (lfsr_openedmdir_t *opened = lfs->opened[type-LFS_TYPE_REG]; opened; opened = opened->next) { if ((opened->mdir.mid & lfsr_midbmask(lfs)) @@ -5754,8 +5883,8 @@ static int lfsr_mdir_commit(lfs_t *lfs, lfsr_mdir_t *mdir, // gstate must have been committed by a lower-level function at this point LFS_ASSERT(lfsr_grm_iszero(lfs->dgrm)); - // update our gstate for (lfs_size_t i = 0; i < attr_count; i++) { + // update gstate if (attrs[i].tag == LFSR_TAG_GRM) { lfs->grm = *(lfsr_grm_t*)attrs[i].data.u.b.buffer; @@ -5765,8 +5894,8 @@ static int lfsr_mdir_commit(lfs_t *lfs, lfsr_mdir_t *mdir, } // update any opened mdirs - for (int type = 0; type < 2; type++) { - for (lfsr_openedmdir_t *opened = lfs->opened[type]; + for (int type = LFS_TYPE_REG; type < LFS_TYPE_REG+3; type++) { + for (lfsr_openedmdir_t *opened = lfs->opened[type-LFS_TYPE_REG]; opened; opened = opened->next) { // avoid double updating current mdir, and avoid updating @@ -5826,7 +5955,8 @@ static int lfsr_mdir_commit(lfs_t *lfs, lfsr_mdir_t *mdir, - lfsr_mtree_weight(lfs); } - // some extra work is needed for opened dirs + // update any changes to directory bookmarks/positions, this + // gets a bit tricky if (type == LFS_TYPE_DIR) { lfsr_dir_t *dir = (lfsr_dir_t*)opened; for (lfs_size_t i = 0; i < attr_count; i++) { @@ -5867,6 +5997,20 @@ static int lfsr_mdir_commit(lfs_t *lfs, lfsr_mdir_t *mdir, - lfsr_mtree_weight(lfs); } } + + // if we compacted, update any staged changes to inlined data/trees + if (type == LFS_TYPE_REG) { + lfsr_file_t *file = (lfsr_file_t*)opened; + if (mdir_.u.m.blocks[0] != mdir->u.m.blocks[0] + && ((lfsr_file_isinlineddata(file) + && file->inlined.u.data.u.d.block + == mdir->u.m.blocks[0]) + || (lfsr_file_isinlinedtree(file) + && file->inlined.u.rbyd.block + == mdir->u.m.blocks[0]))) { + file->inlined = file->inlined_; + } + } next:; } } @@ -7218,7 +7362,7 @@ int lfsr_mkdir(lfs_t *lfs, const char *path) { // the bookmark first risks inserting the bookmark before the metadata // entry, which breaks things. // - lfsr_mdir_addopened(lfs, LFS_TYPE_REG, &bookmark); + lfsr_mdir_addopened(lfs, LFS_TYPE_INTERNAL, &bookmark); // commit our new directory into our parent, creating a grm to self-remove // in case of powerloss @@ -7230,7 +7374,7 @@ int lfsr_mkdir(lfs_t *lfs, const char *path) { goto failed_with_bookmark; } - lfsr_mdir_removeopened(lfs, LFS_TYPE_REG, &bookmark); + lfsr_mdir_removeopened(lfs, LFS_TYPE_INTERNAL, &bookmark); // commit our bookmark and zero the grm, the bookmark tag is an empty // entry that marks our did as allocated @@ -7494,7 +7638,7 @@ static int lfsr_mdir_stat(lfs_t *lfs, lfsr_mdir_t *mdir, lfsr_mid_t mid, } // get file type from the tag - info->type = lfsr_tag_filetype(tag); + info->type = lfsr_tag_subtype(tag); // get file name from the name entry LFS_ASSERT(lfsr_data_size(&data) <= LFS_NAME_MAX); @@ -7724,7 +7868,8 @@ int lfsr_dir_rewind(lfs_t *lfs, lfsr_dir_t *dir) { #define LFSR_FILE_INLINEDDATA 0x80000000 static bool lfsr_file_isinlineddata(const lfsr_file_t *file) { - return file->inlined.u.weight & LFSR_FILE_INLINEDDATA; + // this checks that both the inlineddata bit and non-zero + return (lfs_size_t)file->inlined.u.weight > (LFSR_FILE_INLINEDDATA | 0); } static bool lfsr_file_isinlinedtree(const lfsr_file_t *file) { @@ -7759,9 +7904,9 @@ int lfsr_file_opencfg(lfs_t *lfs, lfsr_file_t *file, file->cfg = cfg; file->pos = 0; // default inlined state - file->inlined.u.d.block = 0; - file->inlined.u.d.off = 0; - file->inlined.u.d.size = LFSR_FILE_INLINEDDATA | 0; + file->inlined.u.data.u.d.block = 0; + file->inlined.u.data.u.d.off = 0; + file->inlined.u.data.u.d.size = LFSR_FILE_INLINEDDATA | 0; // lookup our parent lfsr_tag_t tag; @@ -7840,10 +7985,12 @@ int lfsr_file_opencfg(lfs_t *lfs, lfsr_file_t *file, if (err != LFS_ERR_NOENT) { LFS_ASSERT(lfsr_file_inlinedsize(file) == 0); err = lfsr_data_readtrunk(lfs, &data, - (lfsr_rbyd_t*)&file->inlined); + &file->inlined.u.rbyd); if (err) { return err; } + + file->inlined.u.rbyd.block = file->m.mdir.u.m.blocks[0]; } } } @@ -7941,16 +8088,11 @@ int lfsr_file_read_(lfs_t *lfs, const lfsr_file_t *file, // is the data in an inlined tree? if (lfsr_file_isinlinedtree(file) && pos < lfsr_file_inlinedsize(file)) { - lfsr_rbyd_t rbyd; - rbyd.block = file->m.mdir.u.m.blocks[0]; - rbyd.trunk = file->inlined.u.t.trunk; - rbyd.weight = file->inlined.u.t.weight; - lfsr_srid_t rid; lfsr_tag_t tag; lfsr_rid_t weight; lfsr_data_t data; - int err = lfsr_rbyd_lookupnext(lfs, &rbyd, pos, 0, + int err = lfsr_rbyd_lookupnext(lfs, &file->inlined.u.rbyd, pos, 0, &rid, &tag, &weight, &data); if (err && err != LFS_ERR_NOENT) { return err; @@ -8003,7 +8145,7 @@ lfs_ssize_t lfsr_file_read(lfs_t *lfs, lfsr_file_t *file, static int lfsr_file_flushbuffer(lfs_t *lfs, lfsr_file_t *file) { while (file->buffer_size > 0) { // do we need an inlined tree? - if (lfsr_file_isinlineddata(file)) { + if (!lfsr_file_isinlinedtree(file)) { // TODO rm? we haven't updated file->size yet! // // we shouldn't reach this point if we still fit entirely // // in a simple inlined file @@ -8013,30 +8155,33 @@ static int lfsr_file_flushbuffer(lfs_t *lfs, lfsr_file_t *file) { // do we carve out any data from the inlined data? lfsr_data_t left_data = LFSR_DATA_DISK( - file->inlined.u.d.block, - file->inlined.u.d.off, + file->inlined.u.data.u.d.block, + file->inlined.u.data.u.d.off, lfs_min32( file->buffer_pos, lfsr_file_inlinedsize(file))); lfs_off_t right_pos = file->buffer_pos + file->buffer_size; lfsr_data_t right_data = LFSR_DATA_DISK( - file->inlined.u.d.block, - file->inlined.u.d.off + right_pos, + file->inlined.u.data.u.d.block, + file->inlined.u.data.u.d.off + right_pos, lfsr_file_inlinedsize(file) - lfs_min32( right_pos, lfsr_file_inlinedsize(file))); // create a zero weight trunk - file->inlined.u.t.trunk = 0; - file->inlined.u.t.weight = 0; + // + // make sure to use our staging rbyd so we catch in-flight updates + // caused by mdir compactions + file->inlined_.u.rbyd.block = file->m.mdir.u.m.blocks[0]; + file->inlined_.u.rbyd.trunk = 0; + file->inlined_.u.rbyd.weight = 0; // TODO - file->inlined.u.t.overhead = 0; + file->inlined_.u.deferred.overhead = 0; int err = lfsr_mdir_commit(lfs, &file->m.mdir, LFSR_ATTRS( LFSR_ATTR_(file->m.mdir.mid, DEFER, 0, DEFER( - // TODO bit of a hack... - (lfsr_rbyd_t*)&file->inlined, + &file->inlined_.u.rbyd, // note we always need a zero entry (file->buffer_pos > 0 ? LFSR_ATTR(0, @@ -8058,6 +8203,7 @@ static int lfsr_file_flushbuffer(lfs_t *lfs, lfsr_file_t *file) { return err; } + file->inlined = file->inlined_; file->buffer_size = 0; continue; } @@ -8069,19 +8215,12 @@ static int lfsr_file_flushbuffer(lfs_t *lfs, lfsr_file_t *file) { // this is a complex question since we may be carving out leaves of the // inlined tree, we need to find these leaves to know for sure // - // TODO can we somehow fit an rbyd struct in the file_t so we don't - // need this copy? - lfsr_rbyd_t rbyd; - rbyd.block = file->m.mdir.u.m.blocks[0]; - rbyd.trunk = file->inlined.u.t.trunk; - rbyd.weight = file->inlined.u.t.weight; - lfsr_srid_t left_rid; lfsr_tag_t left_tag; lfsr_rid_t left_weight; lfsr_data_t left_data; - int err = lfsr_rbyd_lookupnext(lfs, &rbyd, - lfs_min32(file->buffer_pos, rbyd.weight-1), 0, + int err = lfsr_rbyd_lookupnext(lfs, &file->inlined.u.rbyd, + lfs_min32(file->buffer_pos, file->inlined.u.rbyd.weight-1), 0, &left_rid, &left_tag, &left_weight, &left_data); if (err && err != LFS_ERR_NOENT) { return err; @@ -8108,7 +8247,7 @@ static int lfsr_file_flushbuffer(lfs_t *lfs, lfsr_file_t *file) { lfsr_tag_t right_tag; lfsr_rid_t right_weight; lfsr_data_t right_data; - err = lfsr_rbyd_lookupnext(lfs, &rbyd, + err = lfsr_rbyd_lookupnext(lfs, &file->inlined.u.rbyd, file->buffer_pos + file->buffer_size-1, 0, &right_rid, &right_tag, &right_weight, &right_data); if (err && err != LFS_ERR_NOENT) { @@ -8136,19 +8275,22 @@ static int lfsr_file_flushbuffer(lfs_t *lfs, lfsr_file_t *file) { } // write out our buffer and any carved data + // + // make sure to use our staging rbyd so we catch in-flight updates + // caused by mdir compactions + file->inlined_ = file->inlined; err = lfsr_mdir_commit(lfs, &file->m.mdir, LFSR_ATTRS( LFSR_ATTR_(file->m.mdir.mid, DEFER, 0, DEFER( - // TODO bit of a hack... - (lfsr_rbyd_t*)&file->inlined, + &file->inlined_.u.rbyd, // first remove anything in the way LFSR_ATTR( lfs_min32( right_pos + lfsr_data_size(&right_data), - rbyd.weight)-1, + file->inlined.u.rbyd.weight)-1, RM, lfs_min32( right_pos + lfsr_data_size(&right_data), - rbyd.weight) - left_pos, + file->inlined.u.rbyd.weight) - left_pos, NULL), // TODO this should handling holes somehow (lfsr_data_size(&left_data) > 0 @@ -8171,6 +8313,7 @@ static int lfsr_file_flushbuffer(lfs_t *lfs, lfsr_file_t *file) { return err; } + file->inlined = file->inlined_; file->buffer_size = 0; continue; @@ -8258,12 +8401,7 @@ int lfsr_file_sync(lfs_t *lfs, lfsr_file_t *file) { return 0; } - // write out any data we need to - int err = lfsr_file_flush(lfs, file); - if (err) { - goto failed; - } - + int err; if (file->flags & LFS_F_UNSYNCED) { // TODO what if buffer_size > inlined_size? // TODO should we also update file to be unbuffered after syncing @@ -8288,7 +8426,7 @@ int lfsr_file_sync(lfs_t *lfs, lfsr_file_t *file) { } // but clear buffer after syncing simple inlined files, otherwise - // we risk O(n^2) behavior + // we risk runaway O(n^2) behavior file->buffer_size = 0; // we need to look up the inlined data again... @@ -8315,8 +8453,7 @@ int lfsr_file_sync(lfs_t *lfs, lfsr_file_t *file) { uint8_t trunk_buf[LFSR_TRUNK_DSIZE]; err = lfsr_mdir_commit(lfs, &file->m.mdir, LFSR_ATTRS( LFSR_ATTR(file->m.mdir.mid, WIDE(TRUNK), 0, FROMTRUNK( - // TODO too much of a hack? - lfs, (const lfsr_rbyd_t*)&file->inlined, trunk_buf)))); + lfs, &file->inlined.u.rbyd, trunk_buf)))); if (err) { goto failed; } @@ -11831,8 +11968,9 @@ static int lfs_init(lfs_t *lfs, const struct lfs_config *cfg) { lfs->mleaf_bits = lfs_nlog2(lfs->cfg->block_size/16); // zero linked-lists of opened mdirs - lfs->opened[LFS_TYPE_REG] = NULL; - lfs->opened[LFS_TYPE_DIR] = NULL; + lfs->opened[LFS_TYPE_REG - LFS_TYPE_REG] = NULL; + lfs->opened[LFS_TYPE_DIR - LFS_TYPE_REG] = NULL; + lfs->opened[LFS_TYPE_INTERNAL - LFS_TYPE_REG] = NULL; // zero gstate memset(lfs->ggrm, 0, LFSR_GRM_DSIZE); diff --git a/lfs.h b/lfs.h index 9a88659f..946e5a79 100644 --- a/lfs.h +++ b/lfs.h @@ -106,8 +106,11 @@ enum lfs_error { // File types enum lfs_type { // file types - LFS_TYPE_REG = 0, - LFS_TYPE_DIR = 1, + LFS_TYPE_REG = 2, + LFS_TYPE_DIR = 3, + + // used internally, don't use this + LFS_TYPE_INTERNAL = 4, // // internally used types // LFS_TYPE_SPLICE = 0x400, @@ -152,6 +155,7 @@ enum lfs_open_flags { #endif // internally used flags + // TODO do we need both unflushed and unsynced? LFS_F_UNFLUSHED = 0x010000, // File's has data that needs to be written LFS_F_UNSYNCED = 0x020000, // File's metadata does not match storage LFS_F_ERRORED = 0x040000, // An error occurred during write @@ -490,6 +494,22 @@ typedef struct lfs_file { const struct lfs_file_config *cfg; } lfs_file_t; +typedef struct lfsr_inlined { + union { + // note sign bit indicates if data is a single inlined data, or an + // inlined tree, this works because inlined data is always on disk, + // so data.size always has sign=1 + lfs_soff_t weight; + lfsr_data_t data; + lfsr_rbyd_t rbyd; + struct { + lfs_soff_t weight; + lfs_size_t trunk; + lfs_size_t overhead; + } deferred; + } u; +} lfsr_inlined_t; + typedef struct lfsr_file { lfsr_openedmdir_t m; uint32_t flags; @@ -500,25 +520,11 @@ typedef struct lfsr_file { uint8_t *buffer; lfs_size_t buffer_size; - struct { - union { - // note sign bit indicates if data is a single inlined data, or an - // inlined tree, this works because inlined data is always on disk, - // so data.size always has sign=1 - lfs_soff_t weight; - lfsr_data_t data; - struct { - lfs_ssize_t size; - lfs_size_t off; - lfs_block_t block; - } d; - struct { - lfs_soff_t weight; - lfs_size_t trunk; - lfs_size_t overhead; - } t; - } u; - } inlined; + // we need copies of inlined references in case of mdir compaction, this + // exists here instead of on the stack becuase we don't know how many + // inlined files may be opened + lfsr_inlined_t inlined; + lfsr_inlined_t inlined_; const struct lfs_file_config *cfg; } lfsr_file_t; @@ -578,7 +584,7 @@ typedef struct lfs { // linked-lists of opened mdirs, we keep a separate linked-list // for each type since these need to be handled a bit differently - lfsr_openedmdir_t *opened[2]; + lfsr_openedmdir_t *opened[3]; #ifdef LFS_MIGRATE struct lfs1 *lfs1; diff --git a/scripts/dbglfs.py b/scripts/dbglfs.py index e8cb95e9..8ac9671a 100755 --- a/scripts/dbglfs.py +++ b/scripts/dbglfs.py @@ -874,6 +874,7 @@ def frepr(mdir, rid, tag): did, _ = fromleb128(data) did = '0x%x' % did return 'bookmark %s' % did + elif tag == TAG_DIR: # read the did did = '?' @@ -882,6 +883,25 @@ def frepr(mdir, rid, tag): did, _ = fromleb128(data) did = '0x%x' % did return 'dir %s' % did + + elif tag == TAG_REG: + size = 0 + structs = [] + # inlined? + done, rid_, tag_, w_, j, d, data, _ = mdir.lookup(rid, TAG_INLINED) + if not done and rid_ == rid and tag_ == TAG_INLINED: + size = max(size, len(data)) + structs.append('inlined 0x%x.%x %d' % (mdir.block, j+d, len(data))) + # inlined tree? + done, rid_, tag_, w_, j, d, data, _ = mdir.lookup(rid, TAG_TRUNK) + if not done and rid_ == rid and tag_ == TAG_TRUNK: + d = 0 + trunk, d_ = fromleb128(data[d:]); d += d_ + weight, d_ = fromleb128(data[d:]); d += d_ + size = max(size, weight) + structs.append('trunk 0x%x.%x' % (mdir.block, trunk)) + return 'reg %s' % ', '.join(it.chain(['%d' % size], structs)) + else: return 'type 0x%02x' % (tag & 0xff) diff --git a/tests/test_mtree.toml b/tests/test_mtree.toml index 0110879b..81f77e55 100644 --- a/tests/test_mtree.toml +++ b/tests/test_mtree.toml @@ -2707,8 +2707,8 @@ code = ''' .mdir={.mid=0, .u.m=lfs.mroot.u.m}}; lfsr_openedmdir_t right_neighbor = { .mdir={.mid=1, .u.m=lfs.mroot.u.m}}; - lfsr_mdir_addopened(&lfs, LFS_TYPE_REG, &left_neighbor); - lfsr_mdir_addopened(&lfs, LFS_TYPE_REG, &right_neighbor); + lfsr_mdir_addopened(&lfs, LFS_TYPE_INTERNAL, &left_neighbor); + lfsr_mdir_addopened(&lfs, LFS_TYPE_INTERNAL, &right_neighbor); // insert a new entry, this should update our neighbors lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS( @@ -2730,8 +2730,8 @@ code = ''' assert(memcmp(&right_neighbor.mdir.u.m, &lfs.mroot.u.m, sizeof(lfs.mroot.u.m)) == 0); - lfsr_mdir_removeopened(&lfs, LFS_TYPE_REG, &left_neighbor); - lfsr_mdir_removeopened(&lfs, LFS_TYPE_REG, &right_neighbor); + lfsr_mdir_removeopened(&lfs, LFS_TYPE_INTERNAL, &left_neighbor); + lfsr_mdir_removeopened(&lfs, LFS_TYPE_INTERNAL, &right_neighbor); lfsr_unmount(&lfs) => 0; ''' @@ -2757,8 +2757,8 @@ code = ''' .mdir={.mid=0, .u.m=lfs.mroot.u.m}}; lfsr_openedmdir_t right_neighbor = { .mdir={.mid=1, .u.m=lfs.mroot.u.m}}; - lfsr_mdir_addopened(&lfs, LFS_TYPE_REG, &left_neighbor); - lfsr_mdir_addopened(&lfs, LFS_TYPE_REG, &right_neighbor); + lfsr_mdir_addopened(&lfs, LFS_TYPE_INTERNAL, &left_neighbor); + lfsr_mdir_addopened(&lfs, LFS_TYPE_INTERNAL, &right_neighbor); // try removing our left entry lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS( @@ -2773,8 +2773,8 @@ code = ''' assert(memcmp(&right_neighbor.mdir.u.m, &lfs.mroot.u.m, sizeof(lfs.mroot.u.m)) == 0); - lfsr_mdir_removeopened(&lfs, LFS_TYPE_REG, &left_neighbor); - lfsr_mdir_removeopened(&lfs, LFS_TYPE_REG, &right_neighbor); + lfsr_mdir_removeopened(&lfs, LFS_TYPE_INTERNAL, &left_neighbor); + lfsr_mdir_removeopened(&lfs, LFS_TYPE_INTERNAL, &right_neighbor); lfsr_unmount(&lfs) => 0; ''' @@ -2800,8 +2800,8 @@ code = ''' .mdir={.mid=0, .u.m=lfs.mroot.u.m}}; lfsr_openedmdir_t right_neighbor = { .mdir={.mid=1, .u.m=lfs.mroot.u.m}}; - lfsr_mdir_addopened(&lfs, LFS_TYPE_REG, &left_neighbor); - lfsr_mdir_addopened(&lfs, LFS_TYPE_REG, &right_neighbor); + lfsr_mdir_addopened(&lfs, LFS_TYPE_INTERNAL, &left_neighbor); + lfsr_mdir_addopened(&lfs, LFS_TYPE_INTERNAL, &right_neighbor); // try removing our left entry lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS( @@ -2816,8 +2816,8 @@ code = ''' sizeof(lfs.mroot.u.m)) == 0); assert(right_neighbor.mdir.mid == -1); - lfsr_mdir_removeopened(&lfs, LFS_TYPE_REG, &left_neighbor); - lfsr_mdir_removeopened(&lfs, LFS_TYPE_REG, &right_neighbor); + lfsr_mdir_removeopened(&lfs, LFS_TYPE_INTERNAL, &left_neighbor); + lfsr_mdir_removeopened(&lfs, LFS_TYPE_INTERNAL, &right_neighbor); lfsr_unmount(&lfs) => 0; ''' @@ -2845,8 +2845,8 @@ code = ''' .mdir={.mid=0, .u.m=lfs.mroot.u.m}}; lfsr_openedmdir_t right_neighbor = { .mdir={.mid=1, .u.m=lfs.mroot.u.m}}; - lfsr_mdir_addopened(&lfs, LFS_TYPE_REG, &left_neighbor); - lfsr_mdir_addopened(&lfs, LFS_TYPE_REG, &right_neighbor); + lfsr_mdir_addopened(&lfs, LFS_TYPE_INTERNAL, &left_neighbor); + lfsr_mdir_addopened(&lfs, LFS_TYPE_INTERNAL, &right_neighbor); // prepare mroot with a large attr so the next entry can not fit uint8_t buffer[SIZE]; @@ -2895,8 +2895,8 @@ code = ''' assert(memcmp(&right_neighbor.mdir.u.m, &msibling.u.m, sizeof(msibling.u.m)) == 0); - lfsr_mdir_removeopened(&lfs, LFS_TYPE_REG, &left_neighbor); - lfsr_mdir_removeopened(&lfs, LFS_TYPE_REG, &right_neighbor); + lfsr_mdir_removeopened(&lfs, LFS_TYPE_INTERNAL, &left_neighbor); + lfsr_mdir_removeopened(&lfs, LFS_TYPE_INTERNAL, &right_neighbor); lfsr_unmount(&lfs) => 0; ''' @@ -2924,8 +2924,8 @@ code = ''' .mdir={.mid=0, .u.m=lfs.mroot.u.m}}; lfsr_openedmdir_t right_neighbor = { .mdir={.mid=1, .u.m=lfs.mroot.u.m}}; - lfsr_mdir_addopened(&lfs, LFS_TYPE_REG, &left_neighbor); - lfsr_mdir_addopened(&lfs, LFS_TYPE_REG, &right_neighbor); + lfsr_mdir_addopened(&lfs, LFS_TYPE_INTERNAL, &left_neighbor); + lfsr_mdir_addopened(&lfs, LFS_TYPE_INTERNAL, &right_neighbor); // create 2 large entries that needs to be uninlined and split uint8_t buffer[SIZE]; @@ -2969,8 +2969,8 @@ code = ''' assert(memcmp(&right_neighbor.mdir.u.m, &msibling.u.m, sizeof(msibling.u.m)) == 0); - lfsr_mdir_removeopened(&lfs, LFS_TYPE_REG, &left_neighbor); - lfsr_mdir_removeopened(&lfs, LFS_TYPE_REG, &right_neighbor); + lfsr_mdir_removeopened(&lfs, LFS_TYPE_INTERNAL, &left_neighbor); + lfsr_mdir_removeopened(&lfs, LFS_TYPE_INTERNAL, &right_neighbor); lfsr_unmount(&lfs) => 0; ''' @@ -3025,8 +3025,8 @@ code = ''' .mdir={.mid=mdir.mid+0, .u.m=mdir.u.m}}; lfsr_openedmdir_t right_neighbor = { .mdir={.mid=mdir.mid+2, .u.m=mdir.u.m}}; - lfsr_mdir_addopened(&lfs, LFS_TYPE_REG, &left_neighbor); - lfsr_mdir_addopened(&lfs, LFS_TYPE_REG, &right_neighbor); + lfsr_mdir_addopened(&lfs, LFS_TYPE_INTERNAL, &left_neighbor); + lfsr_mdir_addopened(&lfs, LFS_TYPE_INTERNAL, &right_neighbor); // now add another large entry to the mdir, forcing a split memset(buffer, 'e', SIZE); @@ -3070,8 +3070,8 @@ code = ''' assert(memcmp(&right_neighbor.mdir.u.m, &msibling.u.m, sizeof(msibling.u.m)) == 0); - lfsr_mdir_removeopened(&lfs, LFS_TYPE_REG, &left_neighbor); - lfsr_mdir_removeopened(&lfs, LFS_TYPE_REG, &right_neighbor); + lfsr_mdir_removeopened(&lfs, LFS_TYPE_INTERNAL, &left_neighbor); + lfsr_mdir_removeopened(&lfs, LFS_TYPE_INTERNAL, &right_neighbor); lfsr_unmount(&lfs) => 0; ''' @@ -3101,8 +3101,8 @@ code = ''' .mdir={.mid=0, .u.m=lfs.mroot.u.m}}; lfsr_openedmdir_t right_neighbor = { .mdir={.mid=1, .u.m=lfs.mroot.u.m}}; - lfsr_mdir_addopened(&lfs, LFS_TYPE_REG, &left_neighbor); - lfsr_mdir_addopened(&lfs, LFS_TYPE_REG, &right_neighbor); + lfsr_mdir_addopened(&lfs, LFS_TYPE_INTERNAL, &left_neighbor); + lfsr_mdir_addopened(&lfs, LFS_TYPE_INTERNAL, &right_neighbor); // prepare mroot with an attr uint8_t buffer[SIZE]; @@ -3136,8 +3136,8 @@ code = ''' assert(memcmp(&right_neighbor.mdir.u.m, &lfs.mroot.u.m, sizeof(lfs.mroot.u.m)) == 0); - lfsr_mdir_removeopened(&lfs, LFS_TYPE_REG, &left_neighbor); - lfsr_mdir_removeopened(&lfs, LFS_TYPE_REG, &right_neighbor); + lfsr_mdir_removeopened(&lfs, LFS_TYPE_INTERNAL, &left_neighbor); + lfsr_mdir_removeopened(&lfs, LFS_TYPE_INTERNAL, &right_neighbor); lfsr_unmount(&lfs) => 0; ''' @@ -3194,8 +3194,8 @@ code = ''' .mdir={.mid=mdir.mid+0, .u.m=mdir.u.m}}; lfsr_openedmdir_t right_neighbor = { .mdir={.mid=mdir.mid+2, .u.m=mdir.u.m}}; - lfsr_mdir_addopened(&lfs, LFS_TYPE_REG, &left_neighbor); - lfsr_mdir_addopened(&lfs, LFS_TYPE_REG, &right_neighbor); + lfsr_mdir_addopened(&lfs, LFS_TYPE_INTERNAL, &left_neighbor); + lfsr_mdir_addopened(&lfs, LFS_TYPE_INTERNAL, &right_neighbor); // force mdir to compact twice, this should relocate lfsr_mdir_t old_mdir = mdir; @@ -3229,8 +3229,8 @@ code = ''' assert(right_neighbor.mdir.mid == 0*lfsr_mleafweight(&lfs)+2); assert(memcmp(&right_neighbor.mdir.u.m, &mdir.u.m, sizeof(mdir.u.m)) == 0); - lfsr_mdir_removeopened(&lfs, LFS_TYPE_REG, &left_neighbor); - lfsr_mdir_removeopened(&lfs, LFS_TYPE_REG, &right_neighbor); + lfsr_mdir_removeopened(&lfs, LFS_TYPE_INTERNAL, &left_neighbor); + lfsr_mdir_removeopened(&lfs, LFS_TYPE_INTERNAL, &right_neighbor); lfsr_unmount(&lfs) => 0; ''' @@ -3296,8 +3296,8 @@ code = ''' &right_neighbor.mdir) => 0; assert(right_neighbor.mdir.u.m.weight == 1); - lfsr_mdir_addopened(&lfs, LFS_TYPE_REG, &left_neighbor); - lfsr_mdir_addopened(&lfs, LFS_TYPE_REG, &right_neighbor); + lfsr_mdir_addopened(&lfs, LFS_TYPE_INTERNAL, &left_neighbor); + lfsr_mdir_addopened(&lfs, LFS_TYPE_INTERNAL, &right_neighbor); // cause middle mdir to split lfsr_mtree_lookup(&lfs, 1*lfsr_mleafweight(&lfs)+1, &mdir) => 0; @@ -3322,8 +3322,8 @@ code = ''' lfsr_mtree_lookup(&lfs, 3*lfsr_mleafweight(&lfs)+0, &mdir) => 0; assert(memcmp(&right_neighbor.mdir.u.m, &mdir.u.m, sizeof(mdir.u.m)) == 0); - lfsr_mdir_removeopened(&lfs, LFS_TYPE_REG, &left_neighbor); - lfsr_mdir_removeopened(&lfs, LFS_TYPE_REG, &right_neighbor); + lfsr_mdir_removeopened(&lfs, LFS_TYPE_INTERNAL, &left_neighbor); + lfsr_mdir_removeopened(&lfs, LFS_TYPE_INTERNAL, &right_neighbor); lfsr_unmount(&lfs) => 0; ''' @@ -3389,8 +3389,8 @@ code = ''' &right_neighbor.mdir) => 0; assert(right_neighbor.mdir.u.m.weight == 1); - lfsr_mdir_addopened(&lfs, LFS_TYPE_REG, &left_neighbor); - lfsr_mdir_addopened(&lfs, LFS_TYPE_REG, &right_neighbor); + lfsr_mdir_addopened(&lfs, LFS_TYPE_INTERNAL, &left_neighbor); + lfsr_mdir_addopened(&lfs, LFS_TYPE_INTERNAL, &right_neighbor); // cause middle mdir to drop lfsr_mtree_lookup(&lfs, 1*lfsr_mleafweight(&lfs)+0, &mdir) => 0; @@ -3410,8 +3410,8 @@ code = ''' lfsr_mtree_lookup(&lfs, 1*lfsr_mleafweight(&lfs)+0, &mdir) => 0; assert(memcmp(&right_neighbor.mdir.u.m, &mdir.u.m, sizeof(mdir.u.m)) == 0); - lfsr_mdir_removeopened(&lfs, LFS_TYPE_REG, &left_neighbor); - lfsr_mdir_removeopened(&lfs, LFS_TYPE_REG, &right_neighbor); + lfsr_mdir_removeopened(&lfs, LFS_TYPE_INTERNAL, &left_neighbor); + lfsr_mdir_removeopened(&lfs, LFS_TYPE_INTERNAL, &right_neighbor); lfsr_unmount(&lfs) => 0; '''