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:
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user