Added block_size search in lfs_mount, moved block_size/count into lfs_t

This adds two new configuration options: erase_size and erase_count,
allowing block_size and block_count to be loaded from the superblock
during mount. For backwards compability these default to block_size and
block_count if zero.

---

Unfortunately this is a bit easier said than done. littlefs keeps its
superblock in the metadata pair located at blocks {0,1}, which is also
where the root directory lives (keep in mind small littlefs images may
only have 2 blocks in total). If we mutate blocks {0,1}, we have to
erase before programming, which means it's possible to have the only
superblock in block 1. This presents a puzzle because how do you find
block 1 if you don't know the size of a block?

One solution presented here is to search for block 1 by trying different
sizes until we find a superblock. This isn't great but there are some
properties of this search that help:

1. If we do find a superblock, the search will never take longer than a
   mount with a known block_size. This is because we stop at block 1,
   searching at most O(block_size) bytes, and metadata fetch is already
   a O(block_size) operation.

   This means the concern is limited to how long it takes to fail when
   littlefs is not present on the disk.

2. We can assume the on-disk block_size is probably a factor of the
   total size of the disk. After a bit of digging into the math, this
   apparently reduces the runtime to the divisor function, d(n), which
   is sublinear.

   According to a blog post by Terence Tao this is bounded by the
   ridiculous O(e^O(log(n)/log(log(n)))):
   https://terrytao.wordpress.com/2008/09/23/the-divisor-bound

   This is apparently somewhere between O(sqrt(n)) and O(log(n)), but
   conveniently O(log(n)) on average and O(log(n)) for powers of 2.

   I've left it as O(d(n)) in the documentation, which might be a bit
   confusing, but I'm not sure how best to capture "mostly log(n)"
   correctly.

3. If we don't know the block_size, or don't know that block_size is
   aligned to the disk size, the best we can do is a O(n) search.

   In this case I've added a warning, so at least it's distinguishable
   from an infinite loop if debugging.
This commit is contained in:
Christopher Haster
2022-11-10 17:43:26 -06:00
parent c2147c45ee
commit 75f80aabd7
5 changed files with 373 additions and 156 deletions
+326 -144
View File
@@ -26,6 +26,100 @@ enum {
};
/// Mapping from logical to physical erase size ///
static int lfs_bd_rawread(lfs_t *lfs, lfs_block_t block,
lfs_off_t off, void *buffer, lfs_size_t size) {
LFS_ASSERT(block < lfs->block_count);
LFS_ASSERT(off + size <= lfs->block_size);
LFS_ASSERT(size % lfs->cfg->read_size == 0);
// adjust to physical erase size
block = (block * (lfs->block_size/lfs->erase_size))
+ (off / lfs->erase_size);
off = off % lfs->erase_size;
uint8_t *buffer_ = buffer;
// read in erase_size chunks
while (size > 0) {
lfs_size_t delta = lfs_min(size, off + lfs->erase_size);
LFS_ASSERT(block <= lfs->erase_count);
LFS_ASSERT(off + size <= lfs->erase_size);
LFS_ASSERT(size % lfs->cfg->read_size == 0);
int err = lfs->cfg->read(lfs->cfg, block, off, buffer_, delta);
LFS_ASSERT(err <= 0);
if (err) {
return err;
}
off += delta;
if (off == lfs->erase_size) {
block += 1;
off = 0;
}
size -= delta;
}
return 0;
}
#ifndef LFS_READONLY
static int lfs_bd_rawprog(lfs_t *lfs, lfs_block_t block,
lfs_off_t off, void *buffer, lfs_size_t size) {
LFS_ASSERT(block < lfs->block_count);
LFS_ASSERT(off + size <= lfs->block_size);
LFS_ASSERT(size % lfs->cfg->prog_size == 0);
// adjust to physical erase size
block = (block * (lfs->block_size/lfs->erase_size))
+ (off / lfs->erase_size);
off = off % lfs->erase_size;
uint8_t *buffer_ = buffer;
// prog in erase_size chunks
while (size > 0) {
lfs_size_t delta = lfs_min(size, off + lfs->erase_size);
LFS_ASSERT(block <= lfs->erase_count);
LFS_ASSERT(off + size <= lfs->erase_size);
LFS_ASSERT(size % lfs->cfg->prog_size == 0);
int err = lfs->cfg->prog(lfs->cfg, block, off, buffer_, delta);
LFS_ASSERT(err <= 0);
if (err) {
return err;
}
off += delta;
if (off == lfs->erase_size) {
block += 1;
off = 0;
}
size -= delta;
}
return 0;
}
#endif
#ifndef LFS_READONLY
static int lfs_bd_erase(lfs_t *lfs, lfs_block_t block) {
LFS_ASSERT(block < lfs->block_count);
// adjust to physical erase size
block = block * (lfs->block_size/lfs->erase_size);
for (lfs_block_t i = 0; i < lfs->block_size/lfs->erase_size; i++) {
LFS_ASSERT(block + i <= lfs->erase_count);
int err = lfs->cfg->erase(lfs->cfg, block + i);
LFS_ASSERT(err <= 0);
if (err < 0) {
return err;
}
}
return 0;
}
#endif
/// Caching block device operations ///
static inline void lfs_cache_drop(lfs_t *lfs, lfs_cache_t *rcache) {
@@ -46,8 +140,8 @@ static int lfs_bd_read(lfs_t *lfs,
lfs_block_t block, lfs_off_t off,
void *buffer, lfs_size_t size) {
uint8_t *data = buffer;
if (block >= lfs->cfg->block_count ||
off+size > lfs->cfg->block_size) {
if (block >= lfs->block_count ||
off+size > lfs->block_size) {
return LFS_ERR_CORRUPT;
}
@@ -92,7 +186,7 @@ static int lfs_bd_read(lfs_t *lfs,
size >= lfs->cfg->read_size) {
// bypass cache?
diff = lfs_aligndown(diff, lfs->cfg->read_size);
int err = lfs->cfg->read(lfs->cfg, block, off, data, diff);
int err = lfs_bd_rawread(lfs, block, off, data, diff);
if (err) {
return err;
}
@@ -104,18 +198,16 @@ static int lfs_bd_read(lfs_t *lfs,
}
// load to cache, first condition can no longer fail
LFS_ASSERT(block < lfs->cfg->block_count);
rcache->block = block;
rcache->off = lfs_aligndown(off, lfs->cfg->read_size);
rcache->size = lfs_min(
lfs_min(
lfs_alignup(off+hint, lfs->cfg->read_size),
lfs->cfg->block_size)
lfs->block_size)
- rcache->off,
lfs->cfg->cache_size);
int err = lfs->cfg->read(lfs->cfg, rcache->block,
int err = lfs_bd_rawread(lfs, rcache->block,
rcache->off, rcache->buffer, rcache->size);
LFS_ASSERT(err <= 0);
if (err) {
return err;
}
@@ -155,11 +247,9 @@ static int lfs_bd_cmp(lfs_t *lfs,
static int lfs_bd_flush(lfs_t *lfs,
lfs_cache_t *pcache, lfs_cache_t *rcache, bool validate) {
if (pcache->block != LFS_BLOCK_NULL && pcache->block != LFS_BLOCK_INLINE) {
LFS_ASSERT(pcache->block < lfs->cfg->block_count);
lfs_size_t diff = lfs_alignup(pcache->size, lfs->cfg->prog_size);
int err = lfs->cfg->prog(lfs->cfg, pcache->block,
int err = lfs_bd_rawprog(lfs, pcache->block,
pcache->off, pcache->buffer, diff);
LFS_ASSERT(err <= 0);
if (err) {
return err;
}
@@ -208,8 +298,8 @@ static int lfs_bd_prog(lfs_t *lfs,
lfs_block_t block, lfs_off_t off,
const void *buffer, lfs_size_t size) {
const uint8_t *data = buffer;
LFS_ASSERT(block == LFS_BLOCK_INLINE || block < lfs->cfg->block_count);
LFS_ASSERT(off + size <= lfs->cfg->block_size);
LFS_ASSERT(block == LFS_BLOCK_INLINE || block < lfs->block_count);
LFS_ASSERT(off + size <= lfs->block_size);
while (size > 0) {
if (block == pcache->block &&
@@ -250,15 +340,6 @@ static int lfs_bd_prog(lfs_t *lfs,
}
#endif
#ifndef LFS_READONLY
static int lfs_bd_erase(lfs_t *lfs, lfs_block_t block) {
LFS_ASSERT(block < lfs->cfg->block_count);
int err = lfs->cfg->erase(lfs->cfg, block);
LFS_ASSERT(err <= 0);
return err;
}
#endif
/// Small type-level utilities ///
// operations on block pairs
@@ -528,7 +609,7 @@ static int lfs_rawunmount(lfs_t *lfs);
static int lfs_alloc_lookahead(void *p, lfs_block_t block) {
lfs_t *lfs = (lfs_t*)p;
lfs_block_t off = ((block - lfs->free.off)
+ lfs->cfg->block_count) % lfs->cfg->block_count;
+ lfs->block_count) % lfs->block_count;
if (off < lfs->free.size) {
lfs->free.buffer[off / 32] |= 1U << (off % 32);
@@ -542,7 +623,7 @@ static int lfs_alloc_lookahead(void *p, lfs_block_t block) {
// is to prevent blocks from being garbage collected in the middle of a
// commit operation
static void lfs_alloc_ack(lfs_t *lfs) {
lfs->free.ack = lfs->cfg->block_count;
lfs->free.ack = lfs->block_count;
}
// drop the lookahead buffer, this is done during mounting and failed
@@ -563,7 +644,7 @@ static int lfs_alloc(lfs_t *lfs, lfs_block_t *block) {
if (!(lfs->free.buffer[off / 32] & (1U << (off % 32)))) {
// found a free block
*block = (lfs->free.off + off) % lfs->cfg->block_count;
*block = (lfs->free.off + off) % lfs->block_count;
// eagerly find next off so an alloc ack can
// discredit old lookahead blocks
@@ -586,7 +667,7 @@ static int lfs_alloc(lfs_t *lfs, lfs_block_t *block) {
}
lfs->free.off = (lfs->free.off + lfs->free.size)
% lfs->cfg->block_count;
% lfs->block_count;
lfs->free.size = lfs_min(8*lfs->cfg->lookahead_size, lfs->free.ack);
lfs->free.i = 0;
@@ -676,7 +757,7 @@ static int lfs_dir_getread(lfs_t *lfs, const lfs_mdir_t *dir,
lfs_tag_t gmask, lfs_tag_t gtag,
lfs_off_t off, void *buffer, lfs_size_t size) {
uint8_t *data = buffer;
if (off+size > lfs->cfg->block_size) {
if (off+size > lfs->block_size) {
return LFS_ERR_CORRUPT;
}
@@ -998,7 +1079,7 @@ static lfs_stag_t lfs_dir_fetchmatch(lfs_t *lfs,
// if either block address is invalid we return LFS_ERR_CORRUPT here,
// otherwise later writes to the pair could fail
if (pair[0] >= lfs->cfg->block_count || pair[1] >= lfs->cfg->block_count) {
if (pair[0] >= lfs->block_count || pair[1] >= lfs->block_count) {
return LFS_ERR_CORRUPT;
}
@@ -1044,7 +1125,7 @@ static lfs_stag_t lfs_dir_fetchmatch(lfs_t *lfs,
lfs_tag_t tag;
off += lfs_tag_dsize(ptag);
int err = lfs_bd_read(lfs,
NULL, &lfs->rcache, lfs->cfg->block_size,
NULL, &lfs->rcache, lfs->block_size,
dir->pair[0], off, &tag, sizeof(tag));
if (err) {
if (err == LFS_ERR_CORRUPT) {
@@ -1063,7 +1144,7 @@ static lfs_stag_t lfs_dir_fetchmatch(lfs_t *lfs,
dir->erased = (lfs_tag_type1(ptag) == LFS_TYPE_CRC &&
dir->off % lfs->cfg->prog_size == 0);
break;
} else if (off + lfs_tag_dsize(tag) > lfs->cfg->block_size) {
} else if (off + lfs_tag_dsize(tag) > lfs->block_size) {
dir->erased = false;
break;
}
@@ -1074,7 +1155,7 @@ static lfs_stag_t lfs_dir_fetchmatch(lfs_t *lfs,
// check the crc attr
uint32_t dcrc;
err = lfs_bd_read(lfs,
NULL, &lfs->rcache, lfs->cfg->block_size,
NULL, &lfs->rcache, lfs->block_size,
dir->pair[0], off+sizeof(tag), &dcrc, sizeof(dcrc));
if (err) {
if (err == LFS_ERR_CORRUPT) {
@@ -1117,7 +1198,7 @@ static lfs_stag_t lfs_dir_fetchmatch(lfs_t *lfs,
for (lfs_off_t j = sizeof(tag); j < lfs_tag_dsize(tag); j++) {
uint8_t dat;
err = lfs_bd_read(lfs,
NULL, &lfs->rcache, lfs->cfg->block_size,
NULL, &lfs->rcache, lfs->block_size,
dir->pair[0], off+j, &dat, 1);
if (err) {
if (err == LFS_ERR_CORRUPT) {
@@ -1150,7 +1231,7 @@ static lfs_stag_t lfs_dir_fetchmatch(lfs_t *lfs,
tempsplit = (lfs_tag_chunk(tag) & 1);
err = lfs_bd_read(lfs,
NULL, &lfs->rcache, lfs->cfg->block_size,
NULL, &lfs->rcache, lfs->block_size,
dir->pair[0], off+sizeof(tag), &temptail, 8);
if (err) {
if (err == LFS_ERR_CORRUPT) {
@@ -1770,7 +1851,7 @@ static int lfs_dir_compact(lfs_t *lfs,
.begin = 0,
.end = (lfs->cfg->metadata_max ?
lfs->cfg->metadata_max : lfs->cfg->block_size) - 8,
lfs->cfg->metadata_max : lfs->block_size) - 8,
};
// erase block to write to
@@ -1940,11 +2021,11 @@ static int lfs_dir_splittingcompact(lfs_t *lfs, lfs_mdir_t *dir,
//
if (end - split < 0xff
&& size <= lfs_min(
lfs->cfg->block_size - 40,
lfs->block_size - 40,
lfs_alignup(
(lfs->cfg->metadata_max
? lfs->cfg->metadata_max
: lfs->cfg->block_size)/2,
: lfs->block_size)/2,
lfs->cfg->prog_size))) {
break;
}
@@ -1986,7 +2067,7 @@ static int lfs_dir_splittingcompact(lfs_t *lfs, lfs_mdir_t *dir,
// do we have extra space? littlefs can't reclaim this space
// by itself, so expand cautiously
if ((lfs_size_t)size < lfs->cfg->block_count/2) {
if ((lfs_size_t)size < lfs->block_count/2) {
LFS_DEBUG("Expanding superblock at rev %"PRIu32, dir->rev);
int err = lfs_dir_split(lfs, dir, attrs, attrcount,
source, begin, end);
@@ -2056,7 +2137,7 @@ static int lfs_dir_relocatingcommit(lfs_t *lfs, lfs_mdir_t *dir,
.begin = dir->off,
.end = (lfs->cfg->metadata_max ?
lfs->cfg->metadata_max : lfs->cfg->block_size) - 8,
lfs->cfg->metadata_max : lfs->block_size) - 8,
};
// traverse attrs that need to be written out
@@ -2647,7 +2728,7 @@ static int lfs_dir_rawrewind(lfs_t *lfs, lfs_dir_t *dir) {
/// File index list operations ///
static int lfs_ctz_index(lfs_t *lfs, lfs_off_t *off) {
lfs_off_t size = *off;
lfs_off_t b = lfs->cfg->block_size - 2*4;
lfs_off_t b = lfs->block_size - 2*4;
lfs_off_t i = size / b;
if (i == 0) {
return 0;
@@ -2725,7 +2806,7 @@ static int lfs_ctz_extend(lfs_t *lfs,
noff = noff + 1;
// just copy out the last block if it is incomplete
if (noff != lfs->cfg->block_size) {
if (noff != lfs->block_size) {
for (lfs_off_t i = 0; i < noff; i++) {
uint8_t data;
err = lfs_bd_read(lfs,
@@ -3265,7 +3346,7 @@ static lfs_ssize_t lfs_file_flushedread(lfs_t *lfs, lfs_file_t *file,
while (nsize > 0) {
// check if we need a new block
if (!(file->flags & LFS_F_READING) ||
file->off == lfs->cfg->block_size) {
file->off == lfs->block_size) {
if (!(file->flags & LFS_F_INLINE)) {
int err = lfs_ctz_find(lfs, NULL, &file->cache,
file->ctz.head, file->ctz.size,
@@ -3282,10 +3363,10 @@ static lfs_ssize_t lfs_file_flushedread(lfs_t *lfs, lfs_file_t *file,
}
// read as much as we can in current block
lfs_size_t diff = lfs_min(nsize, lfs->cfg->block_size - file->off);
lfs_size_t diff = lfs_min(nsize, lfs->block_size - file->off);
if (file->flags & LFS_F_INLINE) {
int err = lfs_dir_getread(lfs, &file->m,
NULL, &file->cache, lfs->cfg->block_size,
NULL, &file->cache, lfs->block_size,
LFS_MKTAG(0xfff, 0x1ff, 0),
LFS_MKTAG(LFS_TYPE_INLINESTRUCT, file->id, 0),
file->off, data, diff);
@@ -3294,7 +3375,7 @@ static lfs_ssize_t lfs_file_flushedread(lfs_t *lfs, lfs_file_t *file,
}
} else {
int err = lfs_bd_read(lfs,
NULL, &file->cache, lfs->cfg->block_size,
NULL, &file->cache, lfs->block_size,
file->block, file->off, data, diff);
if (err) {
return err;
@@ -3339,7 +3420,7 @@ static lfs_ssize_t lfs_file_flushedwrite(lfs_t *lfs, lfs_file_t *file,
lfs_min(0x3fe, lfs_min(
lfs->cfg->cache_size,
(lfs->cfg->metadata_max ?
lfs->cfg->metadata_max : lfs->cfg->block_size) / 8))) {
lfs->cfg->metadata_max : lfs->block_size) / 8))) {
// inline file doesn't fit anymore
int err = lfs_file_outline(lfs, file);
if (err) {
@@ -3351,7 +3432,7 @@ static lfs_ssize_t lfs_file_flushedwrite(lfs_t *lfs, lfs_file_t *file,
while (nsize > 0) {
// check if we need a new block
if (!(file->flags & LFS_F_WRITING) ||
file->off == lfs->cfg->block_size) {
file->off == lfs->block_size) {
if (!(file->flags & LFS_F_INLINE)) {
if (!(file->flags & LFS_F_WRITING) && file->pos > 0) {
// find out which block we're extending from
@@ -3385,7 +3466,7 @@ static lfs_ssize_t lfs_file_flushedwrite(lfs_t *lfs, lfs_file_t *file,
}
// program as much as we can in current block
lfs_size_t diff = lfs_min(nsize, lfs->cfg->block_size - file->off);
lfs_size_t diff = lfs_min(nsize, lfs->block_size - file->off);
while (true) {
int err = lfs_bd_prog(lfs, &file->cache, &lfs->rcache, true,
file->block, file->off, data, diff);
@@ -3915,20 +3996,40 @@ static int lfs_init(lfs_t *lfs, const struct lfs_config *cfg) {
int err = 0;
// validate that the lfs-cfg sizes were initiated properly before
// performing any arithmetic logics with them
// performing any arithmetic logic with them
LFS_ASSERT(lfs->cfg->read_size != 0);
LFS_ASSERT(lfs->cfg->prog_size != 0);
LFS_ASSERT(lfs->cfg->cache_size != 0);
LFS_ASSERT(lfs->cfg->erase_size != 0 || lfs->cfg->block_size != 0);
LFS_ASSERT(lfs->cfg->erase_count != 0 || lfs->cfg->block_count != 0);
// check that block size is a multiple of cache size is a multiple
// of prog and read sizes
// check that cache_size is a multiple of prog_size and read_size
LFS_ASSERT(lfs->cfg->cache_size % lfs->cfg->read_size == 0);
LFS_ASSERT(lfs->cfg->cache_size % lfs->cfg->prog_size == 0);
LFS_ASSERT(lfs->cfg->block_size % lfs->cfg->cache_size == 0);
// setup erase_size/count, these can be zero for backwards compatibility
lfs->erase_size = lfs->cfg->erase_size;
lfs->erase_count = lfs->cfg->erase_count;
lfs->block_size = lfs->cfg->block_size;
lfs->block_count = lfs->cfg->block_count;
if (!lfs->erase_size) {
lfs->erase_size = lfs->block_size;
}
if (!lfs->erase_count) {
lfs->erase_count = lfs->block_count;
}
// check that block_size is a multiple of erase_size is a mulitiple of
// cache_size, this implies everything is a multiple of read_size and
// prog_size
LFS_ASSERT(lfs->erase_size % lfs->cfg->cache_size == 0);
if (lfs->block_size) {
LFS_ASSERT(lfs->block_size % lfs->erase_size == 0);
}
// check that the block size is large enough to fit ctz pointers
LFS_ASSERT(4*lfs_npw2(0xffffffff / (lfs->cfg->block_size-2*4))
<= lfs->cfg->block_size);
LFS_ASSERT(4*lfs_npw2(0xffffffff / (lfs->block_size-2*4))
<= lfs->block_size);
// block_cycles = 0 is no longer supported.
//
@@ -3998,7 +4099,7 @@ static int lfs_init(lfs_t *lfs, const struct lfs_config *cfg) {
lfs->attr_max = LFS_ATTR_MAX;
}
LFS_ASSERT(lfs->cfg->metadata_max <= lfs->cfg->block_size);
LFS_ASSERT(lfs->cfg->metadata_max <= lfs->block_size);
// setup default state
lfs->root[0] = LFS_BLOCK_NULL;
@@ -4045,11 +4146,19 @@ static int lfs_rawformat(lfs_t *lfs, const struct lfs_config *cfg) {
return err;
}
// if block_size/block_count not specified, assume equal to erase blocks
if (!lfs->block_size) {
lfs->block_size = lfs->erase_size;
}
if (!lfs->block_count) {
lfs->block_count = lfs->erase_count;
}
// 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->block_count);
lfs->free.i = 0;
lfs_alloc_ack(lfs);
@@ -4063,8 +4172,8 @@ static int lfs_rawformat(lfs_t *lfs, const struct lfs_config *cfg) {
// write one superblock
lfs_superblock_t superblock = {
.version = LFS_DISK_VERSION,
.block_size = lfs->cfg->block_size,
.block_count = lfs->cfg->block_count,
.block_size = lfs->block_size,
.block_count = lfs->block_count,
.name_max = lfs->name_max,
.file_max = lfs->file_max,
.attr_max = lfs->attr_max,
@@ -4108,111 +4217,180 @@ static int lfs_rawmount(lfs_t *lfs, const struct lfs_config *cfg) {
return err;
}
// scan directory blocks for superblock and any global updates
lfs_mdir_t dir = {.tail = {0, 1}};
lfs_block_t cycle = 0;
while (!lfs_pair_isnull(dir.tail)) {
if (cycle >= lfs->cfg->block_count/2) {
// loop detected
err = LFS_ERR_CORRUPT;
goto cleanup;
// if block_size is unknown we need to search for it
lfs_size_t block_size_limit = lfs->block_size;
if (!lfs->block_size) {
lfs->block_size = lfs->erase_size;
// make sure this doesn't overflow
lfs_size_t limit = lfs->block_count
? lfs->block_count/2
: lfs->erase_count/2;
if (limit > ((lfs_size_t)-1) / lfs->block_size) {
block_size_limit = ((lfs_size_t)-1);
} else {
block_size_limit = limit * lfs->block_size;
}
cycle += 1;
}
// fetch next block in tail list
lfs_stag_t tag = lfs_dir_fetchmatch(lfs, &dir, dir.tail,
LFS_MKTAG(0x7ff, 0x3ff, 0),
LFS_MKTAG(LFS_TYPE_SUPERBLOCK, 0, 8),
NULL,
lfs_dir_find_match, &(struct lfs_dir_find_match){
lfs, "littlefs", 8});
if (tag < 0) {
err = tag;
goto cleanup;
}
// search for the correct block_size
while (true) {
// setup block_size/count so underlying operations work
lfs->block_count = lfs->erase_count
/ (lfs->block_size/lfs->erase_size);
// has superblock?
if (tag && !lfs_tag_isdelete(tag)) {
// update root
lfs->root[0] = dir.pair[0];
lfs->root[1] = dir.pair[1];
// scan directory blocks for superblock and any global updates
lfs_mdir_t dir = {.tail = {0, 1}};
lfs_block_t cycle = 0;
while (!lfs_pair_isnull(dir.tail)) {
if (cycle >= lfs->block_count/2) {
// loop detected
err = LFS_ERR_CORRUPT;
goto cleanup;
}
cycle += 1;
// grab superblock
lfs_superblock_t superblock;
tag = lfs_dir_get(lfs, &dir, LFS_MKTAG(0x7ff, 0x3ff, 0),
LFS_MKTAG(LFS_TYPE_INLINESTRUCT, 0, sizeof(superblock)),
&superblock);
// fetch next block in tail list
lfs_stag_t tag = lfs_dir_fetchmatch(lfs, &dir, dir.tail,
LFS_MKTAG(0x7ff, 0x3ff, 0),
LFS_MKTAG(LFS_TYPE_SUPERBLOCK, 0, 8),
NULL,
lfs_dir_find_match, &(struct lfs_dir_find_match){
lfs, "littlefs", 8});
if (tag < 0) {
if (tag == LFS_ERR_CORRUPT) {
// maybe our block_size is wrong
goto next_block_size;
}
err = tag;
goto cleanup;
}
lfs_superblock_fromle32(&superblock);
// check version
uint16_t major_version = (0xffff & (superblock.version >> 16));
uint16_t minor_version = (0xffff & (superblock.version >> 0));
if ((major_version != LFS_DISK_VERSION_MAJOR ||
minor_version > LFS_DISK_VERSION_MINOR)) {
LFS_ERROR("Invalid version v%"PRIu16".%"PRIu16,
major_version, minor_version);
err = LFS_ERR_INVAL;
goto cleanup;
}
// has superblock?
if (tag && !lfs_tag_isdelete(tag)) {
// grab superblock
lfs_superblock_t superblock;
tag = lfs_dir_get(lfs, &dir, LFS_MKTAG(0x7ff, 0x3ff, 0),
LFS_MKTAG(LFS_TYPE_INLINESTRUCT, 0, sizeof(superblock)),
&superblock);
if (tag < 0) {
if (tag == LFS_ERR_CORRUPT) {
// maybe our block_size is wrong
goto next_block_size;
}
err = tag;
goto cleanup;
}
lfs_superblock_fromle32(&superblock);
// check superblock configuration
if (superblock.name_max) {
if (superblock.name_max > lfs->name_max) {
LFS_ERROR("Unsupported name_max (%"PRIu32" > %"PRIu32")",
superblock.name_max, lfs->name_max);
// we may not be done, first check the stored block_size, it
// it's different we need to remount in case we found an
// outdated superblock
if (superblock.block_size != lfs->block_size) {
if (lfs->cfg->block_size
|| superblock.block_size % lfs->erase_size != 0
|| superblock.block_size < lfs->block_size) {
LFS_ERROR("Invalid block size %"PRIu32,
superblock.block_size);
err = LFS_ERR_INVAL;
goto cleanup;
}
// remount with correct block_size
lfs->block_size = superblock.block_size;
goto next_mount;
}
if (superblock.block_count != lfs->block_count) {
if ((lfs->cfg->block_size && lfs->cfg->block_count)
|| superblock.block_count > lfs->block_count) {
LFS_ERROR("Invalid block count %"PRIu32,
superblock.block_count);
err = LFS_ERR_INVAL;
goto cleanup;
}
lfs->block_count = superblock.block_count;
}
// check version
uint16_t major_version = (0xffff & (superblock.version >> 16));
uint16_t minor_version = (0xffff & (superblock.version >> 0));
if ((major_version != LFS_DISK_VERSION_MAJOR ||
minor_version > LFS_DISK_VERSION_MINOR)) {
LFS_ERROR("Invalid version v%"PRIu16".%"PRIu16,
major_version, minor_version);
err = LFS_ERR_INVAL;
goto cleanup;
}
lfs->name_max = superblock.name_max;
}
// check superblock configuration
if (superblock.name_max) {
if (superblock.name_max > lfs->name_max) {
LFS_ERROR("Unsupported name_max %"PRIu32,
superblock.name_max);
err = LFS_ERR_INVAL;
goto cleanup;
}
if (superblock.file_max) {
if (superblock.file_max > lfs->file_max) {
LFS_ERROR("Unsupported file_max (%"PRIu32" > %"PRIu32")",
superblock.file_max, lfs->file_max);
err = LFS_ERR_INVAL;
goto cleanup;
lfs->name_max = superblock.name_max;
}
lfs->file_max = superblock.file_max;
}
if (superblock.file_max) {
if (superblock.file_max > lfs->file_max) {
LFS_ERROR("Unsupported file_max %"PRIu32,
superblock.file_max);
err = LFS_ERR_INVAL;
goto cleanup;
}
if (superblock.attr_max) {
if (superblock.attr_max > lfs->attr_max) {
LFS_ERROR("Unsupported attr_max (%"PRIu32" > %"PRIu32")",
superblock.attr_max, lfs->attr_max);
err = LFS_ERR_INVAL;
goto cleanup;
lfs->file_max = superblock.file_max;
}
lfs->attr_max = superblock.attr_max;
if (superblock.attr_max) {
if (superblock.attr_max > lfs->attr_max) {
LFS_ERROR("Unsupported attr_max %"PRIu32,
superblock.attr_max);
err = LFS_ERR_INVAL;
goto cleanup;
}
lfs->attr_max = superblock.attr_max;
}
// update root
lfs->root[0] = dir.pair[0];
lfs->root[1] = dir.pair[1];
}
if (superblock.block_count != lfs->cfg->block_count) {
LFS_ERROR("Invalid block count (%"PRIu32" != %"PRIu32")",
superblock.block_count, lfs->cfg->block_count);
err = LFS_ERR_INVAL;
// has gstate?
err = lfs_dir_getgstate(lfs, &dir, &lfs->gstate);
if (err) {
goto cleanup;
}
if (superblock.block_size != lfs->cfg->block_size) {
LFS_ERROR("Invalid block size (%"PRIu32" != %"PRIu32")",
superblock.block_size, lfs->cfg->block_size);
err = LFS_ERR_INVAL;
goto cleanup;
}
// we found a valid superblock, set block_size_limit so block_size
// will no longer change
block_size_limit = lfs->block_size;
}
// has gstate?
err = lfs_dir_getgstate(lfs, &dir, &lfs->gstate);
if (err) {
break;
next_block_size:
lfs->block_size += lfs->erase_size;
if (lfs->block_size > block_size_limit) {
err = LFS_ERR_INVAL;
goto cleanup;
}
// if block_count is set, skip block_sizes that aren't a factor,
// this brings our search down from O(n) to O(d(n)), and
// O(log(n)) for powers of 2
if (lfs->cfg->block_count && lfs->cfg->block_count
% (lfs->block_size/lfs->erase_size) != 0) {
goto next_block_size;
}
next_mount:;
}
// found superblock?
@@ -4233,7 +4411,7 @@ static int lfs_rawmount(lfs_t *lfs, const struct lfs_config *cfg) {
// setup free lookahead, to distribute allocations uniformly across
// boots, we start the allocator at a random location
lfs->free.off = lfs->seed % lfs->cfg->block_count;
lfs->free.off = lfs->seed % lfs->block_count;
lfs_alloc_drop(lfs);
return 0;
@@ -4270,7 +4448,7 @@ int lfs_fs_rawtraverse(lfs_t *lfs,
lfs_block_t cycle = 0;
while (!lfs_pair_isnull(dir.tail)) {
if (cycle >= lfs->cfg->block_count/2) {
if (cycle >= lfs->block_count/2) {
// loop detected
return LFS_ERR_CORRUPT;
}
@@ -4355,7 +4533,7 @@ static int lfs_fs_pred(lfs_t *lfs,
pdir->tail[1] = 1;
lfs_block_t cycle = 0;
while (!lfs_pair_isnull(pdir->tail)) {
if (cycle >= lfs->cfg->block_count/2) {
if (cycle >= lfs->block_count/2) {
// loop detected
return LFS_ERR_CORRUPT;
}
@@ -4392,7 +4570,7 @@ static int lfs_fs_parent_match(void *data,
lfs_block_t child[2];
int err = lfs_bd_read(lfs,
&lfs->pcache, &lfs->rcache, lfs->cfg->block_size,
&lfs->pcache, &lfs->rcache, lfs->block_size,
disk->block, disk->off, &child, sizeof(child));
if (err) {
return err;
@@ -4411,7 +4589,7 @@ static lfs_stag_t lfs_fs_parent(lfs_t *lfs, const lfs_block_t pair[2],
parent->tail[1] = 1;
lfs_block_t cycle = 0;
while (!lfs_pair_isnull(parent->tail)) {
if (cycle >= lfs->cfg->block_count/2) {
if (cycle >= lfs->block_count/2) {
// loop detected
return LFS_ERR_CORRUPT;
}
@@ -4828,7 +5006,7 @@ static int lfs1_dir_fetch(lfs_t *lfs,
}
if ((0x7fffffff & test.size) < sizeof(test)+4 ||
(0x7fffffff & test.size) > lfs->cfg->block_size) {
(0x7fffffff & test.size) > lfs->block_size) {
continue;
}
@@ -5262,8 +5440,8 @@ static int lfs_rawmigrate(lfs_t *lfs, const struct lfs_config *cfg) {
lfs_superblock_t superblock = {
.version = LFS_DISK_VERSION,
.block_size = lfs->cfg->block_size,
.block_count = lfs->cfg->block_count,
.block_size = lfs->block_size,
.block_count = lfs->block_count,
.name_max = lfs->name_max,
.file_max = lfs->file_max,
.attr_max = lfs->attr_max,
@@ -5324,6 +5502,7 @@ int lfs_format(lfs_t *lfs, const struct lfs_config *cfg) {
LFS_TRACE("lfs_format(%p, %p {.context=%p, "
".read=%p, .prog=%p, .erase=%p, .sync=%p, "
".read_size=%"PRIu32", .prog_size=%"PRIu32", "
".erase_size=%"PRIu32", .erase_count=%"PRIu32", "
".block_size=%"PRIu32", .block_count=%"PRIu32", "
".block_cycles=%"PRIu32", .cache_size=%"PRIu32", "
".lookahead_size=%"PRIu32", .read_buffer=%p, "
@@ -5333,8 +5512,9 @@ int lfs_format(lfs_t *lfs, const struct lfs_config *cfg) {
(void*)lfs, (void*)cfg, cfg->context,
(void*)(uintptr_t)cfg->read, (void*)(uintptr_t)cfg->prog,
(void*)(uintptr_t)cfg->erase, (void*)(uintptr_t)cfg->sync,
cfg->read_size, cfg->prog_size, cfg->block_size, cfg->block_count,
cfg->block_cycles, cfg->cache_size, cfg->lookahead_size,
cfg->read_size, cfg->prog_size, cfg->erase_size, cfg->erase_count,
cfg->block_size, cfg->block_count, cfg->block_cycles,
cfg->cache_size, cfg->lookahead_size,
cfg->read_buffer, cfg->prog_buffer, cfg->lookahead_buffer,
cfg->name_max, cfg->file_max, cfg->attr_max);
@@ -5354,6 +5534,7 @@ int lfs_mount(lfs_t *lfs, const struct lfs_config *cfg) {
LFS_TRACE("lfs_mount(%p, %p {.context=%p, "
".read=%p, .prog=%p, .erase=%p, .sync=%p, "
".read_size=%"PRIu32", .prog_size=%"PRIu32", "
".erase_size=%"PRIu32", .erase_count=%"PRIu32", "
".block_size=%"PRIu32", .block_count=%"PRIu32", "
".block_cycles=%"PRIu32", .cache_size=%"PRIu32", "
".lookahead_size=%"PRIu32", .read_buffer=%p, "
@@ -5363,8 +5544,9 @@ int lfs_mount(lfs_t *lfs, const struct lfs_config *cfg) {
(void*)lfs, (void*)cfg, cfg->context,
(void*)(uintptr_t)cfg->read, (void*)(uintptr_t)cfg->prog,
(void*)(uintptr_t)cfg->erase, (void*)(uintptr_t)cfg->sync,
cfg->read_size, cfg->prog_size, cfg->block_size, cfg->block_count,
cfg->block_cycles, cfg->cache_size, cfg->lookahead_size,
cfg->read_size, cfg->prog_size, cfg->erase_size, cfg->erase_count,
cfg->block_size, cfg->block_count, cfg->block_cycles,
cfg->cache_size, cfg->lookahead_size,
cfg->read_buffer, cfg->prog_buffer, cfg->lookahead_buffer,
cfg->name_max, cfg->file_max, cfg->attr_max);
+44 -9
View File
@@ -189,21 +189,52 @@ struct lfs_config {
int (*unlock)(const struct lfs_config *c);
#endif
// Minimum size of a block read in bytes. All read operations will be a
// multiple of this value.
// Minimum size of a read operation in bytes. All read operations
// will be a multiple of this value.
lfs_size_t read_size;
// Minimum size of a block program in bytes. All program operations will be
// a multiple of this value.
// Minimum size of a program operation in bytes. All program operations
// will be a multiple of this value.
lfs_size_t prog_size;
// Size of an erasable block in bytes. This does not impact ram consumption
// and may be larger than the physical erase size. However, non-inlined
// files take up at minimum one block. Must be a multiple of the read and
// program sizes.
// Minimum size of an erase operation in bytes. All erase operations
// will be a multiple of this value. This must be a multiple of the read
// and program sizes.
//
// If zero, the block_size is used as the erase_size. This is mostly for
// backwards compatibility.
lfs_size_t erase_size;
// Number of erase blocks on the device.
//
// If zero, the block_count is used as the erase_count. This is mostly for
// backwards compatibility.
lfs_size_t erase_count;
// Size of a logical block in bytes. This does not impact RAM consumption
// and may be a multiple of the physical erase_size.
//
// Note, non-inlined files take up at minimum one logical block, and
// directories take up at minimum two logical blocks.
//
// If zero, littlefs attempts to find the superblock and use the the
// block_size stored there. This requires searching different block_sizes.
// If a superblock is found this takes no longer than mounting with a known
// block_size, but it can take time to fail if a superblock is not found:
//
// - O(block_size) if a superblock is found
// - O(d(block_count)) if block_count is non-zero
// - O(log(block_count)) if block_count is a power of 2
// - O(erase_count) if block_count is zero
lfs_size_t block_size;
// Number of erasable blocks on the device.
// Number of logical blocks on the device.
//
// If zero, littlefs uses the block_count stored in the superblock.
//
// If non-zero and block_size is zero, littlefs will assume block_size
// is a factor of erase_size*block_count to speed up mount when no
// superblock is found.
lfs_size_t block_count;
// Number of erase cycles before littlefs evicts metadata logs and moves
@@ -408,6 +439,10 @@ typedef struct lfs {
} free;
const struct lfs_config *cfg;
lfs_size_t erase_size;
lfs_size_t erase_count;
lfs_size_t block_size;
lfs_size_t block_count;
lfs_size_t name_max;
lfs_size_t file_max;
lfs_size_t attr_max;
+1 -1
View File
@@ -256,5 +256,5 @@ code = '''
lfs_t lfs;
lfs_format(&lfs, cfg) => LFS_ERR_NOSPC;
lfs_mount(&lfs, cfg) => LFS_ERR_CORRUPT;
lfs_mount(&lfs, cfg) => LFS_ERR_INVAL;
'''
+1 -1
View File
@@ -24,7 +24,7 @@ code = '''
lfs_deinit(&lfs) => 0;
// test that mount fails gracefully
lfs_mount(&lfs, cfg) => LFS_ERR_CORRUPT;
lfs_mount(&lfs, cfg) => LFS_ERR_INVAL;
'''
[cases.test_evil_invalid_dir_pointer]
+1 -1
View File
@@ -31,7 +31,7 @@ code = '''
[cases.test_superblocks_invalid_mount]
code = '''
lfs_t lfs;
lfs_mount(&lfs, cfg) => LFS_ERR_CORRUPT;
lfs_mount(&lfs, cfg) => LFS_ERR_INVAL;
'''
# expanding superblock