mirror of
https://github.com/eledio-devices/thirdparty-littlefs.git
synced 2025-11-01 16:14:13 +01:00
Compare commits
7 Commits
v2.4.1
...
tim-nordel
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e3ace9ce98 | ||
|
|
40bebef368 | ||
|
|
2612ef130b | ||
|
|
a3ea209503 | ||
|
|
45d02e5750 | ||
|
|
5c87b6864f | ||
|
|
8c1931ac55 |
@@ -192,7 +192,7 @@ More details on how littlefs works can be found in [DESIGN.md](DESIGN.md) and
|
|||||||
## Testing
|
## Testing
|
||||||
|
|
||||||
The littlefs comes with a test suite designed to run on a PC using the
|
The littlefs comes with a test suite designed to run on a PC using the
|
||||||
[emulated block device](bd/lfs_testbd.h) found in the `bd` directory.
|
[emulated block device](emubd/lfs_emubd.h) found in the emubd directory.
|
||||||
The tests assume a Linux environment and can be started with make:
|
The tests assume a Linux environment and can be started with make:
|
||||||
|
|
||||||
``` bash
|
``` bash
|
||||||
|
|||||||
221
lfs.c
221
lfs.c
@@ -3190,7 +3190,106 @@ static int lfs_rawstat(lfs_t *lfs, const char *path, struct lfs_info *info) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#ifndef LFS_READONLY
|
#ifndef LFS_READONLY
|
||||||
static int lfs_rawremove(lfs_t *lfs, const char *path) {
|
typedef int (*lfs_dir_prep_helper_t)(lfs_t *, struct lfs_mlist *, lfs_gstate_t *);
|
||||||
|
|
||||||
|
static int lfs_dir_prep_remove_nonempty_folders(lfs_t *lfs, struct lfs_mlist *dir,
|
||||||
|
lfs_gstate_t *tmp_gstate)
|
||||||
|
{
|
||||||
|
lfs_gstate_t split_gstate;
|
||||||
|
uint16_t id = 0;
|
||||||
|
|
||||||
|
// Walk tags stored in this directory and check for any directory
|
||||||
|
// tags. Removal of directories with a directory in them can lead
|
||||||
|
// to additional orphans in the filesystem, so we return
|
||||||
|
// LFS_ERR_NOTEMPTY in this case. Otherwise, leave the loaded
|
||||||
|
// directory for the tail end of the directory split to leave a proper
|
||||||
|
// view of the filesystem after removal.
|
||||||
|
while (true) {
|
||||||
|
if (dir->m.count == id) {
|
||||||
|
if (!dir->m.split) {
|
||||||
|
// We have iterated through the folder to the last
|
||||||
|
// tag.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Before we fetch the next block, update our fetched gstate xor
|
||||||
|
lfs_dir_getgstate(lfs, &dir->m, &split_gstate);
|
||||||
|
lfs_gstate_xor(tmp_gstate, &split_gstate);
|
||||||
|
|
||||||
|
int err = lfs_dir_fetch(lfs, &dir->m, dir->m.tail);
|
||||||
|
if (err) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
id = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
lfs_stag_t tag = lfs_dir_get(lfs, &dir->m, LFS_MKTAG(0x780, 0x3ff, 0),
|
||||||
|
LFS_MKTAG(LFS_TYPE_NAME, id, 0), NULL);
|
||||||
|
|
||||||
|
if (tag < 0) {
|
||||||
|
return tag;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lfs_tag_type3(tag) == LFS_TYPE_DIR) {
|
||||||
|
return LFS_ERR_NOTEMPTY;
|
||||||
|
}
|
||||||
|
|
||||||
|
id += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int lfs_dir_prep_removal(lfs_t *lfs, struct lfs_mlist *dir,
|
||||||
|
lfs_mdir_t *newcwd, uint16_t newid, lfs_block_t *pair,
|
||||||
|
lfs_gstate_t *tmp_gstate, lfs_dir_prep_helper_t helper)
|
||||||
|
{
|
||||||
|
lfs_stag_t res = lfs_dir_get(lfs, newcwd, LFS_MKTAG(0x700, 0x3ff, 0),
|
||||||
|
LFS_MKTAG(LFS_TYPE_STRUCT, newid, 8), pair);
|
||||||
|
if (res < 0) {
|
||||||
|
return (int)res;
|
||||||
|
}
|
||||||
|
lfs_pair_fromle32(pair);
|
||||||
|
|
||||||
|
memset(tmp_gstate, 0, sizeof(*tmp_gstate));
|
||||||
|
|
||||||
|
int err = lfs_dir_fetch(lfs, &dir->m, pair);
|
||||||
|
if (err) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dir->m.count > 0 || dir->m.split) {
|
||||||
|
// Normal POSIX behavior wouldn't allow a non-empty
|
||||||
|
// folder to be removed/renamed into in this manner
|
||||||
|
if (NULL == helper) {
|
||||||
|
return LFS_ERR_NOTEMPTY;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = helper(lfs, dir, tmp_gstate);
|
||||||
|
if (err) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// mark fs as orphaned
|
||||||
|
err = lfs_fs_preporphans(lfs, +1);
|
||||||
|
if (err) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
// I know it's crazy but yes, dir can be changed by our parent's
|
||||||
|
// commit (if predecessor is child)
|
||||||
|
dir->type = 0;
|
||||||
|
dir->id = 0;
|
||||||
|
lfs->mlist = dir;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef LFS_READONLY
|
||||||
|
static int lfs_rawremove(lfs_t *lfs, const char *path, lfs_dir_prep_helper_t helper) {
|
||||||
// deorphan if we haven't yet, needed at most once after poweron
|
// deorphan if we haven't yet, needed at most once after poweron
|
||||||
int err = lfs_fs_forceconsistency(lfs);
|
int err = lfs_fs_forceconsistency(lfs);
|
||||||
if (err) {
|
if (err) {
|
||||||
@@ -3204,37 +3303,16 @@ static int lfs_rawremove(lfs_t *lfs, const char *path) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct lfs_mlist dir;
|
struct lfs_mlist dir;
|
||||||
|
lfs_block_t pair[2];
|
||||||
|
lfs_gstate_t tmp_gstate;
|
||||||
dir.next = lfs->mlist;
|
dir.next = lfs->mlist;
|
||||||
if (lfs_tag_type3(tag) == LFS_TYPE_DIR) {
|
if (lfs_tag_type3(tag) == LFS_TYPE_DIR) {
|
||||||
// must be empty before removal
|
// must be empty before removal to prevent orphans
|
||||||
lfs_block_t pair[2];
|
err = lfs_dir_prep_removal(lfs, &dir, &cwd, lfs_tag_id(tag),
|
||||||
lfs_stag_t res = lfs_dir_get(lfs, &cwd, LFS_MKTAG(0x700, 0x3ff, 0),
|
pair, &tmp_gstate, helper);
|
||||||
LFS_MKTAG(LFS_TYPE_STRUCT, lfs_tag_id(tag), 8), pair);
|
if (err < 0) {
|
||||||
if (res < 0) {
|
|
||||||
return (int)res;
|
|
||||||
}
|
|
||||||
lfs_pair_fromle32(pair);
|
|
||||||
|
|
||||||
err = lfs_dir_fetch(lfs, &dir.m, pair);
|
|
||||||
if (err) {
|
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dir.m.count > 0 || dir.m.split) {
|
|
||||||
return LFS_ERR_NOTEMPTY;
|
|
||||||
}
|
|
||||||
|
|
||||||
// mark fs as orphaned
|
|
||||||
err = lfs_fs_preporphans(lfs, +1);
|
|
||||||
if (err) {
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
// I know it's crazy but yes, dir can be changed by our parent's
|
|
||||||
// commit (if predecessor is child)
|
|
||||||
dir.type = 0;
|
|
||||||
dir.id = 0;
|
|
||||||
lfs->mlist = &dir;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// delete the entry
|
// delete the entry
|
||||||
@@ -3253,11 +3331,15 @@ static int lfs_rawremove(lfs_t *lfs, const char *path) {
|
|||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
err = lfs_fs_pred(lfs, dir.m.pair, &cwd);
|
err = lfs_fs_pred(lfs, pair, &cwd);
|
||||||
if (err) {
|
if (err) {
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Merge in gstate from first block splits within the directory;
|
||||||
|
// lfs_dir_drop will pick up the last gstate entry.
|
||||||
|
lfs_gstate_xor(&lfs->gdelta, &tmp_gstate);
|
||||||
|
|
||||||
err = lfs_dir_drop(lfs, &cwd, &dir.m);
|
err = lfs_dir_drop(lfs, &cwd, &dir.m);
|
||||||
if (err) {
|
if (err) {
|
||||||
return err;
|
return err;
|
||||||
@@ -3269,7 +3351,8 @@ static int lfs_rawremove(lfs_t *lfs, const char *path) {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifndef LFS_READONLY
|
#ifndef LFS_READONLY
|
||||||
static int lfs_rawrename(lfs_t *lfs, const char *oldpath, const char *newpath) {
|
static int lfs_rawrename(lfs_t *lfs, const char *oldpath,
|
||||||
|
const char *newpath, lfs_dir_prep_helper_t helper) {
|
||||||
// deorphan if we haven't yet, needed at most once after poweron
|
// deorphan if we haven't yet, needed at most once after poweron
|
||||||
int err = lfs_fs_forceconsistency(lfs);
|
int err = lfs_fs_forceconsistency(lfs);
|
||||||
if (err) {
|
if (err) {
|
||||||
@@ -3297,6 +3380,8 @@ static int lfs_rawrename(lfs_t *lfs, const char *oldpath, const char *newpath) {
|
|||||||
uint16_t newoldid = lfs_tag_id(oldtag);
|
uint16_t newoldid = lfs_tag_id(oldtag);
|
||||||
|
|
||||||
struct lfs_mlist prevdir;
|
struct lfs_mlist prevdir;
|
||||||
|
lfs_block_t dir_pair[2];
|
||||||
|
lfs_gstate_t tmp_gstate;
|
||||||
prevdir.next = lfs->mlist;
|
prevdir.next = lfs->mlist;
|
||||||
if (prevtag == LFS_ERR_NOENT) {
|
if (prevtag == LFS_ERR_NOENT) {
|
||||||
// check that name fits
|
// check that name fits
|
||||||
@@ -3317,36 +3402,12 @@ static int lfs_rawrename(lfs_t *lfs, const char *oldpath, const char *newpath) {
|
|||||||
// we're renaming to ourselves??
|
// we're renaming to ourselves??
|
||||||
return 0;
|
return 0;
|
||||||
} else if (lfs_tag_type3(prevtag) == LFS_TYPE_DIR) {
|
} else if (lfs_tag_type3(prevtag) == LFS_TYPE_DIR) {
|
||||||
// must be empty before removal
|
// must be empty before removal to prevent orphans
|
||||||
lfs_block_t prevpair[2];
|
err = lfs_dir_prep_removal(lfs, &prevdir, &newcwd, newid,
|
||||||
lfs_stag_t res = lfs_dir_get(lfs, &newcwd, LFS_MKTAG(0x700, 0x3ff, 0),
|
dir_pair, &tmp_gstate, helper);
|
||||||
LFS_MKTAG(LFS_TYPE_STRUCT, newid, 8), prevpair);
|
|
||||||
if (res < 0) {
|
|
||||||
return (int)res;
|
|
||||||
}
|
|
||||||
lfs_pair_fromle32(prevpair);
|
|
||||||
|
|
||||||
// must be empty before removal
|
|
||||||
err = lfs_dir_fetch(lfs, &prevdir.m, prevpair);
|
|
||||||
if (err) {
|
if (err) {
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (prevdir.m.count > 0 || prevdir.m.split) {
|
|
||||||
return LFS_ERR_NOTEMPTY;
|
|
||||||
}
|
|
||||||
|
|
||||||
// mark fs as orphaned
|
|
||||||
err = lfs_fs_preporphans(lfs, +1);
|
|
||||||
if (err) {
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
// I know it's crazy but yes, dir can be changed by our parent's
|
|
||||||
// commit (if predecessor is child)
|
|
||||||
prevdir.type = 0;
|
|
||||||
prevdir.id = 0;
|
|
||||||
lfs->mlist = &prevdir;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!samepair) {
|
if (!samepair) {
|
||||||
@@ -3388,11 +3449,15 @@ static int lfs_rawrename(lfs_t *lfs, const char *oldpath, const char *newpath) {
|
|||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
err = lfs_fs_pred(lfs, prevdir.m.pair, &newcwd);
|
err = lfs_fs_pred(lfs, dir_pair, &newcwd);
|
||||||
if (err) {
|
if (err) {
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Merge in gstate from first split blocks in the directory;
|
||||||
|
// lfs_dir_drop will pick up the other gstate entries.
|
||||||
|
lfs_gstate_xor(&lfs->gdelta, &tmp_gstate);
|
||||||
|
|
||||||
err = lfs_dir_drop(lfs, &newcwd, &prevdir.m);
|
err = lfs_dir_drop(lfs, &newcwd, &prevdir.m);
|
||||||
if (err) {
|
if (err) {
|
||||||
return err;
|
return err;
|
||||||
@@ -4994,7 +5059,7 @@ int lfs_remove(lfs_t *lfs, const char *path) {
|
|||||||
}
|
}
|
||||||
LFS_TRACE("lfs_remove(%p, \"%s\")", (void*)lfs, path);
|
LFS_TRACE("lfs_remove(%p, \"%s\")", (void*)lfs, path);
|
||||||
|
|
||||||
err = lfs_rawremove(lfs, path);
|
err = lfs_rawremove(lfs, path, NULL);
|
||||||
|
|
||||||
LFS_TRACE("lfs_remove -> %d", err);
|
LFS_TRACE("lfs_remove -> %d", err);
|
||||||
LFS_UNLOCK(lfs->cfg);
|
LFS_UNLOCK(lfs->cfg);
|
||||||
@@ -5002,6 +5067,24 @@ int lfs_remove(lfs_t *lfs, const char *path) {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifndef LFS_READONLY
|
||||||
|
int lfs_removeall(lfs_t *lfs, const char *path) {
|
||||||
|
int err = LFS_LOCK(lfs->cfg);
|
||||||
|
if (err) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
LFS_TRACE("lfs_removeall(%p, \"%s\")", (void*)lfs, path);
|
||||||
|
|
||||||
|
// Note: We pass in a helper pointer here so that this extra
|
||||||
|
// logic can be dropped if it is never referenced
|
||||||
|
err = lfs_rawremove(lfs, path, lfs_dir_prep_remove_nonempty_folders);
|
||||||
|
|
||||||
|
LFS_TRACE("lfs_removeall -> %d", err);
|
||||||
|
LFS_UNLOCK(lfs->cfg);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifndef LFS_READONLY
|
#ifndef LFS_READONLY
|
||||||
int lfs_rename(lfs_t *lfs, const char *oldpath, const char *newpath) {
|
int lfs_rename(lfs_t *lfs, const char *oldpath, const char *newpath) {
|
||||||
int err = LFS_LOCK(lfs->cfg);
|
int err = LFS_LOCK(lfs->cfg);
|
||||||
@@ -5010,7 +5093,25 @@ int lfs_rename(lfs_t *lfs, const char *oldpath, const char *newpath) {
|
|||||||
}
|
}
|
||||||
LFS_TRACE("lfs_rename(%p, \"%s\", \"%s\")", (void*)lfs, oldpath, newpath);
|
LFS_TRACE("lfs_rename(%p, \"%s\", \"%s\")", (void*)lfs, oldpath, newpath);
|
||||||
|
|
||||||
err = lfs_rawrename(lfs, oldpath, newpath);
|
err = lfs_rawrename(lfs, oldpath, newpath, NULL);
|
||||||
|
|
||||||
|
LFS_TRACE("lfs_rename -> %d", err);
|
||||||
|
LFS_UNLOCK(lfs->cfg);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef LFS_READONLY
|
||||||
|
int lfs_rename_with_removeall(lfs_t *lfs, const char *oldpath, const char *newpath) {
|
||||||
|
int err = LFS_LOCK(lfs->cfg);
|
||||||
|
if (err) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
LFS_TRACE("lfs_rename(%p, \"%s\", \"%s\")", (void*)lfs, oldpath, newpath);
|
||||||
|
|
||||||
|
// Note: We pass in a helper pointer here so that this extra
|
||||||
|
// logic can be dropped if it is never referenced
|
||||||
|
err = lfs_rawrename(lfs, oldpath, newpath, lfs_dir_prep_remove_nonempty_folders);
|
||||||
|
|
||||||
LFS_TRACE("lfs_rename -> %d", err);
|
LFS_TRACE("lfs_rename -> %d", err);
|
||||||
LFS_UNLOCK(lfs->cfg);
|
LFS_UNLOCK(lfs->cfg);
|
||||||
|
|||||||
24
lfs.h
24
lfs.h
@@ -458,6 +458,17 @@ int lfs_unmount(lfs_t *lfs);
|
|||||||
int lfs_remove(lfs_t *lfs, const char *path);
|
int lfs_remove(lfs_t *lfs, const char *path);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifndef LFS_READONLY
|
||||||
|
// Removes a file or directory
|
||||||
|
//
|
||||||
|
// If removing a directory, the directory must not have
|
||||||
|
// any directories but it may contain files. This is
|
||||||
|
// non-POSIX behavior, and thus is a different call
|
||||||
|
// than lfs_remove(...)
|
||||||
|
// Returns a negative error code on failure.
|
||||||
|
int lfs_removeall(lfs_t *lfs, const char *path);
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifndef LFS_READONLY
|
#ifndef LFS_READONLY
|
||||||
// Rename or move a file or directory
|
// Rename or move a file or directory
|
||||||
//
|
//
|
||||||
@@ -468,6 +479,19 @@ int lfs_remove(lfs_t *lfs, const char *path);
|
|||||||
int lfs_rename(lfs_t *lfs, const char *oldpath, const char *newpath);
|
int lfs_rename(lfs_t *lfs, const char *oldpath, const char *newpath);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifndef LFS_READONLY
|
||||||
|
// Rename or move a file or directory
|
||||||
|
//
|
||||||
|
// If the destination exists, it must match the source in type.
|
||||||
|
// If the destination is a directory, it may not contain
|
||||||
|
// any directories but it may contain files. This is
|
||||||
|
// non-POSIX behavior, and thus is a different call
|
||||||
|
// than lfs_rename(...)
|
||||||
|
//
|
||||||
|
// Returns a negative error code on failure.
|
||||||
|
int lfs_rename_with_removeall(lfs_t *lfs, const char *oldpath, const char *newpath);
|
||||||
|
#endif
|
||||||
|
|
||||||
// Find info about a file or directory
|
// Find info about a file or directory
|
||||||
//
|
//
|
||||||
// Fills out the info structure, based on the specified file or directory.
|
// Fills out the info structure, based on the specified file or directory.
|
||||||
|
|||||||
@@ -292,8 +292,6 @@ class TestCase:
|
|||||||
if e.errno == errno.EIO:
|
if e.errno == errno.EIO:
|
||||||
break
|
break
|
||||||
raise
|
raise
|
||||||
if not line:
|
|
||||||
break;
|
|
||||||
stdout.append(line)
|
stdout.append(line)
|
||||||
if args.get('verbose'):
|
if args.get('verbose'):
|
||||||
sys.stdout.write(line)
|
sys.stdout.write(line)
|
||||||
@@ -689,8 +687,6 @@ def main(**args):
|
|||||||
if e.errno == errno.EIO:
|
if e.errno == errno.EIO:
|
||||||
break
|
break
|
||||||
raise
|
raise
|
||||||
if not line:
|
|
||||||
break;
|
|
||||||
stdout.append(line)
|
stdout.append(line)
|
||||||
if args.get('verbose'):
|
if args.get('verbose'):
|
||||||
sys.stdout.write(line)
|
sys.stdout.write(line)
|
||||||
|
|||||||
305
tests/test_relocations_with_removeall.toml
Normal file
305
tests/test_relocations_with_removeall.toml
Normal file
@@ -0,0 +1,305 @@
|
|||||||
|
# specific corner cases worth explicitly testing for
|
||||||
|
[[case]] # dangling split dir test
|
||||||
|
define.ITERATIONS = 20
|
||||||
|
define.COUNT = 10
|
||||||
|
define.LFS_BLOCK_CYCLES = [8, 1]
|
||||||
|
code = '''
|
||||||
|
lfs_format(&lfs, &cfg) => 0;
|
||||||
|
// fill up filesystem so only ~16 blocks are left
|
||||||
|
lfs_mount(&lfs, &cfg) => 0;
|
||||||
|
lfs_file_open(&lfs, &file, "padding", LFS_O_CREAT | LFS_O_WRONLY) => 0;
|
||||||
|
memset(buffer, 0, 512);
|
||||||
|
while (LFS_BLOCK_COUNT - lfs_fs_size(&lfs) > 16) {
|
||||||
|
lfs_file_write(&lfs, &file, buffer, 512) => 512;
|
||||||
|
}
|
||||||
|
lfs_file_close(&lfs, &file) => 0;
|
||||||
|
// make a child dir to use in bounded space
|
||||||
|
lfs_mkdir(&lfs, "child") => 0;
|
||||||
|
lfs_unmount(&lfs) => 0;
|
||||||
|
|
||||||
|
lfs_mount(&lfs, &cfg) => 0;
|
||||||
|
for (int j = 0; j < ITERATIONS; j++) {
|
||||||
|
for (int i = 0; i < COUNT; i++) {
|
||||||
|
sprintf(path, "child/test%03d_loooooooooooooooooong_name", i);
|
||||||
|
lfs_file_open(&lfs, &file, path, LFS_O_CREAT | LFS_O_WRONLY) => 0;
|
||||||
|
lfs_file_close(&lfs, &file) => 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
lfs_dir_open(&lfs, &dir, "child") => 0;
|
||||||
|
lfs_dir_read(&lfs, &dir, &info) => 1;
|
||||||
|
lfs_dir_read(&lfs, &dir, &info) => 1;
|
||||||
|
for (int i = 0; i < COUNT; i++) {
|
||||||
|
sprintf(path, "test%03d_loooooooooooooooooong_name", i);
|
||||||
|
lfs_dir_read(&lfs, &dir, &info) => 1;
|
||||||
|
strcmp(info.name, path) => 0;
|
||||||
|
}
|
||||||
|
lfs_dir_read(&lfs, &dir, &info) => 0;
|
||||||
|
lfs_dir_close(&lfs, &dir) => 0;
|
||||||
|
|
||||||
|
if (j == ITERATIONS-1) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < COUNT; i++) {
|
||||||
|
sprintf(path, "child/test%03d_loooooooooooooooooong_name", i);
|
||||||
|
lfs_removeall(&lfs, path) => 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lfs_unmount(&lfs) => 0;
|
||||||
|
|
||||||
|
lfs_mount(&lfs, &cfg) => 0;
|
||||||
|
lfs_dir_open(&lfs, &dir, "child") => 0;
|
||||||
|
lfs_dir_read(&lfs, &dir, &info) => 1;
|
||||||
|
lfs_dir_read(&lfs, &dir, &info) => 1;
|
||||||
|
for (int i = 0; i < COUNT; i++) {
|
||||||
|
sprintf(path, "test%03d_loooooooooooooooooong_name", i);
|
||||||
|
lfs_dir_read(&lfs, &dir, &info) => 1;
|
||||||
|
strcmp(info.name, path) => 0;
|
||||||
|
}
|
||||||
|
lfs_dir_read(&lfs, &dir, &info) => 0;
|
||||||
|
lfs_dir_close(&lfs, &dir) => 0;
|
||||||
|
for (int i = 0; i < COUNT; i++) {
|
||||||
|
sprintf(path, "child/test%03d_loooooooooooooooooong_name", i);
|
||||||
|
lfs_removeall(&lfs, path) => 0;
|
||||||
|
}
|
||||||
|
lfs_unmount(&lfs) => 0;
|
||||||
|
'''
|
||||||
|
|
||||||
|
[[case]] # outdated head test
|
||||||
|
define.ITERATIONS = 20
|
||||||
|
define.COUNT = 10
|
||||||
|
define.LFS_BLOCK_CYCLES = [8, 1]
|
||||||
|
code = '''
|
||||||
|
lfs_format(&lfs, &cfg) => 0;
|
||||||
|
// fill up filesystem so only ~16 blocks are left
|
||||||
|
lfs_mount(&lfs, &cfg) => 0;
|
||||||
|
lfs_file_open(&lfs, &file, "padding", LFS_O_CREAT | LFS_O_WRONLY) => 0;
|
||||||
|
memset(buffer, 0, 512);
|
||||||
|
while (LFS_BLOCK_COUNT - lfs_fs_size(&lfs) > 16) {
|
||||||
|
lfs_file_write(&lfs, &file, buffer, 512) => 512;
|
||||||
|
}
|
||||||
|
lfs_file_close(&lfs, &file) => 0;
|
||||||
|
// make a child dir to use in bounded space
|
||||||
|
lfs_mkdir(&lfs, "child") => 0;
|
||||||
|
lfs_unmount(&lfs) => 0;
|
||||||
|
|
||||||
|
lfs_mount(&lfs, &cfg) => 0;
|
||||||
|
for (int j = 0; j < ITERATIONS; j++) {
|
||||||
|
for (int i = 0; i < COUNT; i++) {
|
||||||
|
sprintf(path, "child/test%03d_loooooooooooooooooong_name", i);
|
||||||
|
lfs_file_open(&lfs, &file, path, LFS_O_CREAT | LFS_O_WRONLY) => 0;
|
||||||
|
lfs_file_close(&lfs, &file) => 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
lfs_dir_open(&lfs, &dir, "child") => 0;
|
||||||
|
lfs_dir_read(&lfs, &dir, &info) => 1;
|
||||||
|
lfs_dir_read(&lfs, &dir, &info) => 1;
|
||||||
|
for (int i = 0; i < COUNT; i++) {
|
||||||
|
sprintf(path, "test%03d_loooooooooooooooooong_name", i);
|
||||||
|
lfs_dir_read(&lfs, &dir, &info) => 1;
|
||||||
|
strcmp(info.name, path) => 0;
|
||||||
|
info.size => 0;
|
||||||
|
|
||||||
|
sprintf(path, "child/test%03d_loooooooooooooooooong_name", i);
|
||||||
|
lfs_file_open(&lfs, &file, path, LFS_O_WRONLY) => 0;
|
||||||
|
lfs_file_write(&lfs, &file, "hi", 2) => 2;
|
||||||
|
lfs_file_close(&lfs, &file) => 0;
|
||||||
|
}
|
||||||
|
lfs_dir_read(&lfs, &dir, &info) => 0;
|
||||||
|
|
||||||
|
lfs_dir_rewind(&lfs, &dir) => 0;
|
||||||
|
lfs_dir_read(&lfs, &dir, &info) => 1;
|
||||||
|
lfs_dir_read(&lfs, &dir, &info) => 1;
|
||||||
|
for (int i = 0; i < COUNT; i++) {
|
||||||
|
sprintf(path, "test%03d_loooooooooooooooooong_name", i);
|
||||||
|
lfs_dir_read(&lfs, &dir, &info) => 1;
|
||||||
|
strcmp(info.name, path) => 0;
|
||||||
|
info.size => 2;
|
||||||
|
|
||||||
|
sprintf(path, "child/test%03d_loooooooooooooooooong_name", i);
|
||||||
|
lfs_file_open(&lfs, &file, path, LFS_O_WRONLY) => 0;
|
||||||
|
lfs_file_write(&lfs, &file, "hi", 2) => 2;
|
||||||
|
lfs_file_close(&lfs, &file) => 0;
|
||||||
|
}
|
||||||
|
lfs_dir_read(&lfs, &dir, &info) => 0;
|
||||||
|
|
||||||
|
lfs_dir_rewind(&lfs, &dir) => 0;
|
||||||
|
lfs_dir_read(&lfs, &dir, &info) => 1;
|
||||||
|
lfs_dir_read(&lfs, &dir, &info) => 1;
|
||||||
|
for (int i = 0; i < COUNT; i++) {
|
||||||
|
sprintf(path, "test%03d_loooooooooooooooooong_name", i);
|
||||||
|
lfs_dir_read(&lfs, &dir, &info) => 1;
|
||||||
|
strcmp(info.name, path) => 0;
|
||||||
|
info.size => 2;
|
||||||
|
}
|
||||||
|
lfs_dir_read(&lfs, &dir, &info) => 0;
|
||||||
|
lfs_dir_close(&lfs, &dir) => 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < COUNT; i++) {
|
||||||
|
sprintf(path, "child/test%03d_loooooooooooooooooong_name", i);
|
||||||
|
lfs_removeall(&lfs, path) => 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lfs_unmount(&lfs) => 0;
|
||||||
|
'''
|
||||||
|
|
||||||
|
[[case]] # reentrant testing for relocations, this is the same as the
|
||||||
|
# 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=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);
|
||||||
|
if (err) {
|
||||||
|
lfs_format(&lfs, &cfg) => 0;
|
||||||
|
lfs_mount(&lfs, &cfg) => 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
srand(1);
|
||||||
|
const char alpha[] = "abcdefghijklmnopqrstuvwxyz";
|
||||||
|
for (int i = 0; i < CYCLES; i++) {
|
||||||
|
// create random path
|
||||||
|
char full_path[256];
|
||||||
|
for (int d = 0; d < DEPTH; d++) {
|
||||||
|
sprintf(&full_path[2*d], "/%c", alpha[rand() % FILES]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// if it does not exist, we create it, else we destroy
|
||||||
|
int res = lfs_stat(&lfs, full_path, &info);
|
||||||
|
if (res == LFS_ERR_NOENT) {
|
||||||
|
// create each directory in turn, ignore if dir already exists
|
||||||
|
for (int d = 0; d < DEPTH; d++) {
|
||||||
|
strcpy(path, full_path);
|
||||||
|
path[2*d+2] = '\0';
|
||||||
|
err = lfs_mkdir(&lfs, path);
|
||||||
|
assert(!err || err == LFS_ERR_EXIST);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int d = 0; d < DEPTH; d++) {
|
||||||
|
strcpy(path, full_path);
|
||||||
|
path[2*d+2] = '\0';
|
||||||
|
lfs_stat(&lfs, path, &info) => 0;
|
||||||
|
assert(strcmp(info.name, &path[2*d+1]) == 0);
|
||||||
|
assert(info.type == LFS_TYPE_DIR);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// is valid dir?
|
||||||
|
assert(strcmp(info.name, &full_path[2*(DEPTH-1)+1]) == 0);
|
||||||
|
assert(info.type == LFS_TYPE_DIR);
|
||||||
|
|
||||||
|
// try to delete path in reverse order, ignore if dir is not empty
|
||||||
|
for (int d = DEPTH-1; d >= 0; d--) {
|
||||||
|
strcpy(path, full_path);
|
||||||
|
path[2*d+2] = '\0';
|
||||||
|
err = lfs_removeall(&lfs, path);
|
||||||
|
assert(!err || err == LFS_ERR_NOTEMPTY);
|
||||||
|
}
|
||||||
|
|
||||||
|
lfs_stat(&lfs, full_path, &info) => LFS_ERR_NOENT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lfs_unmount(&lfs) => 0;
|
||||||
|
'''
|
||||||
|
|
||||||
|
[[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=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);
|
||||||
|
if (err) {
|
||||||
|
lfs_format(&lfs, &cfg) => 0;
|
||||||
|
lfs_mount(&lfs, &cfg) => 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
srand(1);
|
||||||
|
const char alpha[] = "abcdefghijklmnopqrstuvwxyz";
|
||||||
|
for (int i = 0; i < CYCLES; i++) {
|
||||||
|
// create random path
|
||||||
|
char full_path[256];
|
||||||
|
for (int d = 0; d < DEPTH; d++) {
|
||||||
|
sprintf(&full_path[2*d], "/%c", alpha[rand() % FILES]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// if it does not exist, we create it, else we destroy
|
||||||
|
int res = lfs_stat(&lfs, full_path, &info);
|
||||||
|
assert(!res || res == LFS_ERR_NOENT);
|
||||||
|
if (res == LFS_ERR_NOENT) {
|
||||||
|
// create each directory in turn, ignore if dir already exists
|
||||||
|
for (int d = 0; d < DEPTH; d++) {
|
||||||
|
strcpy(path, full_path);
|
||||||
|
path[2*d+2] = '\0';
|
||||||
|
err = lfs_mkdir(&lfs, path);
|
||||||
|
assert(!err || err == LFS_ERR_EXIST);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int d = 0; d < DEPTH; d++) {
|
||||||
|
strcpy(path, full_path);
|
||||||
|
path[2*d+2] = '\0';
|
||||||
|
lfs_stat(&lfs, path, &info) => 0;
|
||||||
|
assert(strcmp(info.name, &path[2*d+1]) == 0);
|
||||||
|
assert(info.type == LFS_TYPE_DIR);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
assert(strcmp(info.name, &full_path[2*(DEPTH-1)+1]) == 0);
|
||||||
|
assert(info.type == LFS_TYPE_DIR);
|
||||||
|
|
||||||
|
// create new random path
|
||||||
|
char new_path[256];
|
||||||
|
for (int d = 0; d < DEPTH; d++) {
|
||||||
|
sprintf(&new_path[2*d], "/%c", alpha[rand() % FILES]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// if new path does not exist, rename, otherwise destroy
|
||||||
|
res = lfs_stat(&lfs, new_path, &info);
|
||||||
|
assert(!res || res == LFS_ERR_NOENT);
|
||||||
|
if (res == LFS_ERR_NOENT) {
|
||||||
|
// stop once some dir is renamed
|
||||||
|
for (int d = 0; d < DEPTH; d++) {
|
||||||
|
strcpy(&path[2*d], &full_path[2*d]);
|
||||||
|
path[2*d+2] = '\0';
|
||||||
|
strcpy(&path[128+2*d], &new_path[2*d]);
|
||||||
|
path[128+2*d+2] = '\0';
|
||||||
|
err = lfs_rename(&lfs, path, path+128);
|
||||||
|
assert(!err || err == LFS_ERR_NOTEMPTY);
|
||||||
|
if (!err) {
|
||||||
|
strcpy(path, path+128);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int d = 0; d < DEPTH; d++) {
|
||||||
|
strcpy(path, new_path);
|
||||||
|
path[2*d+2] = '\0';
|
||||||
|
lfs_stat(&lfs, path, &info) => 0;
|
||||||
|
assert(strcmp(info.name, &path[2*d+1]) == 0);
|
||||||
|
assert(info.type == LFS_TYPE_DIR);
|
||||||
|
}
|
||||||
|
|
||||||
|
lfs_stat(&lfs, full_path, &info) => LFS_ERR_NOENT;
|
||||||
|
} else {
|
||||||
|
// try to delete path in reverse order,
|
||||||
|
// ignore if dir is not empty
|
||||||
|
for (int d = DEPTH-1; d >= 0; d--) {
|
||||||
|
strcpy(path, full_path);
|
||||||
|
path[2*d+2] = '\0';
|
||||||
|
err = lfs_removeall(&lfs, path);
|
||||||
|
assert(!err || err == LFS_ERR_NOTEMPTY);
|
||||||
|
}
|
||||||
|
|
||||||
|
lfs_stat(&lfs, full_path, &info) => LFS_ERR_NOENT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lfs_unmount(&lfs) => 0;
|
||||||
|
'''
|
||||||
Reference in New Issue
Block a user