Adopted mtree traversal in lfsr_mountinited

This is a nice bit of deduplication as long as the mtree traversal can
handle both:

1. Cycle detection
2. Btree node validation

Eventually we'll also collect gstate here, which mtree traversal should
make quite easy.

The only catch is if we eventually need a non-fetching way to read the
mroot config, such as if we need to infer the csum type or block-size,
but that's a future problem.
This commit is contained in:
Christopher Haster
2023-05-27 17:30:30 -05:00
parent 1631ca8d78
commit 773278eb26
+229 -269
View File
@@ -6766,84 +6766,111 @@ static int lfs_init(lfs_t *lfs, const struct lfs_config *cfg);
static int lfs_deinit(lfs_t *lfs);
static int lfsr_mountinited(lfs_t *lfs) {
// scan for the first non-fake superblock
lfsr_mpair_t mpair = LFSR_MPAIR(0, 1);
lfsr_mdir_t mdir;
// detect cycles using Brent's algorithm
lfsr_mpair_t tortoise = LFSR_MPAIR(-1, -1);
lfs_size_t tortoise_i = 1;
lfs_size_t tortoise_period = 1;
// traverse the mtree rooted at mroot 0x{1,0}
//
// note that lfsr_mtree_traversal_next will update our mroot/mtree
// based on what mroots it finds
//
// we do validate btree inner nodes here, how can we trust our
// mdirs are valid if we haven't checked the btree inner nodes at
// least once?
lfsr_mtree_traversal_t traversal = LFSR_MTREE_TRAVERSAL_INIT(
LFSR_MTREE_TRAVERSAL_VALIDATE);
while (true) {
// TODO detect cycles with Brent's algorithm
// found a cycle?
if (lfsr_mpair_eq(mpair, tortoise)) {
LFS_WARN("Cycle detected in superblocks");
return LFS_ERR_CORRUPT;
}
if (tortoise_i == tortoise_period) {
tortoise = mpair;
tortoise_i = 0;
tortoise_period *= 2;
}
tortoise_i += 1;
// fetch next possible superblock
int err = lfsr_mdir_fetch(lfs, &mdir, -1, mpair, NULL);
if (err) {
LFS_ERROR("No littlefs superblock found");
// treat corrupt errors as invalid littlefs images
if (err == LFS_ERR_CORRUPT) {
return LFS_ERR_INVAL;
}
return err;
}
// has magic string?
lfsr_tag_t tag;
lfsr_data_t data;
err = lfsr_mdir_lookup(lfs, &mdir, -1, LFSR_TAG_MAGIC, &data);
int err = lfsr_mtree_traversal_next(lfs, &traversal,
NULL, &tag, &data);
if (err && err != LFS_ERR_NOENT) {
return err;
}
if (err == LFS_ERR_NOENT) {
break;
}
if (err != LFS_ERR_NOENT) {
int cmp;
err = lfsr_data_cmp(lfs, data, 0, "littlefs", 8, &cmp);
if (err) {
// we only care about mdirs here
if (tag != LFSR_TAG_MDIR) {
continue;
}
lfsr_mdir_t *mdir = (lfsr_mdir_t*)data.buf.buffer;
// found an mroot?
if (mdir->mid == -1) {
// has magic string?
lfsr_data_t data;
err = lfsr_mdir_lookup(lfs, mdir, -1, LFSR_TAG_MAGIC, &data);
if (err && err != LFS_ERR_NOENT) {
return err;
}
// treat corrupted magic as no magic
if (cmp != 0) {
err = LFS_ERR_NOENT;
if (err != LFS_ERR_NOENT) {
int cmp;
err = lfsr_data_cmp(lfs, data, 0, "littlefs", 8, &cmp);
if (err) {
return err;
}
// treat corrupted magic as no magic
if (cmp != 0) {
err = LFS_ERR_NOENT;
}
}
}
if (err == LFS_ERR_NOENT) {
LFS_ERROR("No littlefs magic found");
return LFS_ERR_INVAL;
}
// lookup the superconfig
err = lfsr_mdir_lookup(lfs, &mdir, -1, LFSR_TAG_CONFIG, &data);
if (err && err != LFS_ERR_NOENT) {
return err;
}
if (err != LFS_ERR_NOENT) {
// check the major/minor version
uint32_t major_version;
uint32_t minor_version;
lfs_size_t d = 0;
lfs_ssize_t d_ = lfsr_data_readleb128(lfs, data, d, &major_version);
// treat any leb128 overflows as out-of-range values
if (d_ < 0 && d_ != LFS_ERR_CORRUPT) {
return d_;
if (err == LFS_ERR_NOENT) {
LFS_ERROR("No littlefs magic found");
return LFS_ERR_INVAL;
}
if (d_ != LFS_ERR_CORRUPT) {
d += d_;
d_ = lfsr_data_readleb128(lfs, data, d, &minor_version);
// lookup the superconfig
err = lfsr_mdir_lookup(lfs, mdir, -1, LFSR_TAG_CONFIG, &data);
if (err && err != LFS_ERR_NOENT) {
return err;
}
if (err != LFS_ERR_NOENT) {
// check the major/minor version
uint32_t major_version;
uint32_t minor_version;
lfs_size_t d = 0;
lfs_ssize_t d_ = lfsr_data_readleb128(lfs, data, d,
&major_version);
// treat any leb128 overflows as out-of-range values
if (d_ < 0 && d_ != LFS_ERR_CORRUPT) {
return d_;
}
if (d_ != LFS_ERR_CORRUPT) {
d += d_;
d_ = lfsr_data_readleb128(lfs, data, d, &minor_version);
// treat any leb128 overflows as out-of-range values
if (d_ < 0 && d_ != LFS_ERR_CORRUPT) {
return d_;
}
if (d_ != LFS_ERR_CORRUPT) {
d += d_;
}
}
if (d_ == LFS_ERR_CORRUPT
|| major_version != LFS_DISK_VERSION_MAJOR
|| minor_version > LFS_DISK_VERSION_MINOR) {
LFS_ERROR("Incompatible version v%"PRIu32".%"PRIu32
" (!= v%"PRIu32".%"PRIu32")",
(d_ == LFS_ERR_CORRUPT
? (uint32_t)-1
: major_version),
(d_ == LFS_ERR_CORRUPT
? (uint32_t)-1
: minor_version),
LFS_DISK_VERSION_MAJOR,
LFS_DISK_VERSION_MINOR);
return LFS_ERR_INVAL;
}
// check the on-disk csum type
uint32_t csum_type;
d_ = lfsr_data_readleb128(lfs, data, d, &csum_type);
// treat any leb128 overflows as out-of-range values
if (d_ < 0 && d_ != LFS_ERR_CORRUPT) {
return d_;
@@ -6851,228 +6878,161 @@ static int lfsr_mountinited(lfs_t *lfs) {
if (d_ != LFS_ERR_CORRUPT) {
d += d_;
}
}
if (d_ == LFS_ERR_CORRUPT
|| major_version != LFS_DISK_VERSION_MAJOR
|| minor_version > LFS_DISK_VERSION_MINOR) {
LFS_ERROR("Incompatible version v%"PRIu32".%"PRIu32
" (!= v%"PRIu32".%"PRIu32")",
(d_ == LFS_ERR_CORRUPT ? (uint32_t)-1 : major_version),
(d_ == LFS_ERR_CORRUPT ? (uint32_t)-1 : minor_version),
LFS_DISK_VERSION_MAJOR,
LFS_DISK_VERSION_MINOR);
return LFS_ERR_INVAL;
}
// check the on-disk csum type
uint32_t csum_type;
d_ = lfsr_data_readleb128(lfs, data, d, &csum_type);
// treat any leb128 overflows as out-of-range values
if (d_ < 0 && d_ != LFS_ERR_CORRUPT) {
return d_;
}
if (d_ != LFS_ERR_CORRUPT) {
d += d_;
}
if (d_ == LFS_ERR_CORRUPT || csum_type != 2) {
LFS_ERROR("Incompatible csum type 0x%"PRIx32
" (!= 0x%"PRIx32")",
(d_ == LFS_ERR_CORRUPT ? (uint32_t)-1 : csum_type),
2);
return LFS_ERR_INVAL;
}
if (d_ == LFS_ERR_CORRUPT || csum_type != 2) {
LFS_ERROR("Incompatible csum type 0x%"PRIx32
" (!= 0x%"PRIx32")",
(d_ == LFS_ERR_CORRUPT ? (uint32_t)-1 : csum_type),
2);
return LFS_ERR_INVAL;
}
// check for any on-disk flags
uint32_t flags;
d_ = lfsr_data_readleb128(lfs, data, d, &flags);
// treat any leb128 overflows as out-of-range values
if (d_ < 0 && d_ != LFS_ERR_CORRUPT) {
return d_;
}
if (d_ != LFS_ERR_CORRUPT) {
d += d_;
}
// check for any on-disk flags
uint32_t flags;
d_ = lfsr_data_readleb128(lfs, data, d, &flags);
// treat any leb128 overflows as out-of-range values
if (d_ < 0 && d_ != LFS_ERR_CORRUPT) {
return d_;
}
if (d_ != LFS_ERR_CORRUPT) {
d += d_;
}
if (d_ == LFS_ERR_CORRUPT || flags != 0) {
LFS_ERROR("Incompatible flags 0x%"PRIx32
" (!= 0x%"PRIx32")",
(d_ == LFS_ERR_CORRUPT ? (uint32_t)-1 : flags),
0);
return LFS_ERR_INVAL;
}
if (d_ == LFS_ERR_CORRUPT || flags != 0) {
LFS_ERROR("Incompatible flags 0x%"PRIx32
" (!= 0x%"PRIx32")",
(d_ == LFS_ERR_CORRUPT ? (uint32_t)-1 : flags),
0);
return LFS_ERR_INVAL;
}
// check the on-disk block size
// TODO actually use this
uint32_t block_size;
d_ = lfsr_data_readleb128(lfs, data, d, &block_size);
// treat any leb128 overflows as out-of-range values
if (d_ < 0 && d_ != LFS_ERR_CORRUPT) {
return d_;
}
if (d_ != LFS_ERR_CORRUPT) {
d += d_;
}
// check the on-disk block size
// TODO actually use this
uint32_t block_size;
d_ = lfsr_data_readleb128(lfs, data, d, &block_size);
// treat any leb128 overflows as out-of-range values
if (d_ < 0 && d_ != LFS_ERR_CORRUPT) {
return d_;
}
if (d_ != LFS_ERR_CORRUPT) {
d += d_;
}
if (d_ == LFS_ERR_CORRUPT
|| block_size != lfs->cfg->block_size) {
LFS_ERROR("Incompatible block size 0x%"PRIx32
" (!= 0x%"PRIx32")",
(d_ == LFS_ERR_CORRUPT ? (uint32_t)-1 : block_size),
lfs->cfg->block_size);
return LFS_ERR_INVAL;
}
if (d_ == LFS_ERR_CORRUPT || block_size != lfs->cfg->block_size) {
LFS_ERROR("Incompatible block size 0x%"PRIx32
" (!= 0x%"PRIx32")",
(d_ == LFS_ERR_CORRUPT ? (uint32_t)-1 : block_size),
lfs->cfg->block_size);
return LFS_ERR_INVAL;
}
// check the on-disk block count
// TODO actually use this
uint32_t block_count;
d_ = lfsr_data_readleb128(lfs, data, d, &block_count);
// treat any leb128 overflows as out-of-range values
if (d_ < 0 && d_ != LFS_ERR_CORRUPT) {
return d_;
}
if (d_ != LFS_ERR_CORRUPT) {
d += d_;
}
// check the on-disk block count
// TODO actually use this
uint32_t block_count;
d_ = lfsr_data_readleb128(lfs, data, d, &block_count);
// treat any leb128 overflows as out-of-range values
if (d_ < 0 && d_ != LFS_ERR_CORRUPT) {
return d_;
}
if (d_ != LFS_ERR_CORRUPT) {
d += d_;
}
if (d_ == LFS_ERR_CORRUPT
|| block_count != lfs->cfg->block_count) {
LFS_ERROR("Incompatible block count 0x%"PRIx32
" (!= 0x%"PRIx32")",
(d_ == LFS_ERR_CORRUPT
? (uint32_t)-1
: block_count),
lfs->cfg->block_count);
return LFS_ERR_INVAL;
}
if (d_ == LFS_ERR_CORRUPT || block_count != lfs->cfg->block_count) {
LFS_ERROR("Incompatible block count 0x%"PRIx32
" (!= 0x%"PRIx32")",
(d_ == LFS_ERR_CORRUPT ? (uint32_t)-1 : block_count),
lfs->cfg->block_count);
return LFS_ERR_INVAL;
}
// check the on-disk utag limit
// TODO actually use this
uint32_t utag_limit;
d_ = lfsr_data_readleb128(lfs, data, d, &utag_limit);
// treat any leb128 overflows as out-of-range values
if (d_ < 0 && d_ != LFS_ERR_CORRUPT) {
return d_;
}
if (d_ != LFS_ERR_CORRUPT) {
d += d_;
}
// check the on-disk utag limit
// TODO actually use this
uint32_t utag_limit;
d_ = lfsr_data_readleb128(lfs, data, d, &utag_limit);
// treat any leb128 overflows as out-of-range values
if (d_ < 0 && d_ != LFS_ERR_CORRUPT) {
return d_;
}
if (d_ != LFS_ERR_CORRUPT) {
d += d_;
}
if (d_ == LFS_ERR_CORRUPT || utag_limit != 0x7f) {
LFS_ERROR("Incompatible utag limit 0x%"PRIx32
" (> 0x%"PRIx32")",
(d_ == LFS_ERR_CORRUPT ? (uint32_t)-1 : utag_limit),
0x7f);
return LFS_ERR_INVAL;
}
if (d_ == LFS_ERR_CORRUPT || utag_limit != 0x7f) {
LFS_ERROR("Incompatible utag limit 0x%"PRIx32
" (> 0x%"PRIx32")",
(d_ == LFS_ERR_CORRUPT ? (uint32_t)-1 : utag_limit),
0x7f);
return LFS_ERR_INVAL;
}
// check the on-disk attr limit
// TODO actually use this
uint32_t attr_limit;
d_ = lfsr_data_readleb128(lfs, data, d, &attr_limit);
// treat any leb128 overflows as out-of-range values
if (d_ < 0 && d_ != LFS_ERR_CORRUPT) {
return d_;
}
if (d_ != LFS_ERR_CORRUPT) {
d += d_;
}
// check the on-disk attr limit
// TODO actually use this
uint32_t attr_limit;
d_ = lfsr_data_readleb128(lfs, data, d, &attr_limit);
// treat any leb128 overflows as out-of-range values
if (d_ < 0 && d_ != LFS_ERR_CORRUPT) {
return d_;
}
if (d_ != LFS_ERR_CORRUPT) {
d += d_;
}
if (d_ == LFS_ERR_CORRUPT || attr_limit != 0x7fffffff) {
LFS_ERROR("Incompatible attr limit 0x%"PRIx32
" (> 0x%"PRIx32")",
(d_ == LFS_ERR_CORRUPT ? (uint32_t)-1 : attr_limit),
0x7fffffff);
return LFS_ERR_INVAL;
}
if (d_ == LFS_ERR_CORRUPT || attr_limit != 0x7fffffff) {
LFS_ERROR("Incompatible attr limit 0x%"PRIx32
" (> 0x%"PRIx32")",
(d_ == LFS_ERR_CORRUPT ? (uint32_t)-1 : attr_limit),
0x7fffffff);
return LFS_ERR_INVAL;
}
// check the on-disk name limit
// TODO actually use this
uint32_t name_limit;
d_ = lfsr_data_readleb128(lfs, data, d, &name_limit);
// treat any leb128 overflows as out-of-range values
if (d_ < 0 && d_ != LFS_ERR_CORRUPT) {
return d_;
}
if (d_ != LFS_ERR_CORRUPT) {
d += d_;
}
// check the on-disk name limit
// TODO actually use this
uint32_t name_limit;
d_ = lfsr_data_readleb128(lfs, data, d, &name_limit);
// treat any leb128 overflows as out-of-range values
if (d_ < 0 && d_ != LFS_ERR_CORRUPT) {
return d_;
}
if (d_ != LFS_ERR_CORRUPT) {
d += d_;
}
if (d_ == LFS_ERR_CORRUPT || name_limit != 0xff) {
LFS_ERROR("Incompatible name limit 0x%"PRIx32
" (> 0x%"PRIx32")",
(d_ == LFS_ERR_CORRUPT ? (uint32_t)-1 : name_limit),
0xff);
return LFS_ERR_INVAL;
}
if (d_ == LFS_ERR_CORRUPT || name_limit != 0xff) {
LFS_ERROR("Incompatible name limit 0x%"PRIx32
" (> 0x%"PRIx32")",
(d_ == LFS_ERR_CORRUPT ? (uint32_t)-1 : name_limit),
0xff);
return LFS_ERR_INVAL;
// check the on-disk file limit
// TODO actually use this
uint32_t file_limit;
d_ = lfsr_data_readleb128(lfs, data, d, &file_limit);
// treat any leb128 overflows as out-of-range values
if (d_ < 0 && d_ != LFS_ERR_CORRUPT) {
return d_;
}
if (d_ != LFS_ERR_CORRUPT) {
d += d_;
}
if (d_ == LFS_ERR_CORRUPT || file_limit != 0x7fffffff) {
LFS_ERROR("Incompatible file limit 0x%"PRIx32
" (> 0x%"PRIx32")",
(d_ == LFS_ERR_CORRUPT ? (uint32_t)-1 : file_limit),
0x7fffffff);
return LFS_ERR_INVAL;
}
}
// check the on-disk file limit
// TODO actually use this
uint32_t file_limit;
d_ = lfsr_data_readleb128(lfs, data, d, &file_limit);
// treat any leb128 overflows as out-of-range values
if (d_ < 0 && d_ != LFS_ERR_CORRUPT) {
return d_;
}
if (d_ != LFS_ERR_CORRUPT) {
d += d_;
}
if (d_ == LFS_ERR_CORRUPT || file_limit != 0x7fffffff) {
LFS_ERROR("Incompatible file limit 0x%"PRIx32
" (> 0x%"PRIx32")",
(d_ == LFS_ERR_CORRUPT ? (uint32_t)-1 : file_limit),
0x7fffffff);
return LFS_ERR_INVAL;
}
}
// lookup mroot
//
// if we have a mroot, this is actually a fake superblock and
// we need to parse the next superblock in the chain
err = lfsr_mdir_lookup(lfs, &mdir, -1, LFSR_TAG_MROOT, &data);
if (err && err != LFS_ERR_NOENT) {
return err;
}
// no more mroots means we found our real superblock
if (err == LFS_ERR_NOENT) {
break;
}
lfs_ssize_t d = lfsr_mpair_fromdisk(lfs, &mpair, data);
if (d < 0) {
return d;
}
}
// do we have an mtree? this could be either a single mdir or a btree
// of mdirs
lfs_ssize_t id;
lfsr_tag_t tag;
lfsr_data_t data;
int err = lfsr_mdir_lookupnext(lfs, &mdir, -1, LFSR_TAG_STRUCT,
&id, &tag, NULL, &data);
if (err && err != LFS_ERR_NOENT) {
return err;
}
if (err != LFS_ERR_NOENT
&& id == -1
&& lfsr_tag_suptype(tag) == LFSR_TAG_STRUCT) {
if (tag != LFSR_TAG_MDIR && tag != LFSR_TAG_BTREE) {
LFS_ERROR("Weird superstruct? 0x%"PRIx32, tag);
return LFS_ERR_CORRUPT;
}
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;
}
lfs->mroot = mdir;
return 0;
}