Implemented lfsr_rename

Only simple tests right now, but the theory is sound.

This mainly required the addition of the fancy in-device move attribute,
which copies all tags associated with an rid from one rbyd to another in
a single transaction.

This is a carryover from the previous littlefs implementation, though it
is easier to implement here since it is effectively a range query on the
rbyd tree, which trees are really good at. This was intentional.

Oh and I suppose this also required implementing lfsr_rename, which has
a few corner cases to watch out for.

It is nice that both lfsr_remove and lfsr_rename can rely on
lfsr_fs_fixgrm to finish all of the removes, which wasn't previously
reasonable due to the overhead of deorphaning.
This commit is contained in:
Christopher Haster
2023-07-23 14:03:05 -05:00
parent e4ba43dd5f
commit ee37f8c7a6
3 changed files with 574 additions and 43 deletions
+241 -43
View File
@@ -639,6 +639,9 @@ enum lfsr_tag_type {
LFSR_TAG_CRC = 0x2000,
LFSR_TAG_FCRC = 0x2100,
// in-device only
LFSR_TAG_MOVE = 0x0800,
};
#define LFSR_TAG_ALTLE(red, key) \
@@ -1259,15 +1262,27 @@ typedef struct lfsr_attr {
lfs_ssize_t id;
lfsr_tag_t tag;
lfs_ssize_t delta;
lfsr_data_t data;
union {
lfsr_data_t data;
struct {
const lfsr_rbyd_t *rbyd;
lfs_ssize_t rid;
} move;
} d;
} lfsr_attr_t;
#define LFSR_ATTR_DATA_(_id, _tag, _delta, _data) \
((const lfsr_attr_t){_id, _tag, _delta, _data})
((const lfsr_attr_t){_id, _tag, _delta, {.data=_data}})
#define LFSR_ATTR_DATA(_id, _type, _delta, _data) \
LFSR_ATTR_DATA_(_id, LFSR_TAG_##_type, _delta, _data)
#define LFSR_ATTR_MOVE_(_new_id, _type, _delta, _rbyd, _old_id) \
((const lfsr_attr_t){_new_id, _type, _delta, {.move={_rbyd, _old_id}}})
#define LFSR_ATTR_MOVE(_new_id, _type, _delta, _rbyd, _old_id) \
LFSR_ATTR_MOVE_(_new_id, LFSR_TAG_##_type, _delta, _rbyd, _old_id)
#define LFSR_ATTR_DNAME_(_id, _tag, _delta, _did, _buffer, _size) \
LFSR_ATTR_DATA_(_id, _tag, _delta, LFSR_DATA_DNAME(_did, _buffer, _size))
@@ -2713,11 +2728,11 @@ static int lfsr_rbyd_appendall(lfs_t *lfs, lfsr_rbyd_t *rbyd,
const lfsr_attr_t *attrs, lfs_size_t attr_count) {
// append each tag to the tree
for (lfs_size_t i = 0; i < attr_count; i++) {
// TODO do we really need this?
// skip unknown internal tags (used by upper layers)
if (lfsr_tag_isinternal(attrs[i].tag)) {
continue;
}
// // TODO do we really need this?
// // skip unknown internal tags (used by upper layers)
// if (lfsr_tag_isinternal(attrs[i].tag)) {
// continue;
// }
// this is a bit of a hack, but ignore any gstate tags here,
// these need to be handled specially by upper-layers
@@ -2725,12 +2740,51 @@ static int lfsr_rbyd_appendall(lfs_t *lfs, lfsr_rbyd_t *rbyd,
continue;
}
// don't write tags outside of the requested range
if (attrs[i].id >= start_id && (end_id < 0 || attrs[i].id < end_id)) {
int err = lfsr_rbyd_append(lfs, rbyd,
attrs[i].id-lfs_smax32(start_id, 0),
attrs[i].tag, attrs[i].delta, attrs[i].data);
if (err) {
return err;
// this is a bit of a hack, but ignore any gstate tags here,
// these need to be handled specially by upper-layers
if (lfsr_tag_suptype(attrs[i].tag) == LFSR_TAG_GSTATE) {
// do nothing
// move tags copy over any tags associated with the source's rid
} else if (lfsr_tag_suptype(attrs[i].tag) == LFSR_TAG_MOVE) {
// weighted moves are not supported
LFS_ASSERT(attrs[i].delta == 0);
// skip the name tag, this is always replaced by upper layers
lfsr_tag_t tag = LFSR_TAG_NAME + 0xff;
while (true) {
lfs_ssize_t rid;
lfsr_data_t data;
int err = lfsr_rbyd_lookupnext(lfs, attrs[i].d.move.rbyd,
attrs[i].d.move.rid, lfsr_tag_next(tag),
&rid, &tag, NULL, &data);
if (err && err != LFS_ERR_NOENT) {
return err;
}
if (err == LFS_ERR_NOENT || rid != attrs[i].d.move.rid) {
break;
}
// append the attr
err = lfsr_rbyd_append(lfs, rbyd, attrs[i].id,
tag, 0, data);
if (err) {
return err;
}
}
// write out normal tags normally
} else {
LFS_ASSERT(!lfsr_tag_isinternal(attrs[i].tag));
int err = lfsr_rbyd_append(lfs, rbyd,
attrs[i].id-lfs_smax32(start_id, 0),
attrs[i].tag, attrs[i].delta, attrs[i].d.data);
if (err) {
return err;
}
}
}
@@ -5776,11 +5830,11 @@ static int lfsr_mdir_commit(lfs_t *lfs, lfsr_mdir_t *mdir, lfs_ssize_t *rid,
lfsr_fs_flushgdelta(lfs);
for (lfs_size_t i = 0; i < attr_count; i++) {
if (attrs[i].tag == LFSR_TAG_GRM) {
LFS_ASSERT(lfsr_data_size(attrs[i].data) <= LFSR_GRM_DSIZE);
LFS_ASSERT(lfsr_data_size(attrs[i].d.data) <= LFSR_GRM_DSIZE);
// xor against current gstate value to get our gdelta
memcpy(lfs->grmd, lfs->grm, LFSR_GRM_DSIZE);
int err = lfsr_grm_xor(lfs, lfs->grmd, attrs[i].data);
int err = lfsr_grm_xor(lfs, lfs->grmd, attrs[i].d.data);
if (err) {
return err;
}
@@ -5965,7 +6019,7 @@ static int lfsr_mdir_commit(lfs_t *lfs, lfsr_mdir_t *mdir, lfs_ssize_t *rid,
// to fix grms, we 1. decode, 2. fix, 3. reencode, 4. xor into
// any other pending grm delta
lfsr_grm_t grm;
lfs_ssize_t d = lfsr_grm_fromdisk(lfs, &grm, attrs[i].data);
lfs_ssize_t d = lfsr_grm_fromdisk(lfs, &grm, attrs[i].d.data);
if (d < 0) {
return d;
}
@@ -6004,7 +6058,7 @@ static int lfsr_mdir_commit(lfs_t *lfs, lfsr_mdir_t *mdir, lfs_ssize_t *rid,
//
// gd' = gd xor (grm' xor grm)
//
int err = lfsr_grm_xor(lfs, buf, attrs[i].data);
int err = lfsr_grm_xor(lfs, buf, attrs[i].d.data);
if (err) {
return err;
}
@@ -6134,7 +6188,7 @@ static int lfsr_mdir_commit(lfs_t *lfs, lfsr_mdir_t *mdir, lfs_ssize_t *rid,
// update our gstate
for (lfs_size_t i = 0; i < attr_count; i++) {
if (attrs[i].tag == LFSR_TAG_GRM) {
lfs_ssize_t d = lfsr_grm_fromdisk(lfs, &lfs->grm_, attrs[i].data);
lfs_ssize_t d = lfsr_grm_fromdisk(lfs, &lfs->grm_, attrs[i].d.data);
if (d < 0) {
return d;
}
@@ -7103,9 +7157,13 @@ static int lfsr_mountinited(lfs_t *lfs) {
if (lfsr_grm_hasrm(&lfs->grm_)) {
LFS_DEBUG("Found pending grm %"PRId32".%"PRId32" %"PRId32".%"PRId32,
lfs->grm_.rms[0].mid,
lfs->grm_.rms[0].rid,
(lfs->grm_.rms[0].mid != LFSR_MID_RM
? lfs->grm_.rms[0].rid
: 0),
lfs->grm_.rms[1].mid,
lfs->grm_.rms[1].rid);
(lfs->grm_.rms[1].mid != LFSR_MID_RM
? lfs->grm_.rms[1].rid
: 0));
}
return 0;
@@ -7453,10 +7511,10 @@ int lfsr_remove(lfs_t *lfs, const char *path) {
}
// if we're removing a directory, we need to also remove the
// dstart entry, first lets figure out the did
uint8_t grm_buf[LFSR_GRM_DSIZE];
lfs_ssize_t grm_d;
// dstart entry
lfsr_grm_t grm_ = lfs->grm_;
if (tag == LFSR_TAG_DIR) {
// first lets figure out the did
lfsr_data_t data;
int err = lfsr_mdir_lookup(lfs, &mdir, rid, LFSR_TAG_DID,
NULL, &data);
@@ -7508,43 +7566,179 @@ int lfsr_remove(lfs_t *lfs, const char *path) {
}
empty:;
// create a grm to remove the dstart entry
lfsr_grm_pushrm(&grm_, dstart_mdir.mid, dstart_rid);
// TODO should we just make this an atomic remove?
// adjust rid if grm is on the same mdir as our dir
if (dstart_mdir.mid == mdir.mid && dstart_rid > rid) {
dstart_rid -= 1;
if (grm_.rms[0].mid == mdir.mid
&& (lfs_ssize_t)grm_.rms[0].rid > rid) {
grm_.rms[0].rid -= 1;
}
}
// create a grm to remove the dstart entry
grm_d = lfsr_grm_todisk(lfs,
&(lfsr_grm_t){.rms={
{.mid=dstart_mdir.mid, .rid=dstart_rid},
{.mid=LFSR_MID_RM}}},
grm_buf);
if (grm_d < 0) {
return grm_d;
}
uint8_t grm_buf[LFSR_GRM_DSIZE];
lfs_ssize_t grm_d = lfsr_grm_todisk(lfs, &grm_, grm_buf);
if (grm_d < 0) {
return grm_d;
}
// remove the metadata entry
err = lfsr_mdir_commit(lfs, &mdir, &rid, LFSR_ATTRS(
LFSR_ATTR(rid, UNR, -1, NULL, 0),
(tag == LFSR_TAG_DIR
? LFSR_ATTR(-1, GRM, 0, grm_buf, grm_d)
: LFSR_ATTR_NOOP)));
LFSR_ATTR(-1, GRM, 0, grm_buf, grm_d)));
if (err) {
return err;
}
// if we were a directory, we need to clean up, fortunately we can leave
// this up to lfsr_fs_fixgrm
if (tag == LFSR_TAG_DIR) {
err = lfsr_fs_fixgrm(lfs);
if (err) {
return err;
return lfsr_fs_fixgrm(lfs);
}
int lfsr_rename(lfs_t *lfs, const char *old_path, const char *new_path) {
// prepare our filesystem for writing
int err = lfsr_fs_preparemutation(lfs);
if (err) {
return err;
}
// lookup old entry
lfsr_mdir_t old_mdir;
lfs_ssize_t old_rid;
lfsr_tag_t old_tag;
err = lfsr_mtree_pathlookup(lfs, old_path,
&old_mdir, &old_rid, &old_tag,
NULL, NULL, NULL);
if (err) {
return err;
}
// mark old entry for removal with a grm
lfsr_grm_t grm_ = lfs->grm_;
lfsr_grm_pushrm(&grm_, old_mdir.mid, old_rid);
// lookup new entry
lfsr_mdir_t new_mdir;
lfs_ssize_t new_rid;
lfsr_tag_t new_tag;
lfs_size_t new_did;
const char *new_name;
lfs_size_t new_name_size;
err = lfsr_mtree_pathlookup(lfs, new_path,
&new_mdir, &new_rid, &new_tag,
&new_did, &new_name, &new_name_size);
if (err && err != LFS_ERR_NOENT) {
return err;
}
bool exists = (err != LFS_ERR_NOENT);
// there are a few cases we need to watch out for
if (!exists) {
// check that name fits
if (new_name_size > lfs->name_max) {
return LFS_ERR_NAMETOOLONG;
}
// TODO should we just make this an atomic rename?
// adjust old rid if grm is on the same mdir as new rid
if (grm_.rms[0].mid == new_mdir.mid
&& (lfs_ssize_t)grm_.rms[0].rid > new_rid) {
grm_.rms[0].rid -= 1;
}
} else {
// renaming different types is an error
if (old_tag != new_tag) {
return LFS_ERR_ISDIR;
}
// TODO is it? is this check necessary?
// renaming to ourself is a noop
if (old_mdir.mid == new_mdir.mid && old_rid == new_rid) {
return 0;
}
// if our destination is a directory, we will be implicitly removing
// the directory, we need to great a grm for this
if (new_tag == LFSR_TAG_DIR) {
// TODO deduplicate the isempty check with lfsr_remove?
lfsr_data_t data;
int err = lfsr_mdir_lookup(lfs, &new_mdir, new_rid, LFSR_TAG_DID,
NULL, &data);
if (err) {
return err;
}
lfs_size_t did;
lfs_ssize_t d = lfsr_data_readleb128(lfs, data, 0, &did);
if (d < 0) {
return d;
}
// check that the directory is empty
lfsr_mdir_t dstart_mdir;
lfs_ssize_t dstart_rid;
err = lfsr_mtree_dnamelookup(lfs, did, NULL, 0,
&dstart_mdir, &dstart_rid, NULL, NULL);
if (err) {
LFS_ASSERT(err != LFS_ERR_NOENT);
return err;
}
lfsr_mdir_t mdir_ = dstart_mdir;
lfs_ssize_t rid_ = dstart_rid + 1;
if (rid_ >= (lfs_ssize_t)mdir_.rbyd.weight) {
// out of mdirs?
lfs_ssize_t mid = mdir_.mid + 1;
if (mid >= (lfs_ssize_t)lfsr_mtree_weight(lfs)) {
goto empty;
}
int err = lfsr_mtree_lookup(lfs, mid, &mdir_);
if (err) {
return err;
}
rid_ = 0;
}
lfsr_tag_t tag_;
err = lfsr_mdir_lookup(lfs, &mdir_, rid_, LFSR_TAG_WIDENAME,
&tag_, NULL);
if (err) {
return err;
}
if (tag_ != LFSR_TAG_DSTART) {
return LFS_ERR_NOTEMPTY;
}
empty:;
// create a grm to remove the dstart entry
lfsr_grm_pushrm(&grm_, dstart_mdir.mid, dstart_rid);
}
}
return 0;
uint8_t grm_buf[LFSR_GRM_DSIZE];
lfs_ssize_t grm_d = lfsr_grm_todisk(lfs, &grm_, grm_buf);
if (grm_d < 0) {
return grm_d;
}
// rename our entry, copying all tags associated with the old rid to the
// new rid, while also marking the old rid for removal
err = lfsr_mdir_commit(lfs, &new_mdir, &new_rid, LFSR_ATTRS(
(exists
? LFSR_ATTR(new_rid, UNR, -1, NULL, 0)
: LFSR_ATTR_NOOP),
LFSR_ATTR_DNAME_(new_rid, old_tag, +1,
new_did, new_name, new_name_size),
LFSR_ATTR_MOVE(new_rid, MOVE, 0, &old_mdir.rbyd, old_rid),
LFSR_ATTR(-1, GRM, 0, grm_buf, grm_d)));
// we need to clean up any pending grms, fortunately we can leave
// this up to lfsr_fs_fixgrm
return lfsr_fs_fixgrm(lfs);
}
int lfsr_stat(lfs_t *lfs, const char *path, struct lfs_info *info) {
@@ -7780,9 +7974,13 @@ static int lfsr_fs_preparemutation(lfs_t *lfs) {
if (lfsr_grm_hasrm(&lfs->grm_)) {
LFS_DEBUG("Fixing grm %"PRId32".%"PRId32" %"PRId32".%"PRId32,
lfs->grm_.rms[0].mid,
lfs->grm_.rms[0].rid,
(lfs->grm_.rms[0].mid != LFSR_MID_RM
? lfs->grm_.rms[0].rid
: 0),
lfs->grm_.rms[1].mid,
lfs->grm_.rms[1].rid);
(lfs->grm_.rms[1].mid != LFSR_MID_RM
? lfs->grm_.rms[1].rid
: 0));
int err = lfsr_fs_fixgrm(lfs);
if (err) {
+1
View File
@@ -566,6 +566,7 @@ int lfsr_remove(lfs_t *lfs, const char *path);
//
// Returns a negative error code on failure.
int lfs_rename(lfs_t *lfs, const char *oldpath, const char *newpath);
int lfsr_rename(lfs_t *lfs, const char *old_path, const char *new_path);
#endif
// Find info about a file or directory
+332
View File
@@ -2803,6 +2803,338 @@ code = '''
lfsr_unmount(&lfs) => 0;
'''
## dir rename tests
[cases.t5_dirs_mv]
defines.REMOUNT = [false, true]
reentrant = true
code = '''
// format once per test
lfs_t lfs;
int err = lfsr_mount(&lfs, cfg);
if (err) {
lfsr_format(&lfs, cfg) => 0;
lfsr_mount(&lfs, cfg) => 0;
}
// make a directory
err = lfsr_mkdir(&lfs, "ardvark");
assert(!err || (TEST_PL && err == LFS_ERR_EXIST));
// clean up interrupted renames
if (TEST_PL) {
err = lfsr_remove(&lfs, "banana");
assert(!err || err == LFS_ERR_NOENT);
}
// remount?
if (REMOUNT) {
lfsr_unmount(&lfs) => 0;
lfsr_mount(&lfs, cfg) => 0;
// grm should be zero here
assert(lfs.grm[0] == 0);
}
// check that our mkdir worked with stat
struct lfs_info info;
lfsr_stat(&lfs, "ardvark", &info) => 0;
assert(strcmp(info.name, "ardvark") == 0);
assert(info.type == LFS_TYPE_DIR);
// and with dir_read
lfsr_dir_t dir;
lfsr_dir_open(&lfs, &dir, "/") => 0;
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, ".") == 0);
assert(info.type == LFS_TYPE_DIR);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "..") == 0);
assert(info.type == LFS_TYPE_DIR);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "ardvark") == 0);
assert(info.type == LFS_TYPE_DIR);
lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT;
lfsr_dir_close(&lfs, &dir) => 0;
// now rename the directory
lfsr_rename(&lfs, "ardvark", "banana") => 0;
// remount?
if (REMOUNT) {
lfsr_unmount(&lfs) => 0;
lfsr_mount(&lfs, cfg) => 0;
// grm should be zero here
assert(lfs.grm[0] == 0);
}
// check that rename worked with stat
lfsr_stat(&lfs, "ardvark", &info) => LFS_ERR_NOENT;
lfsr_stat(&lfs, "banana", &info) => 0;
assert(strcmp(info.name, "banana") == 0);
assert(info.type == LFS_TYPE_DIR);
// and with dir_read
lfsr_dir_open(&lfs, &dir, "/") => 0;
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, ".") == 0);
assert(info.type == LFS_TYPE_DIR);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "..") == 0);
assert(info.type == LFS_TYPE_DIR);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "banana") == 0);
assert(info.type == LFS_TYPE_DIR);
lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT;
lfsr_dir_close(&lfs, &dir) => 0;
lfsr_unmount(&lfs) => 0;
'''
# test that we can rename, and replace, other directories
[cases.t5_dirs_mv_replace]
defines.REMOUNT = [false, true]
reentrant = true
code = '''
// format once per test
lfs_t lfs;
int err = lfsr_mount(&lfs, cfg);
if (err) {
lfsr_format(&lfs, cfg) => 0;
lfsr_mount(&lfs, cfg) => 0;
}
// make directories
err = lfsr_mkdir(&lfs, "ardvark");
assert(!err || (TEST_PL && err == LFS_ERR_EXIST));
err = lfsr_mkdir(&lfs, "banana");
assert(!err || (TEST_PL && err == LFS_ERR_EXIST));
// remount?
if (REMOUNT) {
lfsr_unmount(&lfs) => 0;
lfsr_mount(&lfs, cfg) => 0;
// grm should be zero here
assert(lfs.grm[0] == 0);
}
// check that our mkdir worked with stat
struct lfs_info info;
lfsr_stat(&lfs, "ardvark", &info) => 0;
assert(strcmp(info.name, "ardvark") == 0);
assert(info.type == LFS_TYPE_DIR);
// and with dir_read
lfsr_dir_t dir;
lfsr_dir_open(&lfs, &dir, "/") => 0;
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, ".") == 0);
assert(info.type == LFS_TYPE_DIR);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "..") == 0);
assert(info.type == LFS_TYPE_DIR);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "ardvark") == 0);
assert(info.type == LFS_TYPE_DIR);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "banana") == 0);
assert(info.type == LFS_TYPE_DIR);
lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT;
lfsr_dir_close(&lfs, &dir) => 0;
// now rename the directory
lfsr_rename(&lfs, "ardvark", "banana") => 0;
// remount?
if (REMOUNT) {
lfsr_unmount(&lfs) => 0;
lfsr_mount(&lfs, cfg) => 0;
// grm should be zero here
assert(lfs.grm[0] == 0);
}
// check that rename worked with stat
lfsr_stat(&lfs, "ardvark", &info) => LFS_ERR_NOENT;
lfsr_stat(&lfs, "banana", &info) => 0;
assert(strcmp(info.name, "banana") == 0);
assert(info.type == LFS_TYPE_DIR);
// and with dir_read
lfsr_dir_open(&lfs, &dir, "/") => 0;
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, ".") == 0);
assert(info.type == LFS_TYPE_DIR);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "..") == 0);
assert(info.type == LFS_TYPE_DIR);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "banana") == 0);
assert(info.type == LFS_TYPE_DIR);
lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT;
lfsr_dir_close(&lfs, &dir) => 0;
lfsr_unmount(&lfs) => 0;
'''
# test that we can rename to ourselves
[cases.t5_dirs_mv_noop]
defines.REMOUNT = [false, true]
reentrant = true
code = '''
// format once per test
lfs_t lfs;
int err = lfsr_mount(&lfs, cfg);
if (err) {
lfsr_format(&lfs, cfg) => 0;
lfsr_mount(&lfs, cfg) => 0;
}
// make directories
err = lfsr_mkdir(&lfs, "ardvark");
assert(!err || (TEST_PL && err == LFS_ERR_EXIST));
// remount?
if (REMOUNT) {
lfsr_unmount(&lfs) => 0;
lfsr_mount(&lfs, cfg) => 0;
// grm should be zero here
assert(lfs.grm[0] == 0);
}
// check that our mkdir worked with stat
struct lfs_info info;
lfsr_stat(&lfs, "ardvark", &info) => 0;
assert(strcmp(info.name, "ardvark") == 0);
assert(info.type == LFS_TYPE_DIR);
// and with dir_read
lfsr_dir_t dir;
lfsr_dir_open(&lfs, &dir, "/") => 0;
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, ".") == 0);
assert(info.type == LFS_TYPE_DIR);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "..") == 0);
assert(info.type == LFS_TYPE_DIR);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "ardvark") == 0);
assert(info.type == LFS_TYPE_DIR);
lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT;
lfsr_dir_close(&lfs, &dir) => 0;
// now rename the directory to itself
lfsr_rename(&lfs, "ardvark", "ardvark") => 0;
// remount?
if (REMOUNT) {
lfsr_unmount(&lfs) => 0;
lfsr_mount(&lfs, cfg) => 0;
// grm should be zero here
assert(lfs.grm[0] == 0);
}
// check that rename worked with stat
lfsr_stat(&lfs, "ardvark", &info) => 0;
assert(strcmp(info.name, "ardvark") == 0);
assert(info.type == LFS_TYPE_DIR);
// and with dir_read
lfsr_dir_open(&lfs, &dir, "/") => 0;
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, ".") == 0);
assert(info.type == LFS_TYPE_DIR);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "..") == 0);
assert(info.type == LFS_TYPE_DIR);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "ardvark") == 0);
assert(info.type == LFS_TYPE_DIR);
lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT;
lfsr_dir_close(&lfs, &dir) => 0;
lfsr_unmount(&lfs) => 0;
'''
# test that we catch replacing a non-empty directory
[cases.t5_dirs_mv_notempty]
defines.REMOUNT = [false, true]
reentrant = true
code = '''
// format once per test
lfs_t lfs;
int err = lfsr_mount(&lfs, cfg);
if (err) {
lfsr_format(&lfs, cfg) => 0;
lfsr_mount(&lfs, cfg) => 0;
}
// make directories
err = lfsr_mkdir(&lfs, "ardvark");
assert(!err || (TEST_PL && err == LFS_ERR_EXIST));
err = lfsr_mkdir(&lfs, "banana");
assert(!err || (TEST_PL && err == LFS_ERR_EXIST));
// fill dest with stuff
err = lfsr_mkdir(&lfs, "banana/cat");
assert(!err || (TEST_PL && err == LFS_ERR_EXIST));
// try to rename
lfsr_rename(&lfs, "ardvark", "banana") => LFS_ERR_NOTEMPTY;
// remount?
if (REMOUNT) {
lfsr_unmount(&lfs) => 0;
lfsr_mount(&lfs, cfg) => 0;
// grm should be zero here
assert(lfs.grm[0] == 0);
}
// and check that this didn't interfere with our original directory
struct lfs_info info;
lfsr_stat(&lfs, "ardvark", &info) => 0;
assert(strcmp(info.name, "ardvark") == 0);
assert(info.type == LFS_TYPE_DIR);
lfsr_stat(&lfs, "banana", &info) => 0;
assert(strcmp(info.name, "banana") == 0);
assert(info.type == LFS_TYPE_DIR);
lfsr_stat(&lfs, "banana/cat", &info) => 0;
assert(strcmp(info.name, "cat") == 0);
assert(info.type == LFS_TYPE_DIR);
lfsr_dir_t dir;
lfsr_dir_open(&lfs, &dir, "/") => 0;
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, ".") == 0);
assert(info.type == LFS_TYPE_DIR);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "..") == 0);
assert(info.type == LFS_TYPE_DIR);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "ardvark") == 0);
assert(info.type == LFS_TYPE_DIR);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "banana") == 0);
assert(info.type == LFS_TYPE_DIR);
lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT;
lfsr_dir_close(&lfs, &dir) => 0;
lfsr_dir_open(&lfs, &dir, "banana") => 0;
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, ".") == 0);
assert(info.type == LFS_TYPE_DIR);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "..") == 0);
assert(info.type == LFS_TYPE_DIR);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "cat") == 0);
assert(info.type == LFS_TYPE_DIR);
lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT;
lfsr_dir_close(&lfs, &dir) => 0;
lfsr_unmount(&lfs) => 0;
'''
# TODO test dir seek
# TODO test dir seek with mkdirs
# TODO test dir seek with removes