Reverted limiting file->leaf to reads + erased-state caching

Still on the fence about this, but in hindsight the code/stack
difference is not _that_ much:

           code          stack          ctx
  before: 36460           2280          636
  after:  37092 (+1.7%)   2304 (+1.1%)  636 (+0.0%)

Especially with the potential to significantly speed up linear file
writes/rewrites, which are usually the most common file operation. You
ever just, you know, write a whole file at once?

Note we can still add the previous behavior as an opt-in write strategy
to save code/stack when preferred over linear write/rewrite speed.

This is actually the main reason I think we should prefer
lazy-crystallization by default. Of the theoretical/future write
strategies, lazy-crystallization was the only one trading performance
for code/stack and not vice versa (global-alignment, linear-only,
fully-fragmented, etc).

If we default to a small, but less performant filesystem, it risks users
thinking littlefs is slow when they just haven't turned on the right
flags.

That being said there's a balance here. Users will probably judge
littlefs based on its default code size for the same reason.

---

Note this includes the generalized lfsr_file_crystallize_ API, which
adds a bit of code:

                     code          stack          ctx
  before gen-cryst: 37084           2304          636
  after gen-cryst:  37092 (+0.0%)   2304 (+0.0%)  636 (+0.0%)
This commit is contained in:
Christopher Haster
2025-05-23 12:13:55 -05:00
parent 9d6a94aa07
commit 1cce0dab5c
3 changed files with 230 additions and 56 deletions
+217 -47
View File
@@ -7003,6 +7003,14 @@ static inline bool lfsr_o_isbshrub(uint32_t flags) {
|| lfsr_o_type(flags) == LFS_type_TRAVERSAL; || lfsr_o_type(flags) == LFS_type_TRAVERSAL;
} }
static inline bool lfsr_o_isuncryst(uint32_t flags) {
return flags & LFS_o_UNCRYST;
}
static inline bool lfsr_o_isungraft(uint32_t flags) {
return flags & LFS_o_UNGRAFT;
}
static inline bool lfsr_o_isunflush(uint32_t flags) { static inline bool lfsr_o_isunflush(uint32_t flags) {
return flags & LFS_o_UNFLUSH; return flags & LFS_o_UNFLUSH;
} }
@@ -9711,11 +9719,21 @@ static int lfsr_mtree_traverse_(lfs_t *lfs, lfsr_traversal_t *t,
continue; continue;
} }
// start traversing the file // transition to traversing the file
const lfsr_file_t *file = (const lfsr_file_t*)t->ot; const lfsr_file_t *file = (const lfsr_file_t*)t->ot;
t->b.shrub = file->b.shrub; t->b.shrub = file->b.shrub;
lfsr_btraversal_init(&t->u.bt); lfsr_btraversal_init(&t->u.bt);
lfsr_t_settstate(&t->b.o.flags, LFSR_TSTATE_OBTREE); lfsr_t_settstate(&t->b.o.flags, LFSR_TSTATE_OBTREE);
// wait, do we have an ungrafted leaf?
if (lfsr_o_isungraft(file->b.o.flags)) {
if (tag_) {
*tag_ = LFSR_TAG_BLOCK;
}
*bptr = file->leaf.bptr;
return 0;
}
continue; continue;
// traverse any bshrubs/btrees we see, this includes the mtree // traverse any bshrubs/btrees we see, this includes the mtree
@@ -11237,11 +11255,13 @@ int lfsr_removeattr(lfs_t *lfs, const char *path, uint8_t type) {
// file helpers // file helpers
static inline void lfsr_file_discardcache(lfsr_file_t *file) { static inline void lfsr_file_discardcache(lfsr_file_t *file) {
file->b.o.flags &= ~LFS_o_UNFLUSH;
file->cache.pos = 0; file->cache.pos = 0;
file->cache.size = 0; file->cache.size = 0;
} }
static inline void lfsr_file_discardleaf(lfsr_file_t *file) { static inline void lfsr_file_discardleaf(lfsr_file_t *file) {
file->b.o.flags &= ~LFS_o_UNCRYST & ~LFS_o_UNGRAFT;
file->leaf.pos = 0; file->leaf.pos = 0;
file->leaf.weight = 0; file->leaf.weight = 0;
lfsr_bptr_discard(&file->leaf.bptr); lfsr_bptr_discard(&file->leaf.bptr);
@@ -11258,10 +11278,16 @@ static inline lfs_size_t lfsr_file_cachesize(lfs_t *lfs,
: lfs->cfg->file_cache_size; : lfs->cfg->file_cache_size;
} }
static inline lfs_off_t lfsr_file_weight(const lfsr_file_t *file) {
return lfs_max(
file->leaf.pos + file->leaf.weight,
file->b.shrub.weight);
}
static inline lfs_off_t lfsr_file_size_(const lfsr_file_t *file) { static inline lfs_off_t lfsr_file_size_(const lfsr_file_t *file) {
return lfs_max( return lfs_max(
file->cache.pos + file->cache.size, file->cache.pos + file->cache.size,
file->b.shrub.weight); lfsr_file_weight(file));
} }
@@ -11275,8 +11301,6 @@ static int lfsr_file_fetch(lfs_t *lfs, lfsr_file_t *file, bool trunc) {
lfsr_file_discardcache(file); lfsr_file_discardcache(file);
// discard the current leaf // discard the current leaf
lfsr_file_discardleaf(file); lfsr_file_discardleaf(file);
// mark as flushed
file->b.o.flags &= ~LFS_o_UNFLUSH;
// don't bother reading disk if we're not created or truncating // don't bother reading disk if we're not created or truncating
if (!lfsr_o_isuncreat(file->b.o.flags) && !trunc) { if (!lfsr_o_isuncreat(file->b.o.flags) && !trunc) {
@@ -11647,9 +11671,57 @@ static int lfsr_file_lookup(lfs_t *lfs, const lfsr_file_t *file,
return 0; return 0;
} }
// in between bshrub/btree and ungrafted leaf? pretend there's a
// hole here
if (bid >= file->b.shrub.weight && bid < file->leaf.pos) {
if (bid_) {
*bid_ = file->leaf.pos-1;
}
if (weight_) {
*weight_ = file->leaf.pos - file->b.shrub.weight;
}
lfsr_bptr_discard(bptr_);
return 0;
}
// lookup on disk // lookup on disk
return lfsr_file_lookup_(lfs, file, bid, lfsr_bid_t bid__;
bid_, weight_, bptr_); lfsr_bid_t weight__;
int err = lfsr_file_lookup_(lfs, file, bid,
&bid__, &weight__, bptr_);
if (err) {
return err;
}
// hits our leaf? our leaf takes priority
//
// slice left leaf?
if (bid > file->leaf.pos + file->leaf.weight
&& bid__-(weight__-1) < file->leaf.pos + file->leaf.weight) {
lfs_soff_t d = (file->leaf.pos + file->leaf.weight)
- (bid__-(weight__-1));
weight__ -= d;
bptr_->data = LFSR_DATA_SLICE(bptr_->data,
lfs_min(d, lfsr_bptr_size(bptr_)),
-1);
// slice right leaf?
} else if (bid < file->leaf.pos
&& bid__+1 > file->leaf.pos) {
lfs_soff_t d = bid__+1 - file->leaf.pos;
bid__ -= d;
weight__ -= d;
bptr_->data = LFSR_DATA_SLICE(bptr_->data,
-1,
lfsr_bptr_size(bptr_) - lfs_min(d, lfsr_bptr_size(bptr_)));
}
if (bid_) {
*bid_ = bid__;
}
if (weight_) {
*weight_ = weight__;
}
return 0;
} }
static lfs_ssize_t lfsr_file_read_(lfs_t *lfs, lfsr_file_t *file, static lfs_ssize_t lfsr_file_read_(lfs_t *lfs, lfsr_file_t *file,
@@ -11657,6 +11729,15 @@ static lfs_ssize_t lfsr_file_read_(lfs_t *lfs, lfsr_file_t *file,
// need to fetch a new leaf? // need to fetch a new leaf?
if (!(pos >= file->leaf.pos if (!(pos >= file->leaf.pos
&& pos < file->leaf.pos + file->leaf.weight)) { && pos < file->leaf.pos + file->leaf.weight)) {
// leaf in use? we need to flush it
if (lfsr_o_isungraft(file->b.o.flags)
|| lfsr_o_isuncryst(file->b.o.flags)) {
int err = lfsr_file_flush(lfs, file);
if (err) {
return err;
}
}
// fetch a leaf // fetch a leaf
lfsr_bid_t bid; lfsr_bid_t bid;
lfsr_bid_t weight; lfsr_bid_t weight;
@@ -11757,7 +11838,7 @@ lfs_ssize_t lfsr_file_read(lfs_t *lfs, lfsr_file_t *file,
} }
// any data in our btree? // any data in our btree?
if (pos_ < file->b.shrub.weight) { if (pos_ < lfsr_file_weight(file)) {
// bypass cache? // bypass cache?
if ((lfs_size_t)d >= lfsr_file_cachesize(lfs, file)) { if ((lfs_size_t)d >= lfsr_file_cachesize(lfs, file)) {
lfs_ssize_t d_ = lfsr_file_read_(lfs, file, lfs_ssize_t d_ = lfsr_file_read_(lfs, file,
@@ -12089,9 +12170,6 @@ static int lfsr_file_graft(lfs_t *lfs, lfsr_file_t *file,
return 0; return 0;
} }
// this LFS_NOINLINE is to force lfsr_file_crystallize_ off the stack
// hot-path
LFS_NOINLINE
static int lfsr_file_crystallize_(lfs_t *lfs, lfsr_file_t *file, static int lfsr_file_crystallize_(lfs_t *lfs, lfsr_file_t *file,
lfs_off_t block_pos, lfs_soff_t crystal_min, lfs_soff_t crystal_max, lfs_off_t block_pos, lfs_soff_t crystal_min, lfs_soff_t crystal_max,
lfs_off_t pos, const uint8_t *buffer, lfs_size_t size) { lfs_off_t pos, const uint8_t *buffer, lfs_size_t size) {
@@ -12104,7 +12182,7 @@ static int lfsr_file_crystallize_(lfs_t *lfs, lfsr_file_t *file,
lfs->cfg->block_size), lfs->cfg->block_size),
lfs_max( lfs_max(
pos + size, pos + size,
file->b.shrub.weight)); lfsr_file_weight(file)));
// do we need to allocate a new block? // do we need to allocate a new block?
if (!lfsr_bptr_isbptr(&file->leaf.bptr) if (!lfsr_bptr_isbptr(&file->leaf.bptr)
@@ -12176,7 +12254,7 @@ static int lfsr_file_crystallize_(lfs_t *lfs, lfsr_file_t *file,
} }
// any data on disk? // any data on disk?
if (pos_ < file->b.shrub.weight) { if (pos_ < lfsr_file_weight(file)) {
lfsr_bid_t bid__; lfsr_bid_t bid__;
lfsr_bid_t weight__; lfsr_bid_t weight__;
lfsr_bptr_t bptr__; lfsr_bptr_t bptr__;
@@ -12296,6 +12374,9 @@ static int lfsr_file_crystallize_(lfs_t *lfs, lfsr_file_t *file,
// mark as erased // mark as erased
(pos_ - block_pos) | LFSR_BPTR_ISERASED, (pos_ - block_pos) | LFSR_BPTR_ISERASED,
cksum_); cksum_);
// mark as uncrystallized and ungrafted
file->b.o.flags |= LFS_o_UNCRYST | LFS_o_UNGRAFT;
return 0; return 0;
relocate:; relocate:;
@@ -12316,23 +12397,42 @@ static int lfsr_file_crystallize_(lfs_t *lfs, lfsr_file_t *file,
} }
} }
static int lfsr_file_crystallize(lfs_t *lfs, lfsr_file_t *file, static int lfsr_file_crystallize(lfs_t *lfs, lfsr_file_t *file) {
lfs_off_t block_pos, lfs_soff_t crystal_size, // finish crystallizing
lfs_off_t pos, const uint8_t *buffer, lfs_size_t size) { if (lfsr_o_isuncryst(file->b.o.flags)) {
// this is split into two functions to minimize stack usage // uncrystallized files must be unsynced
LFS_ASSERT(lfsr_o_isunsync(file->b.o.flags));
// only blocks can be uncrystallized
LFS_ASSERT(lfsr_bptr_isbptr(&file->leaf.bptr));
LFS_ASSERT(lfsr_bptr_iserased(&file->leaf.bptr));
// crystallize // finish crystallizing the block
int err = lfsr_file_crystallize_(lfs, file, int err = lfsr_file_crystallize_(lfs, file,
block_pos, crystal_size, -1, file->leaf.pos - lfsr_bptr_off(&file->leaf.bptr),
pos, buffer, size); -1, -1,
file->cache.pos, file->cache.buffer, file->cache.size);
if (err) { if (err) {
return err; return err;
} }
// and graft it into our tree // mark as crystallized
return lfsr_file_graft(lfs, file, file->b.o.flags &= ~LFS_o_UNCRYST;
}
// and graft into tree
if (lfsr_o_isungraft(file->b.o.flags)) {
int err = lfsr_file_graft(lfs, file,
file->leaf.pos, file->leaf.weight, 0, file->leaf.pos, file->leaf.weight, 0,
&file->leaf.bptr.data, -1); &file->leaf.bptr.data, -1);
if (err) {
return err;
}
// mark as grafted
file->b.o.flags &= ~LFS_o_UNGRAFT;
}
return 0;
} }
static int lfsr_file_flush_(lfs_t *lfs, lfsr_file_t *file, static int lfsr_file_flush_(lfs_t *lfs, lfsr_file_t *file,
@@ -12364,8 +12464,10 @@ static int lfsr_file_flush_(lfs_t *lfs, lfsr_file_t *file,
&& pos - block_end < lfs->cfg->crystal_thresh && pos - block_end < lfs->cfg->crystal_thresh
// need to bail if we can't meet prog alignment // need to bail if we can't meet prog alignment
&& (pos + size) - block_end >= lfs->cfg->prog_size) { && (pos + size) - block_end >= lfs->cfg->prog_size) {
int err = lfsr_file_crystallize(lfs, file, int err = lfsr_file_crystallize_(lfs, file,
file->leaf.pos - lfsr_bptr_off(&file->leaf.bptr), file->leaf.pos - lfsr_bptr_off(&file->leaf.bptr),
(pos + size)
- (file->leaf.pos - lfsr_bptr_off(&file->leaf.bptr)),
(pos + size) (pos + size)
- (file->leaf.pos - lfsr_bptr_off(&file->leaf.bptr)), - (file->leaf.pos - lfsr_bptr_off(&file->leaf.bptr)),
pos, buffer, size); pos, buffer, size);
@@ -12408,7 +12510,7 @@ static int lfsr_file_flush_(lfs_t *lfs, lfsr_file_t *file,
0); 0);
if (crystal_end - crystal_start < lfs->cfg->crystal_thresh if (crystal_end - crystal_start < lfs->cfg->crystal_thresh
&& crystal_start > 0 && crystal_start > 0
&& poke < file->b.shrub.weight && poke < lfsr_file_weight(file)
// don't bother looking up left after the first block // don't bother looking up left after the first block
&& !aligned) { && !aligned) {
lfsr_bid_t bid; lfsr_bid_t bid;
@@ -12441,9 +12543,9 @@ static int lfsr_file_flush_(lfs_t *lfs, lfsr_file_t *file,
// find right crystal neighbor // find right crystal neighbor
poke = lfs_min( poke = lfs_min(
crystal_start + (lfs->cfg->crystal_thresh-1), crystal_start + (lfs->cfg->crystal_thresh-1),
file->b.shrub.weight-1); lfsr_file_weight(file)-1);
if (crystal_end - crystal_start < lfs->cfg->crystal_thresh if (crystal_end - crystal_start < lfs->cfg->crystal_thresh
&& crystal_end < file->b.shrub.weight) { && crystal_end < lfsr_file_weight(file)) {
lfsr_bid_t bid; lfsr_bid_t bid;
lfsr_bid_t weight; lfsr_bid_t weight;
lfsr_bptr_t bptr; lfsr_bptr_t bptr;
@@ -12483,11 +12585,18 @@ static int lfsr_file_flush_(lfs_t *lfs, lfsr_file_t *file,
// exceeded crystallization threshold? we need to allocate a // exceeded crystallization threshold? we need to allocate a
// new block // new block
// if we're mid-crystallization, finish crystallizing the block
// and graft it into our bshrub/btree
int err = lfsr_file_crystallize(lfs, file);
if (err) {
return err;
}
// mark as unerased so lfsr_file_crystallize doesn't try to // mark as unerased so lfsr_file_crystallize doesn't try to
// resume crystallizing this block // resume crystallizing this block
lfsr_bptr_claim(&file->leaf.bptr); lfsr_bptr_claim(&file->leaf.bptr);
// before we can crystallize, we need to figure out the best // before we can crystallize we need to figure out the best
// block alignment, we use the entry immediately to the left of // block alignment, we use the entry immediately to the left of
// our crystal for this // our crystal for this
if (crystal_start > 0 if (crystal_start > 0
@@ -12524,9 +12633,9 @@ static int lfsr_file_flush_(lfs_t *lfs, lfsr_file_t *file,
// start crystallizing! // start crystallizing!
// //
// lfsr_file_crystallize handles block allocation/relocation // lfsr_file_crystallize_ handles block allocation/relocation
int err = lfsr_file_crystallize(lfs, file, err = lfsr_file_crystallize_(lfs, file, crystal_start,
crystal_start, crystal_end - crystal_start, crystal_end - crystal_start, crystal_end - crystal_start,
pos, buffer, size); pos, buffer, size);
if (err) { if (err) {
return err; return err;
@@ -12547,6 +12656,24 @@ static int lfsr_file_flush_(lfs_t *lfs, lfsr_file_t *file,
fragment:; fragment:;
// iteratively write fragments (inlined leaves) // iteratively write fragments (inlined leaves)
while (size > 0) { while (size > 0) {
// before we write fragments, we need to make sure our crystal
// is grafted into the tree
//
// but note we're still tracking its erased state for future
// writes!
if (lfsr_o_isungraft(file->b.o.flags)) {
// graft our crystal
int err = lfsr_file_graft(lfs, file,
file->leaf.pos, file->leaf.weight, 0,
&file->leaf.bptr.data, -1);
if (err) {
return err;
}
// mark as grafted
file->b.o.flags &= ~LFS_o_UNGRAFT;
}
// do we need to discard our leaf? we need to discard fragments // do we need to discard our leaf? we need to discard fragments
// in case the underlying rbyd compacts, and we need to discard // in case the underlying rbyd compacts, and we need to discard
// overwritten blocks // overwritten blocks
@@ -12555,8 +12682,8 @@ fragment:;
// single graft may be split up into multiple commits // single graft may be split up into multiple commits
// //
// unfortunately we don't know where our fragment will end up // unfortunately we don't know where our fragment will end up
// until after the commit, so we can't track written fragments // until after the commit, so we can't track it in our leaf
// in our leaf easily // quite yet
if (!lfsr_bptr_isbptr(&file->leaf.bptr) if (!lfsr_bptr_isbptr(&file->leaf.bptr)
|| (pos < file->leaf.pos + lfsr_bptr_size(&file->leaf.bptr) || (pos < file->leaf.pos + lfsr_bptr_size(&file->leaf.bptr)
&& pos + size > file->leaf.pos)) { && pos + size > file->leaf.pos)) {
@@ -12576,7 +12703,7 @@ fragment:;
// is already full // is already full
if (fragment_end - fragment_start < lfs->cfg->fragment_size if (fragment_end - fragment_start < lfs->cfg->fragment_size
&& fragment_start > 0 && fragment_start > 0
&& fragment_start <= file->b.shrub.weight && fragment_start <= lfsr_file_weight(file)
// don't bother to lookup left after first fragment // don't bother to lookup left after first fragment
&& !aligned) { && !aligned) {
lfsr_bid_t bid; lfsr_bid_t bid;
@@ -12625,7 +12752,7 @@ fragment:;
// //
// note this may the same as our left sibling // note this may the same as our left sibling
if (fragment_end - fragment_start < lfs->cfg->fragment_size if (fragment_end - fragment_start < lfs->cfg->fragment_size
&& fragment_end < file->b.shrub.weight) { && fragment_end < lfsr_file_weight(file)) {
lfsr_bid_t bid; lfsr_bid_t bid;
lfsr_bid_t weight; lfsr_bid_t weight;
lfsr_bptr_t bptr; lfsr_bptr_t bptr;
@@ -12846,8 +12973,11 @@ int lfsr_file_flush(lfs_t *lfs, lfsr_file_t *file) {
// can't write to readonly files // can't write to readonly files
LFS_ASSERT(!lfsr_o_isrdonly(file->b.o.flags)); LFS_ASSERT(!lfsr_o_isrdonly(file->b.o.flags));
// do nothing if our file is already flushed // do nothing if our file is already flushed, crystallized,
if (!lfsr_o_isunflush(file->b.o.flags)) { // and grafted
if (!lfsr_o_isunflush(file->b.o.flags)
&& !lfsr_o_isuncryst(file->b.o.flags)
&& !lfsr_o_isungraft(file->b.o.flags)) {
return 0; return 0;
} }
// unflushed files must be unsynced // unflushed files must be unsynced
@@ -12860,6 +12990,7 @@ int lfsr_file_flush(lfs_t *lfs, lfsr_file_t *file) {
int err; int err;
// flush our cache // flush our cache
if (lfsr_o_isunflush(file->b.o.flags)) {
err = lfsr_file_flush_(lfs, file, err = lfsr_file_flush_(lfs, file,
file->cache.pos, file->cache.buffer, file->cache.size); file->cache.pos, file->cache.buffer, file->cache.size);
if (err) { if (err) {
@@ -12868,6 +12999,14 @@ int lfsr_file_flush(lfs_t *lfs, lfsr_file_t *file) {
// mark as flushed // mark as flushed
file->b.o.flags &= ~LFS_o_UNFLUSH; file->b.o.flags &= ~LFS_o_UNFLUSH;
}
// and crystallize/graft our leaf
err = lfsr_file_crystallize(lfs, file);
if (err) {
goto failed;
}
return 0; return 0;
failed:; failed:;
@@ -13047,10 +13186,15 @@ int lfsr_file_sync(lfs_t *lfs, lfsr_file_t *file) {
// //
// though don't flush quite yet if our file is small and can be // though don't flush quite yet if our file is small and can be
// combined with sync in a single commit // combined with sync in a single commit
if (!(file->cache.size == lfsr_file_size_(file) if (file->cache.size == lfsr_file_size_(file)
&& file->cache.size <= lfs->cfg->inline_size && file->cache.size <= lfs->cfg->inline_size
&& file->cache.size <= lfs->cfg->fragment_size && file->cache.size <= lfs->cfg->fragment_size
&& file->cache.size < lfs->cfg->crystal_thresh)) { && file->cache.size < lfs->cfg->crystal_thresh) {
if (lfsr_o_isungraft(file->b.o.flags)) {
file->b.o.flags |= LFS_o_UNFLUSH;
}
lfsr_file_discardleaf(file);
} else {
err = lfsr_file_flush(lfs, file); err = lfsr_file_flush(lfs, file);
if (err) { if (err) {
goto failed; goto failed;
@@ -13084,7 +13228,10 @@ int lfsr_file_sync(lfs_t *lfs, lfsr_file_t *file) {
// update synced files // update synced files
} else { } else {
// update flags // update flags
file_->b.o.flags &= ~LFS_o_UNSYNC & ~LFS_o_UNFLUSH; file_->b.o.flags &= ~LFS_o_UNSYNC
& ~LFS_o_UNFLUSH
& ~LFS_o_UNCRYST
& ~LFS_o_UNGRAFT;
// update shrubs // update shrubs
file_->b.shrub = file->b.shrub; file_->b.shrub = file->b.shrub;
// update leaves // update leaves
@@ -13142,6 +13289,8 @@ int lfsr_file_sync(lfs_t *lfs, lfsr_file_t *file) {
// mark as synced // mark as synced
file->b.o.flags &= ~LFS_o_UNSYNC file->b.o.flags &= ~LFS_o_UNSYNC
& ~LFS_o_UNFLUSH & ~LFS_o_UNFLUSH
& ~LFS_o_UNCRYST
& ~LFS_o_UNGRAFT
& ~LFS_o_UNCREAT & ~LFS_o_UNCREAT
& ~LFS_O_DESYNC; & ~LFS_O_DESYNC;
return 0; return 0;
@@ -13265,18 +13414,22 @@ int lfsr_file_truncate(lfs_t *lfs, lfsr_file_t *file, lfs_off_t size_) {
// mark as unsynced in case we fail // mark as unsynced in case we fail
file->b.o.flags |= LFS_o_UNSYNC; file->b.o.flags |= LFS_o_UNSYNC;
// if our leaf is a fragment or will be fragmented, we need to go // if our leaf is a fragment or will be fragmented, we need
// ahead and discard it, otherwise we risk out-of-date fragments as // to go ahead and graft + discard it, otherwise we risk out-of-date
// btree commits move things around // fragments as btree commits move things around
// //
// note that fruncate is commonly used when logging, where we // note this is mostly to match the behavior of fruncate, where we
// _really_ don't want to discard erased-state, otherwise we'd just // _really_ don't want to discard erased-state
// discard this unconditionally
if (!lfsr_bptr_isbptr(&file->leaf.bptr) if (!lfsr_bptr_isbptr(&file->leaf.bptr)
|| size_ - lfs_min(file->leaf.pos, size_) || size_ - lfs_min(file->leaf.pos, size_)
< lfs_min( < lfs_min(
lfs->cfg->fragment_thresh, lfs->cfg->fragment_thresh,
lfs->cfg->crystal_thresh)) { lfs->cfg->crystal_thresh)) {
err = lfsr_file_crystallize(lfs, file);
if (err) {
goto failed;
}
lfsr_file_discardleaf(file); lfsr_file_discardleaf(file);
} }
@@ -13292,6 +13445,7 @@ int lfsr_file_truncate(lfs_t *lfs, lfsr_file_t *file, lfs_off_t size_) {
// truncate our leaf // truncate our leaf
if (size_ < file->leaf.pos + lfsr_bptr_size(&file->leaf.bptr)) { if (size_ < file->leaf.pos + lfsr_bptr_size(&file->leaf.bptr)) {
lfsr_bptr_claim(&file->leaf.bptr); lfsr_bptr_claim(&file->leaf.bptr);
file->b.o.flags &= ~LFS_o_UNCRYST;
} }
file->leaf.bptr.data = LFSR_DATA_TRUNCATE( file->leaf.bptr.data = LFSR_DATA_TRUNCATE(
file->leaf.bptr.data, file->leaf.bptr.data,
@@ -13340,9 +13494,9 @@ int lfsr_file_fruncate(lfs_t *lfs, lfsr_file_t *file, lfs_off_t size_) {
// mark as unsynced in case we fail // mark as unsynced in case we fail
file->b.o.flags |= LFS_o_UNSYNC; file->b.o.flags |= LFS_o_UNSYNC;
// if our leaf is a fragment or will be fragmented, we need to go // if our leaf is a fragment or will be fragmented, we need
// ahead and discard it, otherwise we risk out-of-date fragments as // to go ahead and graft + discard it, otherwise we risk out-of-date
// btree commits move things around // fragments as btree commits move things around
// //
// note that fruncate is commonly used when logging, where we // note that fruncate is commonly used when logging, where we
// _really_ don't want to discard erased-state, otherwise we'd just // _really_ don't want to discard erased-state, otherwise we'd just
@@ -13356,6 +13510,11 @@ int lfsr_file_fruncate(lfs_t *lfs, lfsr_file_t *file, lfs_off_t size_) {
< lfs_min( < lfs_min(
lfs->cfg->fragment_thresh, lfs->cfg->fragment_thresh,
lfs->cfg->crystal_thresh)) { lfs->cfg->crystal_thresh)) {
err = lfsr_file_crystallize(lfs, file);
if (err) {
goto failed;
}
lfsr_file_discardleaf(file); lfsr_file_discardleaf(file);
} }
@@ -13373,6 +13532,7 @@ int lfsr_file_fruncate(lfs_t *lfs, lfsr_file_t *file, lfs_off_t size_) {
> (lfs_soff_t)(file->leaf.pos > (lfs_soff_t)(file->leaf.pos
+ lfsr_bptr_size(&file->leaf.bptr))) { + lfsr_bptr_size(&file->leaf.bptr))) {
lfsr_bptr_claim(&file->leaf.bptr); lfsr_bptr_claim(&file->leaf.bptr);
file->b.o.flags &= ~LFS_o_UNCRYST;
} }
file->leaf.bptr.data = LFSR_DATA_FRUNCATE( file->leaf.bptr.data = LFSR_DATA_FRUNCATE(
file->leaf.bptr.data, file->leaf.bptr.data,
@@ -13464,6 +13624,16 @@ static int lfsr_file_traverse(lfs_t *lfs, const lfsr_file_t *file,
static int lfsr_file_ck(lfs_t *lfs, const lfsr_file_t *file, static int lfsr_file_ck(lfs_t *lfs, const lfsr_file_t *file,
uint32_t flags) { uint32_t flags) {
// validate ungrafted data block?
if (lfsr_t_isckdata(flags)
&& lfsr_o_isungraft(file->b.o.flags)) {
LFS_ASSERT(lfsr_bptr_isbptr(&file->leaf.bptr));
int err = lfsr_bptr_ck(lfs, &file->leaf.bptr);
if (err) {
return err;
}
}
// traverse the file's bshrub/btree // traverse the file's bshrub/btree
lfsr_btraversal_t bt; lfsr_btraversal_t bt;
lfsr_btraversal_init(&bt); lfsr_btraversal_init(&bt);
+2
View File
@@ -144,6 +144,8 @@ enum lfs_type {
// internally used flags, don't use these // internally used flags, don't use these
#define LFS_o_TYPE 0xf0000000 // The file's type #define LFS_o_TYPE 0xf0000000 // The file's type
#define LFS_o_UNCRYST 0x00400000 // File's leaf not fully crystallized
#define LFS_o_UNGRAFT 0x00800000 // File's leaf does not match bshrub/btree
#define LFS_o_UNFLUSH 0x01000000 // File's data does not match disk #define LFS_o_UNFLUSH 0x01000000 // File's data does not match disk
#define LFS_o_UNSYNC 0x02000000 // File's metadata does not match disk #define LFS_o_UNSYNC 0x02000000 // File's metadata does not match disk
#define LFS_o_UNCREAT 0x04000000 // File does not exist yet #define LFS_o_UNCREAT 0x04000000 // File does not exist yet
+2
View File
@@ -44,6 +44,8 @@ FLAGS = [
('^', 'ORPHAN', 0x50000000, "Type = orphan" ), ('^', 'ORPHAN', 0x50000000, "Type = orphan" ),
('^', 'TRAVERSAL', 0x60000000, "Type = traversal" ), ('^', 'TRAVERSAL', 0x60000000, "Type = traversal" ),
('^', 'UNKNOWN', 0x70000000, "Type = unknown" ), ('^', 'UNKNOWN', 0x70000000, "Type = unknown" ),
('o', 'UNCRYST', 0x00400000, "File's leaf not fully crystallized" ),
('o', 'UNGRAFT', 0x00800000, "File's leaf does not match bshrub/btree" ),
('o', 'UNFLUSH', 0x01000000, "File's data does not match disk" ), ('o', 'UNFLUSH', 0x01000000, "File's data does not match disk" ),
('o', 'UNSYNC', 0x02000000, "File's metadata does not match disk" ), ('o', 'UNSYNC', 0x02000000, "File's metadata does not match disk" ),
('o', 'UNCREAT', 0x04000000, "File does not exist yet" ), ('o', 'UNCREAT', 0x04000000, "File does not exist yet" ),