mirror of
https://github.com/eledio-devices/thirdparty-littlefs.git
synced 2025-11-01 08:48:31 +01:00
Compare commits
8 Commits
devel
...
test-revam
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b9e403d55c | ||
|
|
d58aaf88dc | ||
|
|
71c844be53 | ||
|
|
75cd51b39e | ||
|
|
fc354801fa | ||
|
|
557ec332fe | ||
|
|
5e839df234 | ||
|
|
47ab0426b1 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -7,4 +7,4 @@
|
||||
blocks/
|
||||
lfs
|
||||
test.c
|
||||
tests_/*.toml.*
|
||||
tests/*.toml.*
|
||||
|
||||
2
Makefile
2
Makefile
@@ -45,7 +45,7 @@ test:
|
||||
./scripts/test.py $(TFLAGS)
|
||||
.SECONDEXPANSION:
|
||||
test%: tests/test$$(firstword $$(subst \#, ,%)).toml
|
||||
./scripts/test.py $(TFLAGS) $@
|
||||
./scripts/test.py $@ $(TFLAGS)
|
||||
|
||||
-include $(DEP)
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ int lfs_testbd_createcfg(const struct lfs_config *cfg, const char *path,
|
||||
|
||||
memset(bd->wear, 0, sizeof(lfs_testbd_wear_t) * cfg->block_count);
|
||||
}
|
||||
|
||||
|
||||
// create underlying block device
|
||||
if (bd->persist) {
|
||||
bd->u.file.cfg = (struct lfs_filebd_config){
|
||||
@@ -155,9 +155,8 @@ int lfs_testbd_read(const struct lfs_config *cfg, lfs_block_t block,
|
||||
LFS_ASSERT(block < cfg->block_count);
|
||||
|
||||
// block bad?
|
||||
if (bd->cfg->erase_cycles &&
|
||||
bd->cfg->badblock_behavior == LFS_TESTBD_BADBLOCK_NOREAD &&
|
||||
bd->wear[block] >= bd->cfg->erase_cycles) {
|
||||
if (bd->cfg->erase_cycles && bd->wear[block] >= bd->cfg->erase_cycles &&
|
||||
bd->cfg->badblock_behavior == LFS_TESTBD_BADBLOCK_READERROR) {
|
||||
LFS_TRACE("lfs_testbd_read -> %d", LFS_ERR_CORRUPT);
|
||||
return LFS_ERR_CORRUPT;
|
||||
}
|
||||
@@ -180,11 +179,18 @@ int lfs_testbd_prog(const struct lfs_config *cfg, lfs_block_t block,
|
||||
LFS_ASSERT(block < cfg->block_count);
|
||||
|
||||
// block bad?
|
||||
if (bd->cfg->erase_cycles &&
|
||||
bd->cfg->badblock_behavior == LFS_TESTBD_BADBLOCK_NOPROG &&
|
||||
bd->wear[block] >= bd->cfg->erase_cycles) {
|
||||
LFS_TRACE("lfs_testbd_prog -> %d", LFS_ERR_CORRUPT);
|
||||
return LFS_ERR_CORRUPT;
|
||||
if (bd->cfg->erase_cycles && bd->wear[block] >= bd->cfg->erase_cycles) {
|
||||
if (bd->cfg->badblock_behavior ==
|
||||
LFS_TESTBD_BADBLOCK_PROGERROR) {
|
||||
LFS_TRACE("lfs_testbd_prog -> %d", LFS_ERR_CORRUPT);
|
||||
return LFS_ERR_CORRUPT;
|
||||
} else if (bd->cfg->badblock_behavior ==
|
||||
LFS_TESTBD_BADBLOCK_PROGNOOP ||
|
||||
bd->cfg->badblock_behavior ==
|
||||
LFS_TESTBD_BADBLOCK_ERASENOOP) {
|
||||
LFS_TRACE("lfs_testbd_prog -> %d", 0);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// prog
|
||||
@@ -219,9 +225,14 @@ int lfs_testbd_erase(const struct lfs_config *cfg, lfs_block_t block) {
|
||||
// block bad?
|
||||
if (bd->cfg->erase_cycles) {
|
||||
if (bd->wear[block] >= bd->cfg->erase_cycles) {
|
||||
if (bd->cfg->badblock_behavior == LFS_TESTBD_BADBLOCK_NOERASE) {
|
||||
if (bd->cfg->badblock_behavior ==
|
||||
LFS_TESTBD_BADBLOCK_ERASEERROR) {
|
||||
LFS_TRACE("lfs_testbd_erase -> %d", LFS_ERR_CORRUPT);
|
||||
return LFS_ERR_CORRUPT;
|
||||
} else if (bd->cfg->badblock_behavior ==
|
||||
LFS_TESTBD_BADBLOCK_ERASENOOP) {
|
||||
LFS_TRACE("lfs_testbd_erase -> %d", 0);
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
// mark wear
|
||||
|
||||
@@ -19,14 +19,18 @@ extern "C"
|
||||
#endif
|
||||
|
||||
|
||||
// Mode determining how "bad blocks" behave during testing. This
|
||||
// simulates some real-world circumstances such as writes not
|
||||
// going through (noprog), erases not sticking (noerase), and ECC
|
||||
// failures (noread).
|
||||
// Mode determining how "bad blocks" behave during testing. This simulates
|
||||
// some real-world circumstances such as progs not sticking (prog-noop),
|
||||
// a readonly disk (erase-noop), and ECC failures (read-error).
|
||||
//
|
||||
// Not that read-noop is not allowed. Read _must_ return a consistent (but
|
||||
// may be arbitrary) value on every read.
|
||||
enum lfs_testbd_badblock_behavior {
|
||||
LFS_TESTBD_BADBLOCK_NOPROG = 0,
|
||||
LFS_TESTBD_BADBLOCK_NOERASE = 1,
|
||||
LFS_TESTBD_BADBLOCK_NOREAD = 2,
|
||||
LFS_TESTBD_BADBLOCK_PROGERROR,
|
||||
LFS_TESTBD_BADBLOCK_ERASEERROR,
|
||||
LFS_TESTBD_BADBLOCK_READERROR,
|
||||
LFS_TESTBD_BADBLOCK_PROGNOOP,
|
||||
LFS_TESTBD_BADBLOCK_ERASENOOP,
|
||||
};
|
||||
|
||||
// Type for measuring wear
|
||||
@@ -82,7 +86,7 @@ typedef struct lfs_testbd {
|
||||
/// Block device API ///
|
||||
|
||||
// Create a test block device using the geometry in lfs_config
|
||||
//
|
||||
//
|
||||
// Note that filebd is used if a path is provided, if path is NULL
|
||||
// testbd will use rambd which can be much faster.
|
||||
int lfs_testbd_create(const struct lfs_config *cfg, const char *path);
|
||||
|
||||
235
lfs.c
235
lfs.c
@@ -414,6 +414,9 @@ static lfs_stag_t lfs_fs_parent(lfs_t *lfs, const lfs_block_t dir[2],
|
||||
lfs_mdir_t *parent);
|
||||
static int lfs_fs_relocate(lfs_t *lfs,
|
||||
const lfs_block_t oldpair[2], lfs_block_t newpair[2]);
|
||||
int lfs_fs_traverseraw(lfs_t *lfs,
|
||||
int (*cb)(void *data, lfs_block_t block), void *data,
|
||||
bool includeorphans);
|
||||
static int lfs_fs_forceconsistency(lfs_t *lfs);
|
||||
static int lfs_deinit(lfs_t *lfs);
|
||||
#ifdef LFS_MIGRATE
|
||||
@@ -472,7 +475,7 @@ static int lfs_alloc(lfs_t *lfs, lfs_block_t *block) {
|
||||
|
||||
// find mask of free blocks from tree
|
||||
memset(lfs->free.buffer, 0, lfs->cfg->lookahead_size);
|
||||
int err = lfs_fs_traverse(lfs, lfs_alloc_lookahead, lfs);
|
||||
int err = lfs_fs_traverseraw(lfs, lfs_alloc_lookahead, lfs, true);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
@@ -802,11 +805,13 @@ static lfs_stag_t lfs_dir_fetchmatch(lfs_t *lfs,
|
||||
tag = lfs_frombe32(tag) ^ ptag;
|
||||
|
||||
// next commit not yet programmed or we're not in valid range
|
||||
if (!lfs_tag_isvalid(tag) ||
|
||||
off + lfs_tag_dsize(tag) > lfs->cfg->block_size) {
|
||||
if (!lfs_tag_isvalid(tag)) {
|
||||
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) {
|
||||
dir->erased = false;
|
||||
break;
|
||||
}
|
||||
|
||||
ptag = tag;
|
||||
@@ -1230,6 +1235,7 @@ static int lfs_dir_commitcrc(lfs_t *lfs, struct lfs_commit *commit) {
|
||||
const lfs_off_t off1 = commit->off + sizeof(lfs_tag_t);
|
||||
const lfs_off_t end = lfs_alignup(off1 + sizeof(uint32_t),
|
||||
lfs->cfg->prog_size);
|
||||
uint32_t ncrc = commit->crc;
|
||||
|
||||
// create crc tags to fill up remainder of commit, note that
|
||||
// padding is not crcd, which lets fetches skip padding but
|
||||
@@ -1266,6 +1272,7 @@ static int lfs_dir_commitcrc(lfs_t *lfs, struct lfs_commit *commit) {
|
||||
return err;
|
||||
}
|
||||
|
||||
ncrc = commit->crc;
|
||||
commit->off += sizeof(tag)+lfs_tag_size(tag);
|
||||
commit->ptag = tag ^ ((lfs_tag_t)reset << 31);
|
||||
commit->crc = LFS_BLOCK_NULL; // reset crc for next "commit"
|
||||
@@ -1292,6 +1299,12 @@ static int lfs_dir_commitcrc(lfs_t *lfs, struct lfs_commit *commit) {
|
||||
return err;
|
||||
}
|
||||
|
||||
// check against written crc to detect if block is readonly
|
||||
// (we may pick up old commits)
|
||||
if (i == noff && crc != ncrc) {
|
||||
return LFS_ERR_CORRUPT;
|
||||
}
|
||||
|
||||
crc = lfs_crc(crc, &dat, 1);
|
||||
}
|
||||
|
||||
@@ -1320,6 +1333,9 @@ static int lfs_dir_alloc(lfs_t *lfs, lfs_mdir_t *dir) {
|
||||
}
|
||||
}
|
||||
|
||||
// zero for reproducability in case initial block is unreadable
|
||||
dir->rev = 0;
|
||||
|
||||
// rather than clobbering one of the blocks we just pretend
|
||||
// the revision may be valid
|
||||
int err = lfs_bd_read(lfs,
|
||||
@@ -1420,9 +1436,9 @@ static int lfs_dir_compact(lfs_t *lfs,
|
||||
lfs_mdir_t *dir, const struct lfs_mattr *attrs, int attrcount,
|
||||
lfs_mdir_t *source, uint16_t begin, uint16_t end) {
|
||||
// save some state in case block is bad
|
||||
const lfs_block_t oldpair[2] = {dir->pair[1], dir->pair[0]};
|
||||
const lfs_block_t oldpair[2] = {dir->pair[0], dir->pair[1]};
|
||||
bool relocated = false;
|
||||
bool exhausted = false;
|
||||
bool tired = false;
|
||||
|
||||
// should we split?
|
||||
while (end - begin > 1) {
|
||||
@@ -1469,8 +1485,14 @@ static int lfs_dir_compact(lfs_t *lfs,
|
||||
|
||||
// increment revision count
|
||||
dir->rev += 1;
|
||||
// If our revision count == n * block_cycles, we should force a relocation,
|
||||
// this is how littlefs wear-levels at the metadata-pair level. Note that we
|
||||
// actually use (block_cycles+1)|1, this is to avoid two corner cases:
|
||||
// 1. block_cycles = 1, which would prevent relocations from terminating
|
||||
// 2. block_cycles = 2n, which, due to aliasing, would only ever relocate
|
||||
// one metadata block in the pair, effectively making this useless
|
||||
if (lfs->cfg->block_cycles > 0 &&
|
||||
(dir->rev % (lfs->cfg->block_cycles+1) == 0)) {
|
||||
(dir->rev % ((lfs->cfg->block_cycles+1)|1) == 0)) {
|
||||
if (lfs_pair_cmp(dir->pair, (const lfs_block_t[2]){0, 1}) == 0) {
|
||||
// oh no! we're writing too much to the superblock,
|
||||
// should we expand?
|
||||
@@ -1506,7 +1528,7 @@ static int lfs_dir_compact(lfs_t *lfs,
|
||||
#endif
|
||||
} else {
|
||||
// we're writing too much, time to relocate
|
||||
exhausted = true;
|
||||
tired = true;
|
||||
goto relocate;
|
||||
}
|
||||
}
|
||||
@@ -1630,23 +1652,23 @@ relocate:
|
||||
// commit was corrupted, drop caches and prepare to relocate block
|
||||
relocated = true;
|
||||
lfs_cache_drop(lfs, &lfs->pcache);
|
||||
if (!exhausted) {
|
||||
if (!tired) {
|
||||
LFS_DEBUG("Bad block at %"PRIx32, dir->pair[1]);
|
||||
}
|
||||
|
||||
// can't relocate superblock, filesystem is now frozen
|
||||
if (lfs_pair_cmp(oldpair, (const lfs_block_t[2]){0, 1}) == 0) {
|
||||
LFS_WARN("Superblock %"PRIx32" has become unwritable", oldpair[1]);
|
||||
if (lfs_pair_cmp(dir->pair, (const lfs_block_t[2]){0, 1}) == 0) {
|
||||
LFS_WARN("Superblock %"PRIx32" has become unwritable", dir->pair[1]);
|
||||
return LFS_ERR_NOSPC;
|
||||
}
|
||||
|
||||
// relocate half of pair
|
||||
int err = lfs_alloc(lfs, &dir->pair[1]);
|
||||
if (err && (err != LFS_ERR_NOSPC || !exhausted)) {
|
||||
if (err && (err != LFS_ERR_NOSPC || !tired)) {
|
||||
return err;
|
||||
}
|
||||
|
||||
exhausted = false;
|
||||
tired = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1684,6 +1706,7 @@ static int lfs_dir_commit(lfs_t *lfs, lfs_mdir_t *dir,
|
||||
}
|
||||
|
||||
// calculate changes to the directory
|
||||
lfs_mdir_t olddir = *dir;
|
||||
bool hasdelete = false;
|
||||
for (int i = 0; i < attrcount; i++) {
|
||||
if (lfs_tag_type3(attrs[i].tag) == LFS_TYPE_CREATE) {
|
||||
@@ -1705,11 +1728,16 @@ static int lfs_dir_commit(lfs_t *lfs, lfs_mdir_t *dir,
|
||||
lfs_mdir_t pdir;
|
||||
int err = lfs_fs_pred(lfs, dir->pair, &pdir);
|
||||
if (err && err != LFS_ERR_NOENT) {
|
||||
*dir = olddir;
|
||||
return err;
|
||||
}
|
||||
|
||||
if (err != LFS_ERR_NOENT && pdir.split) {
|
||||
return lfs_dir_drop(lfs, &pdir, dir);
|
||||
err = lfs_dir_drop(lfs, &pdir, dir);
|
||||
if (err) {
|
||||
*dir = olddir;
|
||||
return err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1737,6 +1765,7 @@ static int lfs_dir_commit(lfs_t *lfs, lfs_mdir_t *dir,
|
||||
if (err == LFS_ERR_NOSPC || err == LFS_ERR_CORRUPT) {
|
||||
goto compact;
|
||||
}
|
||||
*dir = olddir;
|
||||
return err;
|
||||
}
|
||||
|
||||
@@ -1749,6 +1778,7 @@ static int lfs_dir_commit(lfs_t *lfs, lfs_mdir_t *dir,
|
||||
if (!lfs_gstate_iszero(&delta)) {
|
||||
err = lfs_dir_getgstate(lfs, dir, &delta);
|
||||
if (err) {
|
||||
*dir = olddir;
|
||||
return err;
|
||||
}
|
||||
|
||||
@@ -1760,6 +1790,7 @@ static int lfs_dir_commit(lfs_t *lfs, lfs_mdir_t *dir,
|
||||
if (err == LFS_ERR_NOSPC || err == LFS_ERR_CORRUPT) {
|
||||
goto compact;
|
||||
}
|
||||
*dir = olddir;
|
||||
return err;
|
||||
}
|
||||
}
|
||||
@@ -1770,6 +1801,7 @@ static int lfs_dir_commit(lfs_t *lfs, lfs_mdir_t *dir,
|
||||
if (err == LFS_ERR_NOSPC || err == LFS_ERR_CORRUPT) {
|
||||
goto compact;
|
||||
}
|
||||
*dir = olddir;
|
||||
return err;
|
||||
}
|
||||
|
||||
@@ -1788,6 +1820,7 @@ compact:
|
||||
int err = lfs_dir_compact(lfs, dir, attrs, attrcount,
|
||||
dir, 0, dir->count);
|
||||
if (err) {
|
||||
*dir = olddir;
|
||||
return err;
|
||||
}
|
||||
}
|
||||
@@ -1800,9 +1833,8 @@ compact:
|
||||
// we need to copy the pair so they don't get clobbered if we refetch
|
||||
// our mdir.
|
||||
for (struct lfs_mlist *d = lfs->mlist; d; d = d->next) {
|
||||
if (&d->m != dir && lfs_pair_cmp(d->m.pair, dir->pair) == 0) {
|
||||
if (&d->m != dir && lfs_pair_cmp(d->m.pair, olddir.pair) == 0) {
|
||||
d->m = *dir;
|
||||
|
||||
for (int i = 0; i < attrcount; i++) {
|
||||
if (lfs_tag_type3(attrs[i].tag) == LFS_TYPE_DELETE &&
|
||||
d->id == lfs_tag_id(attrs[i].tag)) {
|
||||
@@ -1825,9 +1857,8 @@ compact:
|
||||
}
|
||||
}
|
||||
|
||||
lfs_block_t pair[2] = {dir->pair[0], dir->pair[1]};
|
||||
for (struct lfs_mlist *d = lfs->mlist; d; d = d->next) {
|
||||
if (lfs_pair_cmp(d->m.pair, pair) == 0) {
|
||||
if (lfs_pair_cmp(d->m.pair, olddir.pair) == 0) {
|
||||
while (d->id >= d->m.count && d->m.split) {
|
||||
// we split and id is on tail now
|
||||
d->id -= d->m.count;
|
||||
@@ -2204,16 +2235,16 @@ static int lfs_ctz_extend(lfs_t *lfs,
|
||||
return 0;
|
||||
}
|
||||
|
||||
size -= 1;
|
||||
lfs_off_t index = lfs_ctz_index(lfs, &size);
|
||||
size += 1;
|
||||
lfs_size_t noff = size - 1;
|
||||
lfs_off_t index = lfs_ctz_index(lfs, &noff);
|
||||
noff = noff + 1;
|
||||
|
||||
// just copy out the last block if it is incomplete
|
||||
if (size != lfs->cfg->block_size) {
|
||||
for (lfs_off_t i = 0; i < size; i++) {
|
||||
if (noff != lfs->cfg->block_size) {
|
||||
for (lfs_off_t i = 0; i < noff; i++) {
|
||||
uint8_t data;
|
||||
err = lfs_bd_read(lfs,
|
||||
NULL, rcache, size-i,
|
||||
NULL, rcache, noff-i,
|
||||
head, i, &data, 1);
|
||||
if (err) {
|
||||
return err;
|
||||
@@ -2231,19 +2262,19 @@ static int lfs_ctz_extend(lfs_t *lfs,
|
||||
}
|
||||
|
||||
*block = nblock;
|
||||
*off = size;
|
||||
*off = noff;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// append block
|
||||
index += 1;
|
||||
lfs_size_t skips = lfs_ctz(index) + 1;
|
||||
|
||||
lfs_block_t nhead = head;
|
||||
for (lfs_off_t i = 0; i < skips; i++) {
|
||||
head = lfs_tole32(head);
|
||||
nhead = lfs_tole32(nhead);
|
||||
err = lfs_bd_prog(lfs, pcache, rcache, true,
|
||||
nblock, 4*i, &head, 4);
|
||||
head = lfs_fromle32(head);
|
||||
nblock, 4*i, &nhead, 4);
|
||||
nhead = lfs_fromle32(nhead);
|
||||
if (err) {
|
||||
if (err == LFS_ERR_CORRUPT) {
|
||||
goto relocate;
|
||||
@@ -2253,15 +2284,15 @@ static int lfs_ctz_extend(lfs_t *lfs,
|
||||
|
||||
if (i != skips-1) {
|
||||
err = lfs_bd_read(lfs,
|
||||
NULL, rcache, sizeof(head),
|
||||
head, 4*i, &head, sizeof(head));
|
||||
head = lfs_fromle32(head);
|
||||
NULL, rcache, sizeof(nhead),
|
||||
nhead, 4*i, &nhead, sizeof(nhead));
|
||||
nhead = lfs_fromle32(nhead);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
LFS_ASSERT(head >= 2 && head <= lfs->cfg->block_count);
|
||||
LFS_ASSERT(nhead >= 2 && nhead <= lfs->cfg->block_count);
|
||||
}
|
||||
|
||||
*block = nblock;
|
||||
@@ -2677,66 +2708,52 @@ int lfs_file_sync(lfs_t *lfs, lfs_file_t *file) {
|
||||
LFS_TRACE("lfs_file_sync(%p, %p)", (void*)lfs, (void*)file);
|
||||
LFS_ASSERT(file->flags & LFS_F_OPENED);
|
||||
|
||||
while (true) {
|
||||
int err = lfs_file_flush(lfs, file);
|
||||
if (err) {
|
||||
file->flags |= LFS_F_ERRED;
|
||||
LFS_TRACE("lfs_file_sync -> %d", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
if ((file->flags & LFS_F_DIRTY) &&
|
||||
!(file->flags & LFS_F_ERRED) &&
|
||||
!lfs_pair_isnull(file->m.pair)) {
|
||||
// update dir entry
|
||||
uint16_t type;
|
||||
const void *buffer;
|
||||
lfs_size_t size;
|
||||
struct lfs_ctz ctz;
|
||||
if (file->flags & LFS_F_INLINE) {
|
||||
// inline the whole file
|
||||
type = LFS_TYPE_INLINESTRUCT;
|
||||
buffer = file->cache.buffer;
|
||||
size = file->ctz.size;
|
||||
} else {
|
||||
// update the ctz reference
|
||||
type = LFS_TYPE_CTZSTRUCT;
|
||||
// copy ctz so alloc will work during a relocate
|
||||
ctz = file->ctz;
|
||||
lfs_ctz_tole32(&ctz);
|
||||
buffer = &ctz;
|
||||
size = sizeof(ctz);
|
||||
}
|
||||
|
||||
// commit file data and attributes
|
||||
err = lfs_dir_commit(lfs, &file->m, LFS_MKATTRS(
|
||||
{LFS_MKTAG(type, file->id, size), buffer},
|
||||
{LFS_MKTAG(LFS_FROM_USERATTRS, file->id,
|
||||
file->cfg->attr_count), file->cfg->attrs}));
|
||||
if (err) {
|
||||
if (err == LFS_ERR_NOSPC && (file->flags & LFS_F_INLINE)) {
|
||||
goto relocate;
|
||||
}
|
||||
file->flags |= LFS_F_ERRED;
|
||||
LFS_TRACE("lfs_file_sync -> %d", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
file->flags &= ~LFS_F_DIRTY;
|
||||
}
|
||||
|
||||
LFS_TRACE("lfs_file_sync -> %d", 0);
|
||||
return 0;
|
||||
|
||||
relocate:
|
||||
// inline file doesn't fit anymore
|
||||
err = lfs_file_outline(lfs, file);
|
||||
if (err) {
|
||||
file->flags |= LFS_F_ERRED;
|
||||
LFS_TRACE("lfs_file_sync -> %d", err);
|
||||
return err;
|
||||
}
|
||||
int err = lfs_file_flush(lfs, file);
|
||||
if (err) {
|
||||
file->flags |= LFS_F_ERRED;
|
||||
LFS_TRACE("lfs_file_sync -> %d", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
if ((file->flags & LFS_F_DIRTY) &&
|
||||
!(file->flags & LFS_F_ERRED) &&
|
||||
!lfs_pair_isnull(file->m.pair)) {
|
||||
// update dir entry
|
||||
uint16_t type;
|
||||
const void *buffer;
|
||||
lfs_size_t size;
|
||||
struct lfs_ctz ctz;
|
||||
if (file->flags & LFS_F_INLINE) {
|
||||
// inline the whole file
|
||||
type = LFS_TYPE_INLINESTRUCT;
|
||||
buffer = file->cache.buffer;
|
||||
size = file->ctz.size;
|
||||
} else {
|
||||
// update the ctz reference
|
||||
type = LFS_TYPE_CTZSTRUCT;
|
||||
// copy ctz so alloc will work during a relocate
|
||||
ctz = file->ctz;
|
||||
lfs_ctz_tole32(&ctz);
|
||||
buffer = &ctz;
|
||||
size = sizeof(ctz);
|
||||
}
|
||||
|
||||
// commit file data and attributes
|
||||
err = lfs_dir_commit(lfs, &file->m, LFS_MKATTRS(
|
||||
{LFS_MKTAG(type, file->id, size), buffer},
|
||||
{LFS_MKTAG(LFS_FROM_USERATTRS, file->id,
|
||||
file->cfg->attr_count), file->cfg->attrs}));
|
||||
if (err) {
|
||||
file->flags |= LFS_F_ERRED;
|
||||
LFS_TRACE("lfs_file_sync -> %d", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
file->flags &= ~LFS_F_DIRTY;
|
||||
}
|
||||
|
||||
LFS_TRACE("lfs_file_sync -> %d", 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
lfs_ssize_t lfs_file_read(lfs_t *lfs, lfs_file_t *file,
|
||||
@@ -3764,10 +3781,9 @@ int lfs_unmount(lfs_t *lfs) {
|
||||
|
||||
|
||||
/// Filesystem filesystem operations ///
|
||||
int lfs_fs_traverse(lfs_t *lfs,
|
||||
int (*cb)(void *data, lfs_block_t block), void *data) {
|
||||
LFS_TRACE("lfs_fs_traverse(%p, %p, %p)",
|
||||
(void*)lfs, (void*)(uintptr_t)cb, data);
|
||||
int lfs_fs_traverseraw(lfs_t *lfs,
|
||||
int (*cb)(void *data, lfs_block_t block), void *data,
|
||||
bool includeorphans) {
|
||||
// iterate over metadata pairs
|
||||
lfs_mdir_t dir = {.tail = {0, 1}};
|
||||
|
||||
@@ -3776,7 +3792,6 @@ int lfs_fs_traverse(lfs_t *lfs,
|
||||
if (lfs->lfs1) {
|
||||
int err = lfs1_traverse(lfs, cb, data);
|
||||
if (err) {
|
||||
LFS_TRACE("lfs_fs_traverse -> %d", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
@@ -3789,7 +3804,6 @@ int lfs_fs_traverse(lfs_t *lfs,
|
||||
for (int i = 0; i < 2; i++) {
|
||||
int err = cb(data, dir.tail[i]);
|
||||
if (err) {
|
||||
LFS_TRACE("lfs_fs_traverse -> %d", err);
|
||||
return err;
|
||||
}
|
||||
}
|
||||
@@ -3797,7 +3811,6 @@ int lfs_fs_traverse(lfs_t *lfs,
|
||||
// iterate through ids in directory
|
||||
int err = lfs_dir_fetch(lfs, &dir, dir.tail);
|
||||
if (err) {
|
||||
LFS_TRACE("lfs_fs_traverse -> %d", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
@@ -3809,7 +3822,6 @@ int lfs_fs_traverse(lfs_t *lfs,
|
||||
if (tag == LFS_ERR_NOENT) {
|
||||
continue;
|
||||
}
|
||||
LFS_TRACE("lfs_fs_traverse -> %"PRId32, tag);
|
||||
return tag;
|
||||
}
|
||||
lfs_ctz_fromle32(&ctz);
|
||||
@@ -3818,9 +3830,16 @@ int lfs_fs_traverse(lfs_t *lfs,
|
||||
err = lfs_ctz_traverse(lfs, NULL, &lfs->rcache,
|
||||
ctz.head, ctz.size, cb, data);
|
||||
if (err) {
|
||||
LFS_TRACE("lfs_fs_traverse -> %d", err);
|
||||
return err;
|
||||
}
|
||||
} else if (includeorphans &&
|
||||
lfs_tag_type3(tag) == LFS_TYPE_DIRSTRUCT) {
|
||||
for (int i = 0; i < 2; i++) {
|
||||
err = cb(data, (&ctz.head)[i]);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3835,7 +3854,6 @@ int lfs_fs_traverse(lfs_t *lfs,
|
||||
int err = lfs_ctz_traverse(lfs, &f->cache, &lfs->rcache,
|
||||
f->ctz.head, f->ctz.size, cb, data);
|
||||
if (err) {
|
||||
LFS_TRACE("lfs_fs_traverse -> %d", err);
|
||||
return err;
|
||||
}
|
||||
}
|
||||
@@ -3844,16 +3862,23 @@ int lfs_fs_traverse(lfs_t *lfs,
|
||||
int err = lfs_ctz_traverse(lfs, &f->cache, &lfs->rcache,
|
||||
f->block, f->pos, cb, data);
|
||||
if (err) {
|
||||
LFS_TRACE("lfs_fs_traverse -> %d", err);
|
||||
return err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LFS_TRACE("lfs_fs_traverse -> %d", 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int lfs_fs_traverse(lfs_t *lfs,
|
||||
int (*cb)(void *data, lfs_block_t block), void *data) {
|
||||
LFS_TRACE("lfs_fs_traverse(%p, %p, %p)",
|
||||
(void*)lfs, (void*)(uintptr_t)cb, data);
|
||||
int err = lfs_fs_traverseraw(lfs, cb, data, true);
|
||||
LFS_TRACE("lfs_fs_traverse -> %d", 0);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int lfs_fs_pred(lfs_t *lfs,
|
||||
const lfs_block_t pair[2], lfs_mdir_t *pdir) {
|
||||
// iterate over all directory directory entries
|
||||
@@ -3902,7 +3927,9 @@ static lfs_stag_t lfs_fs_parent(lfs_t *lfs, const lfs_block_t pair[2],
|
||||
// use fetchmatch with callback to find pairs
|
||||
parent->tail[0] = 0;
|
||||
parent->tail[1] = 1;
|
||||
int i = 0;
|
||||
while (!lfs_pair_isnull(parent->tail)) {
|
||||
i += 1;
|
||||
lfs_stag_t tag = lfs_dir_fetchmatch(lfs, parent, parent->tail,
|
||||
LFS_MKTAG(0x7ff, 0, 0x3ff),
|
||||
LFS_MKTAG(LFS_TYPE_DIRSTRUCT, 0, 8),
|
||||
@@ -4157,7 +4184,7 @@ static int lfs_fs_size_count(void *p, lfs_block_t block) {
|
||||
lfs_ssize_t lfs_fs_size(lfs_t *lfs) {
|
||||
LFS_TRACE("lfs_fs_size(%p)", (void*)lfs);
|
||||
lfs_size_t size = 0;
|
||||
int err = lfs_fs_traverse(lfs, lfs_fs_size_count, &size);
|
||||
int err = lfs_fs_traverseraw(lfs, lfs_fs_size_count, &size, false);
|
||||
if (err) {
|
||||
LFS_TRACE("lfs_fs_size -> %d", err);
|
||||
return err;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import struct
|
||||
import binascii
|
||||
import sys
|
||||
import itertools as it
|
||||
|
||||
TAG_TYPES = {
|
||||
@@ -271,37 +272,39 @@ class MetadataPair:
|
||||
|
||||
raise KeyError(gmask, gtag)
|
||||
|
||||
def _dump_tags(self, tags, truncate=True):
|
||||
sys.stdout.write("%-8s %-8s %-13s %4s %4s %s\n" % (
|
||||
'off', 'tag', 'type', 'id', 'len',
|
||||
'data (truncated)' if truncate else 12*' '+'data'))
|
||||
def _dump_tags(self, tags, f=sys.stdout, truncate=True):
|
||||
f.write("%-8s %-8s %-13s %4s %4s" % (
|
||||
'off', 'tag', 'type', 'id', 'len'))
|
||||
if truncate:
|
||||
f.write(' data (truncated)')
|
||||
f.write('\n')
|
||||
|
||||
for tag in tags:
|
||||
sys.stdout.write("%08x: %08x %-13s %4s %4s" % (
|
||||
f.write("%08x: %08x %-13s %4s %4s" % (
|
||||
tag.off, tag,
|
||||
tag.typerepr(), tag.idrepr(), tag.sizerepr()))
|
||||
if truncate:
|
||||
sys.stdout.write(" %-23s %-8s\n" % (
|
||||
f.write(" %-23s %-8s\n" % (
|
||||
' '.join('%02x' % c for c in tag.data[:8]),
|
||||
''.join(c if c >= ' ' and c <= '~' else '.'
|
||||
for c in map(chr, tag.data[:8]))))
|
||||
else:
|
||||
sys.stdout.write("\n")
|
||||
f.write("\n")
|
||||
for i in range(0, len(tag.data), 16):
|
||||
sys.stdout.write("%08x: %-47s %-16s\n" % (
|
||||
f.write(" %08x: %-47s %-16s\n" % (
|
||||
tag.off+i,
|
||||
' '.join('%02x' % c for c in tag.data[i:i+16]),
|
||||
''.join(c if c >= ' ' and c <= '~' else '.'
|
||||
for c in map(chr, tag.data[i:i+16]))))
|
||||
|
||||
def dump_tags(self, truncate=True):
|
||||
self._dump_tags(self.tags, truncate=truncate)
|
||||
def dump_tags(self, f=sys.stdout, truncate=True):
|
||||
self._dump_tags(self.tags, f=f, truncate=truncate)
|
||||
|
||||
def dump_log(self, truncate=True):
|
||||
self._dump_tags(self.log, truncate=truncate)
|
||||
def dump_log(self, f=sys.stdout, truncate=True):
|
||||
self._dump_tags(self.log, f=f, truncate=truncate)
|
||||
|
||||
def dump_all(self, truncate=True):
|
||||
self._dump_tags(self.all_, truncate=truncate)
|
||||
def dump_all(self, f=sys.stdout, truncate=True):
|
||||
self._dump_tags(self.all_, f=f, truncate=truncate)
|
||||
|
||||
def main(args):
|
||||
blocks = []
|
||||
@@ -337,10 +340,10 @@ if __name__ == "__main__":
|
||||
help="First block address for finding the metadata pair.")
|
||||
parser.add_argument('block2', nargs='?', type=lambda x: int(x, 0),
|
||||
help="Second block address for finding the metadata pair.")
|
||||
parser.add_argument('-a', '--all', action='store_true',
|
||||
help="Show all tags in log, included tags in corrupted commits.")
|
||||
parser.add_argument('-l', '--log', action='store_true',
|
||||
help="Show tags in log.")
|
||||
parser.add_argument('-a', '--all', action='store_true',
|
||||
help="Show all tags in log, included tags in corrupted commits.")
|
||||
parser.add_argument('-T', '--no-truncate', action='store_true',
|
||||
help="Don't truncate large amounts of data in tags.")
|
||||
help="Don't truncate large amounts of data.")
|
||||
sys.exit(main(parser.parse_args()))
|
||||
|
||||
@@ -13,45 +13,6 @@ def popc(x):
|
||||
def ctz(x):
|
||||
return len(bin(x)) - len(bin(x).rstrip('0'))
|
||||
|
||||
def dumptags(args, mdir, f):
|
||||
if args.all:
|
||||
tags = mdir.all_
|
||||
elif args.log:
|
||||
tags = mdir.log
|
||||
else:
|
||||
tags = mdir.tags
|
||||
|
||||
for k, tag in enumerate(tags):
|
||||
f.write("tag %08x %s" % (tag, tag.typerepr()))
|
||||
if tag.id != 0x3ff:
|
||||
f.write(" id %d" % tag.id)
|
||||
if tag.size != 0x3ff:
|
||||
f.write(" size %d" % tag.size)
|
||||
if tag.is_('name'):
|
||||
f.write(" name %s" %
|
||||
json.dumps(tag.data.decode('utf8')))
|
||||
if tag.is_('dirstruct'):
|
||||
f.write(" dir {%#x, %#x}" % struct.unpack(
|
||||
'<II', tag.data[:8].ljust(8, b'\xff')))
|
||||
if tag.is_('ctzstruct'):
|
||||
f.write(" ctz {%#x} size %d" % struct.unpack(
|
||||
'<II', tag.data[:8].ljust(8, b'\xff')))
|
||||
if tag.is_('inlinestruct'):
|
||||
f.write(" inline size %d" % tag.size)
|
||||
if tag.is_('gstate'):
|
||||
f.write(" 0x%s" % ''.join('%02x' % c for c in tag.data))
|
||||
if tag.is_('tail'):
|
||||
f.write(" tail {%#x, %#x}" % struct.unpack(
|
||||
'<II', tag.data[:8].ljust(8, b'\xff')))
|
||||
f.write("\n")
|
||||
|
||||
if args.data:
|
||||
for i in range(0, len(tag.data), 16):
|
||||
f.write(" %-47s %-16s\n" % (
|
||||
' '.join('%02x' % c for c in tag.data[i:i+16]),
|
||||
''.join(c if c >= ' ' and c <= '~' else '.'
|
||||
for c in map(chr, tag.data[i:i+16]))))
|
||||
|
||||
def dumpentries(args, mdir, f):
|
||||
for k, id_ in enumerate(mdir.ids):
|
||||
name = mdir[Tag('name', id_, 0)]
|
||||
@@ -72,8 +33,8 @@ def dumpentries(args, mdir, f):
|
||||
|
||||
if args.data and struct_.is_('inlinestruct'):
|
||||
for i in range(0, len(struct_.data), 16):
|
||||
f.write(" %-47s %-16s\n" % (
|
||||
' '.join('%02x' % c for c in struct_.data[i:i+16]),
|
||||
f.write(" %08x: %-47s %-16s\n" % (
|
||||
i, ' '.join('%02x' % c for c in struct_.data[i:i+16]),
|
||||
''.join(c if c >= ' ' and c <= '~' else '.'
|
||||
for c in map(chr, struct_.data[i:i+16]))))
|
||||
elif args.data and struct_.is_('ctzstruct'):
|
||||
@@ -95,8 +56,8 @@ def dumpentries(args, mdir, f):
|
||||
it.chain.from_iterable(reversed(data)), size))
|
||||
for i in range(0, min(len(data), 256)
|
||||
if not args.no_truncate else len(data), 16):
|
||||
f.write(" %-47s %-16s\n" % (
|
||||
' '.join('%02x' % c for c in data[i:i+16]),
|
||||
f.write(" %08x: %-47s %-16s\n" % (
|
||||
i, ' '.join('%02x' % c for c in data[i:i+16]),
|
||||
''.join(c if c >= ' ' and c <= '~' else '.'
|
||||
for c in map(chr, data[i:i+16]))))
|
||||
|
||||
@@ -118,9 +79,17 @@ def main(args):
|
||||
superblock = None
|
||||
gstate = b''
|
||||
mdirs = []
|
||||
cycle = False
|
||||
tail = (args.block1, args.block2)
|
||||
hard = False
|
||||
while True:
|
||||
for m in it.chain((m for d in dirs for m in d), mdirs):
|
||||
if set(m.blocks) == set(tail):
|
||||
# cycle detected
|
||||
cycle = m.blocks
|
||||
if cycle:
|
||||
break
|
||||
|
||||
# load mdir
|
||||
data = []
|
||||
blocks = {}
|
||||
@@ -129,6 +98,7 @@ def main(args):
|
||||
data.append(f.read(args.block_size)
|
||||
.ljust(args.block_size, b'\xff'))
|
||||
blocks[id(data[-1])] = block
|
||||
|
||||
mdir = MetadataPair(data)
|
||||
mdir.blocks = tuple(blocks[id(p.data)] for p in mdir.pair)
|
||||
|
||||
@@ -171,7 +141,7 @@ def main(args):
|
||||
# find paths
|
||||
dirtable = {}
|
||||
for dir in dirs:
|
||||
dirtable[tuple(sorted(dir[0].blocks))] = dir
|
||||
dirtable[frozenset(dir[0].blocks)] = dir
|
||||
|
||||
pending = [("/", dirs[0])]
|
||||
while pending:
|
||||
@@ -183,7 +153,7 @@ def main(args):
|
||||
npath = tag.data.decode('utf8')
|
||||
dirstruct = mdir[Tag('dirstruct', tag.id, 0)]
|
||||
nblocks = struct.unpack('<II', dirstruct.data)
|
||||
nmdir = dirtable[tuple(sorted(nblocks))]
|
||||
nmdir = dirtable[frozenset(nblocks)]
|
||||
pending.append(((path + '/' + npath), nmdir))
|
||||
except KeyError:
|
||||
pass
|
||||
@@ -230,8 +200,12 @@ def main(args):
|
||||
' (corrupted)' if not mdir else ''))
|
||||
|
||||
f = io.StringIO()
|
||||
if args.tags or args.all or args.log:
|
||||
dumptags(args, mdir, f)
|
||||
if args.tags:
|
||||
mdir.dump_tags(f, truncate=not args.no_truncate)
|
||||
elif args.log:
|
||||
mdir.dump_log(f, truncate=not args.no_truncate)
|
||||
elif args.all:
|
||||
mdir.dump_all(f, truncate=not args.no_truncate)
|
||||
else:
|
||||
dumpentries(args, mdir, f)
|
||||
|
||||
@@ -243,7 +217,15 @@ def main(args):
|
||||
'|',
|
||||
line))
|
||||
|
||||
return 0 if all(mdir for dir in dirs for mdir in dir) else 1
|
||||
if cycle:
|
||||
print("*** cycle detected! -> {%#x, %#x} ***" % (cycle[0], cycle[1]))
|
||||
|
||||
if cycle:
|
||||
return 2
|
||||
elif not all(mdir for dir in dirs for mdir in dir):
|
||||
return 1
|
||||
else:
|
||||
return 0;
|
||||
|
||||
if __name__ == "__main__":
|
||||
import argparse
|
||||
@@ -268,12 +250,12 @@ if __name__ == "__main__":
|
||||
help="Show contents of metadata-pairs/directories.")
|
||||
parser.add_argument('-t', '--tags', action='store_true',
|
||||
help="Show metadata tags instead of reconstructing entries.")
|
||||
parser.add_argument('-a', '--all', action='store_true',
|
||||
help="Show all tags in log, included tags in corrupted commits.")
|
||||
parser.add_argument('-l', '--log', action='store_true',
|
||||
help="Show tags in log.")
|
||||
parser.add_argument('-a', '--all', action='store_true',
|
||||
help="Show all tags in log, included tags in corrupted commits.")
|
||||
parser.add_argument('-d', '--data', action='store_true',
|
||||
help="Also show the raw contents of files/attrs/tags.")
|
||||
parser.add_argument('-T', '--no-truncate', action='store_true',
|
||||
help="Don't truncate large amounts of data in files.")
|
||||
help="Don't truncate large amounts of data.")
|
||||
sys.exit(main(parser.parse_args()))
|
||||
|
||||
@@ -52,7 +52,7 @@ DEFINES = {
|
||||
'LFS_LOOKAHEAD_SIZE': 16,
|
||||
'LFS_ERASE_VALUE': 0xff,
|
||||
'LFS_ERASE_CYCLES': 0,
|
||||
'LFS_BADBLOCK_BEHAVIOR': 'LFS_TESTBD_BADBLOCK_NOPROG',
|
||||
'LFS_BADBLOCK_BEHAVIOR': 'LFS_TESTBD_BADBLOCK_PROGERROR',
|
||||
}
|
||||
PROLOGUE = """
|
||||
// prologue
|
||||
@@ -182,7 +182,19 @@ class TestCase:
|
||||
elif args.get('no_internal', False) and self.in_ is not None:
|
||||
return False
|
||||
elif self.if_ is not None:
|
||||
return eval(self.if_, None, self.defines.copy())
|
||||
if_ = self.if_
|
||||
while True:
|
||||
for k, v in self.defines.items():
|
||||
if k in if_:
|
||||
if_ = if_.replace(k, '(%s)' % v)
|
||||
break
|
||||
else:
|
||||
break
|
||||
if_ = (
|
||||
re.sub('(\&\&|\?)', ' and ',
|
||||
re.sub('(\|\||:)', ' or ',
|
||||
re.sub('!(?!=)', ' not ', if_))))
|
||||
return eval(if_)
|
||||
else:
|
||||
return True
|
||||
|
||||
@@ -235,33 +247,37 @@ class TestCase:
|
||||
mpty = os.fdopen(mpty, 'r', 1)
|
||||
stdout = []
|
||||
assert_ = None
|
||||
while True:
|
||||
try:
|
||||
line = mpty.readline()
|
||||
except OSError as e:
|
||||
if e.errno == errno.EIO:
|
||||
break
|
||||
raise
|
||||
stdout.append(line)
|
||||
if args.get('verbose', False):
|
||||
sys.stdout.write(line)
|
||||
# intercept asserts
|
||||
m = re.match(
|
||||
'^{0}([^:]+):(\d+):(?:\d+:)?{0}{1}:{0}(.*)$'
|
||||
.format('(?:\033\[[\d;]*.| )*', 'assert'),
|
||||
line)
|
||||
if m and assert_ is None:
|
||||
try:
|
||||
while True:
|
||||
try:
|
||||
with open(m.group(1)) as f:
|
||||
lineno = int(m.group(2))
|
||||
line = next(it.islice(f, lineno-1, None)).strip('\n')
|
||||
assert_ = {
|
||||
'path': m.group(1),
|
||||
'line': line,
|
||||
'lineno': lineno,
|
||||
'message': m.group(3)}
|
||||
except:
|
||||
pass
|
||||
line = mpty.readline()
|
||||
except OSError as e:
|
||||
if e.errno == errno.EIO:
|
||||
break
|
||||
raise
|
||||
stdout.append(line)
|
||||
if args.get('verbose', False):
|
||||
sys.stdout.write(line)
|
||||
# intercept asserts
|
||||
m = re.match(
|
||||
'^{0}([^:]+):(\d+):(?:\d+:)?{0}{1}:{0}(.*)$'
|
||||
.format('(?:\033\[[\d;]*.| )*', 'assert'),
|
||||
line)
|
||||
if m and assert_ is None:
|
||||
try:
|
||||
with open(m.group(1)) as f:
|
||||
lineno = int(m.group(2))
|
||||
line = (next(it.islice(f, lineno-1, None))
|
||||
.strip('\n'))
|
||||
assert_ = {
|
||||
'path': m.group(1),
|
||||
'line': line,
|
||||
'lineno': lineno,
|
||||
'message': m.group(3)}
|
||||
except:
|
||||
pass
|
||||
except KeyboardInterrupt:
|
||||
raise TestFailure(self, 1, stdout, None)
|
||||
proc.wait()
|
||||
|
||||
# did we pass?
|
||||
@@ -654,6 +670,10 @@ def main(**args):
|
||||
if filtered != sum(len(suite.perms) for suite in suites):
|
||||
print('filtered down to %d permutations' % filtered)
|
||||
|
||||
# only requested to build?
|
||||
if args.get('build', False):
|
||||
return 0
|
||||
|
||||
print('====== testing ======')
|
||||
try:
|
||||
for suite in suites:
|
||||
@@ -678,18 +698,19 @@ def main(**args):
|
||||
perm=perm, path=perm.suite.path, lineno=perm.lineno,
|
||||
returncode=perm.result.returncode or 0))
|
||||
if perm.result.stdout:
|
||||
for line in (perm.result.stdout
|
||||
if not perm.result.assert_
|
||||
else perm.result.stdout[:-1]):
|
||||
if perm.result.assert_:
|
||||
stdout = perm.result.stdout[:-1]
|
||||
else:
|
||||
stdout = perm.result.stdout
|
||||
if (not args.get('verbose', False) and len(stdout) > 5):
|
||||
sys.stdout.write('...\n')
|
||||
for line in stdout[-5:]:
|
||||
sys.stdout.write(line)
|
||||
if perm.result.assert_:
|
||||
sys.stdout.write(
|
||||
"\033[01m{path}:{lineno}:\033[01;31massert:\033[m "
|
||||
"{message}\n{line}\n".format(
|
||||
**perm.result.assert_))
|
||||
else:
|
||||
for line in perm.result.stdout:
|
||||
sys.stdout.write(line)
|
||||
sys.stdout.write('\n')
|
||||
failed += 1
|
||||
|
||||
@@ -728,6 +749,8 @@ if __name__ == "__main__":
|
||||
parser.add_argument('-p', '--persist', choices=['erase', 'noerase'],
|
||||
nargs='?', const='erase',
|
||||
help="Store disk image in a file.")
|
||||
parser.add_argument('-b', '--build', action='store_true',
|
||||
help="Only build the tests, do not execute.")
|
||||
parser.add_argument('-g', '--gdb', choices=['init', 'start', 'assert'],
|
||||
nargs='?', const='assert',
|
||||
help="Drop into gdb on test failure.")
|
||||
|
||||
@@ -329,7 +329,7 @@ code = '''
|
||||
[[case]] # chained dir exhaustion test
|
||||
define.LFS_BLOCK_SIZE = 512
|
||||
define.LFS_BLOCK_COUNT = 1024
|
||||
if = 'LFS_BLOCK_SIZE == 512 and LFS_BLOCK_COUNT == 1024'
|
||||
if = 'LFS_BLOCK_SIZE == 512 && LFS_BLOCK_COUNT == 1024'
|
||||
code = '''
|
||||
lfs_format(&lfs, &cfg) => 0;
|
||||
lfs_mount(&lfs, &cfg) => 0;
|
||||
@@ -400,7 +400,7 @@ code = '''
|
||||
[[case]] # split dir test
|
||||
define.LFS_BLOCK_SIZE = 512
|
||||
define.LFS_BLOCK_COUNT = 1024
|
||||
if = 'LFS_BLOCK_SIZE == 512 and LFS_BLOCK_COUNT == 1024'
|
||||
if = 'LFS_BLOCK_SIZE == 512 && LFS_BLOCK_COUNT == 1024'
|
||||
code = '''
|
||||
lfs_format(&lfs, &cfg) => 0;
|
||||
lfs_mount(&lfs, &cfg) => 0;
|
||||
@@ -445,7 +445,7 @@ code = '''
|
||||
[[case]] # outdated lookahead test
|
||||
define.LFS_BLOCK_SIZE = 512
|
||||
define.LFS_BLOCK_COUNT = 1024
|
||||
if = 'LFS_BLOCK_SIZE == 512 and LFS_BLOCK_COUNT == 1024'
|
||||
if = 'LFS_BLOCK_SIZE == 512 && LFS_BLOCK_COUNT == 1024'
|
||||
code = '''
|
||||
lfs_format(&lfs, &cfg) => 0;
|
||||
lfs_mount(&lfs, &cfg) => 0;
|
||||
@@ -510,7 +510,7 @@ code = '''
|
||||
[[case]] # outdated lookahead and split dir test
|
||||
define.LFS_BLOCK_SIZE = 512
|
||||
define.LFS_BLOCK_COUNT = 1024
|
||||
if = 'LFS_BLOCK_SIZE == 512 and LFS_BLOCK_COUNT == 1024'
|
||||
if = 'LFS_BLOCK_SIZE == 512 && LFS_BLOCK_COUNT == 1024'
|
||||
code = '''
|
||||
lfs_format(&lfs, &cfg) => 0;
|
||||
lfs_mount(&lfs, &cfg) => 0;
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
[[case]] # single bad blocks
|
||||
define.LFS_BLOCK_COUNT = 256 # small bd so test runs faster
|
||||
define.LFS_ERASE_CYCLES = 0xffffffff
|
||||
define.LFS_BADBLOCK_BEHAVIOR = 'LFS_TESTBD_BADBLOCK_NOPROG'
|
||||
define.LFS_ERASE_VALUE = [0x00, 0xff, -1]
|
||||
define.LFS_BADBLOCK_BEHAVIOR = [
|
||||
'LFS_TESTBD_BADBLOCK_PROGERROR',
|
||||
'LFS_TESTBD_BADBLOCK_ERASEERROR',
|
||||
'LFS_TESTBD_BADBLOCK_READERROR',
|
||||
'LFS_TESTBD_BADBLOCK_PROGNOOP',
|
||||
'LFS_TESTBD_BADBLOCK_ERASENOOP',
|
||||
]
|
||||
define.NAMEMULT = 64
|
||||
define.FILEMULT = 1
|
||||
code = '''
|
||||
@@ -64,144 +72,16 @@ code = '''
|
||||
}
|
||||
'''
|
||||
|
||||
[[case]] # single persistent blocks (can't erase)
|
||||
define.LFS_ERASE_CYCLES = 0xffffffff
|
||||
define.LFS_BADBLOCK_BEHAVIOR = 'LFS_TESTBD_BADBLOCK_NOERASE'
|
||||
define.NAMEMULT = 64
|
||||
define.FILEMULT = 1
|
||||
code = '''
|
||||
for (lfs_block_t badblock = 2; badblock < LFS_BLOCK_COUNT; badblock++) {
|
||||
lfs_testbd_setwear(&cfg, badblock-1, 0) => 0;
|
||||
lfs_testbd_setwear(&cfg, badblock, 0xffffffff) => 0;
|
||||
|
||||
lfs_format(&lfs, &cfg) => 0;
|
||||
|
||||
lfs_mount(&lfs, &cfg) => 0;
|
||||
for (int i = 1; i < 10; i++) {
|
||||
for (int j = 0; j < NAMEMULT; j++) {
|
||||
buffer[j] = '0'+i;
|
||||
}
|
||||
buffer[NAMEMULT] = '\0';
|
||||
lfs_mkdir(&lfs, (char*)buffer) => 0;
|
||||
|
||||
buffer[NAMEMULT] = '/';
|
||||
for (int j = 0; j < NAMEMULT; j++) {
|
||||
buffer[j+NAMEMULT+1] = '0'+i;
|
||||
}
|
||||
buffer[2*NAMEMULT+1] = '\0';
|
||||
lfs_file_open(&lfs, &file, (char*)buffer,
|
||||
LFS_O_WRONLY | LFS_O_CREAT) => 0;
|
||||
|
||||
size = NAMEMULT;
|
||||
for (int j = 0; j < i*FILEMULT; j++) {
|
||||
lfs_file_write(&lfs, &file, buffer, size) => size;
|
||||
}
|
||||
|
||||
lfs_file_close(&lfs, &file) => 0;
|
||||
}
|
||||
lfs_unmount(&lfs) => 0;
|
||||
|
||||
lfs_mount(&lfs, &cfg) => 0;
|
||||
for (int i = 1; i < 10; i++) {
|
||||
for (int j = 0; j < NAMEMULT; j++) {
|
||||
buffer[j] = '0'+i;
|
||||
}
|
||||
buffer[NAMEMULT] = '\0';
|
||||
lfs_stat(&lfs, (char*)buffer, &info) => 0;
|
||||
info.type => LFS_TYPE_DIR;
|
||||
|
||||
buffer[NAMEMULT] = '/';
|
||||
for (int j = 0; j < NAMEMULT; j++) {
|
||||
buffer[j+NAMEMULT+1] = '0'+i;
|
||||
}
|
||||
buffer[2*NAMEMULT+1] = '\0';
|
||||
lfs_file_open(&lfs, &file, (char*)buffer, LFS_O_RDONLY) => 0;
|
||||
|
||||
size = NAMEMULT;
|
||||
for (int j = 0; j < i*FILEMULT; j++) {
|
||||
uint8_t rbuffer[1024];
|
||||
lfs_file_read(&lfs, &file, rbuffer, size) => size;
|
||||
memcmp(buffer, rbuffer, size) => 0;
|
||||
}
|
||||
|
||||
lfs_file_close(&lfs, &file) => 0;
|
||||
}
|
||||
lfs_unmount(&lfs) => 0;
|
||||
}
|
||||
'''
|
||||
|
||||
[[case]] # single unreadable blocks (can't read)
|
||||
define.LFS_ERASE_CYCLES = 0xffffffff
|
||||
define.LFS_BADBLOCK_BEHAVIOR = 'LFS_TESTBD_BADBLOCK_NOREAD'
|
||||
define.NAMEMULT = 64
|
||||
define.FILEMULT = 1
|
||||
code = '''
|
||||
for (lfs_block_t badblock = 2; badblock < LFS_BLOCK_COUNT/2; badblock++) {
|
||||
lfs_testbd_setwear(&cfg, badblock-1, 0) => 0;
|
||||
lfs_testbd_setwear(&cfg, badblock, 0xffffffff) => 0;
|
||||
|
||||
lfs_format(&lfs, &cfg) => 0;
|
||||
|
||||
lfs_mount(&lfs, &cfg) => 0;
|
||||
for (int i = 1; i < 10; i++) {
|
||||
for (int j = 0; j < NAMEMULT; j++) {
|
||||
buffer[j] = '0'+i;
|
||||
}
|
||||
buffer[NAMEMULT] = '\0';
|
||||
lfs_mkdir(&lfs, (char*)buffer) => 0;
|
||||
|
||||
buffer[NAMEMULT] = '/';
|
||||
for (int j = 0; j < NAMEMULT; j++) {
|
||||
buffer[j+NAMEMULT+1] = '0'+i;
|
||||
}
|
||||
buffer[2*NAMEMULT+1] = '\0';
|
||||
lfs_file_open(&lfs, &file, (char*)buffer,
|
||||
LFS_O_WRONLY | LFS_O_CREAT) => 0;
|
||||
|
||||
size = NAMEMULT;
|
||||
for (int j = 0; j < i*FILEMULT; j++) {
|
||||
lfs_file_write(&lfs, &file, buffer, size) => size;
|
||||
}
|
||||
|
||||
lfs_file_close(&lfs, &file) => 0;
|
||||
}
|
||||
lfs_unmount(&lfs) => 0;
|
||||
|
||||
lfs_mount(&lfs, &cfg) => 0;
|
||||
for (int i = 1; i < 10; i++) {
|
||||
for (int j = 0; j < NAMEMULT; j++) {
|
||||
buffer[j] = '0'+i;
|
||||
}
|
||||
buffer[NAMEMULT] = '\0';
|
||||
lfs_stat(&lfs, (char*)buffer, &info) => 0;
|
||||
info.type => LFS_TYPE_DIR;
|
||||
|
||||
buffer[NAMEMULT] = '/';
|
||||
for (int j = 0; j < NAMEMULT; j++) {
|
||||
buffer[j+NAMEMULT+1] = '0'+i;
|
||||
}
|
||||
buffer[2*NAMEMULT+1] = '\0';
|
||||
lfs_file_open(&lfs, &file, (char*)buffer, LFS_O_RDONLY) => 0;
|
||||
|
||||
size = NAMEMULT;
|
||||
for (int j = 0; j < i*FILEMULT; j++) {
|
||||
uint8_t rbuffer[1024];
|
||||
lfs_file_read(&lfs, &file, rbuffer, size) => size;
|
||||
memcmp(buffer, rbuffer, size) => 0;
|
||||
}
|
||||
|
||||
lfs_file_close(&lfs, &file) => 0;
|
||||
}
|
||||
lfs_unmount(&lfs) => 0;
|
||||
}
|
||||
'''
|
||||
|
||||
[[case]] # region corruption (causes cascading failures)
|
||||
define.LFS_BLOCK_COUNT = 256 # small bd so test runs faster
|
||||
define.LFS_ERASE_CYCLES = 0xffffffff
|
||||
define.LFS_ERASE_VALUE = [0x00, 0xff, -1]
|
||||
define.LFS_BADBLOCK_BEHAVIOR = [
|
||||
'LFS_TESTBD_BADBLOCK_NOPROG',
|
||||
'LFS_TESTBD_BADBLOCK_NOERASE',
|
||||
'LFS_TESTBD_BADBLOCK_NOREAD',
|
||||
'LFS_TESTBD_BADBLOCK_PROGERROR',
|
||||
'LFS_TESTBD_BADBLOCK_ERASEERROR',
|
||||
'LFS_TESTBD_BADBLOCK_READERROR',
|
||||
'LFS_TESTBD_BADBLOCK_PROGNOOP',
|
||||
'LFS_TESTBD_BADBLOCK_ERASENOOP',
|
||||
]
|
||||
define.NAMEMULT = 64
|
||||
define.FILEMULT = 1
|
||||
@@ -266,11 +146,15 @@ code = '''
|
||||
'''
|
||||
|
||||
[[case]] # alternating corruption (causes cascading failures)
|
||||
define.LFS_BLOCK_COUNT = 256 # small bd so test runs faster
|
||||
define.LFS_ERASE_CYCLES = 0xffffffff
|
||||
define.LFS_ERASE_VALUE = [0x00, 0xff, -1]
|
||||
define.LFS_BADBLOCK_BEHAVIOR = [
|
||||
'LFS_TESTBD_BADBLOCK_NOPROG',
|
||||
'LFS_TESTBD_BADBLOCK_NOERASE',
|
||||
'LFS_TESTBD_BADBLOCK_NOREAD',
|
||||
'LFS_TESTBD_BADBLOCK_PROGERROR',
|
||||
'LFS_TESTBD_BADBLOCK_ERASEERROR',
|
||||
'LFS_TESTBD_BADBLOCK_READERROR',
|
||||
'LFS_TESTBD_BADBLOCK_PROGNOOP',
|
||||
'LFS_TESTBD_BADBLOCK_ERASENOOP',
|
||||
]
|
||||
define.NAMEMULT = 64
|
||||
define.FILEMULT = 1
|
||||
@@ -337,10 +221,13 @@ code = '''
|
||||
# other corner cases
|
||||
[[case]] # bad superblocks (corrupt 1 or 0)
|
||||
define.LFS_ERASE_CYCLES = 0xffffffff
|
||||
define.LFS_ERASE_VALUE = [0x00, 0xff, -1]
|
||||
define.LFS_BADBLOCK_BEHAVIOR = [
|
||||
'LFS_TESTBD_BADBLOCK_NOPROG',
|
||||
'LFS_TESTBD_BADBLOCK_NOERASE',
|
||||
'LFS_TESTBD_BADBLOCK_NOREAD',
|
||||
'LFS_TESTBD_BADBLOCK_PROGERROR',
|
||||
'LFS_TESTBD_BADBLOCK_ERASEERROR',
|
||||
'LFS_TESTBD_BADBLOCK_READERROR',
|
||||
'LFS_TESTBD_BADBLOCK_PROGNOOP',
|
||||
'LFS_TESTBD_BADBLOCK_ERASENOOP',
|
||||
]
|
||||
code = '''
|
||||
lfs_testbd_setwear(&cfg, 0, 0xffffffff) => 0;
|
||||
|
||||
@@ -155,7 +155,7 @@ code = '''
|
||||
'''
|
||||
|
||||
[[case]] # reentrant many directory creation/rename/removal
|
||||
define.N = [5, 25]
|
||||
define.N = [5, 10] # TODO changed from 20, should we be able to do more?
|
||||
reentrant = true
|
||||
code = '''
|
||||
err = lfs_mount(&lfs, &cfg);
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
[[case]] # test running a filesystem to exhaustion
|
||||
define.LFS_ERASE_CYCLES = 10
|
||||
define.LFS_BLOCK_COUNT = 256 # small bd so it runs faster
|
||||
define.LFS_BLOCK_COUNT = 256 # small bd so test runs faster
|
||||
define.LFS_BLOCK_CYCLES = 'LFS_ERASE_CYCLES / 2'
|
||||
define.LFS_BADBLOCK_BEHAVIOR = [
|
||||
'LFS_TESTBD_BADBLOCK_NOPROG',
|
||||
'LFS_TESTBD_BADBLOCK_NOERASE',
|
||||
'LFS_TESTBD_BADBLOCK_NOREAD',
|
||||
'LFS_TESTBD_BADBLOCK_PROGERROR',
|
||||
'LFS_TESTBD_BADBLOCK_ERASEERROR',
|
||||
'LFS_TESTBD_BADBLOCK_READERROR',
|
||||
'LFS_TESTBD_BADBLOCK_PROGNOOP',
|
||||
'LFS_TESTBD_BADBLOCK_ERASENOOP',
|
||||
]
|
||||
define.FILES = 10
|
||||
code = '''
|
||||
@@ -40,7 +42,7 @@ code = '''
|
||||
if (err == LFS_ERR_NOSPC) {
|
||||
goto exhausted;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < FILES; i++) {
|
||||
// check for errors
|
||||
@@ -57,7 +59,7 @@ code = '''
|
||||
}
|
||||
|
||||
lfs_file_close(&lfs, &file) => 0;
|
||||
}
|
||||
}
|
||||
lfs_unmount(&lfs) => 0;
|
||||
|
||||
cycle += 1;
|
||||
@@ -70,7 +72,7 @@ exhausted:
|
||||
// check for errors
|
||||
sprintf(path, "roadrunner/test%d", i);
|
||||
lfs_stat(&lfs, path, &info) => 0;
|
||||
}
|
||||
}
|
||||
lfs_unmount(&lfs) => 0;
|
||||
|
||||
LFS_WARN("completed %d cycles", cycle);
|
||||
@@ -79,12 +81,14 @@ exhausted:
|
||||
[[case]] # test running a filesystem to exhaustion
|
||||
# which also requires expanding superblocks
|
||||
define.LFS_ERASE_CYCLES = 10
|
||||
define.LFS_BLOCK_COUNT = 256 # small bd so it runs faster
|
||||
define.LFS_BLOCK_COUNT = 256 # small bd so test runs faster
|
||||
define.LFS_BLOCK_CYCLES = 'LFS_ERASE_CYCLES / 2'
|
||||
define.LFS_BADBLOCK_BEHAVIOR = [
|
||||
'LFS_TESTBD_BADBLOCK_NOPROG',
|
||||
'LFS_TESTBD_BADBLOCK_NOERASE',
|
||||
'LFS_TESTBD_BADBLOCK_NOREAD',
|
||||
'LFS_TESTBD_BADBLOCK_PROGERROR',
|
||||
'LFS_TESTBD_BADBLOCK_ERASEERROR',
|
||||
'LFS_TESTBD_BADBLOCK_READERROR',
|
||||
'LFS_TESTBD_BADBLOCK_PROGNOOP',
|
||||
'LFS_TESTBD_BADBLOCK_ERASENOOP',
|
||||
]
|
||||
define.FILES = 10
|
||||
code = '''
|
||||
@@ -116,7 +120,7 @@ code = '''
|
||||
if (err == LFS_ERR_NOSPC) {
|
||||
goto exhausted;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < FILES; i++) {
|
||||
// check for errors
|
||||
@@ -133,7 +137,7 @@ code = '''
|
||||
}
|
||||
|
||||
lfs_file_close(&lfs, &file) => 0;
|
||||
}
|
||||
}
|
||||
lfs_unmount(&lfs) => 0;
|
||||
|
||||
cycle += 1;
|
||||
@@ -146,7 +150,7 @@ exhausted:
|
||||
// check for errors
|
||||
sprintf(path, "test%d", i);
|
||||
lfs_stat(&lfs, path, &info) => 0;
|
||||
}
|
||||
}
|
||||
lfs_unmount(&lfs) => 0;
|
||||
|
||||
LFS_WARN("completed %d cycles", cycle);
|
||||
@@ -158,21 +162,11 @@ exhausted:
|
||||
# check for.
|
||||
|
||||
[[case]] # wear-level test running a filesystem to exhaustion
|
||||
define.LFS_ERASE_CYCLES = 10
|
||||
define.LFS_BLOCK_COUNT = 256 # small bd so it runs faster
|
||||
define.LFS_ERASE_CYCLES = 20
|
||||
define.LFS_BLOCK_COUNT = 256 # small bd so test runs faster
|
||||
define.LFS_BLOCK_CYCLES = 'LFS_ERASE_CYCLES / 2'
|
||||
define.LFS_BADBLOCK_BEHAVIOR = [
|
||||
'LFS_TESTBD_BADBLOCK_NOPROG',
|
||||
'LFS_TESTBD_BADBLOCK_NOERASE',
|
||||
'LFS_TESTBD_BADBLOCK_NOREAD',
|
||||
]
|
||||
define.FILES = 10
|
||||
code = '''
|
||||
lfs_format(&lfs, &cfg) => 0;
|
||||
lfs_mount(&lfs, &cfg) => 0;
|
||||
lfs_mkdir(&lfs, "roadrunner") => 0;
|
||||
lfs_unmount(&lfs) => 0;
|
||||
|
||||
uint32_t run_cycles[2];
|
||||
const uint32_t run_block_count[2] = {LFS_BLOCK_COUNT/2, LFS_BLOCK_COUNT};
|
||||
|
||||
@@ -182,6 +176,11 @@ code = '''
|
||||
(b < run_block_count[run]) ? 0 : LFS_ERASE_CYCLES) => 0;
|
||||
}
|
||||
|
||||
lfs_format(&lfs, &cfg) => 0;
|
||||
lfs_mount(&lfs, &cfg) => 0;
|
||||
lfs_mkdir(&lfs, "roadrunner") => 0;
|
||||
lfs_unmount(&lfs) => 0;
|
||||
|
||||
uint32_t cycle = 0;
|
||||
while (true) {
|
||||
lfs_mount(&lfs, &cfg) => 0;
|
||||
@@ -208,7 +207,7 @@ code = '''
|
||||
if (err == LFS_ERR_NOSPC) {
|
||||
goto exhausted;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < FILES; i++) {
|
||||
// check for errors
|
||||
@@ -225,7 +224,7 @@ code = '''
|
||||
}
|
||||
|
||||
lfs_file_close(&lfs, &file) => 0;
|
||||
}
|
||||
}
|
||||
lfs_unmount(&lfs) => 0;
|
||||
|
||||
cycle += 1;
|
||||
@@ -238,7 +237,7 @@ exhausted:
|
||||
// check for errors
|
||||
sprintf(path, "roadrunner/test%d", i);
|
||||
lfs_stat(&lfs, path, &info) => 0;
|
||||
}
|
||||
}
|
||||
lfs_unmount(&lfs) => 0;
|
||||
|
||||
run_cycles[run] = cycle;
|
||||
@@ -247,22 +246,15 @@ exhausted:
|
||||
}
|
||||
|
||||
// check we increased the lifetime by 2x with ~10% error
|
||||
LFS_ASSERT(run_cycles[1] > 2*run_cycles[0]-run_cycles[0]/10);
|
||||
LFS_ASSERT(run_cycles[1]*110/100 > 2*run_cycles[0]);
|
||||
'''
|
||||
|
||||
[[case]] # wear-level test + expanding superblock
|
||||
define.LFS_ERASE_CYCLES = 10
|
||||
define.LFS_BLOCK_COUNT = 256 # small bd so it runs faster
|
||||
define.LFS_ERASE_CYCLES = 20
|
||||
define.LFS_BLOCK_COUNT = 256 # small bd so test runs faster
|
||||
define.LFS_BLOCK_CYCLES = 'LFS_ERASE_CYCLES / 2'
|
||||
define.LFS_BADBLOCK_BEHAVIOR = [
|
||||
'LFS_TESTBD_BADBLOCK_NOPROG',
|
||||
'LFS_TESTBD_BADBLOCK_NOERASE',
|
||||
'LFS_TESTBD_BADBLOCK_NOREAD',
|
||||
]
|
||||
define.FILES = 10
|
||||
code = '''
|
||||
lfs_format(&lfs, &cfg) => 0;
|
||||
|
||||
uint32_t run_cycles[2];
|
||||
const uint32_t run_block_count[2] = {LFS_BLOCK_COUNT/2, LFS_BLOCK_COUNT};
|
||||
|
||||
@@ -272,6 +264,8 @@ code = '''
|
||||
(b < run_block_count[run]) ? 0 : LFS_ERASE_CYCLES) => 0;
|
||||
}
|
||||
|
||||
lfs_format(&lfs, &cfg) => 0;
|
||||
|
||||
uint32_t cycle = 0;
|
||||
while (true) {
|
||||
lfs_mount(&lfs, &cfg) => 0;
|
||||
@@ -298,7 +292,7 @@ code = '''
|
||||
if (err == LFS_ERR_NOSPC) {
|
||||
goto exhausted;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < FILES; i++) {
|
||||
// check for errors
|
||||
@@ -315,7 +309,7 @@ code = '''
|
||||
}
|
||||
|
||||
lfs_file_close(&lfs, &file) => 0;
|
||||
}
|
||||
}
|
||||
lfs_unmount(&lfs) => 0;
|
||||
|
||||
cycle += 1;
|
||||
@@ -328,7 +322,7 @@ exhausted:
|
||||
// check for errors
|
||||
sprintf(path, "test%d", i);
|
||||
lfs_stat(&lfs, path, &info) => 0;
|
||||
}
|
||||
}
|
||||
lfs_unmount(&lfs) => 0;
|
||||
|
||||
run_cycles[run] = cycle;
|
||||
@@ -337,5 +331,115 @@ exhausted:
|
||||
}
|
||||
|
||||
// check we increased the lifetime by 2x with ~10% error
|
||||
LFS_ASSERT(run_cycles[1] > 2*run_cycles[0]-run_cycles[0]/10);
|
||||
LFS_ASSERT(run_cycles[1]*110/100 > 2*run_cycles[0]);
|
||||
'''
|
||||
|
||||
[[case]] # test that we wear blocks roughly evenly
|
||||
define.LFS_ERASE_CYCLES = 0xffffffff
|
||||
define.LFS_BLOCK_COUNT = 256 # small bd so test runs faster
|
||||
define.LFS_BLOCK_CYCLES = [5, 4, 3, 2, 1]
|
||||
#define.LFS_BLOCK_CYCLES = [4, 2]
|
||||
define.CYCLES = 100
|
||||
define.FILES = 10
|
||||
code = '''
|
||||
lfs_format(&lfs, &cfg) => 0;
|
||||
lfs_mount(&lfs, &cfg) => 0;
|
||||
lfs_mkdir(&lfs, "roadrunner") => 0;
|
||||
lfs_unmount(&lfs) => 0;
|
||||
|
||||
uint32_t cycle = 0;
|
||||
while (cycle < CYCLES) {
|
||||
lfs_mount(&lfs, &cfg) => 0;
|
||||
for (uint32_t i = 0; i < FILES; i++) {
|
||||
// chose name, roughly random seed, and random 2^n size
|
||||
sprintf(path, "roadrunner/test%d", i);
|
||||
srand(cycle * i);
|
||||
size = 1 << 4; //((rand() % 10)+2);
|
||||
|
||||
lfs_file_open(&lfs, &file, path,
|
||||
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0;
|
||||
|
||||
for (lfs_size_t j = 0; j < size; j++) {
|
||||
char c = 'a' + (rand() % 26);
|
||||
lfs_ssize_t res = lfs_file_write(&lfs, &file, &c, 1);
|
||||
assert(res == 1 || res == LFS_ERR_NOSPC);
|
||||
if (res == LFS_ERR_NOSPC) {
|
||||
goto exhausted;
|
||||
}
|
||||
}
|
||||
|
||||
err = lfs_file_close(&lfs, &file);
|
||||
assert(err == 0 || err == LFS_ERR_NOSPC);
|
||||
if (err == LFS_ERR_NOSPC) {
|
||||
goto exhausted;
|
||||
}
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < FILES; i++) {
|
||||
// check for errors
|
||||
sprintf(path, "roadrunner/test%d", i);
|
||||
srand(cycle * i);
|
||||
size = 1 << 4; //((rand() % 10)+2);
|
||||
|
||||
lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0;
|
||||
for (lfs_size_t j = 0; j < size; j++) {
|
||||
char c = 'a' + (rand() % 26);
|
||||
char r;
|
||||
lfs_file_read(&lfs, &file, &r, 1) => 1;
|
||||
assert(r == c);
|
||||
}
|
||||
|
||||
lfs_file_close(&lfs, &file) => 0;
|
||||
}
|
||||
lfs_unmount(&lfs) => 0;
|
||||
|
||||
cycle += 1;
|
||||
}
|
||||
|
||||
exhausted:
|
||||
// should still be readable
|
||||
lfs_mount(&lfs, &cfg) => 0;
|
||||
for (uint32_t i = 0; i < FILES; i++) {
|
||||
// check for errors
|
||||
sprintf(path, "roadrunner/test%d", i);
|
||||
lfs_stat(&lfs, path, &info) => 0;
|
||||
}
|
||||
lfs_unmount(&lfs) => 0;
|
||||
|
||||
LFS_WARN("completed %d cycles", cycle);
|
||||
|
||||
// check the wear on our block device
|
||||
lfs_testbd_wear_t minwear = -1;
|
||||
lfs_testbd_wear_t totalwear = 0;
|
||||
lfs_testbd_wear_t maxwear = 0;
|
||||
// skip 0 and 1 as superblock movement is intentionally avoided
|
||||
for (lfs_block_t b = 2; b < LFS_BLOCK_COUNT; b++) {
|
||||
lfs_testbd_wear_t wear = lfs_testbd_getwear(&cfg, b);
|
||||
printf("%08x: wear %d\n", b, wear);
|
||||
assert(wear >= 0);
|
||||
if (wear < minwear) {
|
||||
minwear = wear;
|
||||
}
|
||||
if (wear > maxwear) {
|
||||
maxwear = wear;
|
||||
}
|
||||
totalwear += wear;
|
||||
}
|
||||
lfs_testbd_wear_t avgwear = totalwear / LFS_BLOCK_COUNT;
|
||||
LFS_WARN("max wear: %d cycles", maxwear);
|
||||
LFS_WARN("avg wear: %d cycles", totalwear / LFS_BLOCK_COUNT);
|
||||
LFS_WARN("min wear: %d cycles", minwear);
|
||||
|
||||
// find standard deviation^2
|
||||
lfs_testbd_wear_t dev2 = 0;
|
||||
for (lfs_block_t b = 2; b < LFS_BLOCK_COUNT; b++) {
|
||||
lfs_testbd_wear_t wear = lfs_testbd_getwear(&cfg, b);
|
||||
assert(wear >= 0);
|
||||
lfs_testbd_swear_t diff = wear - avgwear;
|
||||
dev2 += diff*diff;
|
||||
}
|
||||
dev2 /= totalwear;
|
||||
LFS_WARN("std dev^2: %d", dev2);
|
||||
assert(dev2 < 8);
|
||||
'''
|
||||
|
||||
|
||||
@@ -57,10 +57,12 @@ code = '''
|
||||
|
||||
[[case]] # reentrant testing for orphans, basically just spam mkdir/remove
|
||||
reentrant = true
|
||||
# TODO fix this case, caused by non-DAG trees
|
||||
if = '!(DEPTH == 3 && LFS_CACHE_SIZE != 64)'
|
||||
define = [
|
||||
{FILES=6, DEPTH=1, CYCLES=50},
|
||||
{FILES=26, DEPTH=1, CYCLES=50},
|
||||
{FILES=3, DEPTH=3, CYCLES=50},
|
||||
{FILES=6, DEPTH=1, CYCLES=20},
|
||||
{FILES=26, DEPTH=1, CYCLES=20},
|
||||
{FILES=3, DEPTH=3, CYCLES=20},
|
||||
]
|
||||
code = '''
|
||||
err = lfs_mount(&lfs, &cfg);
|
||||
|
||||
@@ -147,10 +147,12 @@ code = '''
|
||||
# orphan testing, except here we also set block_cycles so that
|
||||
# almost every tree operation needs a relocation
|
||||
reentrant = true
|
||||
# TODO fix this case, caused by non-DAG trees
|
||||
if = '!(DEPTH == 3 && LFS_CACHE_SIZE != 64)'
|
||||
define = [
|
||||
{FILES=6, DEPTH=1, CYCLES=50, LFS_BLOCK_CYCLES=1},
|
||||
{FILES=26, DEPTH=1, CYCLES=50, LFS_BLOCK_CYCLES=1},
|
||||
{FILES=3, DEPTH=3, CYCLES=50, LFS_BLOCK_CYCLES=1},
|
||||
{FILES=6, DEPTH=1, CYCLES=20, LFS_BLOCK_CYCLES=1},
|
||||
{FILES=26, DEPTH=1, CYCLES=20, LFS_BLOCK_CYCLES=1},
|
||||
{FILES=3, DEPTH=3, CYCLES=20, LFS_BLOCK_CYCLES=1},
|
||||
]
|
||||
code = '''
|
||||
err = lfs_mount(&lfs, &cfg);
|
||||
@@ -207,10 +209,12 @@ code = '''
|
||||
|
||||
[[case]] # reentrant testing for relocations, but now with random renames!
|
||||
reentrant = true
|
||||
# TODO fix this case, caused by non-DAG trees
|
||||
if = '!(DEPTH == 3 && LFS_CACHE_SIZE != 64)'
|
||||
define = [
|
||||
{FILES=6, DEPTH=1, CYCLES=50, LFS_BLOCK_CYCLES=1},
|
||||
{FILES=26, DEPTH=1, CYCLES=50, LFS_BLOCK_CYCLES=1},
|
||||
{FILES=3, DEPTH=3, CYCLES=50, LFS_BLOCK_CYCLES=1},
|
||||
{FILES=6, DEPTH=1, CYCLES=20, LFS_BLOCK_CYCLES=1},
|
||||
{FILES=26, DEPTH=1, CYCLES=20, LFS_BLOCK_CYCLES=1},
|
||||
{FILES=3, DEPTH=3, CYCLES=20, LFS_BLOCK_CYCLES=1},
|
||||
]
|
||||
code = '''
|
||||
err = lfs_mount(&lfs, &cfg);
|
||||
|
||||
Reference in New Issue
Block a user