From ee37f8c7a6560ef5bff711c4c93f250a5cc227f8 Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Sun, 23 Jul 2023 14:03:05 -0500 Subject: [PATCH] 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. --- lfs.c | 284 ++++++++++++++++++++++++++++++++------ lfs.h | 1 + tests/t5_dirs.toml | 332 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 574 insertions(+), 43 deletions(-) diff --git a/lfs.c b/lfs.c index bbf1bb16..692efa6b 100644 --- a/lfs.c +++ b/lfs.c @@ -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) { diff --git a/lfs.h b/lfs.h index 8b58f9c2..dbcfac35 100644 --- a/lfs.h +++ b/lfs.h @@ -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 diff --git a/tests/t5_dirs.toml b/tests/t5_dirs.toml index c82f5388..4730e6c4 100644 --- a/tests/t5_dirs.toml +++ b/tests/t5_dirs.toml @@ -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