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_CRC = 0x2000,
|
||||||
LFSR_TAG_FCRC = 0x2100,
|
LFSR_TAG_FCRC = 0x2100,
|
||||||
|
|
||||||
|
// in-device only
|
||||||
|
LFSR_TAG_MOVE = 0x0800,
|
||||||
};
|
};
|
||||||
|
|
||||||
#define LFSR_TAG_ALTLE(red, key) \
|
#define LFSR_TAG_ALTLE(red, key) \
|
||||||
@@ -1259,15 +1262,27 @@ typedef struct lfsr_attr {
|
|||||||
lfs_ssize_t id;
|
lfs_ssize_t id;
|
||||||
lfsr_tag_t tag;
|
lfsr_tag_t tag;
|
||||||
lfs_ssize_t delta;
|
lfs_ssize_t delta;
|
||||||
|
union {
|
||||||
lfsr_data_t data;
|
lfsr_data_t data;
|
||||||
|
struct {
|
||||||
|
const lfsr_rbyd_t *rbyd;
|
||||||
|
lfs_ssize_t rid;
|
||||||
|
} move;
|
||||||
|
} d;
|
||||||
} lfsr_attr_t;
|
} lfsr_attr_t;
|
||||||
|
|
||||||
#define LFSR_ATTR_DATA_(_id, _tag, _delta, _data) \
|
#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) \
|
#define LFSR_ATTR_DATA(_id, _type, _delta, _data) \
|
||||||
LFSR_ATTR_DATA_(_id, LFSR_TAG_##_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) \
|
#define LFSR_ATTR_DNAME_(_id, _tag, _delta, _did, _buffer, _size) \
|
||||||
LFSR_ATTR_DATA_(_id, _tag, _delta, LFSR_DATA_DNAME(_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) {
|
const lfsr_attr_t *attrs, lfs_size_t attr_count) {
|
||||||
// append each tag to the tree
|
// append each tag to the tree
|
||||||
for (lfs_size_t i = 0; i < attr_count; i++) {
|
for (lfs_size_t i = 0; i < attr_count; i++) {
|
||||||
// TODO do we really need this?
|
// // TODO do we really need this?
|
||||||
// skip unknown internal tags (used by upper layers)
|
// // skip unknown internal tags (used by upper layers)
|
||||||
if (lfsr_tag_isinternal(attrs[i].tag)) {
|
// if (lfsr_tag_isinternal(attrs[i].tag)) {
|
||||||
continue;
|
// continue;
|
||||||
}
|
// }
|
||||||
|
|
||||||
// this is a bit of a hack, but ignore any gstate tags here,
|
// this is a bit of a hack, but ignore any gstate tags here,
|
||||||
// these need to be handled specially by upper-layers
|
// these need to be handled specially by upper-layers
|
||||||
@@ -2725,15 +2740,54 @@ static int lfsr_rbyd_appendall(lfs_t *lfs, lfsr_rbyd_t *rbyd,
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// don't write tags outside of the requested range
|
||||||
if (attrs[i].id >= start_id && (end_id < 0 || attrs[i].id < end_id)) {
|
if (attrs[i].id >= start_id && (end_id < 0 || attrs[i].id < end_id)) {
|
||||||
int err = lfsr_rbyd_append(lfs, rbyd,
|
// this is a bit of a hack, but ignore any gstate tags here,
|
||||||
attrs[i].id-lfs_smax32(start_id, 0),
|
// these need to be handled specially by upper-layers
|
||||||
attrs[i].tag, attrs[i].delta, attrs[i].data);
|
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) {
|
if (err) {
|
||||||
return 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// we need to make sure we keep start_id/end_id updated with
|
// we need to make sure we keep start_id/end_id updated with
|
||||||
// weight changes
|
// weight changes
|
||||||
if (attrs[i].id < start_id) {
|
if (attrs[i].id < start_id) {
|
||||||
@@ -5776,11 +5830,11 @@ static int lfsr_mdir_commit(lfs_t *lfs, lfsr_mdir_t *mdir, lfs_ssize_t *rid,
|
|||||||
lfsr_fs_flushgdelta(lfs);
|
lfsr_fs_flushgdelta(lfs);
|
||||||
for (lfs_size_t i = 0; i < attr_count; i++) {
|
for (lfs_size_t i = 0; i < attr_count; i++) {
|
||||||
if (attrs[i].tag == LFSR_TAG_GRM) {
|
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
|
// xor against current gstate value to get our gdelta
|
||||||
memcpy(lfs->grmd, lfs->grm, LFSR_GRM_DSIZE);
|
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) {
|
if (err) {
|
||||||
return 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
|
// to fix grms, we 1. decode, 2. fix, 3. reencode, 4. xor into
|
||||||
// any other pending grm delta
|
// any other pending grm delta
|
||||||
lfsr_grm_t grm;
|
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) {
|
if (d < 0) {
|
||||||
return d;
|
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)
|
// 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) {
|
if (err) {
|
||||||
return 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
|
// update our gstate
|
||||||
for (lfs_size_t i = 0; i < attr_count; i++) {
|
for (lfs_size_t i = 0; i < attr_count; i++) {
|
||||||
if (attrs[i].tag == LFSR_TAG_GRM) {
|
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) {
|
if (d < 0) {
|
||||||
return d;
|
return d;
|
||||||
}
|
}
|
||||||
@@ -7103,9 +7157,13 @@ static int lfsr_mountinited(lfs_t *lfs) {
|
|||||||
if (lfsr_grm_hasrm(&lfs->grm_)) {
|
if (lfsr_grm_hasrm(&lfs->grm_)) {
|
||||||
LFS_DEBUG("Found pending grm %"PRId32".%"PRId32" %"PRId32".%"PRId32,
|
LFS_DEBUG("Found pending grm %"PRId32".%"PRId32" %"PRId32".%"PRId32,
|
||||||
lfs->grm_.rms[0].mid,
|
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].mid,
|
||||||
lfs->grm_.rms[1].rid);
|
(lfs->grm_.rms[1].mid != LFSR_MID_RM
|
||||||
|
? lfs->grm_.rms[1].rid
|
||||||
|
: 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
return 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
|
// if we're removing a directory, we need to also remove the
|
||||||
// dstart entry, first lets figure out the did
|
// dstart entry
|
||||||
uint8_t grm_buf[LFSR_GRM_DSIZE];
|
lfsr_grm_t grm_ = lfs->grm_;
|
||||||
lfs_ssize_t grm_d;
|
|
||||||
if (tag == LFSR_TAG_DIR) {
|
if (tag == LFSR_TAG_DIR) {
|
||||||
|
// first lets figure out the did
|
||||||
lfsr_data_t data;
|
lfsr_data_t data;
|
||||||
int err = lfsr_mdir_lookup(lfs, &mdir, rid, LFSR_TAG_DID,
|
int err = lfsr_mdir_lookup(lfs, &mdir, rid, LFSR_TAG_DID,
|
||||||
NULL, &data);
|
NULL, &data);
|
||||||
@@ -7508,43 +7566,179 @@ int lfsr_remove(lfs_t *lfs, const char *path) {
|
|||||||
}
|
}
|
||||||
empty:;
|
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?
|
// TODO should we just make this an atomic remove?
|
||||||
// adjust rid if grm is on the same mdir as our dir
|
// adjust rid if grm is on the same mdir as our dir
|
||||||
if (dstart_mdir.mid == mdir.mid && dstart_rid > rid) {
|
if (grm_.rms[0].mid == mdir.mid
|
||||||
dstart_rid -= 1;
|
&& (lfs_ssize_t)grm_.rms[0].rid > rid) {
|
||||||
|
grm_.rms[0].rid -= 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// create a grm to remove the dstart entry
|
uint8_t grm_buf[LFSR_GRM_DSIZE];
|
||||||
grm_d = lfsr_grm_todisk(lfs,
|
lfs_ssize_t grm_d = lfsr_grm_todisk(lfs, &grm_, grm_buf);
|
||||||
&(lfsr_grm_t){.rms={
|
|
||||||
{.mid=dstart_mdir.mid, .rid=dstart_rid},
|
|
||||||
{.mid=LFSR_MID_RM}}},
|
|
||||||
grm_buf);
|
|
||||||
if (grm_d < 0) {
|
if (grm_d < 0) {
|
||||||
return grm_d;
|
return grm_d;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// remove the metadata entry
|
// remove the metadata entry
|
||||||
err = lfsr_mdir_commit(lfs, &mdir, &rid, LFSR_ATTRS(
|
err = lfsr_mdir_commit(lfs, &mdir, &rid, LFSR_ATTRS(
|
||||||
LFSR_ATTR(rid, UNR, -1, NULL, 0),
|
LFSR_ATTR(rid, UNR, -1, NULL, 0),
|
||||||
(tag == LFSR_TAG_DIR
|
LFSR_ATTR(-1, GRM, 0, grm_buf, grm_d)));
|
||||||
? LFSR_ATTR(-1, GRM, 0, grm_buf, grm_d)
|
|
||||||
: LFSR_ATTR_NOOP)));
|
|
||||||
if (err) {
|
if (err) {
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
// if we were a directory, we need to clean up, fortunately we can leave
|
// if we were a directory, we need to clean up, fortunately we can leave
|
||||||
// this up to lfsr_fs_fixgrm
|
// this up to lfsr_fs_fixgrm
|
||||||
if (tag == LFSR_TAG_DIR) {
|
return lfsr_fs_fixgrm(lfs);
|
||||||
err = 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) {
|
if (err) {
|
||||||
return 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;
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
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_)) {
|
if (lfsr_grm_hasrm(&lfs->grm_)) {
|
||||||
LFS_DEBUG("Fixing grm %"PRId32".%"PRId32" %"PRId32".%"PRId32,
|
LFS_DEBUG("Fixing grm %"PRId32".%"PRId32" %"PRId32".%"PRId32,
|
||||||
lfs->grm_.rms[0].mid,
|
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].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);
|
int err = lfsr_fs_fixgrm(lfs);
|
||||||
if (err) {
|
if (err) {
|
||||||
|
|||||||
@@ -566,6 +566,7 @@ int lfsr_remove(lfs_t *lfs, const char *path);
|
|||||||
//
|
//
|
||||||
// Returns a negative error code on failure.
|
// Returns a negative error code on failure.
|
||||||
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 lfsr_rename(lfs_t *lfs, const char *old_path, const char *new_path);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Find info about a file or directory
|
// Find info about a file or directory
|
||||||
|
|||||||
@@ -2803,6 +2803,338 @@ code = '''
|
|||||||
lfsr_unmount(&lfs) => 0;
|
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
|
||||||
# TODO test dir seek with mkdirs
|
# TODO test dir seek with mkdirs
|
||||||
# TODO test dir seek with removes
|
# TODO test dir seek with removes
|
||||||
|
|||||||
Reference in New Issue
Block a user