Partial implementation of a rudimentary mtree
This became surprisingly tricky. The main issue is knowing when to split mdirs, and how to determine this without wasting erase cycles. Unlike splitting btree nodes, we can't salvage failed compacts here. As soon as the salvage commit is written to disk, the commit becomes immediately visibile to the filesystem because it still exists in the mtree. This is a problem if we lose power. We're likely going to need to implement rbyd estimates. This is something I hoped to avoid because it brings in quite a bit of complexity and might lead to an annoying amount of storage waste since our estimates will need to be conservative to avoid unrecoverable situations. --- Also changed the on-disk btree/branch struct to store a copy of the weight. This was already required for the root of the btree, requiring the weight to be stored in every btree pointer allows better code deduplication at the cost of some redundancy on btree branches, where the weight is already implied by the rbyd structure. This weight is usually a single byte for most branches anyways. This may be worth revisiting at some point to see if there's any other unexpected tradeoffs.
This commit is contained in:
@@ -604,7 +604,11 @@ enum lfsr_tag_type {
|
|||||||
LFSR_TAG_BLOCK = 0x3100,
|
LFSR_TAG_BLOCK = 0x3100,
|
||||||
LFSR_TAG_BRANCH = 0x3200,
|
LFSR_TAG_BRANCH = 0x3200,
|
||||||
LFSR_TAG_MKBRANCH = 0x3204, // in-device only
|
LFSR_TAG_MKBRANCH = 0x3204, // in-device only
|
||||||
LFSR_TAG_BTREE = 0x3300,
|
LFSR_TAG_RMBRANCH = 0x3202,
|
||||||
|
// TODO do we actually need BTREE?
|
||||||
|
// LFSR_TAG_BTREE = 0x3300,
|
||||||
|
LFSR_TAG_MDIR = 0x3400,
|
||||||
|
LFSR_TAG_RMMDIR = 0x3402,
|
||||||
|
|
||||||
LFSR_TAG_UATTR = 0x4000,
|
LFSR_TAG_UATTR = 0x4000,
|
||||||
LFSR_TAG_MKUATTR = 0x4004, // in-device only
|
LFSR_TAG_MKUATTR = 0x4004, // in-device only
|
||||||
@@ -2704,31 +2708,42 @@ static int lfsr_rbyd_cutoff(lfs_t *lfs, const lfsr_rbyd_t *rbyd,
|
|||||||
|
|
||||||
// convenience operations
|
// convenience operations
|
||||||
|
|
||||||
|
// TODO need null btrees?
|
||||||
#define LFSR_BTREE_NULL ((lfsr_btree_t){.weight=0x80000000})
|
#define LFSR_BTREE_NULL ((lfsr_btree_t){.weight=0x80000000})
|
||||||
|
|
||||||
static bool lfsr_btree_isinlined(const lfsr_btree_t *btree) {
|
static inline bool lfsr_btree_isinlined(const lfsr_btree_t *btree) {
|
||||||
return btree->weight & 0x80000000;
|
return btree->weight & 0x80000000;
|
||||||
}
|
}
|
||||||
|
|
||||||
static lfs_size_t lfsr_btree_weight(const lfsr_btree_t *btree) {
|
static inline bool lfsr_btree_isnull(const lfsr_btree_t *btree) {
|
||||||
|
return lfsr_btree_isinlined(btree) && btree->inlined.tag == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline lfs_size_t lfsr_btree_weight(const lfsr_btree_t *btree) {
|
||||||
return btree->weight & 0x7fffffff;
|
return btree->weight & 0x7fffffff;
|
||||||
}
|
}
|
||||||
|
|
||||||
static lfs_size_t lfsr_btree_setinlined(lfs_size_t weight) {
|
static inline lfs_size_t lfsr_btree_setinlined(lfs_size_t weight) {
|
||||||
return weight | 0x80000000;
|
return weight | 0x80000000;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// branch on-disk encoding
|
// branch on-disk encoding
|
||||||
|
|
||||||
// 2 leb128 + 1 crc32c => 14 bytes (worst case)
|
// 3 leb128 + 1 crc32c => 19 bytes (worst case)
|
||||||
#define LFSR_BRANCH_DSIZE (5+5+4)
|
#define LFSR_BRANCH_DSIZE (5+5+5+4)
|
||||||
|
|
||||||
static lfs_ssize_t lfsr_branch_todisk(lfs_t *lfs, const lfsr_rbyd_t *branch,
|
static lfs_ssize_t lfsr_branch_todisk(lfs_t *lfs, const lfsr_rbyd_t *branch,
|
||||||
uint8_t buffer[static LFSR_BRANCH_DSIZE]) {
|
uint8_t buffer[static LFSR_BRANCH_DSIZE]) {
|
||||||
(void)lfs;
|
(void)lfs;
|
||||||
lfs_ssize_t d = 0;
|
lfs_ssize_t d = 0;
|
||||||
lfs_ssize_t d_ = lfs_toleb128(branch->trunk, &buffer[d], 5);
|
lfs_ssize_t d_ = lfs_toleb128(branch->weight, &buffer[d], 5);
|
||||||
|
if (d_ < 0) {
|
||||||
|
return d_;
|
||||||
|
}
|
||||||
|
d += d_;
|
||||||
|
|
||||||
|
d_ = lfs_toleb128(branch->trunk, &buffer[d], 5);
|
||||||
if (d_ < 0) {
|
if (d_ < 0) {
|
||||||
return d_;
|
return d_;
|
||||||
}
|
}
|
||||||
@@ -2747,15 +2762,19 @@ static lfs_ssize_t lfsr_branch_todisk(lfs_t *lfs, const lfsr_rbyd_t *branch,
|
|||||||
}
|
}
|
||||||
|
|
||||||
static lfs_ssize_t lfsr_branch_fromdisk(lfs_t *lfs, lfsr_rbyd_t *branch,
|
static lfs_ssize_t lfsr_branch_fromdisk(lfs_t *lfs, lfsr_rbyd_t *branch,
|
||||||
lfs_size_t weight, lfsr_data_t data) {
|
lfsr_data_t data) {
|
||||||
// we usually inherit weight from the parent
|
|
||||||
branch->weight = weight;
|
|
||||||
// setting off to 0 here will trigger asserts if we try to append
|
// setting off to 0 here will trigger asserts if we try to append
|
||||||
// without fetching first
|
// without fetching first
|
||||||
branch->off = 0;
|
branch->off = 0;
|
||||||
|
|
||||||
lfs_ssize_t d = 0;
|
lfs_ssize_t d = 0;
|
||||||
lfs_ssize_t d_ = lfsr_data_readleb128(lfs, data, d, &branch->trunk);
|
lfs_ssize_t d_ = lfsr_data_readleb128(lfs, data, d, &branch->weight);
|
||||||
|
if (d_ < 0) {
|
||||||
|
return d_;
|
||||||
|
}
|
||||||
|
d += d_;
|
||||||
|
|
||||||
|
d_ = lfsr_data_readleb128(lfs, data, d, &branch->trunk);
|
||||||
if (d_ < 0) {
|
if (d_ < 0) {
|
||||||
return d_;
|
return d_;
|
||||||
}
|
}
|
||||||
@@ -2776,6 +2795,52 @@ static lfs_ssize_t lfsr_branch_fromdisk(lfs_t *lfs, lfsr_rbyd_t *branch,
|
|||||||
return d;
|
return d;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// btree on-disk encoding
|
||||||
|
//
|
||||||
|
// note we leave disambiguating inlined/non-inlined btrees up to the caller
|
||||||
|
#define LFSR_BTREE_DSIZE ( \
|
||||||
|
LFSR_BRANCH_DSIZE > LFSR_BTREE_INLINESIZE \
|
||||||
|
? LFSR_BRANCH_DSIZE \
|
||||||
|
: LFSR_BTREE_INLINESIZE)
|
||||||
|
|
||||||
|
static lfs_ssize_t lfsr_btree_todisk(lfs_t *lfs, const lfsr_btree_t *btree,
|
||||||
|
lfsr_tag_t *tag_, uint8_t buffer[static LFSR_BTREE_DSIZE]) {
|
||||||
|
// we shouldn't write null btrees to disk, we just don't write out btrees
|
||||||
|
// if they're null
|
||||||
|
LFS_ASSERT(!lfsr_btree_isnull(btree));
|
||||||
|
// inlined?
|
||||||
|
if (lfsr_btree_isinlined(btree)) {
|
||||||
|
*tag_ = btree->inlined.tag;
|
||||||
|
memcpy(buffer, btree->inlined.buffer, btree->inlined.size);
|
||||||
|
return btree->inlined.size;
|
||||||
|
// not inlined
|
||||||
|
} else {
|
||||||
|
*tag_ = LFSR_TAG_BRANCH;
|
||||||
|
return lfsr_branch_todisk(lfs, &btree->root, buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO wait we actually need to store the weight on-disk for btrees
|
||||||
|
static lfs_ssize_t lfsr_btree_fromdisk(lfs_t *lfs, lfsr_btree_t *btree,
|
||||||
|
lfsr_tag_t tag, lfs_size_t weight, lfsr_data_t data) {
|
||||||
|
// inlined?
|
||||||
|
if (tag != LFSR_TAG_BRANCH) {
|
||||||
|
btree->weight = lfsr_btree_setinlined(weight);
|
||||||
|
btree->inlined.tag = tag;
|
||||||
|
lfs_ssize_t size = lfsr_data_read(lfs, data, 0,
|
||||||
|
btree->inlined.buffer, LFSR_BTREE_INLINESIZE);
|
||||||
|
if (size < 0) {
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
btree->inlined.size = size;
|
||||||
|
return size;
|
||||||
|
|
||||||
|
// not inlined
|
||||||
|
} else {
|
||||||
|
return lfsr_branch_fromdisk(lfs, &btree->root, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// B-tree operations
|
// B-tree operations
|
||||||
|
|
||||||
@@ -2857,6 +2922,9 @@ static int lfsr_btree_lookupnext(lfs_t *lfs,
|
|||||||
// each branch is a pair of optional name + on-disk structure
|
// each branch is a pair of optional name + on-disk structure
|
||||||
lfs_ssize_t rid__;
|
lfs_ssize_t rid__;
|
||||||
lfsr_tag_t tag__;
|
lfsr_tag_t tag__;
|
||||||
|
// TODO do we really need to fetch weight__ if we get it in our
|
||||||
|
// btree struct?
|
||||||
|
// TODO maybe only when validating?
|
||||||
lfs_size_t weight__;
|
lfs_size_t weight__;
|
||||||
lfsr_data_t data__;
|
lfsr_data_t data__;
|
||||||
int err = lfsr_rbyd_lookupnext(lfs, &branch, rid, 0,
|
int err = lfsr_rbyd_lookupnext(lfs, &branch, rid, 0,
|
||||||
@@ -2881,11 +2949,11 @@ static int lfsr_btree_lookupnext(lfs_t *lfs,
|
|||||||
rid -= (rid__ - (weight__-1));
|
rid -= (rid__ - (weight__-1));
|
||||||
|
|
||||||
// fetch the next branch
|
// fetch the next branch
|
||||||
lfs_ssize_t d = lfsr_branch_fromdisk(lfs, &branch,
|
lfs_ssize_t d = lfsr_branch_fromdisk(lfs, &branch, data__);
|
||||||
weight__, data__);
|
|
||||||
if (d < 0) {
|
if (d < 0) {
|
||||||
return d;
|
return d;
|
||||||
}
|
}
|
||||||
|
LFS_ASSERT(branch.weight == weight__);
|
||||||
|
|
||||||
// found our bid
|
// found our bid
|
||||||
} else {
|
} else {
|
||||||
@@ -2971,6 +3039,9 @@ static int lfsr_btree_parent(lfs_t *lfs,
|
|||||||
// each branch is a pair of optional name + on-disk structure
|
// each branch is a pair of optional name + on-disk structure
|
||||||
lfs_ssize_t rid__;
|
lfs_ssize_t rid__;
|
||||||
lfsr_tag_t tag__;
|
lfsr_tag_t tag__;
|
||||||
|
// TODO do we really need to fetch weight__ if we get it in our
|
||||||
|
// btree struct?
|
||||||
|
// TODO maybe only when validating?
|
||||||
lfs_size_t weight__;
|
lfs_size_t weight__;
|
||||||
lfsr_data_t data__;
|
lfsr_data_t data__;
|
||||||
int err = lfsr_rbyd_lookupnext(lfs, &branch, rid, 0,
|
int err = lfsr_rbyd_lookupnext(lfs, &branch, rid, 0,
|
||||||
@@ -2999,11 +3070,11 @@ static int lfsr_btree_parent(lfs_t *lfs,
|
|||||||
|
|
||||||
// fetch the next branch
|
// fetch the next branch
|
||||||
lfsr_rbyd_t branch_;
|
lfsr_rbyd_t branch_;
|
||||||
lfs_ssize_t d = lfsr_branch_fromdisk(lfs, &branch_,
|
lfs_ssize_t d = lfsr_branch_fromdisk(lfs, &branch_, data__);
|
||||||
weight__, data__);
|
|
||||||
if (d < 0) {
|
if (d < 0) {
|
||||||
return d;
|
return d;
|
||||||
}
|
}
|
||||||
|
LFS_ASSERT(branch_.weight == weight__);
|
||||||
|
|
||||||
// found our child?
|
// found our child?
|
||||||
if (branch_.block == child->block && branch_.trunk == child->trunk) {
|
if (branch_.block == child->block && branch_.trunk == child->trunk) {
|
||||||
@@ -3101,6 +3172,9 @@ static lfs_ssize_t lfsr_btree_namelookupnext(lfs_t *lfs,
|
|||||||
// lookup to find the weight.
|
// lookup to find the weight.
|
||||||
lfsr_tag_t tag__;
|
lfsr_tag_t tag__;
|
||||||
lfs_ssize_t rid__;
|
lfs_ssize_t rid__;
|
||||||
|
// TODO do we really need to fetch weight__ if we get it in our
|
||||||
|
// btree struct?
|
||||||
|
// TODO maybe only when validating?
|
||||||
lfs_size_t weight__;
|
lfs_size_t weight__;
|
||||||
lfsr_data_t data__;
|
lfsr_data_t data__;
|
||||||
err = lfsr_rbyd_lookupnext(lfs, &branch, find.found_id, 0,
|
err = lfsr_rbyd_lookupnext(lfs, &branch, find.found_id, 0,
|
||||||
@@ -3125,11 +3199,11 @@ static lfs_ssize_t lfsr_btree_namelookupnext(lfs_t *lfs,
|
|||||||
bid += rid__-(weight__-1);
|
bid += rid__-(weight__-1);
|
||||||
|
|
||||||
// fetch the next branch
|
// fetch the next branch
|
||||||
lfs_ssize_t d = lfsr_branch_fromdisk(lfs, &branch,
|
lfs_ssize_t d = lfsr_branch_fromdisk(lfs, &branch, data__);
|
||||||
weight__, data__);
|
|
||||||
if (d < 0) {
|
if (d < 0) {
|
||||||
return d;
|
return d;
|
||||||
}
|
}
|
||||||
|
LFS_ASSERT(branch.weight == weight__);
|
||||||
|
|
||||||
// found our id
|
// found our id
|
||||||
} else {
|
} else {
|
||||||
@@ -3468,6 +3542,7 @@ static int lfsr_btree_commit(lfs_t *lfs,
|
|||||||
// finalize commit
|
// finalize commit
|
||||||
err = lfsr_rbyd_commit(lfs, &rbyd_, NULL, 0);
|
err = lfsr_rbyd_commit(lfs, &rbyd_, NULL, 0);
|
||||||
if (err) {
|
if (err) {
|
||||||
|
LFS_ASSERT(err != LFS_ERR_RANGE);
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3627,6 +3702,8 @@ static int lfsr_btree_commit(lfs_t *lfs,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// try looking up the sibling
|
// try looking up the sibling
|
||||||
|
// TODO do we really need to fetch sweight if we get it in our
|
||||||
|
// btree struct?
|
||||||
lfs_size_t sweight;
|
lfs_size_t sweight;
|
||||||
err = lfsr_rbyd_lookupnext(lfs, &parent, sid, LFSR_TAG_NAME,
|
err = lfsr_rbyd_lookupnext(lfs, &parent, sid, LFSR_TAG_NAME,
|
||||||
&sid, &stag, &sweight, &sdata);
|
&sid, &stag, &sweight, &sdata);
|
||||||
@@ -3652,10 +3729,11 @@ static int lfsr_btree_commit(lfs_t *lfs,
|
|||||||
goto merge_abort;
|
goto merge_abort;
|
||||||
}
|
}
|
||||||
|
|
||||||
d = lfsr_branch_fromdisk(lfs, &sibling, sweight, sdata);
|
d = lfsr_branch_fromdisk(lfs, &sibling, sdata);
|
||||||
if (d < 0) {
|
if (d < 0) {
|
||||||
return d;
|
return d;
|
||||||
}
|
}
|
||||||
|
LFS_ASSERT(sibling.weight == sweight);
|
||||||
|
|
||||||
// try to add our sibling's tags to our rbyd
|
// try to add our sibling's tags to our rbyd
|
||||||
lfs_size_t rweight_ = rbyd_.weight;
|
lfs_size_t rweight_ = rbyd_.weight;
|
||||||
@@ -4151,7 +4229,47 @@ static lfsr_mpair_t lfsr_mdir_mpair(const lfsr_mdir_t *mdir) {
|
|||||||
return LFSR_MPAIR(mdir->rbyd.block, mdir->other_block);
|
return LFSR_MPAIR(mdir->rbyd.block, mdir->other_block);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int lfsr_mdir_fetch(lfs_t *lfs, lfsr_mdir_t *mdir, lfsr_mpair_t mpair,
|
static int lfsr_mdir_alloc(lfs_t *lfs, lfsr_mdir_t *mdir, lfs_ssize_t mid) {
|
||||||
|
// allocate two blocks
|
||||||
|
lfs_block_t blocks[2];
|
||||||
|
for (int i = 0; i < 2; i++) {
|
||||||
|
int err = lfs_alloc(lfs, &blocks[i]);
|
||||||
|
if (err) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// rather than performing an unecessary erase, treat the current contents
|
||||||
|
// of the other block as a valid revision count
|
||||||
|
uint32_t rev;
|
||||||
|
int err = lfsr_bd_read(lfs, blocks[1], 0, 0, &rev, sizeof(uint32_t));
|
||||||
|
if (err && err != LFS_ERR_CORRUPT) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
// note we can allow blocks that report corrupt here, with the assumption
|
||||||
|
// that all future reads will also return corrupt, this can happen if
|
||||||
|
// the underlying block device uses ECC that may be invalid when
|
||||||
|
// uninitialized
|
||||||
|
if (err == LFS_ERR_CORRUPT) {
|
||||||
|
rev = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO align to block_cycles
|
||||||
|
|
||||||
|
// setup mdir struct
|
||||||
|
mdir->mid = mid;
|
||||||
|
mdir->other_block = blocks[0];
|
||||||
|
mdir->rbyd.weight = 0;
|
||||||
|
mdir->rbyd.block = blocks[1];
|
||||||
|
// mark mdir as needing compaction
|
||||||
|
mdir->rbyd.off = lfs->cfg->block_size;
|
||||||
|
mdir->rbyd.trunk = 0;
|
||||||
|
mdir->rbyd.rev = rev;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int lfsr_mdir_fetch(lfs_t *lfs, lfsr_mdir_t *mdir,
|
||||||
|
lfs_ssize_t mid, lfsr_mpair_t mpair,
|
||||||
lfsr_find_t *find) {
|
lfsr_find_t *find) {
|
||||||
// read both revision counts, try to figure out which block
|
// read both revision counts, try to figure out which block
|
||||||
// has the most recent revision
|
// has the most recent revision
|
||||||
@@ -4173,29 +4291,25 @@ static int lfsr_mdir_fetch(lfs_t *lfs, lfsr_mdir_t *mdir, lfsr_mpair_t mpair,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// try to fetch rbyds in the order of most recent to least recent
|
// try to fetch rbyds in the order of most recent to least recent
|
||||||
for (int i = 0;; i++) {
|
for (int i = 0; i < 2; i++) {
|
||||||
int err = lfsr_rbyd_fetch(lfs, &mdir->rbyd, mpair.blocks[0], 0, find);
|
int err = lfsr_rbyd_fetch(lfs, &mdir->rbyd, mpair.blocks[0], 0, find);
|
||||||
if (err && err != LFS_ERR_CORRUPT) {
|
if (err && err != LFS_ERR_CORRUPT) {
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (err) {
|
if (!err) {
|
||||||
// could not find a non-corrupt rbyd
|
mdir->mid = mid;
|
||||||
if (i >= 2-1) {
|
// keep track of other block for compactions
|
||||||
return LFS_ERR_CORRUPT;
|
mdir->other_block = mpair.blocks[1];
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
lfs_swap32(&mpair.blocks[0], &mpair.blocks[1]);
|
lfs_swap32(&mpair.blocks[0], &mpair.blocks[1]);
|
||||||
lfs_swap32(&revs[0], &revs[1]);
|
lfs_swap32(&revs[0], &revs[1]);
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
// could not find a non-corrupt rbyd
|
||||||
}
|
return LFS_ERR_CORRUPT;
|
||||||
|
|
||||||
// keep track of other block for compactions
|
|
||||||
mdir->other_block = mpair.blocks[1];
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int lfsr_mdir_lookupnext(lfs_t *lfs, const lfsr_mdir_t *mdir,
|
static int lfsr_mdir_lookupnext(lfs_t *lfs, const lfsr_mdir_t *mdir,
|
||||||
@@ -4219,8 +4333,14 @@ static lfs_ssize_t lfsr_mdir_get(lfs_t *lfs, const lfsr_mdir_t *mdir,
|
|||||||
return lfsr_rbyd_get(lfs, &mdir->rbyd, id, tag, buffer, size);
|
return lfsr_rbyd_get(lfs, &mdir->rbyd, id, tag, buffer, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO how much of this code can we share with btree_commit?
|
||||||
|
// TODO share commit?
|
||||||
|
// TODO share split?
|
||||||
|
// TODO would be awfully convenient if c supported multiple returns
|
||||||
static int lfsr_mdir_commit(lfs_t *lfs, lfsr_mdir_t *mdir,
|
static int lfsr_mdir_commit(lfs_t *lfs, lfsr_mdir_t *mdir,
|
||||||
const lfsr_attr_t *attrs, lfs_size_t attr_count) {
|
const lfsr_attr_t *attrs, lfs_size_t attr_count) {
|
||||||
|
// TODO wait do we really need this loop?
|
||||||
|
while (true) {
|
||||||
// try to commit
|
// try to commit
|
||||||
int err = lfsr_rbyd_commit(lfs, &mdir->rbyd, attrs, attr_count);
|
int err = lfsr_rbyd_commit(lfs, &mdir->rbyd, attrs, attr_count);
|
||||||
if (err && err != LFS_ERR_RANGE) {
|
if (err && err != LFS_ERR_RANGE) {
|
||||||
@@ -4231,28 +4351,41 @@ static int lfsr_mdir_commit(lfs_t *lfs, lfsr_mdir_t *mdir,
|
|||||||
// can't commit, try to compact
|
// can't commit, try to compact
|
||||||
// TODO splits
|
// TODO splits
|
||||||
// TODO relocations
|
// TODO relocations
|
||||||
|
lfsr_mdir_t mdir_;
|
||||||
|
bool issupermdirsplit = false; // TODO do this differently?
|
||||||
|
lfs_size_t lower_dsize = 0;
|
||||||
if (err) {
|
if (err) {
|
||||||
// prepare the other block
|
// normally the new mdir is just the flipped version of our
|
||||||
// TODO rev quirks?
|
// current mdir
|
||||||
lfsr_rbyd_t rbyd_ = (lfsr_rbyd_t){
|
mdir_ = (lfsr_mdir_t){
|
||||||
.block=mdir->other_block,
|
.mid = mdir->mid,
|
||||||
.rev=mdir->rbyd.rev+1,
|
.other_block = mdir->rbyd.block,
|
||||||
.off=0,
|
.rbyd.block = mdir->other_block,
|
||||||
.trunk=0
|
// TODO rev things
|
||||||
|
.rbyd.rev = mdir->rbyd.rev + 1,
|
||||||
|
.rbyd.off = 0,
|
||||||
|
.rbyd.trunk = 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
int err = lfsr_bd_erase(lfs, rbyd_.block);
|
// TODO does this work with a chain of supermdirs?
|
||||||
if (err) {
|
// We do something a bit different here if we're the supermdir.
|
||||||
return err;
|
//
|
||||||
}
|
// Unlike btree splits, we can't resolve the transition to a
|
||||||
|
// non-inlined mtree with a single pcache. To work around this
|
||||||
// try to copy over ids
|
// we estimate a worst-case size before compacting. This is more
|
||||||
lfs_ssize_t id = 0;
|
// expensive in terms of reads, but avoids multiple erases.
|
||||||
|
if (lfsr_btree_isnull(&lfs->mtree)) {
|
||||||
|
// estimate the worst-case rbyd size
|
||||||
|
// TODO function for this?
|
||||||
|
lfs_size_t dsize = 4; // 4 bytes for rev
|
||||||
|
lfs_size_t dcount = 0;
|
||||||
|
lfs_ssize_t id = -1;
|
||||||
lfsr_tag_t tag = 0;
|
lfsr_tag_t tag = 0;
|
||||||
while (true) {
|
while (true) {
|
||||||
lfs_size_t w;
|
lfs_size_t w;
|
||||||
lfsr_data_t data;
|
lfsr_data_t data;
|
||||||
err = lfsr_rbyd_lookupnext(lfs, &mdir->rbyd, id, lfsr_tag_next(tag),
|
err = lfsr_rbyd_lookupnext(lfs, &mdir->rbyd,
|
||||||
|
id, lfsr_tag_next(tag),
|
||||||
&id, &tag, &w, &data);
|
&id, &tag, &w, &data);
|
||||||
if (err && err != LFS_ERR_NOENT) {
|
if (err && err != LFS_ERR_NOENT) {
|
||||||
return err;
|
return err;
|
||||||
@@ -4261,34 +4394,218 @@ static int lfsr_mdir_commit(lfs_t *lfs, lfsr_mdir_t *mdir,
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// keep track of size and count of tags
|
||||||
|
dsize += LFSR_TAG_DSIZE + lfsr_data_size(data);
|
||||||
|
dcount += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO account for block_size limits in attr size dsizes?
|
||||||
|
// account for alt pointers
|
||||||
|
dsize += dcount*(LFSR_TAG_DSIZE * (2*lfs_nlog2(dcount)+1));
|
||||||
|
|
||||||
|
// keep rbyd < our compaction threshold (1/2) to avoid
|
||||||
|
// degenerate cases
|
||||||
|
if (dsize > lfs->cfg->block_size/2) {
|
||||||
|
// if we're an inlined mtree, convert to a normal tree
|
||||||
|
// _before_ splitting, this handles two cases nicely:
|
||||||
|
// 1. if our supermetadata takes up enough space we just
|
||||||
|
// need one child
|
||||||
|
// 2. if we need two children we need to separate the
|
||||||
|
// supermetadata out of the tree anyways
|
||||||
|
int err = lfsr_mdir_alloc(lfs, &mdir_, -2);
|
||||||
|
if (err) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO should mdir_alloc return something different?
|
||||||
|
// prepare for compact
|
||||||
|
mdir_.rbyd.off = 0;
|
||||||
|
issupermdirsplit = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int err = lfsr_bd_erase(lfs, mdir_.rbyd.block);
|
||||||
|
if (err) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
// try to copy over ids
|
||||||
|
//
|
||||||
|
// note we skip -1 ids if we're splitting our supermdir
|
||||||
|
lfs_ssize_t id = (issupermdirsplit ? 0 : -1);
|
||||||
|
lfsr_tag_t tag = 0;
|
||||||
|
while (true) {
|
||||||
|
lfs_size_t w;
|
||||||
|
lfsr_data_t data;
|
||||||
|
err = lfsr_rbyd_lookupnext(lfs, &mdir->rbyd,
|
||||||
|
id, lfsr_tag_next(tag),
|
||||||
|
&id, &tag, &w, &data);
|
||||||
|
if (err && err != LFS_ERR_NOENT) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
if (err == LFS_ERR_NOENT) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// keep track of worst-case encoding size in case we need to
|
||||||
|
// split
|
||||||
|
lower_dsize += LFSR_TAG_DSIZE + lfsr_data_size(data);
|
||||||
|
|
||||||
// append the attr
|
// append the attr
|
||||||
err = lfsr_rbyd_append(lfs, &rbyd_,
|
err = lfsr_rbyd_append(lfs, &mdir_.rbyd,
|
||||||
id-lfs_smax32(w-1, 0), lfsr_tag_setmk(tag), +w,
|
id-lfs_smax32(w-1, 0), lfsr_tag_setmk(tag), +w,
|
||||||
data);
|
data);
|
||||||
if (err) {
|
if (err) {
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO split on exceeding 1/2 block size
|
|
||||||
// keep rbyd < our compaction threshold (1/2) to avoid
|
// keep rbyd < our compaction threshold (1/2) to avoid
|
||||||
// degenerate cases
|
// degenerate cases
|
||||||
if (rbyd_.off > lfs->cfg->block_size/2) {
|
if (mdir_.rbyd.off > lfs->cfg->block_size/2) {
|
||||||
// TODO
|
LFS_ASSERT(!lfsr_btree_isnull(&lfs->mtree)
|
||||||
LFS_ASSERT(false);
|
|| issupermdirsplit);
|
||||||
|
goto split;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// append any pending attrs, it's up to upper
|
// commit pending attrs, taking care to split supermdir attrs
|
||||||
// layers to make sure these always fit
|
// from regular attrs if there is an mdir split or mtree update
|
||||||
err = lfsr_rbyd_commit(lfs, &rbyd_, attrs, attr_count);
|
//
|
||||||
|
// note we assume supermdir attrs are any -1 ids for now
|
||||||
|
for (lfs_size_t i = 0; i < attr_count; i++) {
|
||||||
|
if (!issupermdirsplit || attrs[i].id >= 0) {
|
||||||
|
err = lfsr_rbyd_append(lfs, &mdir_.rbyd,
|
||||||
|
attrs[i].id, attrs[i].tag, attrs[i].delta,
|
||||||
|
attrs[i].data);
|
||||||
|
if (err) {
|
||||||
|
LFS_ASSERT(err != LFS_ERR_RANGE);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// finalize commit
|
||||||
|
err = lfsr_rbyd_commit(lfs, &mdir_.rbyd, NULL, 0);
|
||||||
if (err) {
|
if (err) {
|
||||||
LFS_ASSERT(err != LFS_ERR_RANGE);
|
LFS_ASSERT(err != LFS_ERR_RANGE);
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
// update our mdir
|
// update our mdir
|
||||||
mdir->other_block = mdir->rbyd.block;
|
//
|
||||||
mdir->rbyd = rbyd_;
|
// note we take care not to clobber the supermdir
|
||||||
|
// TODO ???
|
||||||
|
if (!(issupermdirsplit && mdir == &lfs->supermdir)) {
|
||||||
|
*mdir = mdir_;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (issupermdirsplit) {
|
||||||
|
// update our mtree
|
||||||
|
uint8_t buf[LFSR_MPAIR_DSIZE];
|
||||||
|
lfs_ssize_t d = lfsr_mpair_todisk(lfs, lfsr_mdir_mpair(&mdir_),
|
||||||
|
buf);
|
||||||
|
if (d < 0) {
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = lfsr_btree_push(lfs, &lfs->mtree, 0, LFSR_TAG_MDIR, 1,
|
||||||
|
buf, d);
|
||||||
|
if (err) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
// we only reach this point if our supermdir is in need of
|
||||||
|
// compaction, so go ahead and compact
|
||||||
|
mdir_ = (lfsr_mdir_t){
|
||||||
|
.mid = -1,
|
||||||
|
.other_block = lfs->supermdir.rbyd.block,
|
||||||
|
.rbyd.block = lfs->supermdir.other_block,
|
||||||
|
// TODO rev things
|
||||||
|
.rbyd.rev = lfs->supermdir.rbyd.rev + 1,
|
||||||
|
.rbyd.off = 0,
|
||||||
|
.rbyd.trunk = 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
int err = lfsr_bd_erase(lfs, mdir_.rbyd.block);
|
||||||
|
if (err) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
// try to copy over ids, since we split the supermdir
|
||||||
|
// we should only copy over supermdir attrs
|
||||||
|
//
|
||||||
|
// note we assume supermdir attrs are any -1 ids for now
|
||||||
|
lfs_ssize_t id = -1;
|
||||||
|
lfsr_tag_t tag = 0;
|
||||||
|
while (true) {
|
||||||
|
lfs_size_t w;
|
||||||
|
lfsr_data_t data;
|
||||||
|
err = lfsr_rbyd_lookupnext(lfs, &mdir->rbyd,
|
||||||
|
id, lfsr_tag_next(tag),
|
||||||
|
&id, &tag, &w, &data);
|
||||||
|
if (err && err != LFS_ERR_NOENT) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
if (err == LFS_ERR_NOENT || id != -1) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO we could clean this up if we don't deduplicate, but
|
||||||
|
// we should probably deduplicate all lfsr_rbyd_compact
|
||||||
|
// things
|
||||||
|
// append the attr
|
||||||
|
err = lfsr_rbyd_append(lfs, &mdir_.rbyd,
|
||||||
|
id-lfs_smax32(w-1, 0), lfsr_tag_setmk(tag), +w,
|
||||||
|
data);
|
||||||
|
if (err) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
// this must always fit our compaction threshold (1/2)
|
||||||
|
LFS_ASSERT(mdir_.rbyd.off > lfs->cfg->block_size/2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// commit pending attrs, but only if they belong in the
|
||||||
|
// supermdir
|
||||||
|
for (lfs_size_t i = 0; i < attr_count; i++) {
|
||||||
|
if (attrs[i].id == -1) {
|
||||||
|
err = lfsr_rbyd_append(lfs, &mdir_.rbyd,
|
||||||
|
attrs[i].id, attrs[i].tag, attrs[i].delta,
|
||||||
|
attrs[i].data);
|
||||||
|
if (err) {
|
||||||
|
LFS_ASSERT(err != LFS_ERR_RANGE);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// finalize commit, and update the mtree
|
||||||
|
uint8_t buf_[LFSR_BTREE_DSIZE];
|
||||||
|
d = lfsr_btree_todisk(lfs, &lfs->mtree, &tag, buf_);
|
||||||
|
if (d < 0) {
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = lfsr_rbyd_commit(lfs, &mdir_.rbyd, LFSR_ATTRS(
|
||||||
|
// TODO yeah we're going to need a wide-rm
|
||||||
|
LFSR_ATTR(-1, RMMDIR, 0, NULL, 0),
|
||||||
|
LFSR_ATTR(-1, RMBRANCH, 0, NULL, 0),
|
||||||
|
LFSR_ATTR_(-1, tag, 0, buf_, d)));
|
||||||
|
if (err) {
|
||||||
|
LFS_ASSERT(err != LFS_ERR_RANGE);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
// update the supermdir
|
||||||
|
lfs->supermdir = mdir_;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// done!
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
split:;
|
||||||
|
LFS_ASSERT(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// done
|
// done
|
||||||
@@ -4296,6 +4613,7 @@ static int lfsr_mdir_commit(lfs_t *lfs, lfsr_mdir_t *mdir,
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/// Superblock things ///
|
/// Superblock things ///
|
||||||
|
|
||||||
// These are all leb128s, but we can expect smaller encodings
|
// These are all leb128s, but we can expect smaller encodings
|
||||||
@@ -4403,7 +4721,7 @@ static int lfsr_mountinited(lfs_t *lfs) {
|
|||||||
tortoise_i += 1;
|
tortoise_i += 1;
|
||||||
|
|
||||||
// fetch next possible superblock
|
// fetch next possible superblock
|
||||||
int err = lfsr_mdir_fetch(lfs, &mdir, mpair, NULL);
|
int err = lfsr_mdir_fetch(lfs, &mdir, -1, mpair, NULL);
|
||||||
if (err) {
|
if (err) {
|
||||||
LFS_ERROR("No littlefs superblock found");
|
LFS_ERROR("No littlefs superblock found");
|
||||||
// treat corrupt errors as invalid littlefs images
|
// treat corrupt errors as invalid littlefs images
|
||||||
@@ -4659,27 +4977,34 @@ static int lfsr_mountinited(lfs_t *lfs) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
// do we have an mtree? this could be either a single mdir or a btree
|
||||||
}
|
// of mdirs
|
||||||
|
lfs_ssize_t id;
|
||||||
int lfsr_mount(lfs_t *lfs, const struct lfs_config *cfg) {
|
lfsr_tag_t tag;
|
||||||
int err = lfs_init(lfs, cfg);
|
lfsr_data_t data;
|
||||||
if (err) {
|
int err = lfsr_mdir_lookupnext(lfs, &mdir, -1, LFSR_TAG_STRUCT,
|
||||||
|
&id, &tag, NULL, &data);
|
||||||
|
if (err && err != LFS_ERR_NOENT) {
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
err = lfsr_mountinited(lfs);
|
if (err != LFS_ERR_NOENT && id == -1) {
|
||||||
if (err) {
|
if (tag != LFSR_TAG_MDIR && tag != LFSR_TAG_BRANCH) {
|
||||||
// make sure we clean up on error
|
LFS_ERROR("Weird superstruct? 0x%"PRIx32, tag);
|
||||||
lfs_deinit(lfs);
|
return LFS_ERR_CORRUPT;
|
||||||
return err;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
lfs_ssize_t d = lfsr_btree_fromdisk(lfs, &lfs->mtree, tag, 1, data);
|
||||||
}
|
if (d < 0) {
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// TODO null?
|
||||||
|
lfs->mtree = LFSR_BTREE_NULL;
|
||||||
|
}
|
||||||
|
|
||||||
int lfsr_unmount(lfs_t *lfs) {
|
lfs->supermdir = mdir;
|
||||||
return lfs_deinit(lfs);
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int lfsr_formatinited(lfs_t *lfs) {
|
static int lfsr_formatinited(lfs_t *lfs) {
|
||||||
@@ -4716,12 +5041,54 @@ static int lfsr_formatinited(lfs_t *lfs) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int lfsr_mount(lfs_t *lfs, const struct lfs_config *cfg) {
|
||||||
|
int err = lfs_init(lfs, cfg);
|
||||||
|
if (err) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO this is a big hack to scaffold things until we have a working
|
||||||
|
// block allocator
|
||||||
|
//
|
||||||
|
// create free lookahead
|
||||||
|
memset(lfs->free.buffer, 0, lfs->cfg->lookahead_size);
|
||||||
|
lfs->free.off = 0;
|
||||||
|
lfs->free.size = lfs_min(8*lfs->cfg->lookahead_size,
|
||||||
|
lfs->cfg->block_count);
|
||||||
|
lfs->free.i = 2;
|
||||||
|
lfs_alloc_ack(lfs);
|
||||||
|
|
||||||
|
err = lfsr_mountinited(lfs);
|
||||||
|
if (err) {
|
||||||
|
// make sure we clean up on error
|
||||||
|
lfs_deinit(lfs);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int lfsr_unmount(lfs_t *lfs) {
|
||||||
|
return lfs_deinit(lfs);
|
||||||
|
}
|
||||||
|
|
||||||
int lfsr_format(lfs_t *lfs, const struct lfs_config *cfg) {
|
int lfsr_format(lfs_t *lfs, const struct lfs_config *cfg) {
|
||||||
int err = lfs_init(lfs, cfg);
|
int err = lfs_init(lfs, cfg);
|
||||||
if (err) {
|
if (err) {
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO this is a big hack to scaffold things until we have a working
|
||||||
|
// block allocator
|
||||||
|
//
|
||||||
|
// create free lookahead
|
||||||
|
memset(lfs->free.buffer, 0, lfs->cfg->lookahead_size);
|
||||||
|
lfs->free.off = 0;
|
||||||
|
lfs->free.size = lfs_min(8*lfs->cfg->lookahead_size,
|
||||||
|
lfs->cfg->block_count);
|
||||||
|
lfs->free.i = 2;
|
||||||
|
lfs_alloc_ack(lfs);
|
||||||
|
|
||||||
err = lfsr_formatinited(lfs);
|
err = lfsr_formatinited(lfs);
|
||||||
if (err) {
|
if (err) {
|
||||||
// make sure we clean up on error
|
// make sure we clean up on error
|
||||||
|
|||||||
@@ -371,8 +371,12 @@ typedef union lfsr_btree {
|
|||||||
} lfsr_btree_t;
|
} lfsr_btree_t;
|
||||||
|
|
||||||
typedef struct lfsr_mdir {
|
typedef struct lfsr_mdir {
|
||||||
lfsr_rbyd_t rbyd;
|
// -2 => an out-of-tree mdir
|
||||||
|
// -1 => supermdir
|
||||||
|
// >=0 => bid in the mtree
|
||||||
|
lfs_ssize_t mid;
|
||||||
lfs_block_t other_block;
|
lfs_block_t other_block;
|
||||||
|
lfsr_rbyd_t rbyd;
|
||||||
} lfsr_mdir_t;
|
} lfsr_mdir_t;
|
||||||
|
|
||||||
|
|
||||||
@@ -464,6 +468,10 @@ typedef struct lfs {
|
|||||||
lfs_size_t file_max;
|
lfs_size_t file_max;
|
||||||
lfs_size_t attr_max;
|
lfs_size_t attr_max;
|
||||||
|
|
||||||
|
// begin lfsr things
|
||||||
|
lfsr_mdir_t supermdir;
|
||||||
|
lfsr_btree_t mtree;
|
||||||
|
|
||||||
#ifdef LFS_MIGRATE
|
#ifdef LFS_MIGRATE
|
||||||
struct lfs1 *lfs1;
|
struct lfs1 *lfs1;
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
+4
-3
@@ -367,9 +367,10 @@ def main(disk, root=0, *,
|
|||||||
# is it another branch? continue down tree
|
# is it another branch? continue down tree
|
||||||
if struct_tag == TAG_BRANCH and (
|
if struct_tag == TAG_BRANCH and (
|
||||||
depth is None or depth_ < depth):
|
depth is None or depth_ < depth):
|
||||||
trunk, d1 = fromleb128(struct_)
|
w, d1 = fromleb128(struct_)
|
||||||
block, d2 = fromleb128(struct_[d1:])
|
trunk, d2 = fromleb128(struct_[d1:])
|
||||||
crc = fromle32(struct_[d1+d2:])
|
block, d3 = fromleb128(struct_[d1+d2:])
|
||||||
|
crc = fromle32(struct_[d1+d2+d3:])
|
||||||
rbyd = Rbyd.fetch(f, block_size, block, trunk)
|
rbyd = Rbyd.fetch(f, block_size, block, trunk)
|
||||||
|
|
||||||
# corrupted? bail here so we can keep traversing the tree
|
# corrupted? bail here so we can keep traversing the tree
|
||||||
|
|||||||
@@ -0,0 +1,78 @@
|
|||||||
|
# test a single supermdir
|
||||||
|
[cases.test_mtree_one_supermdir]
|
||||||
|
code = '''
|
||||||
|
lfs_t lfs;
|
||||||
|
lfsr_format(&lfs, cfg) => 0;
|
||||||
|
lfsr_mount(&lfs, cfg) => 0;
|
||||||
|
lfsr_unmount(&lfs) => 0;
|
||||||
|
'''
|
||||||
|
|
||||||
|
# test a single supermdir with many commits
|
||||||
|
[cases.test_mtree_one_supermdir_many_commits]
|
||||||
|
defines.N = 5000
|
||||||
|
in = 'lfs.c'
|
||||||
|
code = '''
|
||||||
|
lfs_t lfs;
|
||||||
|
lfsr_format(&lfs, cfg) => 0;
|
||||||
|
|
||||||
|
for (lfs_size_t i = 0; i < N; i++) {
|
||||||
|
lfsr_mount(&lfs, cfg) => 0;
|
||||||
|
lfsr_mdir_commit(&lfs, &lfs.supermdir, NULL, 0) => 0;
|
||||||
|
lfsr_unmount(&lfs) => 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
lfsr_mount(&lfs, cfg) => 0;
|
||||||
|
lfsr_unmount(&lfs) => 0;
|
||||||
|
'''
|
||||||
|
|
||||||
|
# TODO test many supermdirs
|
||||||
|
|
||||||
|
# try creating a few entries in our mdir
|
||||||
|
[cases.test_mtree_entries]
|
||||||
|
defines.N = 5
|
||||||
|
in = 'lfs.c'
|
||||||
|
code = '''
|
||||||
|
lfs_t lfs;
|
||||||
|
lfsr_format(&lfs, cfg) => 0;
|
||||||
|
const char *alphas = "abcdefghijklmnopqrstuvwxyz";
|
||||||
|
|
||||||
|
lfsr_mount(&lfs, cfg) => 0;
|
||||||
|
for (lfs_size_t i = 0; i < N; i++) {
|
||||||
|
lfsr_mdir_commit(&lfs, &lfs.supermdir, LFSR_ATTRS(
|
||||||
|
LFSR_ATTR(i, MKREG, +1, &alphas[i % 26], 1))) => 0;
|
||||||
|
}
|
||||||
|
lfsr_unmount(&lfs) => 0;
|
||||||
|
|
||||||
|
lfsr_mount(&lfs, cfg) => 0;
|
||||||
|
for (lfs_size_t i = 0; i < N; i++) {
|
||||||
|
uint8_t buffer[4];
|
||||||
|
lfsr_mdir_get(&lfs, &lfs.supermdir, i, LFSR_TAG_REG, buffer, 4) => 1;
|
||||||
|
assert(memcmp(buffer, &alphas[i % 26], 1) == 0);
|
||||||
|
}
|
||||||
|
lfsr_unmount(&lfs) => 0;
|
||||||
|
'''
|
||||||
|
|
||||||
|
## try creating many entries, this should trigger a split
|
||||||
|
#[cases.test_mtree_split]
|
||||||
|
#defines.N = 5000
|
||||||
|
#in = 'lfs.c'
|
||||||
|
#code = '''
|
||||||
|
# lfs_t lfs;
|
||||||
|
# lfsr_format(&lfs, cfg) => 0;
|
||||||
|
# const char *alphas = "abcdefghijklmnopqrstuvwxyz";
|
||||||
|
#
|
||||||
|
# lfsr_mount(&lfs, cfg) => 0;
|
||||||
|
# for (lfs_size_t i = 0; i < N; i++) {
|
||||||
|
# lfsr_mdir_commit(&lfs, &lfs.supermdir, LFSR_ATTRS(
|
||||||
|
# LFSR_ATTR(i, MKREG, +1, &alphas[i % 26], 1))) => 0;
|
||||||
|
# }
|
||||||
|
# lfsr_unmount(&lfs) => 0;
|
||||||
|
#
|
||||||
|
# lfsr_mount(&lfs, cfg) => 0;
|
||||||
|
# for (lfs_size_t i = 0; i < N; i++) {
|
||||||
|
# uint8_t buffer[4];
|
||||||
|
# lfsr_mdir_get(&lfs, &lfs.supermdir, i, LFSR_TAG_REG, buffer, 4) => 1;
|
||||||
|
# assert(memcmp(buffer, &alphas[i % 26], 1) == 0);
|
||||||
|
# }
|
||||||
|
# lfsr_unmount(&lfs) => 0;
|
||||||
|
#'''
|
||||||
@@ -42,12 +42,11 @@ code = '''
|
|||||||
lfs_t lfs;
|
lfs_t lfs;
|
||||||
lfsr_format(&lfs, cfg) => 0;
|
lfsr_format(&lfs, cfg) => 0;
|
||||||
lfsr_mount(&lfs, cfg) => 0;
|
lfsr_mount(&lfs, cfg) => 0;
|
||||||
lfsr_mdir_t mdir;
|
|
||||||
lfsr_mdir_fetch(&lfs, &mdir, LFSR_MPAIR(0, 1), NULL) => 0;
|
|
||||||
uint8_t buf[LFSR_MPAIR_DSIZE];
|
uint8_t buf[LFSR_MPAIR_DSIZE];
|
||||||
lfs_ssize_t d = lfsr_mpair_todisk(&lfs, LFSR_MPAIR(0, 1), buf);
|
lfs_ssize_t d = lfsr_mpair_todisk(&lfs,
|
||||||
|
lfsr_mdir_mpair(&lfs.supermdir), buf);
|
||||||
assert(d >= 0);
|
assert(d >= 0);
|
||||||
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
lfsr_mdir_commit(&lfs, &lfs.supermdir, LFSR_ATTRS(
|
||||||
LFSR_ATTR(-1, SUPERMDIR, 0, buf, d))) => 0;
|
LFSR_ATTR(-1, SUPERMDIR, 0, buf, d))) => 0;
|
||||||
lfsr_unmount(&lfs) => 0;
|
lfsr_unmount(&lfs) => 0;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user