t: Added lookahead to lfsr_traversal_t, adopted in lfs_alloc

This sort of turned into a complete refactor of lfs_alloc in order to
move/reuse the lookahead buffer filling logic into lfsr_fs_traverse.

lfs_alloc now calls lfsr_fs_traverse to fill the lookahead buffer when
no more blocks are available, but also you can too with lfsr_traversal_t
+ LFS_T_LOOKAHEAD.

The one big caveat being if any mutation happens to the filesystem, any
incomplete lookahead needs to be tossed out. To help with this,
lfsr_traversal_read now returns LFS_ERR_BUSY (-16) instead of
LFS_ERR_NOENT (-2) if the filesystem has been modified since the
traversal was opened.

Note that by default lfsr_traversal_t will still try to keep traversing
blocks, but can be told to terminate immediately with LFS_T_EXCL.
Continuing the traversal is probably desired for checking checksums,
debugging, etc, as otherwise you could end up looping over only the
first couple blocks in a write-heavy system, but if you are trying to
populate the lookahead buffer you probably want to just abort and start
over.

I considered adding a flags field to lfs_tinfo for this, but decided
against it since it would be the only place in the current API where we
don't use error codes to convey behavior-changing information. Though
this may be worth reconsidering at some point...

---

In reworking lfs_alloc, a lot of the internal logic was broken up into
specific functions:

- lfs_alloc_ckpoint - checkpoint the allocator
- lfs_alloc_discard - discard any lookahead
- lfs_alloc_shift - discard/shift lookahead if progress can be made
- lfs_alloc_markinuse - mark a block as in-use
- lfs_alloc_markfree - mark any remaining blocks as free
- lfs_alloc_findnext - find the next free block in lookahead

If anything this probably makes lfs_alloc more readable, though the
original motivation was to allow lfsr_traversal_t to only shift/zero the
lookahead buffer if there's a chance we can make progress.

This was based on upstream work by opilat and myself.

Code changes:

           code          stack
  before: 34226           2560
  after:  34474 (+0.7%)   2552 (-0.3%)
This commit is contained in:
Christopher Haster
2024-06-14 20:44:01 -05:00
parent 670b9fbf99
commit 635e1fe8d4
5 changed files with 2396 additions and 252 deletions
+269 -176
View File
@@ -2109,7 +2109,7 @@ static int lfsr_bptr_ck(lfs_t *lfs, const lfsr_bptr_t *bptr) {
// predeclare block allocator functions // predeclare block allocator functions
static int lfs_alloc(lfs_t *lfs, lfs_block_t *block, bool erase); static lfs_sblock_t lfs_alloc(lfs_t *lfs, bool erase);
static void lfs_alloc_ckpoint(lfs_t *lfs); static void lfs_alloc_ckpoint(lfs_t *lfs);
@@ -2152,12 +2152,16 @@ static inline int lfsr_rbyd_cmp(
// allocate an rbyd block // allocate an rbyd block
static int lfsr_rbyd_alloc(lfs_t *lfs, lfsr_rbyd_t *rbyd) { static int lfsr_rbyd_alloc(lfs_t *lfs, lfsr_rbyd_t *rbyd) {
*rbyd = (lfsr_rbyd_t){.weight=0, .trunk=0, .eoff=0, .cksum=0}; lfs_sblock_t block = lfs_alloc(lfs, true);
int err = lfs_alloc(lfs, &rbyd->blocks[0], true); if (block < 0) {
if (err) { return block;
return err;
} }
rbyd->blocks[0] = block;
rbyd->trunk = 0;
rbyd->weight = 0;
rbyd->eoff = 0;
rbyd->cksum = 0;
return 0; return 0;
} }
@@ -6016,10 +6020,11 @@ static int lfsr_mdir_alloc__(lfs_t *lfs, lfsr_mdir_t *mdir,
if (all) { if (all) {
// allocate one block without an erase // allocate one block without an erase
int err = lfs_alloc(lfs, &mdir->rbyd.blocks[1], false); lfs_sblock_t block = lfs_alloc(lfs, false);
if (err) { if (block < 0) {
return err; return block;
} }
mdir->rbyd.blocks[1] = block;
} }
// read the new revision count // read the new revision count
@@ -6039,10 +6044,11 @@ static int lfsr_mdir_alloc__(lfs_t *lfs, lfsr_mdir_t *mdir,
relocate:; relocate:;
// allocate another block with an erase // allocate another block with an erase
err = lfs_alloc(lfs, &mdir->rbyd.blocks[0], true); lfs_sblock_t block = lfs_alloc(lfs, true);
if (err) { if (block < 0) {
return err; return block;
} }
mdir->rbyd.blocks[0] = block;
mdir->rbyd.weight = 0; mdir->rbyd.weight = 0;
mdir->rbyd.trunk = 0; mdir->rbyd.trunk = 0;
mdir->rbyd.eoff = 0; mdir->rbyd.eoff = 0;
@@ -7718,18 +7724,15 @@ static int lfsr_mtree_pathlookup(lfs_t *lfs, const lfsr_mtree_t *mtree,
// traversing littlefs is a bit complex, so we use a state machine to keep // traversing littlefs is a bit complex, so we use a state machine to keep
// track of where we are // track of where we are
//
// note the lower two bits are reserved so upper layers can iterate over
// redund blocks easily
enum { enum {
LFSR_TSTATE_MROOTANCHOR = 0 << 2, LFSR_TSTATE_MROOTANCHOR = 0,
LFSR_TSTATE_MROOTCHAIN = 1 << 2, LFSR_TSTATE_MROOTCHAIN = 1,
LFSR_TSTATE_MTREE = 2 << 2, LFSR_TSTATE_MTREE = 2,
LFSR_TSTATE_MDIR = 3 << 2, LFSR_TSTATE_MDIR = 3,
LFSR_TSTATE_MDIRBTREE = 4 << 2, LFSR_TSTATE_MDIRBTREE = 4,
LFSR_TSTATE_OMDIR = 5 << 2, LFSR_TSTATE_OMDIR = 5,
LFSR_TSTATE_OMDIRBTREE = 6 << 2, LFSR_TSTATE_OMDIRBTREE = 6,
LFSR_TSTATE_DONE = 7 << 2, LFSR_TSTATE_DONE = 7,
}; };
#define LFSR_MTRAVERSAL(_flags) \ #define LFSR_MTRAVERSAL(_flags) \
@@ -7742,10 +7745,29 @@ enum {
.u.mtortoise.step=0, \ .u.mtortoise.step=0, \
.u.mtortoise.power=0}) .u.mtortoise.power=0})
static void lfsr_fs_traverserewind(lfs_t *lfs, lfsr_mtraversal_t *mt) {
(void)lfs;
mt->o.flags &= ~LFS_F_DIRTY;
mt->o.state = LFSR_TSTATE_MROOTANCHOR;
mt->o.mdir.mid = -1;
mt->u.mtortoise.mptr.blocks[0] = 0;
mt->u.mtortoise.mptr.blocks[1] = 0;
mt->u.mtortoise.step = 0;
mt->u.mtortoise.power = 0;
}
static inline bool lfsr_t_ismtreeonly(uint32_t flags) { static inline bool lfsr_t_ismtreeonly(uint32_t flags) {
return flags & LFS_T_MTREEONLY; return flags & LFS_T_MTREEONLY;
} }
static inline bool lfsr_t_isexcl(uint32_t flags) {
return flags & LFS_T_EXCL;
}
static inline bool lfsr_t_ismkconsistent(uint32_t flags) {
return flags & LFS_T_MKCONSISTENT;
}
static inline bool lfsr_t_islookahead(uint32_t flags) { static inline bool lfsr_t_islookahead(uint32_t flags) {
return flags & LFS_T_LOOKAHEAD; return flags & LFS_T_LOOKAHEAD;
} }
@@ -7762,16 +7784,8 @@ static inline bool lfsr_t_isckdata(uint32_t flags) {
return flags & LFS_T_CKDATA; return flags & LFS_T_CKDATA;
} }
static inline bool lfsr_t_isdirty(uint32_t flags) { static inline bool lfsr_f_isdirty(uint32_t flags) {
return flags & LFS_T_DIRTY; return flags & LFS_F_DIRTY;
}
static inline bool lfsr_t_iscorruptmetadata(uint32_t flags) {
return flags & LFS_T_CORRUPTMETADATA;
}
static inline bool lfsr_t_iscorruptdata(uint32_t flags) {
return flags & LFS_T_CORRUPTDATA;
} }
@@ -7793,12 +7807,12 @@ static int lfsr_bshrub_traverse(lfs_t *lfs, const lfsr_file_t *file,
static int lfsr_fs_traverse_(lfs_t *lfs, lfsr_mtraversal_t *mt, static int lfsr_fs_traverse_(lfs_t *lfs, lfsr_mtraversal_t *mt,
lfsr_mtinfo_t *mtinfo) { lfsr_mtinfo_t *mtinfo) {
while (true) { while (true) {
switch (mt->o.state >> 2) { switch (mt->o.state) {
// start with the mrootanchor 0x{0,1} // start with the mrootanchor 0x{0,1}
// //
// note we make sure to include all mroots in our mroot chain! // note we make sure to include all mroots in our mroot chain!
// //
case LFSR_TSTATE_MROOTANCHOR >> 2:; case LFSR_TSTATE_MROOTANCHOR:;
// fetch the first mroot 0x{0,1} // fetch the first mroot 0x{0,1}
int err = lfsr_mdir_fetch(lfs, &mt->o.mdir, int err = lfsr_mdir_fetch(lfs, &mt->o.mdir,
-1, &LFSR_MPTR_MROOTANCHOR()); -1, &LFSR_MPTR_MROOTANCHOR());
@@ -7814,7 +7828,7 @@ static int lfsr_fs_traverse_(lfs_t *lfs, lfsr_mtraversal_t *mt,
return 0; return 0;
// traverse the mroot chain, checking for mroot/mtree/mdir // traverse the mroot chain, checking for mroot/mtree/mdir
case LFSR_TSTATE_MROOTCHAIN >> 2:; case LFSR_TSTATE_MROOTCHAIN:;
// lookup mroot, if we find one this just an mroot chain link // lookup mroot, if we find one this just an mroot chain link
lfsr_tag_t tag; lfsr_tag_t tag;
lfsr_data_t data; lfsr_data_t data;
@@ -7915,7 +7929,7 @@ static int lfsr_fs_traverse_(lfs_t *lfs, lfsr_mtraversal_t *mt,
} }
// traverse the mtree, including both inner btree nodes and mdirs // traverse the mtree, including both inner btree nodes and mdirs
case LFSR_TSTATE_MTREE >> 2:; case LFSR_TSTATE_MTREE:;
// no mtree? transition to traversing any opened mdirs // no mtree? transition to traversing any opened mdirs
if (lfsr_mtree_ismptr(&lfs->mtree)) { if (lfsr_mtree_ismptr(&lfs->mtree)) {
mt->u.o = lfs->opened; mt->u.o = lfs->opened;
@@ -7981,7 +7995,7 @@ static int lfsr_fs_traverse_(lfs_t *lfs, lfsr_mtraversal_t *mt,
} }
// scan for blocks/btrees in the current mdir // scan for blocks/btrees in the current mdir
case LFSR_TSTATE_MDIR >> 2:; case LFSR_TSTATE_MDIR:;
// not traversing all blocks? have we exceeded our mdir's weight? // not traversing all blocks? have we exceeded our mdir's weight?
// return to mtree traversal // return to mtree traversal
if (lfsr_t_ismtreeonly(mt->o.flags) if (lfsr_t_ismtreeonly(mt->o.flags)
@@ -8033,7 +8047,7 @@ static int lfsr_fs_traverse_(lfs_t *lfs, lfsr_mtraversal_t *mt,
continue; continue;
// scan for blocks/btrees in our opened file list // scan for blocks/btrees in our opened file list
case LFSR_TSTATE_OMDIR >> 2:; case LFSR_TSTATE_OMDIR:;
// not traversing all blocks? reached end of opened file list? // not traversing all blocks? reached end of opened file list?
if (lfsr_t_ismtreeonly(mt->o.flags) || !mt->u.o) { if (lfsr_t_ismtreeonly(mt->o.flags) || !mt->u.o) {
mt->o.state = LFSR_TSTATE_DONE; mt->o.state = LFSR_TSTATE_DONE;
@@ -8056,21 +8070,19 @@ static int lfsr_fs_traverse_(lfs_t *lfs, lfsr_mtraversal_t *mt,
// traverse any file btrees, including both inner btree nodes and // traverse any file btrees, including both inner btree nodes and
// block pointers // block pointers
case LFSR_TSTATE_MDIRBTREE >> 2:; case LFSR_TSTATE_MDIRBTREE:;
case LFSR_TSTATE_OMDIRBTREE >> 2:; case LFSR_TSTATE_OMDIRBTREE:;
// traverse through our file // traverse through our file
err = lfsr_bshrub_traverse(lfs, (const lfsr_file_t*)mt, &mt->bt, err = lfsr_bshrub_traverse(lfs, (const lfsr_file_t*)mt, &mt->bt,
&btinfo); &btinfo);
if (err) { if (err) {
if (err == LFS_ERR_NOENT) { if (err == LFS_ERR_NOENT) {
// end of btree? go to next file // end of btree? go to next file
if ((mt->o.state >> 2) if (mt->o.state == LFSR_TSTATE_MDIRBTREE) {
== (LFSR_TSTATE_MDIRBTREE >> 2)) {
mt->o.mdir.mid += 1; mt->o.mdir.mid += 1;
mt->o.state = LFSR_TSTATE_MDIR; mt->o.state = LFSR_TSTATE_MDIR;
continue; continue;
} else if ((mt->o.state >> 2) } else if (mt->o.state == LFSR_TSTATE_OMDIRBTREE) {
== (LFSR_TSTATE_OMDIRBTREE >> 2)) {
mt->u.o = mt->u.o->next; mt->u.o = mt->u.o->next;
mt->o.state = LFSR_TSTATE_OMDIR; mt->o.state = LFSR_TSTATE_OMDIR;
continue; continue;
@@ -8101,7 +8113,7 @@ static int lfsr_fs_traverse_(lfs_t *lfs, lfsr_mtraversal_t *mt,
LFS_UNREACHABLE(); LFS_UNREACHABLE();
} }
case LFSR_TSTATE_DONE >> 2:; case LFSR_TSTATE_DONE:;
return LFS_ERR_NOENT; return LFS_ERR_NOENT;
default:; default:;
@@ -8110,7 +8122,11 @@ static int lfsr_fs_traverse_(lfs_t *lfs, lfsr_mtraversal_t *mt,
} }
} }
// high-level traversal, handle extra features here // needed in lfsr_fs_traverse
static void lfs_alloc_markinuse(lfs_t *lfs, lfs_block_t block);
// high-level immutable traversal, handle extra features here,
// but no mutation!
static int lfsr_fs_traverse(lfs_t *lfs, lfsr_mtraversal_t *mt, static int lfsr_fs_traverse(lfs_t *lfs, lfsr_mtraversal_t *mt,
lfsr_mtinfo_t *mtinfo) { lfsr_mtinfo_t *mtinfo) {
int err = lfsr_fs_traverse_(lfs, mt, mtinfo); int err = lfsr_fs_traverse_(lfs, mt, mtinfo);
@@ -8139,9 +8155,34 @@ static int lfsr_fs_traverse(lfs_t *lfs, lfsr_mtraversal_t *mt,
} }
} }
// track in-use blocks
if (lfsr_t_islookahead(mt->o.flags)) {
if (mtinfo->tag == LFSR_TAG_MDIR) {
lfs_alloc_markinuse(lfs, mtinfo->u.mdir.rbyd.blocks[0]);
lfs_alloc_markinuse(lfs, mtinfo->u.mdir.rbyd.blocks[1]);
} else if (mtinfo->tag == LFSR_TAG_BRANCH) {
lfs_alloc_markinuse(lfs, mtinfo->u.rbyd.blocks[0]);
} else if (mtinfo->tag == LFSR_TAG_BLOCK) {
lfs_alloc_markinuse(lfs, mtinfo->u.bptr.data.u.disk.block);
} else {
LFS_UNREACHABLE();
}
}
return 0; return 0;
} }
// high-level mutating traversal, handle extra features that require
// mutation here, upper layers should call lfs_alloc_ckpoint as needed
static int lfsr_fs_traversemut(lfs_t *lfs, lfsr_mtraversal_t *mt,
lfsr_mtinfo_t *mtinfo) {
// TODO
return lfsr_fs_traverse(lfs, mt, mtinfo);
}
/// Superblock things /// /// Superblock things ///
@@ -8898,81 +8939,128 @@ int lfsr_format(lfs_t *lfs, const struct lfs_config *cfg) {
/// Block allocator /// /// Block allocator ///
// Allocations should call this when all allocated blocks are committed to // checkpoint the allocator
// the filesystem, either in the mtree or in tracked mdirs. After a //
// checkpoint, the block allocator may realloc any untracked blocks. // operations that need to alloc should call this to indicate all in-use
// blocks are either committed into the filesystem or tracked by an opened
// mdir
static void lfs_alloc_ckpoint(lfs_t *lfs) { static void lfs_alloc_ckpoint(lfs_t *lfs) {
lfs->lookahead.ckpoint = lfs->block_count; lfs->lookahead.ckpoint = lfs->block_count;
// mark all opened traversals as dirty
for (lfsr_omdir_t *o = lfs->opened; o; o = o->next) {
if (o->type == LFS_TYPE_TRAVERSAL) {
o->flags |= LFS_F_DIRTY;
}
}
} }
// Discard lookahead state, this is necessary if block_count changes // discard any lookahead state, this is necessary if block_count changes
static void lfs_alloc_discard(lfs_t *lfs) { static void lfs_alloc_discard(lfs_t *lfs) {
// go ahead shift to next available block, we probably want the next
// scan to start here
lfs->lookahead.start = (lfs->lookahead.start + lfs->lookahead.next) lfs->lookahead.start = (lfs->lookahead.start + lfs->lookahead.next)
% lfs->block_count; % lfs->block_count;
lfs->lookahead.next = 0; lfs->lookahead.next = 0;
lfs->lookahead.size = 0; lfs->lookahead.size = 0;
lfs->lookahead.ckpoint = 0;
} }
static inline void lfs_alloc_setinuse(lfs_t *lfs, lfs_block_t block) { // shift the lookahead buffer to try to allocate more blocks, may do nothing
static void lfs_alloc_shift(lfs_t *lfs) {
// do nothing if shifting would make no progress
if (lfs->lookahead.next > 0) {
// discard already shifts to the next block
lfs_alloc_discard(lfs);
}
// zero lookahead buffer
if (lfs->lookahead.size == 0) {
lfs_memset(lfs->lookahead.buffer, 0, lfs->cfg->lookahead_size);
}
// don't update size until a successful lookahead scan
}
// mark a block as in-use
static void lfs_alloc_markinuse(lfs_t *lfs, lfs_block_t block) {
// translate to lookahead-relative // translate to lookahead-relative
lfs_block_t rel = (block + lfs->block_count - lfs->lookahead.start) lfs_block_t mark = (block + lfs->block_count - lfs->lookahead.start)
% lfs->block_count; % lfs->block_count;
if (rel < lfs->lookahead.size) { if (mark < 8*lfs->cfg->lookahead_size) {
// mark as in-use // mark as in-use
lfs->lookahead.buffer[rel / 8] |= 1 << (rel % 8); lfs->lookahead.buffer[mark / 8] |= 1 << (mark % 8);
} }
} }
static int lfs_alloc(lfs_t *lfs, lfs_block_t *block, bool erase) { // needed in lfs_alloc_markfree
static lfs_sblock_t lfs_alloc_findnext(lfs_t *lfs);
// mark any not-in-use blocks as free
static void lfs_alloc_markfree(lfs_t *lfs) {
// make lookahead buffer usable
lfs->lookahead.size = lfs_min(
8*lfs->cfg->lookahead_size,
lfs->lookahead.ckpoint);
// eagerly find the next free block so shift can make progress
lfs_alloc_findnext(lfs);
}
// find next free block in lookahead buffer, if there is one
static lfs_sblock_t lfs_alloc_findnext(lfs_t *lfs) {
while (lfs->lookahead.next < lfs->lookahead.size) {
if (!(lfs->lookahead.buffer[lfs->lookahead.next / 8]
& (1 << (lfs->lookahead.next % 8)))) {
// found a free block
return (lfs->lookahead.start + lfs->lookahead.next)
% lfs->block_count;
}
lfs->lookahead.next += 1;
lfs->lookahead.ckpoint -= 1;
}
return LFS_ERR_NOSPC;
}
static lfs_sblock_t lfs_alloc(lfs_t *lfs, bool erase) {
while (true) { while (true) {
// scan our lookahead buffer for free blocks // scan our lookahead buffer for free blocks
while (lfs->lookahead.next < lfs->lookahead.size) { lfs_sblock_t block = lfs_alloc_findnext(lfs);
if (!(lfs->lookahead.buffer[lfs->lookahead.next / 8] if (block < 0 && block != LFS_ERR_NOSPC) {
& (1 << (lfs->lookahead.next % 8)))) { return block;
// found a free block }
*block = (lfs->lookahead.start + lfs->lookahead.next)
% lfs->block_count;
// we should never alloc blocks {0,1} if (block != LFS_ERR_NOSPC) {
LFS_ASSERT(*block != 0 && *block != 1); // we should never alloc blocks {0,1}
LFS_ASSERT(block != 0 && block != 1);
// erase requested? // erase requested?
if (erase) { if (erase) {
int err = lfsr_bd_erase(lfs, *block); int err = lfsr_bd_erase(lfs, block);
if (err) { if (err) {
// bad erase? try another block // bad erase? try another block
if (err == LFS_ERR_CORRUPT) { if (err == LFS_ERR_CORRUPT) {
goto next; lfs->lookahead.next += 1;
} lfs->lookahead.ckpoint -= 1;
return err; continue;
} }
return err;
} }
// eagerly find next free block to maximize how many blocks
// lfs_alloc_ckpoint makes available for scanning
while (true) {
lfs->lookahead.next += 1;
lfs->lookahead.ckpoint -= 1;
if (lfs->lookahead.next >= lfs->lookahead.size
|| !(lfs->lookahead.buffer[lfs->lookahead.next / 8]
& (1 << (lfs->lookahead.next % 8)))) {
break;
}
}
return 0;
} }
next:; // eagerly find the next free block to maximize how many blocks
// lfs_alloc_ckpoint makes available for scanning
lfs->lookahead.next += 1; lfs->lookahead.next += 1;
lfs->lookahead.ckpoint -= 1; lfs->lookahead.ckpoint -= 1;
lfs_alloc_findnext(lfs);
return block;
} }
// In order to keep our block allocator from spinning forever when our // In order to keep our block allocator from spinning forever when our
// filesystem is full, we mark points where there are no in-flight // filesystem is full, we mark points where there are no in-flight
// allocations with a checkpoint before starting a set of allocaitons. // allocations with a checkpoint before starting a set of allocations.
// //
// If we've looked at all blocks since the last checkpoint, we report // If we've looked at all blocks since the last checkpoint, we report
// the filesystem as out of storage. // the filesystem as out of storage.
@@ -8984,48 +9072,27 @@ static int lfs_alloc(lfs_t *lfs, lfs_block_t *block, bool erase) {
return LFS_ERR_NOSPC; return LFS_ERR_NOSPC;
} }
// No blocks in our lookahead buffer, we need to scan the filesystem for // no blocks in our lookahead buffer, we need to scan the filesystem
// unused blocks in the next lookahead window. // for unused blocks in the next lookahead window
// lfs_alloc_shift(lfs);
// note we limit the lookahead window to at most the amount of blocks
// checkpointed, this prevents the above math from underflowing
//
lfs->lookahead.start = (lfs->lookahead.start + lfs->lookahead.size)
% lfs->block_count;
lfs->lookahead.next = 0;
lfs->lookahead.size = lfs_min(
8*lfs->cfg->lookahead_size,
lfs->lookahead.ckpoint);
lfs_memset(lfs->lookahead.buffer, 0, lfs->cfg->lookahead_size);
// traverse the filesystem, building up knowledge of what blocks are // traverse the filesystem, building up knowledge of what blocks are
// in use in our lookahead window // in use in our lookahead window
lfsr_mtraversal_t mt = LFSR_MTRAVERSAL(0); lfsr_mtraversal_t mt = LFSR_MTRAVERSAL(LFS_T_LOOKAHEAD);
while (true) { while (true) {
lfsr_mtinfo_t mtinfo; lfsr_mtinfo_t mtinfo;
int err = lfsr_fs_traverse(lfs, &mt, &mtinfo); int err = lfsr_fs_traverse(lfs, &mt, &mtinfo);
if (err) { if (err) {
LFS_ASSERT(err != LFS_ERR_BUSY);
if (err == LFS_ERR_NOENT) { if (err == LFS_ERR_NOENT) {
break; break;
} }
return err; return err;
} }
// mark any blocks we see at in-use, including any btree/mdir blocks
if (mtinfo.tag == LFSR_TAG_MDIR) {
lfs_alloc_setinuse(lfs, mtinfo.u.mdir.rbyd.blocks[1]);
lfs_alloc_setinuse(lfs, mtinfo.u.mdir.rbyd.blocks[0]);
} else if (mtinfo.tag == LFSR_TAG_BRANCH) {
lfs_alloc_setinuse(lfs, mtinfo.u.rbyd.blocks[0]);
} else if (mtinfo.tag == LFSR_TAG_BLOCK) {
lfs_alloc_setinuse(lfs, mtinfo.u.bptr.data.u.disk.block);
} else {
LFS_UNREACHABLE();
}
} }
// mark anything not seen as free
lfs_alloc_markfree(lfs);
} }
} }
@@ -9270,20 +9337,21 @@ failed:;
/// High-level filesystem traversal /// /// High-level filesystem traversal ///
int lfsr_traversal_open(lfs_t *lfs, lfsr_traversal_t *traversal, // needed in lfsr_traversal_open
uint32_t flags) { static int lfsr_traversal_rewind_(lfs_t *lfs, lfsr_traversal_t *t);
int lfsr_traversal_open(lfs_t *lfs, lfsr_traversal_t *t, uint32_t flags) {
// already open? // already open?
LFS_ASSERT(!lfsr_opened_isopen(lfs, &traversal->mt.o)); LFS_ASSERT(!lfsr_opened_isopen(lfs, &t->mt.o));
// some flags don't make sense when only traversing the mtree // some flags don't make sense when only traversing the mtree
LFS_ASSERT(!lfsr_t_ismtreeonly(flags) || !lfsr_t_islookahead(flags)); LFS_ASSERT(!lfsr_t_ismtreeonly(flags) || !lfsr_t_islookahead(flags));
LFS_ASSERT(!lfsr_t_ismtreeonly(flags) || !lfsr_t_isckdata(flags)); LFS_ASSERT(!lfsr_t_ismtreeonly(flags) || !lfsr_t_isckdata(flags));
// these flags are returned by tinfo, not provided here // these flags are internal and shouldn't be provided by the user
LFS_ASSERT(!lfsr_t_isdirty(flags)); LFS_ASSERT(!lfsr_f_isdirty(flags));
LFS_ASSERT(!lfsr_t_iscorruptmetadata(flags));
LFS_ASSERT(!lfsr_t_iscorruptdata(flags));
// some flags mutate the filesystem // some flags mutate the filesystem
if (lfsr_t_iscompact(flags)) { if (lfsr_t_ismkconsistent(flags)
|| lfsr_t_iscompact(flags)) {
// prepare our filesystem for writing // prepare our filesystem for writing
int err = lfsr_fs_mkconsistent(lfs); int err = lfsr_fs_mkconsistent(lfs);
if (err) { if (err) {
@@ -9292,90 +9360,115 @@ int lfsr_traversal_open(lfs_t *lfs, lfsr_traversal_t *traversal,
} }
// setup traversal state // setup traversal state
traversal->mt = LFSR_MTRAVERSAL(flags); t->mt.o.type = LFS_TYPE_TRAVERSAL;
traversal->count = 0; t->mt.o.flags = flags;
// let rewind initialize/reset things
int err = lfsr_traversal_rewind_(lfs, t);
if (err) {
return err;
}
// add to tracked mdirs // add to tracked mdirs
lfsr_opened_add(lfs, &traversal->mt.o); lfsr_opened_add(lfs, &t->mt.o);
return 0; return 0;
} }
int lfsr_traversal_close(lfs_t *lfs, lfsr_traversal_t *traversal) { int lfsr_traversal_close(lfs_t *lfs, lfsr_traversal_t *t) {
LFS_ASSERT(lfsr_opened_isopen(lfs, &traversal->mt.o)); LFS_ASSERT(lfsr_opened_isopen(lfs, &t->mt.o));
// remove from tracked mdirs // remove from tracked mdirs
lfsr_opened_remove(lfs, &traversal->mt.o); lfsr_opened_remove(lfs, &t->mt.o);
return 0; return 0;
} }
int lfsr_traversal_read(lfs_t *lfs, lfsr_traversal_t *traversal, int lfsr_traversal_read(lfs_t *lfs, lfsr_traversal_t *t,
struct lfs_tinfo *tinfo) { struct lfs_tinfo *tinfo) {
LFS_ASSERT(lfsr_opened_isopen(lfs, &traversal->mt.o)); LFS_ASSERT(lfsr_opened_isopen(lfs, &t->mt.o));
// traversal dirty and excl? terminate early
if (lfsr_t_isexcl(t->mt.o.flags)
&& lfsr_f_isdirty(t->mt.o.flags)) {
return LFS_ERR_BUSY;
}
while (true) { while (true) {
// some redund blocks left over? // some redund blocks left over?
if (traversal->count > 0) { if (t->blocks[0] != -1) {
// write our traversal info // write our traversal info
tinfo->flags = traversal->mt.o.flags tinfo->btype = t->btype;
& LFS_T_DIRTY tinfo->block = t->blocks[0];
& LFS_T_CORRUPTMETADATA
& LFS_T_CORRUPTDATA;
tinfo->btype = traversal->btype;
tinfo->block = traversal->blocks[0];
traversal->blocks[0] = traversal->blocks[1]; t->blocks[0] = t->blocks[1];
traversal->count -= 1; t->blocks[1] = -1;
return 0; return 0;
} }
// find next block // find next block
lfsr_mtinfo_t mtinfo; lfsr_mtinfo_t mtinfo;
int err = lfsr_fs_traverse(lfs, &traversal->mt, &mtinfo); int err = lfsr_fs_traversemut(lfs, &t->mt, &mtinfo);
if (err) { if (err) {
// end of traversal?
if (err == LFS_ERR_NOENT) {
goto done;
}
return err; return err;
} }
// figure out type/blocks // figure out type/blocks
if (mtinfo.tag == LFSR_TAG_MDIR) { if (mtinfo.tag == LFSR_TAG_MDIR) {
traversal->btype = LFS_BTYPE_MDIR; t->btype = LFS_BTYPE_MDIR;
traversal->blocks[0] = mtinfo.u.mdir.rbyd.blocks[0]; t->blocks[0] = mtinfo.u.mdir.rbyd.blocks[0];
traversal->blocks[1] = mtinfo.u.mdir.rbyd.blocks[1]; t->blocks[1] = mtinfo.u.mdir.rbyd.blocks[1];
traversal->count = 2;
} else if (mtinfo.tag == LFSR_TAG_BRANCH) { } else if (mtinfo.tag == LFSR_TAG_BRANCH) {
traversal->btype = LFS_BTYPE_BTREE; t->btype = LFS_BTYPE_BTREE;
traversal->blocks[0] = mtinfo.u.rbyd.blocks[0]; t->blocks[0] = mtinfo.u.rbyd.blocks[0];
traversal->count = 1; t->blocks[1] = -1;
} else if (mtinfo.tag == LFSR_TAG_BLOCK) { } else if (mtinfo.tag == LFSR_TAG_BLOCK) {
traversal->btype = LFS_BTYPE_DATA; t->btype = LFS_BTYPE_DATA;
traversal->blocks[0] = mtinfo.u.bptr.data.u.disk.block; t->blocks[0] = mtinfo.u.bptr.data.u.disk.block;
traversal->count = 1; t->blocks[1] = -1;
} else { } else {
LFS_UNREACHABLE(); LFS_UNREACHABLE();
} }
} }
done:;
// was a lookahead scan successful?
if (lfsr_t_islookahead(t->mt.o.flags)
&& !lfsr_f_isdirty(t->mt.o.flags)) {
lfs_alloc_markfree(lfs);
}
// return BUSY if we're dirty, NOENT if we're clean
return (lfsr_f_isdirty(t->mt.o.flags))
? LFS_ERR_BUSY
: LFS_ERR_NOENT;
} }
int lfsr_traversal_rewind(lfs_t *lfs, lfsr_traversal_t *traversal) { static int lfsr_traversal_rewind_(lfs_t *lfs, lfsr_traversal_t *t) {
LFS_ASSERT(lfsr_opened_isopen(lfs, &traversal->mt.o));
// reset traversal state // reset traversal state
traversal->mt.o.flags &= ~LFS_T_DIRTY lfsr_fs_traverserewind(lfs, &t->mt);
& ~LFS_T_CORRUPTMETADATA t->blocks[0] = -1;
& ~LFS_T_CORRUPTDATA; t->blocks[1] = -1;
traversal->mt.o.state = LFSR_TSTATE_MROOTANCHOR;
traversal->mt.o.mdir.mid = -1; // shift the lookahead buffer if requested
traversal->mt.u.mtortoise.mptr.blocks[0] = 0; if (lfsr_t_islookahead(t->mt.o.flags)) {
traversal->mt.u.mtortoise.mptr.blocks[1] = 0; lfs_alloc_shift(lfs);
traversal->mt.u.mtortoise.step = 0; }
traversal->mt.u.mtortoise.power = 0;
traversal->count = 0;
return 0; return 0;
} }
int lfsr_traversal_rewind(lfs_t *lfs, lfsr_traversal_t *t) {
LFS_ASSERT(lfsr_opened_isopen(lfs, &t->mt.o));
return lfsr_traversal_rewind_(lfs, t);
}
@@ -11400,12 +11493,12 @@ static int lfsr_file_flush_(lfs_t *lfs, lfsr_file_t *file,
// //
// note if we relocate, we rewrite the entire block from block_start // note if we relocate, we rewrite the entire block from block_start
// using what we can find in our tree // using what we can find in our tree
int err = lfs_alloc(lfs, &bptr.data.u.disk.block, true); lfs_sblock_t block = lfs_alloc(lfs, true);
if (err) { if (block < 0) {
return err; return block;
} }
bptr.data = LFSR_DATA_DISK(bptr.data.u.disk.block, 0, 0); bptr.data = LFSR_DATA_DISK(block, 0, 0);
bptr.cksize = 0; bptr.cksize = 0;
bptr.cksum = 0; bptr.cksum = 0;
@@ -11435,7 +11528,7 @@ static int lfsr_file_flush_(lfs_t *lfs, lfsr_file_t *file,
lfs_ssize_t d_ = lfs_min( lfs_ssize_t d_ = lfs_min(
d, d,
size - (pos_ - pos)); size - (pos_ - pos));
err = lfsr_bd_prog(lfs, bptr.data.u.disk.block, int err = lfsr_bd_prog(lfs, bptr.data.u.disk.block,
bptr.cksize, bptr.cksize,
&buffer[pos_ - pos], d_, &buffer[pos_ - pos], d_,
&bptr.cksum, true); &bptr.cksum, true);
@@ -11463,7 +11556,7 @@ static int lfsr_file_flush_(lfs_t *lfs, lfsr_file_t *file,
lfsr_tag_t tag_; lfsr_tag_t tag_;
lfsr_bid_t weight_; lfsr_bid_t weight_;
lfsr_bptr_t bptr_; lfsr_bptr_t bptr_;
err = lfsr_bshrub_lookupnext(lfs, file, pos_, int err = lfsr_bshrub_lookupnext(lfs, file, pos_,
&bid_, &tag_, &weight_, &bptr_); &bid_, &tag_, &weight_, &bptr_);
if (err) { if (err) {
LFS_ASSERT(err != LFS_ERR_NOENT); LFS_ASSERT(err != LFS_ERR_NOENT);
@@ -11518,7 +11611,7 @@ static int lfsr_file_flush_(lfs_t *lfs, lfsr_file_t *file,
} }
// found a hole? fill with zeros // found a hole? fill with zeros
err = lfsr_bd_set(lfs, bptr.data.u.disk.block, bptr.cksize, int err = lfsr_bd_set(lfs, bptr.data.u.disk.block, bptr.cksize,
0, d, 0, d,
&bptr.cksum, true); &bptr.cksum, true);
if (err) { if (err) {
@@ -11543,7 +11636,7 @@ static int lfsr_file_flush_(lfs_t *lfs, lfsr_file_t *file,
bptr.cksize -= d; bptr.cksize -= d;
// finalize our write // finalize our write
err = lfsr_bd_flush(lfs, &bptr.cksum, true); int err = lfsr_bd_flush(lfs, &bptr.cksum, true);
if (err) { if (err) {
// bad prog? try another block // bad prog? try another block
if (err == LFS_ERR_CORRUPT) { if (err == LFS_ERR_CORRUPT) {
+16 -19
View File
@@ -43,6 +43,7 @@ typedef uint32_t lfs_off_t;
typedef int32_t lfs_soff_t; typedef int32_t lfs_soff_t;
typedef uint32_t lfs_block_t; typedef uint32_t lfs_block_t;
typedef int32_t lfs_sblock_t;
typedef uint32_t lfsr_rid_t; typedef uint32_t lfsr_rid_t;
typedef int32_t lfsr_srid_t; typedef int32_t lfsr_srid_t;
@@ -97,6 +98,7 @@ enum lfs_error {
LFS_ERR_UNKNOWN = -1, // Unknown error LFS_ERR_UNKNOWN = -1, // Unknown error
LFS_ERR_INVAL = -22, // Invalid parameter LFS_ERR_INVAL = -22, // Invalid parameter
LFS_ERR_NOTSUP = -95, // Operation not supported LFS_ERR_NOTSUP = -95, // Operation not supported
LFS_ERR_BUSY = -16, // Device or resource busy
LFS_ERR_IO = -5, // Error during device operation LFS_ERR_IO = -5, // Error during device operation
LFS_ERR_CORRUPT = -84, // Corrupted LFS_ERR_CORRUPT = -84, // Corrupted
LFS_ERR_NOENT = -2, // No directory entry LFS_ERR_NOENT = -2, // No directory entry
@@ -166,20 +168,19 @@ enum lfs_btype {
// Traversal flags // Traversal flags
enum lfs_traversal_flags { enum lfs_traversal_flags {
// traversal open flags // traversal open flags
LFS_T_MTREEONLY = 0x0010, // Only traverse the mtree LFS_T_MTREEONLY = 0x0008, // Only traverse the mtree
LFS_T_LOOKAHEAD = 0x0020, // Populate lookahead buffer LFS_T_EXCL = 0x0010, // Terminate if filesystem modified
LFS_T_COMPACT = 0x0040, // Compact metadata logs LFS_T_MKCONSISTENT = 0x0020, // Make the filesystem consistent
LFS_T_CKMETADATA = 0x0080, // Check metadata checksums LFS_T_LOOKAHEAD = 0x0040, // Populate lookahead buffer
LFS_T_CKDATA = 0x0100, // Check data checksums LFS_T_COMPACT = 0x0080, // Compact metadata logs
LFS_T_CKMETADATA = 0x0100, // Check metadata checksums
LFS_T_CKDATA = 0x0200, // Check data checksums
// TODO // TODO
// LFS_T_REPAIRMETADATA = 0x0100, // Repair metadata blocks // LFS_T_REPAIRMETADATA = 0x0400, // Repair metadata blocks
// LFS_T_REPAIRDATA = 0x0200, // Repair data blocks // LFS_T_REPAIRDATA = 0x0800, // Repair data blocks
// flags set in tinfo by lfsr_traversal_read // internally used flags
LFS_T_DIRTY = 0x0001, // Filesystem mutated LFS_F_DIRTY = 0x1000, // Filesystem has been modified
// TODO should we have CORRUPTMETADATA vs just error?
LFS_T_CORRUPTMETADATA = 0x0002, // Found corrupted metadata
LFS_T_CORRUPTDATA = 0x0004, // Found corrupted data
}; };
@@ -372,9 +373,6 @@ struct lfs_fsinfo {
// Traversal info structure // Traversal info structure
struct lfs_tinfo { struct lfs_tinfo {
// Traversal flags
uint8_t flags;
// Type of the block // Type of the block
uint8_t btype; uint8_t btype;
@@ -632,8 +630,7 @@ typedef struct lfsr_traversal {
// lfsr_mtraversal_t contains most of what we need // lfsr_mtraversal_t contains most of what we need
lfsr_mtraversal_t mt; lfsr_mtraversal_t mt;
uint8_t btype; uint8_t btype;
uint8_t count; lfs_sblock_t blocks[2];
lfs_block_t blocks[2];
} lfsr_traversal_t; } lfsr_traversal_t;
//typedef struct lfs_superblock { //typedef struct lfs_superblock {
@@ -1061,8 +1058,8 @@ int lfsr_traversal_close(lfs_t *lfs, lfsr_traversal_t *traversal);
// //
// Fills out the tinfo structure. // Fills out the tinfo structure.
// //
// Returns 0 on success, LFS_ERR_NOENT at the end of traversal, or a // Returns 0 on success, LFS_ERR_NOENT at the end of traversal, LFS_ERR_BUSY
// negative error code on failure. // if filesystem has been modified, or a negative error code on failure.
int lfsr_traversal_read(lfs_t *lfs, lfsr_traversal_t *traversal, int lfsr_traversal_read(lfs_t *lfs, lfsr_traversal_t *traversal,
struct lfs_tinfo *tinfo); struct lfs_tinfo *tinfo);
+1
View File
@@ -6,6 +6,7 @@ ERRS = [
('UNKNOWN', -1, "Unknown error" ), ('UNKNOWN', -1, "Unknown error" ),
('INVAL', -22, "Invalid parameter" ), ('INVAL', -22, "Invalid parameter" ),
('NOTSUP', -95, "Operation not supported" ), ('NOTSUP', -95, "Operation not supported" ),
('BUSY', -16, "Device or resource busy" ),
('IO', -5, "Error during device operation" ), ('IO', -5, "Error during device operation" ),
('CORRUPT', -84, "Corrupted" ), ('CORRUPT', -84, "Corrupted" ),
('NOENT', -2, "No directory entry" ), ('NOENT', -2, "No directory entry" ),
+9 -12
View File
@@ -27,11 +27,10 @@ code = '''
lfs_alloc_ckpoint(&lfs); lfs_alloc_ckpoint(&lfs);
lfs_size_t alloced = 0; lfs_size_t alloced = 0;
while (true) { while (true) {
lfs_block_t block; lfs_sblock_t block = lfs_alloc(&lfs, ERASE);
int err = lfs_alloc(&lfs, &block, ERASE); assert(block >= 0 || block == LFS_ERR_NOSPC);
assert(!err || err == LFS_ERR_NOSPC);
if (err == LFS_ERR_NOSPC) { if (block == LFS_ERR_NOSPC) {
break; break;
} }
alloced += 1; alloced += 1;
@@ -61,11 +60,10 @@ code = '''
lfs_alloc_ckpoint(&lfs); lfs_alloc_ckpoint(&lfs);
lfs_size_t alloced = 0; lfs_size_t alloced = 0;
while (true) { while (true) {
lfs_block_t block; lfs_sblock_t block = lfs_alloc(&lfs, ERASE);
int err = lfs_alloc(&lfs, &block, ERASE); assert(block >= 0 || block == LFS_ERR_NOSPC);
assert(!err || err == LFS_ERR_NOSPC);
if (err == LFS_ERR_NOSPC) { if (block == LFS_ERR_NOSPC) {
break; break;
} }
alloced += 1; alloced += 1;
@@ -83,11 +81,10 @@ code = '''
lfs_alloc_ckpoint(&lfs); lfs_alloc_ckpoint(&lfs);
alloced = 0; alloced = 0;
while (true) { while (true) {
lfs_block_t block; lfs_sblock_t block = lfs_alloc(&lfs, ERASE);
int err = lfs_alloc(&lfs, &block, ERASE); assert(block >= 0 || block == LFS_ERR_NOSPC);
assert(!err || err == LFS_ERR_NOSPC);
if (err == LFS_ERR_NOSPC) { if (block == LFS_ERR_NOSPC) {
break; break;
} }
alloced += 1; alloced += 1;
+2101 -45
View File
File diff suppressed because it is too large Load Diff