From fbb6a27b05920bc94f52a9ae15f929dcc4190fdb Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Thu, 12 Oct 2023 22:02:35 -0500 Subject: [PATCH] Changed crystallization strategy in btrees to rely on coalescing This is a pretty big rewrite, but is necessary to avoid "dagging". "Dagging" (I just made this term up) is when you transform a pure tree into a directed acyclic graph (DAG). Normally DAGs are perfectly fine in a copy-on-write system, but in littlefs's cases, it creates havoc for future block allocator plans, and it's interaction with parity blocks raises some uncomfortable questions. How does dagging happen? Consider an innocent little btree with a single block: .-----. |btree| | | '-----' | v .-----. |abcde| | | '-----' Say we wanted to write a small amount of data in the middle of our block. Since the data is so small, the previous scheme would simply inline the data, carving the left and right sibling (in the case the same block) to make space: .-----. |btree| | | '-----' .' v '. | c' | '. .' v v .-----. |ab de| | | '-----' Oh no! A DAG! With the potential for multiple pointers to reference the same block in our btree, some invariants break down: - Blocks no longer have a single reference - If you remove a reference you can no longer assume the block is free - Knowing when a block is free requires scanning the whole btree - This split operation effectively creates two blocks, does that mean we need to rewrite parity blocks? --- To avoid this whole situation, this commit adopts a new crystallization algorithm. Instead of allowing crystallization data to be arbitrarily fragmented, we eagerly coalesce any data under our crystallization threshold, and if we can't coalesce, we compact everything into a block. Much like a Knuth heap, simply checking both siblings to coalesce has the effect that any data will always coalesce up to the maximum size where possible. And when checking for siblings, we can easily find the block alignment. This also has the effect of always rewriting blocks if we are writing a small amount of data into a block. Unfortunately I think this is just necessary in order to avoid dagging. At the very least crystallization is still useful for files not quite block aligned at the edges, and sparse files. This also avoids concerns of random writes inflating a file via sparse crystallization. --- lfs.c | 1473 +++++++++++++++++++++++---------------------- lfs.h | 2 +- scripts/dbglfs.py | 4 +- 3 files changed, 761 insertions(+), 718 deletions(-) diff --git a/lfs.c b/lfs.c index 572b3ab7..660e1e1f 100644 --- a/lfs.c +++ b/lfs.c @@ -2101,7 +2101,7 @@ static int lfsr_data_readbptr(lfs_t *lfs, lfsr_data_t *data, return err; } - err = lfsr_data_readleb128(lfs, data, &bptr->size); + err = lfsr_data_readleb128(lfs, data, (int32_t*)&bptr->size); if (err) { return err; } @@ -8845,6 +8845,7 @@ int lfsr_file_opencfg(lfs_t *lfs, lfsr_file_t *file, file->flags = flags; file->cfg = cfg; file->pos = 0; + file->size = 0; // default inlined state file->inlined.u.data = LFSR_DATA_DISK(0, 0, 0); // default btree state @@ -8919,6 +8920,7 @@ int lfsr_file_opencfg(lfs_t *lfs, lfsr_file_t *file, // may be a sprout (simple inlined data) if (err != LFS_ERR_NOENT && tag == LFSR_TAG_INLINED) { file->inlined.u.data = data; + file->size = lfsr_data_size(&file->inlined.u.data); // or a shrub (inlined tree) } else if (err != LFS_ERR_NOENT && tag == LFSR_TAG_TRUNK) { @@ -8929,6 +8931,7 @@ int lfsr_file_opencfg(lfs_t *lfs, lfsr_file_t *file, } file->inlined.u.rbyd.block = file->m.mdir.u.m.blocks[0]; + file->size = file->inlined.u.rbyd.weight; // in order to prevent our shrub from overflowing the mdir, we // need to flush when the shrub exceeds our inlined size, @@ -8959,6 +8962,8 @@ int lfsr_file_opencfg(lfs_t *lfs, lfsr_file_t *file, return err; } + file->size = lfs_max32(file->size, file->u.bptr.size); + // or a full btree } else if (err != LFS_ERR_NOENT && tag == LFSR_TAG_BTREE) { // TODO why does this not take a btree? @@ -8966,14 +8971,13 @@ int lfsr_file_opencfg(lfs_t *lfs, lfsr_file_t *file, if (err) { return err; } + + file->size = lfs_max32(file->size, + lfsr_btree_weight(&file->u.btree)); } } } - // TODO common function for this? - // figure out the total size - file->size = lfsr_file_inlinedsize(file); - // allocate buffer if necessary if (file->cfg->buffer) { file->buffer = file->cfg->buffer; @@ -9016,186 +9020,240 @@ int lfsr_file_close(lfs_t *lfs, lfsr_file_t *file) { return err; } +// common iterator over all of the different places data can live in a file +static int lfsr_file_next(lfs_t *lfs, const lfsr_file_t *file, + lfs_off_t pos, lfs_off_t size, + lfs_off_t *weight_, lfsr_data_t *data_) { + // past end of file? + if (pos >= file->size) { + return LFS_ERR_NOENT; + } + + // keep track of the next highest priority data offset + lfs_ssize_t d = lfs_min32( + size, + file->size - pos); + + // any data in our write buffer? + if (pos < file->buffer_pos + file->buffer_size) { + if (pos >= file->buffer_pos) { + d = lfs_min32( + d, + file->buffer_size - (pos - file->buffer_pos)); + + if (weight_) { + *weight_ = d; + } + if (data_) { + *data_ = LFSR_DATA_BUF( + &file->buffer[pos - file->buffer_pos], + d); + } + return 0; + } + + // buffered data takes priority + d = lfs_min32(d, file->buffer_pos - pos); + } + + // has a sprout? + if (lfsr_file_hassprout(file) + && pos < lfsr_file_inlinedsize(file)) { + // note one important side-effect here is any reads to this + // data get a strict read hint + d = lfs_min32( + d, + lfsr_data_size(&file->inlined.u.data)); + + if (weight_) { + *weight_ = d; + } + if (data_) { + *data_ = LFSR_DATA_DISK( + file->inlined.u.data.u.disk.block, + file->inlined.u.data.u.disk.off + pos, + d); + } + return 0; + + // has a shrub? + } else if (lfsr_file_hasshrub(file) + && pos < lfsr_file_inlinedsize(file)) { + lfsr_srid_t rid; + lfsr_tag_t tag; + lfsr_rid_t weight; + lfsr_data_t data; + int err = lfsr_rbyd_lookupnext(lfs, &file->inlined.u.rbyd, pos, 0, + &rid, &tag, &weight, &data); + if (err) { + LFS_ASSERT(err != LFS_ERR_NOENT); + return err; + } + LFS_ASSERT(tag == LFSR_TAG_SHRUB(INLINED)); + LFS_ASSERT(lfsr_data_size(&data) <= weight); + + if (pos < rid-(weight-1) + lfsr_data_size(&data)) { + // note one important side-effect here is any reads to this + // data get a strict read hint + d = lfs_min32( + d, + lfsr_data_size(&data) - (pos - (rid-(weight-1)))); + + if (weight_) { + *weight_ = d; + } + if (data_) { + *data_ = LFSR_DATA_DISK( + data.u.disk.block, + data.u.disk.off + (pos - (rid-(weight-1))), + d); + } + return 0; + } + + // found a hole, just make sure next leaf takes priority + d = lfs_min32(d, rid+1 - pos); + } + + // has a direct block? + if (lfsr_file_hasbptr(file) + && pos < lfsr_file_bsize(file)) { + d = lfs_min32( + d, + file->u.bptr.size - pos); + + if (weight_) { + *weight_ = d; + } + if (data_) { + *data_ = LFSR_DATA_DISK( + file->u.bptr.block, + file->u.bptr.off + pos, + d); + } + return 0; + + // has an indirect btree? + } else if (lfsr_file_hasbtree(file) + && pos < lfsr_file_bsize(file)) { + lfsr_bid_t bid; + lfsr_tag_t tag; + lfsr_bid_t weight; + lfsr_data_t data; + int err = lfsr_btree_lookupnext(lfs, &file->u.btree, pos, + &bid, &tag, &weight, &data); + if (err) { + LFS_ASSERT(err != LFS_ERR_NOENT); + return err; + } + LFS_ASSERT(tag == LFSR_TAG_INLINED + || tag == LFSR_TAG_BLOCK); + + if (tag == LFSR_TAG_INLINED) { + LFS_ASSERT(lfsr_data_size(&data) <= weight); + if (pos < bid-(weight-1) + lfsr_data_size(&data)) { + // note one important side-effect here is any reads to this + // data get a strict read hint + d = lfs_min32( + d, + lfsr_data_size(&data) - (pos - (bid-(weight-1)))); + + if (weight_) { + *weight_ = d; + } + if (data_) { + *data_ = LFSR_DATA_DISK( + data.u.disk.block, + data.u.disk.off + (pos - (bid-(weight-1))), + d); + } + return 0; + } + } else if (tag == LFSR_TAG_BLOCK) { + lfsr_bptr_t bptr; + err = lfsr_data_readbptr(lfs, &data, &bptr); + if (err) { + return err; + } + LFS_ASSERT(bptr.size <= weight); + + if (pos < bid-(weight-1) + bptr.size) { + d = lfs_min32( + d, + bptr.size - (pos - (bid-(weight-1)))); + + if (weight_) { + *weight_ = d; + } + if (data_) { + *data_ = LFSR_DATA_DISK( + bptr.block, + bptr.off + (pos - (bid-(weight-1))), + d); + } + return 0; + } + } + + // found a hole, just make sure next leaf takes priority + d = lfs_min32(d, bid+1 - pos); + } + + // TODO should we have a special LFSR_DATA_HOLE representation? + + // found a hole? + if (weight_) { + *weight_ = d; + } + if (data_) { + *data_ = LFSR_DATA_NULL; + } + return 0; +} + + lfs_ssize_t lfsr_file_read(lfs_t *lfs, lfsr_file_t *file, void *buffer, lfs_size_t size) { LFS_ASSERT(lfsr_file_isreadable(file)); LFS_ASSERT(file->pos + size <= 0x7fffffff); - // limit to our file size - size = lfs_min32( - size, - file->size - lfs_min32(file->pos, file->size)); - lfs_off_t pos = file->pos; uint8_t *buffer_ = buffer; while (size > 0) { - lfs_ssize_t d = size; - - // is the data in our write buffer? - if (pos < file->buffer_pos + file->buffer_size) { - if (pos >= file->buffer_pos) { - d = lfs_min32( - d, - file->buffer_size - (pos - file->buffer_pos)); - memcpy(buffer_, &file->buffer[pos - file->buffer_pos], d); - - pos += d; - buffer_ += d; - size -= d; - continue; + // read each data/hole + lfs_off_t weight; + lfsr_data_t data; + int err = lfsr_file_next(lfs, file, pos, size, + &weight, &data); + if (err) { + // hit end of file? + if (err == LFS_ERR_NOENT) { + break; } - - // buffered data takes priority - d = lfs_min32(d, file->buffer_pos - pos); + return err; } + LFS_ASSERT(weight > 0); - // has a sprout? - if (lfsr_file_hassprout(file) - && pos < lfsr_file_inlinedsize(file)) { - // note we use bd read directly to provide a strict hint - d = lfs_min32( - d, - lfsr_data_size(&file->inlined.u.data)); - int err = lfsr_bd_read(lfs, - file->inlined.u.data.u.disk.block, - file->inlined.u.data.u.disk.off + pos, d, - buffer_, d); - if (err) { - return err; + // found data? + if (lfsr_data_size(&data) > 0) { + lfs_ssize_t d = lfsr_data_read(lfs, &data, + buffer_, size); + if (d < 0) { + return d; } pos += d; buffer_ += d; size -= d; - continue; - // has a shrub? - } else if (lfsr_file_hasshrub(file) - && pos < lfsr_file_inlinedsize(file)) { - lfsr_srid_t rid; - lfsr_tag_t tag; - lfsr_rid_t weight; - lfsr_data_t data; - int err = lfsr_rbyd_lookupnext(lfs, &file->inlined.u.rbyd, pos, 0, - &rid, &tag, &weight, &data); - if (err) { - LFS_ASSERT(err != LFS_ERR_NOENT); - return err; - } - LFS_ASSERT(tag == LFSR_TAG_SHRUB(INLINED)); - LFS_ASSERT(lfsr_data_size(&data) <= weight); + // found a hole? just fill with zeros + } else { + memset(buffer_, 0, weight); - if (pos < rid-(weight-1) + lfsr_data_size(&data)) { - // note we use bd read directly to provide a strict hint - d = lfs_min32( - d, - lfsr_data_size(&data) - (pos - (rid-(weight-1)))); - int err = lfsr_bd_read(lfs, - data.u.disk.block, - data.u.disk.off + (pos - (rid-(weight-1))), d, - buffer_, d); - if (err) { - return err; - } - - pos += d; - buffer_ += d; - size -= d; - continue; - } - - // found a hole, just make sure next leaf takes priority - d = lfs_min32(d, rid+1 - pos); + pos += weight; + buffer_ += weight; + size -= weight; } - - // has a direct block? - if (lfsr_file_hasbptr(file) - && pos < lfsr_file_bsize(file)) { - d = lfs_min32( - d, - file->u.bptr.size - pos); - int err = lfsr_bd_read(lfs, file->u.bptr.block, - file->u.bptr.off + pos, d, - buffer_, d); - if (err) { - return err; - } - - pos += d; - buffer_ -= d; - size -= d; - continue; - - // has an indirect btree? - } else if (lfsr_file_hasbtree(file) - && pos < lfsr_file_bsize(file)) { - lfsr_bid_t bid; - lfsr_tag_t tag; - lfsr_bid_t weight; - lfsr_data_t data; - int err = lfsr_btree_lookupnext(lfs, &file->u.btree, pos, - &bid, &tag, &weight, &data); - if (err) { - LFS_ASSERT(err != LFS_ERR_NOENT); - return err; - } - - if (tag == LFSR_TAG_INLINED) { - LFS_ASSERT(lfsr_data_size(&data) <= weight); - if (pos < bid-(weight-1) + lfsr_data_size(&data)) { - // note we use bd read directly to provide a strict hint - d = lfs_min32( - d, - lfsr_data_size(&data) - (pos - (bid-(weight-1)))); - int err = lfsr_bd_read(lfs, - data.u.disk.block, - data.u.disk.off + (pos - (bid-(weight-1))), d, - buffer_, d); - if (err) { - return err; - } - - pos += d; - buffer_ += d; - size -= d; - continue; - } - } else if (tag == LFSR_TAG_BLOCK) { - lfsr_bptr_t bptr; - err = lfsr_data_readbptr(lfs, &data, &bptr); - if (err) { - return err; - } - LFS_ASSERT(bptr.size <= weight); - - if (pos < bid-(weight-1) + bptr.size) { - d = lfs_min32( - d, - bptr.size - (pos - (bid-(weight-1)))); - err = lfsr_bd_read(lfs, bptr.block, - bptr.off + (pos - (bid-(weight-1))), d, - buffer_, d); - if (err) { - return err; - } - - pos += d; - buffer_ += d; - size -= d; - continue; - } - } - - // found a hole, just make sure next leaf takes priority - d = lfs_min32(d, bid+1 - pos); - } - - // no data? found a hole, just fill with zeros - memset(buffer_, 0, d); - - pos += d; - buffer_ += d; - size -= d; } lfs_size_t read = pos - file->pos; @@ -9267,7 +9325,7 @@ static int lfsr_file_carveinlined(lfs_t *lfs, lfsr_file_t *file, LFS_ASSERT(lfsr_data_size(&left_data) <= left_weight); } - // this can be negative! + // note this can be negative! left_overlap = (left_rid+1) - pos; LFS_ASSERT(left_overlap >= 0 || lfsr_file_inlinedsize(file) < pos); @@ -9351,11 +9409,11 @@ static int lfsr_file_carveinlined(lfs_t *lfs, lfsr_file_t *file, // can we coalesce right data? if ((lfsr_data_size(&data) == weight + delta - || lfsr_data_size(&right_data) - right_overlap == 0) - && (lfs_soff_t)(lfsr_data_size(&data) - + lfsr_data_size(&right_data) - - right_overlap) - <= (lfs_soff_t)lfs->cfg->coalesce_size) { + && (lfs_soff_t)(lfsr_data_size(&data) + + lfsr_data_size(&right_data) + - right_overlap) + <= (lfs_soff_t)lfs->cfg->coalesce_size) + || lfsr_data_size(&right_data) - right_overlap == 0) { *datas_++ = LFSR_DATA_DISK( right_data.u.disk.block, right_data.u.disk.off + right_overlap, @@ -9452,7 +9510,7 @@ static int lfsr_file_carveinlined(lfs_t *lfs, lfsr_file_t *file, // TODO can truncate/fruncate trigger this? say fruncate creates a hole, // should carveinlined just call flushinlined if we overflow? if (estimate > lfs->cfg->inline_size) { - //return LFS_ERR_RANGE; + return LFS_ERR_RANGE; } // commit our attributes @@ -9473,187 +9531,336 @@ static int lfsr_file_carveinlined(lfs_t *lfs, lfsr_file_t *file, static int lfsr_file_carvebtree(lfs_t *lfs, lfsr_file_t *file, lfs_off_t pos, lfs_off_t weight, lfs_soff_t delta, lfsr_tag_t tag, lfsr_data_t data) { - // first remove any existing data we're overwriting - lfs_off_t rm = lfs_min32( - weight, - lfsr_btree_weight(&file->u.btree) - lfs_min32( - pos, - lfsr_btree_weight(&file->u.btree))); - while (rm > 0) { + // this is similar to lfsr_file_carveinlined, except instead of building + // an attribute list, we modify the btree directly in a copy-on-write + // fashion + // + // in theory range-deletions could make this faster, but that would + // require btree range-deletions to be implemented + + // TODO actually, should lfsr_file_carveinlined be structured more like + // this one? with a single loop and no rm estimate call? + + // scratch datas to track any coalescing data + lfsr_data_t scratch_datas[4]; + lfsr_data_t *datas_ = scratch_datas; + *datas_++ = data; + + // TODO support direct blocks here + LFS_ASSERT(lfsr_file_hasbtree(file)); + + // first coalesce/carve/remove any existing data + // + // note we start/end +1 to see if we can coalesce any siblings + // + lfs_off_t pos_ = lfs_smax32( + lfs_min32(pos, lfsr_btree_weight(&file->u.btree)) - 1, + 0); + lfs_off_t rm = 0; + while (pos_+rm < pos+weight+1 && pos_ < lfsr_btree_weight(&file->u.btree)) { lfsr_bid_t bid_; lfsr_tag_t tag_; lfsr_bid_t weight_; lfsr_data_t data_; - int err = lfsr_btree_lookupnext(lfs, &file->u.btree, pos, + int err = lfsr_btree_lookupnext(lfs, &file->u.btree, pos_, &bid_, &tag_, &weight_, &data_); if (err) { LFS_ASSERT(err != LFS_ERR_NOENT); return err; } + LFS_ASSERT(tag == LFSR_TAG_INLINED + || tag == LFSR_TAG_BLOCK); + + // found left sibling? + if (pos > bid_-(weight_-1)) { + // note! this may go negative + lfs_soff_t overlap_ = (bid_+1) - pos; + LFS_ASSERT(overlap_ >= 0 + || pos > lfsr_btree_weight(&file->u.btree)); - // need to carve land-locked data? if this is a block, this turns - // our btree into a DAG - if (pos > bid_-(weight_-1) && pos + rm < bid_+1) { if (tag_ == LFSR_TAG_INLINED) { LFS_ASSERT(lfsr_data_size(&data_) <= weight_); - err = lfsr_btree_commit(lfs, &file->u.btree, LFSR_ATTRS( - LFSR_ATTR(bid_, - GROW(INLINED), -(bid_+1 - pos), - DISK( - data_.u.disk.block, - data_.u.disk.off, - lfs_min32( - lfsr_data_size(&data_), - weight_ - (bid_+1 - pos)))), - LFSR_ATTR(pos, - INLINED, +weight_ - (pos+rm - (bid_-(weight_-1))), - DISK( - data_.u.disk.block, - data_.u.disk.off - + (pos+rm - (bid_-(weight_-1))), - lfs_min32( - lfsr_data_size(&data_), - weight_ - - (pos+rm - (bid_-(weight_-1))) - ))))); - if (err) { - return err; - } - } else if (tag_ == LFSR_TAG_BLOCK) { - lfsr_bptr_t l_bptr; - err = lfsr_data_readbptr(lfs, &data_, &l_bptr); - if (err) { - return err; - } - LFS_ASSERT(l_bptr.size <= weight_); - lfsr_bptr_t r_bptr = l_bptr; - l_bptr.size = lfs_min32( - l_bptr.size, - weight_ - (bid_+1 - pos)); + // can we coalesce left data? this is only possible if our + // new data is not a block and there is no hole on the left + if (tag == LFSR_TAG_INLINED + && pos <= bid_-(weight_-1)+lfsr_data_size(&data_) + && lfsr_data_size(&data) + (weight_ - overlap_) + <= lfs->cfg->crystallize_size) { + // note! this is a reference to cow data! this may need + // special care for non-pure-cow allocators + scratch_datas[0] = LFSR_DATA_DISK( + data_.u.disk.block, + data_.u.disk.off, + weight_ - overlap_); + scratch_datas[1] = data; + datas_ = &scratch_datas[2]; + data = lfsr_data_fromcat(&scratch_datas[0], 2); + pos = bid_-(weight_-1); + weight += weight_ - overlap_; - r_bptr.off += pos+rm - (bid_-(weight_-1)); - r_bptr.size = weight_ - (pos+rm - (bid_-(weight_-1))); - - uint8_t l_bptr_buf[LFSR_BPTR_DSIZE]; - uint8_t r_bptr_buf[LFSR_BPTR_DSIZE]; - err = lfsr_btree_commit(lfs, &file->u.btree, LFSR_ATTRS( - LFSR_ATTR(bid_, - GROW(BLOCK), -(bid_+1 - pos), - FROMBPTR(&l_bptr, l_bptr_buf)), - LFSR_ATTR(pos, - BLOCK, +weight_ - (pos+rm - (bid_-(weight_-1))), - FROMBPTR(&r_bptr, r_bptr_buf)))); - if (err) { - return err; - } - } else { - LFS_UNREACHABLE(); - } - - rm -= rm; - - // need to carve data in left sibling? - } else if (pos > bid_-(weight_-1)) { - if (tag_ == LFSR_TAG_INLINED) { - LFS_ASSERT(lfsr_data_size(&data_) <= weight_); - err = lfsr_btree_commit(lfs, &file->u.btree, LFSR_ATTRS( - LFSR_ATTR(bid_, - GROW(INLINED), -(bid_+1 - pos), - DISK( - data_.u.disk.block, - data_.u.disk.off, - lfs_min32( - lfsr_data_size(&data_), - weight_ - (bid_+1 - pos)))))); - if (err) { - return err; - } - } else if (tag_ == LFSR_TAG_BLOCK) { - lfsr_bptr_t bptr; - err = lfsr_data_readbptr(lfs, &data_, &bptr); - if (err) { - return err; - } - LFS_ASSERT(bptr.size <= weight_); - - bptr.size = lfs_min32( - bptr.size, - weight_ - (bid_+1 - pos)); - - uint8_t bptr_buf[LFSR_BPTR_DSIZE]; - err = lfsr_btree_commit(lfs, &file->u.btree, LFSR_ATTRS( - LFSR_ATTR(bid_, - GROW(BLOCK), -(bid_+1 - pos), - FROMBPTR(&bptr, bptr_buf)))); - if (err) { - return err; - } - } else { - LFS_UNREACHABLE(); - } - - rm -= bid_+1 - pos; - - // need to carve data in right sibling? - } else if (pos + rm < bid_+1) { - if (tag_ == LFSR_TAG_INLINED) { - LFS_ASSERT(lfsr_data_size(&data_) <= weight_); - err = lfsr_btree_commit(lfs, &file->u.btree, LFSR_ATTRS( - LFSR_ATTR(bid_, - GROW(INLINED), -(pos+rm - (bid_-(weight_-1))), - DISK( - data_.u.disk.block, - data_.u.disk.off - + (pos+rm - (bid_-(weight_-1))), - lfs_min32( - lfsr_data_size(&data_), - weight_ - - (pos+rm - (bid_-(weight_-1))) - ))))); - if (err) { - return err; - } - } else if (tag_ == LFSR_TAG_BLOCK) { - lfsr_bptr_t bptr; - err = lfsr_data_readbptr(lfs, &data_, &bptr); - if (err) { - return err; - } - LFS_ASSERT(bptr.size <= weight_); - - // are we completely eliminating access to the block? change - // to a data-less hole so we can garbage collect the block - if ((lfs_off_t)bptr.size < pos+rm - (bid_-(weight_-1))) { + // removing data we're referencing? this gets pretty + // cursed... err = lfsr_btree_commit(lfs, &file->u.btree, LFSR_ATTRS( - LFSR_ATTR(bid_, - GROW(WIDE(INLINED)), - -(pos+rm - (bid_-(weight_-1))), - NULL))); + LFSR_ATTR(bid_, RM, -weight_, NULL))); if (err) { return err; } + + // can we get away with a grow attribute? + } else if (pos >= bid_-(weight_-1)+lfsr_data_size(&data_)) { + err = lfsr_btree_commit(lfs, &file->u.btree, LFSR_ATTRS( + LFSR_ATTR(bid_, GROW, -overlap_, NULL))); + if (err) { + return err; + } + + // need to carve left data } else { - bptr.off += pos+rm - (bid_-(weight_-1)); - bptr.size = weight_ - (pos+rm - (bid_-(weight_-1))); + err = lfsr_btree_commit(lfs, &file->u.btree, LFSR_ATTRS( + LFSR_ATTR(bid_, + GROW(INLINED), -overlap_, + DISK( + data_.u.disk.block, + data_.u.disk.off, + weight_ - overlap_)))); + if (err) { + return err; + } + } + + // did we split data? + if (overlap_ > (lfs_soff_t)weight) { + // did we split actual data? + if (pos+weight < bid_-(weight_-1)+lfsr_data_size(&data_)) { + // we MUST coalesce here, otherwise we risk dagging + LFS_ASSERT(tag == LFSR_TAG_INLINED); + LFS_ASSERT(lfsr_data_size(&data_) + <= lfs->cfg->crystallize_size); + LFS_ASSERT(datas_ - scratch_datas == 2); + + // note! this is a reference to cow data! this may need + // special care for non-pure-cow allocators + *datas_++ = LFSR_DATA_DISK( + data_.u.disk.block, + data_.u.disk.off + + (weight_ - overlap_) + weight, + lfsr_data_size(&data_) - lfs_min32( + (weight_ - overlap_) + weight, + lfsr_data_size(&data_))); + data = lfsr_data_fromcat( + scratch_datas, + datas_ - scratch_datas); + weight += weight_ - overlap_; + + // otherwise we just add our hole to our weight + } else { + weight += overlap_ - weight; + } + } + + } else if (tag_ == LFSR_TAG_BLOCK) { + lfsr_bptr_t bptr_; + err = lfsr_data_readbptr(lfs, &data_, &bptr_); + if (err) { + return err; + } + LFS_ASSERT(bptr_.size <= weight_); + + // can we coalesce left data? this is only possible if our + // new data is not a block and there is no hole on the left + if (tag == LFSR_TAG_INLINED + && pos <= bid_-(weight_-1)+bptr_.size + && lfsr_data_size(&data) + (weight_ - overlap_) + <= lfs->cfg->crystallize_size) { + // note! this is a reference to cow data! this may need + // special care for non-pure-cow allocators + scratch_datas[0] = LFSR_DATA_DISK( + bptr_.block, + bptr_.off, + weight_ - overlap_); + scratch_datas[1] = data; + datas_ = &scratch_datas[2]; + data = lfsr_data_fromcat(&scratch_datas[0], 2); + pos = bid_-(weight_-1); + weight += weight_ - overlap_; + + // removing data we're referencing? this gets pretty + // cursed... + err = lfsr_btree_commit(lfs, &file->u.btree, LFSR_ATTRS( + LFSR_ATTR(bid_, RM, -weight_, NULL))); + if (err) { + return err; + } + + // can we get away with a grow attribute? + } else if (pos >= bid_-(weight_-1)+bptr_.size) { + err = lfsr_btree_commit(lfs, &file->u.btree, LFSR_ATTRS( + LFSR_ATTR(bid_, GROW, -overlap_, NULL))); + if (err) { + return err; + } + + // need to carve left data + } else { + bptr_.size = weight_ - overlap_; uint8_t bptr_buf[LFSR_BPTR_DSIZE]; err = lfsr_btree_commit(lfs, &file->u.btree, LFSR_ATTRS( LFSR_ATTR(bid_, - GROW(BLOCK), -(pos+rm - (bid_-(weight_-1))), - FROMBPTR(&bptr, bptr_buf)))); + GROW(BLOCK), -overlap_, + FROMBPTR(&bptr_, bptr_buf)))); + if (err) { + return err; + } + } + + // did we split data? + if (overlap_ > (lfs_soff_t)weight) { + // did we split actual data? + if (pos+weight < bid_-(weight_-1)+bptr_.size) { + // we MUST coalesce here, otherwise we risk dagging + LFS_ASSERT(tag == LFSR_TAG_INLINED); + LFS_ASSERT(bptr_.size <= lfs->cfg->crystallize_size); + LFS_ASSERT(datas_ - scratch_datas == 2); + + // note! this is a reference to cow data! this may need + // special care for non-pure-cow allocators + *datas_++ = LFSR_DATA_DISK( + bptr_.block, + bptr_.off + + (weight_ - overlap_) + weight, + bptr_.size - lfs_min32( + (weight_ - overlap_) + weight, + bptr_.size)); + data = lfsr_data_fromcat( + scratch_datas, + datas_ - scratch_datas); + weight += weight_ - overlap_; + + // otherwise we just add our hole to our weight + } else { + weight += overlap_ - weight; + } + } + } + + pos_ = pos; + rm += lfs_smax32(overlap_, 0); + + // found right sibling? + } else if (pos + weight-rm < bid_+1) { + lfs_soff_t overlap_ = (pos + weight-rm) - (bid_-(weight_-1)); + LFS_ASSERT(overlap_ >= 0); + + if (tag_ == LFSR_TAG_INLINED) { + LFS_ASSERT(lfsr_data_size(&data_) <= weight_); + + // can we coalesce right data? this is only possible if our + // new data is not a block, or our right data has no data + if ((tag == LFSR_TAG_INLINED + && lfsr_data_size(&data) == weight + delta + && lfsr_data_size(&data) + + lfsr_data_size(&data_) + - overlap_ + <= lfs->cfg->crystallize_size) + || lfsr_data_size(&data_) - overlap_ == 0) { + // note! this is a reference to cow data! this may need + // special care for non-pure-cow allocators + *datas_++ = LFSR_DATA_DISK( + data_.u.disk.block, + data_.u.disk.off + overlap_, + lfsr_data_size(&data_) - lfs_min32( + overlap_, + lfsr_data_size(&data_))); + data = lfsr_data_fromcat( + scratch_datas, + datas_ - scratch_datas); + weight += weight_ - overlap_; + + // removing data we're referencing? this gets pretty + // cursed... + err = lfsr_btree_commit(lfs, &file->u.btree, LFSR_ATTRS( + LFSR_ATTR(bid_, RM, -weight_, NULL))); + if (err) { + return err; + } + + // need to carve right data + } else { + err = lfsr_btree_commit(lfs, &file->u.btree, LFSR_ATTRS( + LFSR_ATTR(bid_, + GROW(INLINED), -overlap_, + DISK( + data_.u.disk.block, + data_.u.disk.off + overlap_, + lfsr_data_size(&data_) - lfs_min32( + overlap_, + lfsr_data_size(&data_)))))); + if (err) { + return err; + } + } + + } else if (tag_ == LFSR_TAG_BLOCK) { + lfsr_bptr_t bptr_; + err = lfsr_data_readbptr(lfs, &data_, &bptr_); + if (err) { + return err; + } + LFS_ASSERT(bptr_.size <= weight_); + + // can we coalesce right data? this is only possible if our + // new data is not a block, or our right data has no data + if ((tag == LFSR_TAG_INLINED + && lfsr_data_size(&data) == weight + delta + && lfsr_data_size(&data) + + bptr_.size + - overlap_ + <= lfs->cfg->crystallize_size) + || bptr_.size - overlap_ == 0) { + // note! this is a reference to cow data! this may need + // special care for non-pure-cow allocators + *datas_++ = LFSR_DATA_DISK( + bptr_.block, + bptr_.off + overlap_, + bptr_.size - lfs_min32( + overlap_, + bptr_.size)); + data = lfsr_data_fromcat( + scratch_datas, + datas_ - scratch_datas); + weight += weight_ - overlap_; + + // removing data we're referencing? this gets pretty + // cursed... + err = lfsr_btree_commit(lfs, &file->u.btree, LFSR_ATTRS( + LFSR_ATTR(bid_, RM, -weight_, NULL))); + if (err) { + return err; + } + + // need to carve right data + } else { + bptr_.off += overlap_; + bptr_.size -= overlap_; + + uint8_t bptr_buf[LFSR_BPTR_DSIZE]; + err = lfsr_btree_commit(lfs, &file->u.btree, LFSR_ATTRS( + LFSR_ATTR(bid_, + GROW(BLOCK), -overlap_, + FROMBPTR(&bptr_, bptr_buf)))); if (err) { return err; } } - } else { - LFS_UNREACHABLE(); } - rm -= pos+rm - (bid_-(weight_-1)); + pos_ += 1; + rm += overlap_; - // need to completely remove this entry + // found fully overwritten entry, remove } else { err = lfsr_btree_commit(lfs, &file->u.btree, LFSR_ATTRS( LFSR_ATTR(bid_, RM, -weight_, NULL))); @@ -9661,12 +9868,16 @@ static int lfsr_file_carvebtree(lfs_t *lfs, lfsr_file_t *file, return err; } - rm -= weight_; + rm += weight_; } } // need a hole? if (pos > lfsr_btree_weight(&file->u.btree)) { + // we should be able to handle non-zero btree holes with grow + // attributes above + LFS_ASSERT(lfsr_btree_weight(&file->u.btree) == 0); + int err = lfsr_btree_commit(lfs, &file->u.btree, LFSR_ATTRS( LFSR_ATTR(lfsr_btree_weight(&file->u.btree), INLINED, +(pos - lfsr_btree_weight(&file->u.btree)), @@ -9687,100 +9898,96 @@ static int lfsr_file_carvebtree(lfs_t *lfs, lfsr_file_t *file, } static int lfsr_file_flushinlined(lfs_t *lfs, lfsr_file_t *file) { -// TODO should we aim for overflow? -// while (overflow > 0) { - // TODO is there some way to deduplicate this "read" loop? seems - // to be common + // iterate through our buffer/sprout/shrub and flush everything into + // our btree + lfs_off_t inlined_pos = 0; + while (inlined_pos < file->size) { + lfs_ssize_t d = file->size - inlined_pos; + lfsr_data_t inlined_data; - // first find the start of unflushed data and how much data can be - // written in a contiguous run - lfs_off_t pos_ = 0; - lfs_off_t size_ = 0; - lfs_off_t pos = 0; - lfs_off_t size = lfs_max32( - lfsr_file_inlinedsize(file), - file->buffer_pos + file->buffer_size); - while (pos < size) { - lfs_off_t d = size - pos; + // TODO would it make more sense to move this into a separate function? + // TODO lfsr_file_inlinednext maybe? and call from lfsr_file_next? - // prioritize our buffer - if (pos < file->buffer_pos + file->buffer_size) { - if (pos >= file->buffer_pos) { - d = lfs_min32( - file->buffer_size - (pos - file->buffer_pos), - d); - size_ += d; - pos += d; - continue; - } + // any data in our write buffer? + if (inlined_pos < file->buffer_pos + file->buffer_size) { + if (inlined_pos >= file->buffer_pos) { + d = lfs_min32( + d, + file->buffer_size - (inlined_pos - file->buffer_pos)); - d = lfs_min32(d, file->buffer_pos - pos); + inlined_data = LFSR_DATA_BUF( + &file->buffer[inlined_pos - file->buffer_pos], + d); + goto flush; } - // has a sprout? - if (lfsr_file_hassprout(file) - && pos < lfsr_file_inlinedsize(file)) { - d = lfs_min32(lfsr_file_inlinedsize(file) - pos, d); - size_ += d; - pos += d; - continue; - } - - // has a shrub? - if (lfsr_file_hasshrub(file) - && pos < lfsr_file_inlinedsize(file)) { - lfsr_srid_t rid; - lfsr_tag_t tag; - lfsr_rid_t weight; - lfsr_data_t data; - int err = lfsr_rbyd_lookupnext(lfs, &file->inlined.u.rbyd, - pos, 0, - &rid, &tag, &weight, &data); - if (err) { - LFS_ASSERT(err != LFS_ERR_NOENT); - return err; - } - LFS_ASSERT(tag == LFSR_TAG_SHRUB(INLINED)); - LFS_ASSERT(lfsr_data_size(&data) <= weight); - - if (pos < rid-(weight-1) + lfsr_data_size(&data)) { - d = lfs_min32( - lfsr_data_size(&data) - (pos - (rid-(weight-1))), - d); - size_ += d; - pos += d; - continue; - } - - d = lfs_min32(d, rid+1 - pos); - } - - // found a hole - // - // we can skip it, but only if we haven't seen any contiguous - // data yet - // - if (size_ == 0) { - pos_ += d; - pos += d; - } else { - break; - } + // buffered data takes priority + d = lfs_min32(d, file->buffer_pos - inlined_pos); } - // TODO is this true? - // we should have some data to write, why else would this function - // be called? - LFS_ASSERT(size_ > 0); + // has a sprout? + if (lfsr_file_hassprout(file) + && inlined_pos < lfsr_file_inlinedsize(file)) { + d = lfs_min32( + d, + lfsr_data_size(&file->inlined.u.data)); - // TODO check for possible ecksums + inlined_data = LFSR_DATA_DISK( + file->inlined.u.data.u.disk.block, + file->inlined.u.data.u.disk.off + inlined_pos, + d); + goto flush; - // no btree? need to allocate? + // has a shrub? + } else if (lfsr_file_hasshrub(file) + && inlined_pos < lfsr_file_inlinedsize(file)) { + lfsr_srid_t rid; + lfsr_tag_t tag; + lfsr_rid_t weight; + lfsr_data_t data; + int err = lfsr_rbyd_lookupnext(lfs, &file->inlined.u.rbyd, + inlined_pos, 0, + &rid, &tag, &weight, &data); + if (err) { + LFS_ASSERT(err != LFS_ERR_NOENT); + return err; + } + LFS_ASSERT(tag == LFSR_TAG_SHRUB(INLINED)); + LFS_ASSERT(lfsr_data_size(&data) <= weight); + + if (inlined_pos < rid-(weight-1) + lfsr_data_size(&data)) { + d = lfs_min32( + d, + lfsr_data_size(&data) + - (inlined_pos - (rid-(weight-1)))); + + inlined_data = LFSR_DATA_DISK( + data.u.disk.block, + data.u.disk.off + (inlined_pos - (rid-(weight-1))), + d); + goto flush; + } + + // found a hole, just make sure next leaf takes priority + d = lfs_min32(d, rid+1 - inlined_pos); + } + + // found a hole? skip + inlined_pos += d; + continue; + + flush:; + // first we need to figure out if we can coalesce or need a new block + lfs_off_t crystal_pos = inlined_pos; + lfs_off_t crystal_size = lfsr_data_size(&inlined_data); + + // don't check our crystallization threshold yet, we always need to + // lookup our left sibling to determine the best crystal alignment + + // TODO when do we create single blocks? + + // no btree yet? alloc a new root node if (!lfsr_file_hasbtree(file)) { - // TODO what if we fit in crystallize_size but are clearly writing - // linearly? should we have a special heuristic to avoid early - // btrees? - // TODO allow single blocks LFS_ASSERT(!lfsr_file_hasbptr(file)); @@ -9789,180 +9996,96 @@ static int lfsr_file_flushinlined(lfs_t *lfs, lfsr_file_t *file) { if (err) { return err; } - } - // the next step is to check for over-crystallization - // - // To do this, we need to figure out the best block alignment. This - // gets tricky with the possibility of fruncate/push/pop, so we aim - // for local alignment, with the expectation that contiguous writes - // will sort themselves out much like actual crystallization in - // nature. - // - lfs_off_t block_pos; - - // By far the most common case is we're appending new data, try to - // find a block to the left, this gets a bit tricky to account for - // carved blocks. - // - // best case worst case - // .-----.-----. .-----.-----. - // |xxxxx|p | | xxxx| | - // |xxxx | | |xxxxx| p| - // '-----'-----' '-----'-----' - // '+' '----+----' - // pos-1 pos-2*bs+1 - // - pos = lfs_min32(pos_, lfsr_btree_weight(&file->u.btree)) - 1; - while ((lfs_soff_t)pos >= 0 - && (lfs_soff_t)pos - >= (lfs_soff_t)(pos_ - 2*lfs->cfg->block_size+1)) { - lfsr_bid_t bid; - lfsr_tag_t tag; - lfsr_bid_t weight; - lfsr_data_t data; - int err = lfsr_btree_lookupnext(lfs, &file->u.btree, pos, - &bid, &tag, &weight, &data); - if (err) { - LFS_ASSERT(err != LFS_ERR_NOENT); - return err; - } - - // found a block? - if (tag == LFSR_TAG_BLOCK) { - lfsr_bptr_t bptr; - err = lfsr_data_readbptr(lfs, &data, &bptr); - if (err) { - return err; - } - - // ignore if we're contained in the block, we want our - // sibling - lfs_off_t block_pos_ = bid-(weight-1) - bptr.off - + lfs->cfg->block_size; - if (pos_ >= block_pos_) { - // found left sibling's alignment - block_pos = block_pos_; - goto aligned; - } - } - - pos = bid - weight; - } - - // No block to the left? Maybe we're fruncating, try to find the - // block to the right. - // - // best case worst case - // .-----.-----. .-----.-----. - // | | xxxx| |p |xxxxx| - // | p|xxxxx| | |xxxx | - // '-----'-----' '-----'-----' - // '+' '----+----' - // pos+1 pos+2*bs-1 - // - pos = pos_+1; - while (pos < lfsr_btree_weight(&file->u.btree) - && pos < pos_ + 2*lfs->cfg->block_size-1) { - lfsr_bid_t bid; - lfsr_tag_t tag; - lfsr_bid_t weight; - lfsr_data_t data; - int err = lfsr_btree_lookupnext(lfs, &file->u.btree, pos, - &bid, &tag, &weight, &data); - if (err) { - LFS_ASSERT(err != LFS_ERR_NOENT); - return err; - } - - // found a block? - if (tag == LFSR_TAG_BLOCK) { - lfsr_bptr_t bptr; - err = lfsr_data_readbptr(lfs, &data, &bptr); - if (err) { - return err; - } - - // ignore if we're contained in the block, we want our - // sibling - lfs_soff_t block_pos_ = bid-(weight-1) - bptr.off - - lfs->cfg->block_size; - if (pos_ < block_pos_ + lfs->cfg->block_size) { - // found right sibling's alignment - block_pos = block_pos_; - goto aligned; - } - } - - pos = bid + 1; - } - - // No sibling either direction? This can happen if we have a bunch - // of holes. Fall back to 0-based alignment. - block_pos = lfs_aligndown(pos_, lfs->cfg->block_size); - - aligned:; - // do we exceed our crystallize threshold? - lfs_off_t crystallized = lfs_min32( - size_, - lfs->cfg->block_size - (pos_ - block_pos)); - pos = block_pos; - while (pos < lfsr_btree_weight(&file->u.btree) - && pos < block_pos + lfs->cfg->block_size - && crystallized <= lfs->cfg->crystallize_size) { - lfs_off_t d = lfs->cfg->block_size - (pos - block_pos); - - // prioritize our inlined data - if (pos < pos_ + size_) { - if (pos >= pos_) { - // inlined size already accounted to encourage early - // loop termination - pos += size_ - (pos - pos_); - continue; - } - - d = lfs_min32(d, pos_ - pos); - } - - lfsr_bid_t bid; - lfsr_tag_t tag; - lfsr_bid_t weight; - lfsr_data_t data; - int err = lfsr_btree_lookupnext(lfs, &file->u.btree, pos, - &bid, &tag, &weight, &data); - if (err) { - return err; - } - - // found crystallizing inlined data? - if (tag == LFSR_TAG_INLINED) { - crystallized += lfs_min32( - lfsr_data_size(&data) - (pos - (bid-(weight-1))), - d); - } - - pos += lfs_min32(d, (bid+1) - pos); - } - - // can we inline into the btree inner nodes? - lfs_off_t pos__; - lfs_off_t size__; - lfsr_tag_t tag__; - lfsr_data_t data__; - uint8_t bptr_buf[LFSR_BPTR_DSIZE]; - if (crystallized <= lfs->cfg->crystallize_size) { - pos__ = pos_; - size__ = size_; - tag__ = LFSR_TAG_INLINED; - data__ = LFSR_DATA_FILE(file, pos_, size_); - - // exceeded crystallization threshold, compact into a new block } else { - // can't cross a block boundary here - size_ = lfs_min32( - size_, - lfs->cfg->block_size - (pos - block_pos)); + // has left sibling? + if (inlined_pos > 0) { + lfsr_bid_t left_bid; + lfsr_tag_t left_tag; + lfsr_bid_t left_weight; + lfsr_data_t left_data; + int err = lfsr_btree_lookupnext(lfs, &file->u.btree, + lfs_min32( + inlined_pos, + lfsr_btree_weight(&file->u.btree))-1, + &left_bid, &left_tag, &left_weight, &left_data); + if (err) { + LFS_ASSERT(err != LFS_ERR_NOENT); + return err; + } + LFS_ASSERT(left_tag == LFSR_TAG_INLINED + || left_tag == LFSR_TAG_BLOCK); + lfs_off_t left_size; + // is inlined data? + if (left_tag == LFSR_TAG_INLINED) { + left_size = lfsr_data_size(&left_data); + LFS_ASSERT(left_size <= left_weight); + + // is a block? + } else if (left_tag == LFSR_TAG_BLOCK) { + lfsr_bptr_t bptr; + err = lfsr_data_readbptr(lfs, &left_data, &bptr); + if (err) { + return err; + } + LFS_ASSERT(bptr.size <= left_weight); + left_size = bptr.size; + } + + // coalescable? this requires we either overlap, or left sibling + // is not a full block + if (crystal_pos - (left_bid-(left_weight-1)) + < lfs->cfg->block_size + && crystal_pos + <= left_bid-(left_weight-1)+left_size) { + crystal_size += crystal_pos - (left_bid-(left_weight-1)); + crystal_pos = left_bid-(left_weight-1); + } + } + + // scan our crystallizing region to see how much data could be + // merged if we created a block here + while (crystal_size <= lfs->cfg->crystallize_size + && crystal_pos + crystal_size < file->size) { + lfs_off_t weight; + lfsr_data_t data; + int err = lfsr_file_next(lfs, file, + crystal_pos + crystal_size, file->size, + &weight, &data); + if (err) { + // end of file? + if (err == LFS_ERR_NOENT) { + break; + } + return err; + } + LFS_ASSERT(weight > 0); + + // found a hole? just stop here + if (lfsr_data_size(&data) == 0) { + break; + } + + crystal_size += lfsr_data_size(&data); + } + } + + // not enough crystallized data for a block? inline the data directly, + // potentially coalescing with any neighbors + if (crystal_size < lfs->cfg->crystallize_size) { + // write inlined data into our tree + int err = lfsr_file_carvebtree(lfs, file, + inlined_pos, lfsr_data_size(&inlined_data), 0, + LFSR_TAG_INLINED, inlined_data); + if (err) { + return err; + } + + inlined_pos += lfsr_data_size(&inlined_data); + + // exceeded crystallization threshold? create a new block + } else { // allocate a new block lfs_block_t block; int err = lfs_alloc(lfs, &block); @@ -9976,102 +10099,41 @@ static int lfsr_file_flushinlined(lfs_t *lfs, lfsr_file_t *file) { return err; } - // iterate through data in our btree and write it into the block - pos = block_pos; - size = lfs_min32( - lfs->cfg->block_size, - lfs_max32(pos_+size_, file->size) - block_pos); - while (pos < block_pos + size) { - lfs_off_t d = block_pos + size - pos; - - // prioritize our inlined data - if (pos < pos_ + size_) { - if (pos >= pos_) { - // TODO becksum? - err = lfsr_bd_progdata(lfs, block, pos - block_pos, - LFSR_DATA_FILE(file, pos_, lfs_min32(size_, d)), - NULL); - if (err) { - return err; - } - - pos += size_; - continue; + // TODO becksum? + // copy any data underneath our block into our block + lfs_off_t pos = crystal_pos; + lfs_off_t size = lfs->cfg->block_size; + while (size > 0) { + lfs_off_t weight; + lfsr_data_t data; + err = lfsr_file_next(lfs, file, pos, size, + &weight, &data); + if (err) { + // end of file? + if (err == LFS_ERR_NOENT) { + break; } + return err; + } + LFS_ASSERT(weight > 0); - d = lfs_min32(d, pos_ - pos); + // TODO we should probably NOT stop or we risk excessive + // fragmentation when there are small holes + + // found a hole? just stop here + if (lfsr_data_size(&data) == 0) { + break; } - // write any previously crystallized data - if (pos < lfsr_btree_weight(&file->u.btree)) { - lfsr_bid_t bid; - lfsr_tag_t tag; - lfsr_bid_t weight; - lfsr_data_t data; - int err = lfsr_btree_lookupnext(lfs, &file->u.btree, pos, - &bid, &tag, &weight, &data); - if (err) { - return err; - } - - // crystallizing inlined data? - if (tag == LFSR_TAG_INLINED) { - // data is data - LFS_ASSERT(lfsr_data_size(&data) <= weight); - - // a previous block? - } else if (tag == LFSR_TAG_BLOCK) { - lfsr_bptr_t bptr; - err = lfsr_data_readbptr(lfs, &data, &bptr); - if (err) { - return err; - } - LFS_ASSERT(bptr.size <= weight); - - // TODO, wait, are lfsr_data_t and lfsr_bptr_t - // the same thing? - data = LFSR_DATA_DISK( - bptr.block, - bptr.off, - bptr.size); - - } else { - LFS_UNREACHABLE(); - } - - if (pos < bid-(weight-1) + lfsr_data_size(&data)) { - data = LFSR_DATA_DISK( - data.u.disk.block, - data.u.disk.off + pos - (bid-(weight-1)), - lfs_min32( - lfsr_data_size(&data) - - (pos - (bid-(weight-1))), - d)); - err = lfsr_bd_progdata(lfs, block, pos - block_pos, - data, - NULL); - if (err) { - return err; - } - - pos += lfsr_data_size(&data); - continue; - } - - // found a hole, just make sure next leaf takes priority - d = lfs_min32(d, bid+1 - pos); + err = lfsr_bd_progdata(lfs, block, pos - crystal_pos, + data, + NULL); + if (err) { + return err; } - // hole? we do fill with actual zeros here - // TODO do this more efficiently? - for (lfs_size_t i = 0; i < d; i++) { - err = lfsr_bd_prog(lfs, block, pos+i, &(uint8_t){0}, 1, - NULL); - if (err) { - return err; - } - } - pos += d; + pos += lfsr_data_size(&data); + size -= lfsr_data_size(&data); } // TODO validate? @@ -10081,52 +10143,30 @@ static int lfsr_file_flushinlined(lfs_t *lfs, lfsr_file_t *file) { return err; } - // setup our new block to be committed into the btree - pos__ = block_pos; - size__ = size; - tag__ = LFSR_TAG_BLOCK; - data__ = lfsr_data_frombptr(&(lfsr_bptr_t){ - .block=block, - .off=0, - .size=size}, bptr_buf); + // create our block pointer + lfsr_bptr_t bptr = { + .block = block, + .off = 0, + .size = pos - crystal_pos, + }; + + // and write it into our tree + uint8_t bptr_buf[LFSR_BPTR_DSIZE]; + err = lfsr_file_carvebtree(lfs, file, + crystal_pos, bptr.size, 0, + LFSR_TAG_BLOCK, lfsr_data_frombptr(&bptr, bptr_buf)); + if (err) { + return err; + } + + inlined_pos = crystal_pos + bptr.size; } + } - // commit to btree, carving out any underlying data - int err = lfsr_file_carvebtree(lfs, file, pos__, size__, 0, - tag__, data__); - if (err) { - return err; - } - - // remove any flushed data from shrub - // - // note this clears any data from 0, we should never have data < pos - // in our current flushinlined implementation and this coalesces any - // holes together - err = lfsr_file_carveinlined(lfs, file, 0, pos_ + size_, 0, - LFSR_DATA_NULL); - if (err) { - LFS_ASSERT(err != LFS_ERR_RANGE); - return err; - } - - // and remove any flushed data from our buffer - if (pos_ + size_ > file->buffer_pos) { - lfs_size_t d = lfs_min32( - pos_+size_ - file->buffer_pos, - file->buffer_size); - memmove(file->buffer, file->buffer + d, file->buffer_size - d); - file->buffer_pos += d; - file->buffer_size -= d; - } - - // TODO update buffer_pos/size? - -// // update our overflow estimate -// overflow -= LFSR_ATTR_ESTIMATE + size_; -// } - - // TODO + // at this point we should have flushed both our inlined data and + // our buffer + file->buffer_size = 0; + file->inlined.u.data = LFSR_DATA_DISK(0, 0, 0); return 0; } @@ -10304,8 +10344,11 @@ int lfsr_file_sync(lfs_t *lfs, lfsr_file_t *file) { ? LFSR_BPTR_DSIZE : LFSR_BTREE_DSIZE]; err = lfsr_mdir_commit(lfs, &file->m.mdir, LFSR_ATTRS( - LFSR_ATTR(file->m.mdir.mid, - WIDE(SHRUBTRUNK), 0, FILE(file, 0, 0)), + (lfsr_file_hasshrub(file) + ? LFSR_ATTR(file->m.mdir.mid, + WIDE(SHRUBTRUNK), 0, FILE(file, 0, 0)) + : LFSR_ATTR(file->m.mdir.mid, + RM(WIDE(STRUCT)), 0, NULL)), // and any btree metadata? (lfsr_file_hasbptr(file) ? LFSR_ATTR(file->m.mdir.mid, diff --git a/lfs.h b/lfs.h index c84317f5..340816fd 100644 --- a/lfs.h +++ b/lfs.h @@ -360,7 +360,7 @@ typedef struct lfsr_rbyd { typedef struct lfsr_bptr { // note size lines up with weight in lfsr_btree_t - lfs_soff_t size; + lfs_off_t size; lfs_block_t block; lfs_size_t off; // TODO how do we track ecksum? diff --git a/scripts/dbglfs.py b/scripts/dbglfs.py index 784d6e41..f930844e 100755 --- a/scripts/dbglfs.py +++ b/scripts/dbglfs.py @@ -1496,7 +1496,7 @@ def dbg_fstruct(f, block_size, btree, inlined=False, *, if not rbyd: print(' %04x.%04x: %*s %*s%s%s%s' % ( rbyd.block, rbyd.trunk, - m_width, + m_width, '', t_width, '', '\x1b[31m' if color else '', '(corrupted rbyd %s)' % rbyd.addr(), @@ -1764,7 +1764,7 @@ def main(disk, mroots=None, *, config.version[0] if config.version[0] is not None else '?', config.version[1] if config.version[1] is not None else '?', mroot.addr(), mroot.rev, bweight//mleaf_weight, 1*mleaf_weight, - config.block_limit+1, config.disk_limit+1)) + (config.block_limit or -1)+1, (config.disk_limit or -1)+1)) # dynamically size the id field w_width = max(