From f51dc5c5af6639ccdc77eced9eb10652b25f83ce Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Mon, 15 Jan 2024 15:08:12 -0600 Subject: [PATCH] Implemented zombied file handles A "zombie file" is a term I just made up to describe what happens when you remove a file that is currently open. To match POSIX, the opened file handle should still be available for reading/writing, even though the file doesn't really exist in the filesystem anymore. We don't have inodes, which makes this a bit more complicated, but this is where scratch files are handy again. By creating a scratch file when we remove an opened file, we preserve the mid slot for the file's sprout/shrub. We also mark the opened file as desync, so the existing orphan reclaimation circuitry kicks in when the last file handle is closed. Really the only difference between zombie files and desync files is what happens when you call lfsr_file_sync: - Desynced lfsr_file_sync => Become synced, broadcast file state. - Zombied lfsr_file_sync => Return ENOENT, you can't sync a zombie. This _is_ a bit different from POSIX, where sync on a removed file returns 0. I considered returning 0 in this case, but with all the extra behavior around sync/desync state, I figured returning ENOENT was clearer at indicating to the user sync is no longer possible. Worst case, ENOENT is not returned from sync for any other reason, so users can always treat ENOENT and 0 as the same in higher layers. The zombie file is already desynced, so close will never error. --- Implementation wise, zombies get a bit crazy. Fortunately they add little extra code, but they make up for it by adding extra subtlety. Zombie files introduce a ton of corner cases, now even directories can have zombied shrubs. This means more tests. - Seemingly unrelated operations need to be able to remove scratch files (mkdir, rename, etc). - UNCREAT state needs to be broadcasted in seemingly unrelated operations (mkdir, rename, etc). - Zombied files need to be copied over during seemingly unrelated rename operations. - And I'm sure more corner cases I'm already forgetting. One interesting tweak that simplifies things that's worth mentioning is the change to the implicitly file mid updates on rm in lfsr_mdir_commit. For non-reg files, an rm attr causes lfsr_mdir_commit to increment the mid to the next mid in the mtree. This is the correct behavior for dirs, traversals, etc. Previously, reg files were a special case that marks the mid as -1. But by changing this to also increment the mid, as well as set the zombie flag, upper layers can broadcast zombie changes by simply creating a new file and then deleting the old file in the same commit. This seems to Just Work^TM, and avoids needing to do additional state broadcasting in upper layers, which gets tricky since we may not know exactly what the new mid is post-mdir-commit. Downside: The order matters, we need to create the new file first. This violates the normal delete-then-insert order we use elsewhere to avoid overflow issues. This isn't that bad here, since we increment by at most 1. But it is something to be wary of... Still, this is much better than any other option I can think of right now. --- Uh, ignore the test_fscratch_rename* tests for now. I somehow forgot file renaming was not yet implemented... --- lfs.c | 149 ++- lfs.h | 1 + tests/test_fscratch.toml | 2581 +++++++++++++++++++++++++++++++++++++- 3 files changed, 2672 insertions(+), 59 deletions(-) diff --git a/lfs.c b/lfs.c index c8318007..c5a2357b 100644 --- a/lfs.c +++ b/lfs.c @@ -6589,15 +6589,10 @@ static int lfsr_mdir_commit(lfs_t *lfs, lfsr_mdir_t *mdir, && opened->mdir.mid >= attrs[i].rid) { // removed? if (opened->mdir.mid < attrs[i].rid - attrs[i].delta) { - // for dir's second mdir (the position mdir), move - // on to the next rid - if (opened->type == LFS_TYPE_DIR) { - opened->mdir.mid = attrs[i].rid; - // for normal mdirs mark as dropped - } else { - opened->mdir.mid = -1; - goto next; - } + // mark as zombied and move onto the next rid, upper + // layers should handle the repercussions + opened->flags |= LFS_F_ZOMBIE; + opened->mdir.mid = attrs[i].rid; } else { opened->mdir.mid += attrs[i].delta; // adjust dir position? @@ -8286,6 +8281,9 @@ static int lfsr_fs_preparemutation(lfs_t *lfs) { /// Directory operations /// +// needed in lfsr_mkdir +static inline bool lfsr_f_iszombie(uint32_t flags); + int lfsr_mkdir(lfs_t *lfs, const char *path) { // prepare our filesystem for writing int err = lfsr_fs_preparemutation(lfs); @@ -8295,18 +8293,20 @@ int lfsr_mkdir(lfs_t *lfs, const char *path) { // lookup our parent lfsr_mdir_t mdir; + lfsr_tag_t tag; lfsr_did_t did; const char *name; lfs_size_t name_size; err = lfsr_mtree_pathlookup(lfs, path, - &mdir, NULL, + &mdir, &tag, &did, &name, &name_size); if (err && (err != LFS_ERR_NOENT || mdir.mid == -1)) { return err; } - // already exists? - if (err != LFS_ERR_NOENT) { + // already exists? note scratch files don't really exist + bool exists = (err != LFS_ERR_NOENT); + if (exists && tag != LFSR_TAG_SCRATCH) { return LFS_ERR_EXIST; } @@ -8386,12 +8386,18 @@ int lfsr_mkdir(lfs_t *lfs, const char *path) { // commit our new directory into our parent, creating a grm to self-remove // in case of powerloss err = lfsr_mdir_commit(lfs, &mdir, LFSR_ATTRS( - LFSR_ATTR(mdir.mid, + LFSR_ATTR(mdir.mid + ((exists) ? 1 : 0), DIR, +1, CAT( LFSR_DATA_LEB128(did), LFSR_DATA_BUF(name, name_size))), - LFSR_ATTR(mdir.mid, DID, 0, LEB128(did_)), - LFSR_ATTR(-1, GRM, 0, GRM(&((lfsr_grm_t){{mdir.mid, -1}}))))); + LFSR_ATTR(mdir.mid + ((exists) ? 1 : 0), + DID, 0, LEB128(did_)), + (exists) + ? LFSR_ATTR(mdir.mid, RM, -1, NULL()) + : LFSR_ATTR_NOOP(), + LFSR_ATTR(-1, GRM, 0, GRM(&((lfsr_grm_t){{ + mdir.mid + ((exists) ? 1 : 0), + -1}}))))); if (err) { goto failed_with_bookmark; } @@ -8407,6 +8413,18 @@ int lfsr_mkdir(lfs_t *lfs, const char *path) { return err; } + // mark any zombied files as created to avoid a remove from beyond + // the grave + for (lfsr_opened_t *opened = lfs->opened; + opened; + opened = opened->next) { + if (opened->type == LFS_TYPE_REG + && opened->mdir.mid == mdir.mid) { + LFS_ASSERT(lfsr_f_iszombie(opened->flags)); + opened->flags &= ~LFS_F_UNCREAT; + } + } + return 0; failed_with_bookmark: @@ -8424,13 +8442,22 @@ int lfsr_remove(lfs_t *lfs, const char *path) { // lookup our entry lfsr_mdir_t mdir; lfsr_tag_t tag; + lfsr_did_t did; + const char *name; + lfs_size_t name_size; err = lfsr_mtree_pathlookup(lfs, path, &mdir, &tag, - NULL, NULL, NULL); + &did, &name, &name_size); if (err) { return err; } + // found a zombie? + if (tag == LFSR_TAG_SCRATCH) { + // don't worry, zombies aren't real and cannot hurt you + return LFS_ERR_NOENT; + } + // as funny as it would be, you can't remove the root if (lfsr_mdir_isroot(&mdir)) { return LFS_ERR_INVAL; @@ -8493,14 +8520,47 @@ int lfsr_remove(lfs_t *lfs, const char *path) { } } + // are we removing an opened file? + bool zombie = false; + for (lfsr_opened_t *opened = lfs->opened; + opened; + opened = opened->next) { + if (opened->type == LFS_TYPE_REG + && opened->mdir.mid == mdir.mid) { + zombie = true; + break; + } + } + // remove the metadata entry err = lfsr_mdir_commit(lfs, &mdir, LFSR_ATTRS( + // create a scratch file if zombied + // + // we use a create+delete here to also clear any attrs + // and trim the entry size + (zombie) + ? LFSR_ATTR(mdir.mid+1, SCRATCH, +1, CAT( + LFSR_DATA_LEB128(did), + LFSR_DATA_BUF(name, name_size))) + : LFSR_ATTR_NOOP(), LFSR_ATTR(mdir.mid, RM, -1, NULL()), LFSR_ATTR(-1, GRM, 0, GRM(&grm)))); if (err) { return err; } + // lfsr_mdir_commit implicitly marks removed files as zombied, but + // we also need to mark them as uncreate to indicate that the mid + // needs to be cleaned up on close + for (lfsr_opened_t *opened = lfs->opened; + opened; + opened = opened->next) { + if (opened->type == LFS_TYPE_REG + && opened->mdir.mid == mdir.mid) { + opened->flags |= LFS_F_UNCREAT; + } + } + // if we were a directory, we need to clean up, fortunately we can leave // this up to lfsr_fs_fixgrm return lfsr_fs_fixgrm(lfs); @@ -8523,6 +8583,12 @@ int lfsr_rename(lfs_t *lfs, const char *old_path, const char *new_path) { return err; } + // found a zombie? + if (old_tag == LFSR_TAG_SCRATCH) { + // don't worry, zombies aren't real and cannot hurt you + return LFS_ERR_NOENT; + } + // as funny as it would be, you can't rename the root if (lfsr_mdir_isroot(&old_mdir)) { return LFS_ERR_INVAL; @@ -8544,6 +8610,7 @@ int lfsr_rename(lfs_t *lfs, const char *old_path, const char *new_path) { if (err && (err != LFS_ERR_NOENT || new_mdir.mid == -1)) { return err; } + // already exists? bool exists = (err != LFS_ERR_NOENT); // there are a few cases we need to watch out for @@ -8561,7 +8628,9 @@ int lfsr_rename(lfs_t *lfs, const char *old_path, const char *new_path) { } else { // renaming different types is an error - if (old_tag != new_tag) { + // + // unless we found a scratch file, these don't really exist + if (old_tag != new_tag && new_tag != LFSR_TAG_SCRATCH) { return (new_tag == LFSR_TAG_DIR) ? LFS_ERR_ISDIR : LFS_ERR_NOTDIR; @@ -8629,19 +8698,32 @@ int lfsr_rename(lfs_t *lfs, const char *old_path, const char *new_path) { // 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, LFSR_ATTRS( - (exists - ? LFSR_ATTR(new_mdir.mid, RM, -1, NULL()) - : LFSR_ATTR_NOOP()), - LFSR_ATTR(new_mdir.mid, + LFSR_ATTR(new_mdir.mid + ((exists) ? 1 : 0), TAG(old_tag), +1, CAT( LFSR_DATA_LEB128(new_did), LFSR_DATA_BUF(new_name, new_name_size))), - LFSR_ATTR(new_mdir.mid, MOVE, 0, MOVE(&old_mdir)), + LFSR_ATTR(new_mdir.mid + ((exists) ? 1 : 0), + MOVE, 0, MOVE(&old_mdir)), + (exists) + ? LFSR_ATTR(new_mdir.mid, RM, -1, NULL()) + : LFSR_ATTR_NOOP(), LFSR_ATTR(-1, GRM, 0, GRM(&grm)))); if (err) { return err; } + // mark any zombied files as created to avoid a remove from beyond + // the grave + for (lfsr_opened_t *opened = lfs->opened; + opened; + opened = opened->next) { + if (opened->type == LFS_TYPE_REG + && opened->mdir.mid == new_mdir.mid) { + LFS_ASSERT(lfsr_f_iszombie(opened->flags)); + opened->flags &= ~LFS_F_UNCREAT; + } + } + // we need to clean up any pending grms, fortunately we can leave // this up to lfsr_fs_fixgrm return lfsr_fs_fixgrm(lfs); @@ -9022,6 +9104,10 @@ static inline bool lfsr_f_isuncreat(uint32_t flags) { return flags & LFS_F_UNCREAT; } +static inline bool lfsr_f_iszombie(uint32_t flags) { + return flags & LFS_F_ZOMBIE; +} + static inline lfs_off_t lfsr_file_size_(const lfsr_file_t *file) { return lfs_max32( file->buffer_pos + file->buffer_size, @@ -9222,10 +9308,11 @@ int lfsr_file_open(lfs_t *lfs, lfsr_file_t *file, int lfsr_file_sync(lfs_t *lfs, lfsr_file_t *file); int lfsr_file_close(lfs_t *lfs, lfsr_file_t *file) { - // don't call lfsr_file_sync if we're readonly or desynced + // don't call lfsr_file_sync if we're readonly, desynced, or zombied int err = 0; if (!lfsr_o_isrdonly(file->flags) - && !lfsr_o_isdesync(file->flags)) { + && !lfsr_o_isdesync(file->flags) + && !lfsr_f_iszombie(file->flags)) { err = lfsr_file_sync(lfs, file); } @@ -10817,9 +10904,9 @@ failed:; } int lfsr_file_sync(lfs_t *lfs, lfsr_file_t *file) { - // do nothing if our file has been removed - if (file->mdir.mid == -1) { - return 0; + // removed? we can't sync + if (lfsr_f_iszombie(file->flags)) { + return LFS_ERR_NOENT; } // first flush any data in our buffer, this is a noop if already @@ -10852,6 +10939,9 @@ int lfsr_file_sync(lfs_t *lfs, lfsr_file_t *file) { || (file->buffer_size <= lfs->cfg->cache_size && file->buffer_size <= lfs->cfg->inline_size && file->buffer_size <= lfs->cfg->fragment_size)); + // uncreat files must be unsync + LFS_ASSERT(!lfsr_f_isuncreat(file->flags) + || lfsr_f_isunsync(file->flags)); // don't write to disk if our disk is already in-sync if (lfsr_f_isunsync(file->flags)) { @@ -10944,8 +11034,9 @@ int lfsr_file_sync(lfs_t *lfs, lfsr_file_t *file) { // notify all files of creation file_->flags &= ~LFS_F_UNCREAT; - // mark desynced files an unsynced - if (lfsr_o_isdesync(file_->flags)) { + // mark desynced/zombied files an unsynced + if (lfsr_o_isdesync(file_->flags) + || lfsr_f_iszombie(file_->flags)) { file_->flags |= LFS_F_UNSYNC; // update synced files diff --git a/lfs.h b/lfs.h index d3ff6c64..c7878b1f 100644 --- a/lfs.h +++ b/lfs.h @@ -140,6 +140,7 @@ enum lfs_open_flags { LFS_F_UNFLUSH = 0x1000, // File's data does not match storage LFS_F_UNSYNC = 0x2000, // File's metadata does not match storage LFS_F_UNCREAT = 0x4000, // File does not exist yet + LFS_F_ZOMBIE = 0x8000, // File has been removed }; // File seek flags diff --git a/tests/test_fscratch.toml b/tests/test_fscratch.toml index 91620e9d..bd585371 100644 --- a/tests/test_fscratch.toml +++ b/tests/test_fscratch.toml @@ -1346,8 +1346,8 @@ code = ''' [cases.test_fscratch_orphan_open] defines.ORPHANS = [1, 2, 3, 100] # 0 => don't remount -# 1 => remount after write -# 2 => remount before write +# 1 => remount after op +# 2 => remount before op defines.REMOUNT = [0, 1, 2] code = ''' lfs_t lfs; @@ -1428,6 +1428,7 @@ code = ''' uint8_t rbuf[256]; lfsr_file_read(&lfs, &file_, rbuf, sizeof(rbuf)) => strlen("hello!"); assert(memcmp(rbuf, "hello!", strlen("hello!")) == 0); + lfsr_file_close(&lfs, &file_) => 0; lfsr_unmount(&lfs) => 0; ''' @@ -1435,8 +1436,8 @@ code = ''' [cases.test_fscratch_orphan_mkdir] defines.ORPHANS = [1, 2, 3, 100] # 0 => don't remount -# 1 => remount after write -# 2 => remount before write +# 1 => remount after op +# 2 => remount before op defines.REMOUNT = [0, 1, 2] code = ''' lfs_t lfs; @@ -1512,8 +1513,8 @@ code = ''' [cases.test_fscratch_orphan_remove] defines.ORPHANS = [1, 2, 3, 100] # 0 => don't remount -# 1 => remount after write -# 2 => remount before write +# 1 => remount after op +# 2 => remount before op defines.REMOUNT = [0, 1, 2] code = ''' lfs_t lfs; @@ -1582,24 +1583,46 @@ code = ''' ''' [cases.test_fscratch_orphan_rename_dst] +defines.DIR = [false, true] +defines.INTERDIR = [false, true] +defines.DISTANCE = [0, 1, 100] defines.ORPHANS = [1, 2, 3, 100] # 0 => don't remount -# 1 => remount after write -# 2 => remount before write +# 1 => remount after op +# 2 => remount before op defines.REMOUNT = [0, 1, 2] code = ''' lfs_t lfs; lfsr_format(&lfs, CFG) => 0; lfsr_mount(&lfs, CFG) => 0; + // create an interesting directory structure + if (INTERDIR) { + lfsr_mkdir(&lfs, "a") => 0; + lfsr_mkdir(&lfs, "b") => 0; + lfsr_mkdir(&lfs, "c") => 0; + } + for (lfs_size_t i = 0; i < DISTANCE; i++) { + char name[256]; + sprintf(name, (INTERDIR) ? "b/hello%03x" : "hello%03x", i); + lfsr_file_t file; + lfsr_file_open(&lfs, &file, name, + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + lfsr_file_close(&lfs, &file) => 0; + } + // make our src file before orphans, otherwise open just cleans // things up - lfsr_file_t file; - lfsr_file_open(&lfs, &file, "hello", - LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; - lfsr_file_write(&lfs, &file, "hello!", strlen("hello!")) - => strlen("hello!"); - lfsr_file_close(&lfs, &file) => 0; + if (DIR) { + lfsr_mkdir(&lfs, (INTERDIR) ? "a/iello" : "iello") => 0; + } else { + lfsr_file_t file; + lfsr_file_open(&lfs, &file, (INTERDIR) ? "a/iello" : "iello", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + lfsr_file_write(&lfs, &file, "hello!", strlen("hello!")) + => strlen("hello!"); + lfsr_file_close(&lfs, &file) => 0; + } // create neighboring orphaned files // @@ -1607,7 +1630,7 @@ code = ''' lfsr_file_t orphans[ORPHANS-1]; for (lfs_size_t i = 0; i < ORPHANS-1; i++) { char name[256]; - sprintf(name, "fello%03x", i); + sprintf(name, (INTERDIR) ? "c/fello%03x" : "fello%03x", i); lfsr_file_open(&lfs, &orphans[i], name, LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL | LFS_O_DESYNC) => 0; lfsr_file_write(&lfs, &orphans[i], @@ -1616,7 +1639,8 @@ code = ''' } // create an orphaned file - lfsr_file_open(&lfs, &file, "gello", + lfsr_file_t file; + lfsr_file_open(&lfs, &file, (INTERDIR) ? "c/gello" : "gello", LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL | LFS_O_DESYNC) => 0; lfsr_file_write(&lfs, &file, "WoOoOoOoOoO", strlen("WoOoOoOoOoO")) @@ -1635,7 +1659,9 @@ code = ''' } // rename onto orphan - lfsr_rename(&lfs, "hello", "gello") => 0; + lfsr_rename(&lfs, + (INTERDIR) ? "a/iello" : "iello", + (INTERDIR) ? "c/gello" : "gello") => 0; if (REMOUNT == 1) { lfsr_unmount(&lfs) => 0; @@ -1645,13 +1671,17 @@ code = ''' // make sure the new file is readable // via stat struct lfs_info info; - lfsr_stat(&lfs, "gello", &info) => 0; + lfsr_stat(&lfs, (INTERDIR) ? "c/gello" : "gello", &info) => 0; assert(strcmp(info.name, "gello") == 0); - assert(info.type == LFS_TYPE_REG); - assert(info.size == strlen("hello!")); + if (DIR) { + assert(info.type == LFS_TYPE_DIR); + } else { + assert(info.type == LFS_TYPE_REG); + assert(info.size == strlen("hello!")); + } // via readdir lfsr_dir_t dir; - lfsr_dir_open(&lfs, &dir, "/") => 0; + lfsr_dir_open(&lfs, &dir, (INTERDIR) ? "c" : "/") => 0; lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); @@ -1660,16 +1690,38 @@ code = ''' assert(info.type == LFS_TYPE_DIR); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "gello") == 0); - assert(info.type == LFS_TYPE_REG); - assert(info.size == strlen("hello!")); + if (DIR) { + assert(info.type == LFS_TYPE_DIR); + } else { + assert(info.type == LFS_TYPE_REG); + assert(info.size == strlen("hello!")); + } + if (!INTERDIR) { + for (lfs_size_t i = 0; i < DISTANCE; i++) { + char name[256]; + sprintf(name, "hello%03x", i); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, name) == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == 0); + } + } lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; // via open - lfsr_file_t file_; - lfsr_file_open(&lfs, &file_, "gello", LFS_O_RDONLY) => 0; - uint8_t rbuf[256]; - lfsr_file_read(&lfs, &file_, rbuf, sizeof(rbuf)) => strlen("hello!"); - assert(memcmp(rbuf, "hello!", strlen("hello!")) == 0); + if (DIR) { + lfsr_file_t file_; + lfsr_file_open(&lfs, &file_, (INTERDIR) ? "c/gello" : "gello", + LFS_O_RDONLY) => LFS_ERR_ISDIR; + } else { + lfsr_file_t file_; + lfsr_file_open(&lfs, &file_, (INTERDIR) ? "c/gello" : "gello", + LFS_O_RDONLY) => 0; + uint8_t rbuf[256]; + lfsr_file_read(&lfs, &file_, rbuf, sizeof(rbuf)) => strlen("hello!"); + assert(memcmp(rbuf, "hello!", strlen("hello!")) == 0); + lfsr_file_close(&lfs, &file_) => 0; + } lfsr_unmount(&lfs) => 0; ''' @@ -1677,8 +1729,8 @@ code = ''' [cases.test_fscratch_orphan_rename_src] defines.ORPHANS = [1, 2, 3, 100] # 0 => don't remount -# 1 => remount after write -# 2 => remount before write +# 1 => remount after op +# 2 => remount before op defines.REMOUNT = [0, 1, 2] code = ''' lfs_t lfs; @@ -1745,3 +1797,2472 @@ code = ''' lfsr_unmount(&lfs) => 0; ''' + +[cases.test_fscratch_zombie] +defines.SIZE = '4*BLOCK_SIZE' +defines.CHUNK = 64 +code = ''' + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + + // create a file + lfsr_file_t file; + lfsr_file_open(&lfs, &file, "gello", + LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0; + + // write to the file + uint32_t prng = 42; + for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { + uint8_t wbuf[CHUNK]; + for (lfs_size_t j = 0; j < CHUNK; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + lfsr_file_write(&lfs, &file, wbuf, CHUNK) => CHUNK; + } + + // need to sync so we can remove + lfsr_file_sync(&lfs, &file) => 0; + + // remove the file + lfsr_remove(&lfs, "gello") => 0; + + // as far as the filesystem is concerned, the file does not exist yet + // via stat + struct lfs_info info; + lfsr_stat(&lfs, "gello", &info) => LFS_ERR_NOENT; + // via readdir + 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) => LFS_ERR_NOENT; + lfsr_dir_close(&lfs, &dir) => 0; + // rdonly rejected + lfsr_file_t file_; + lfsr_file_open(&lfs, &file_, "gello", LFS_O_RDONLY) => LFS_ERR_NOENT; + // non-create rejected + lfsr_file_open(&lfs, &file_, "gello", LFS_O_WRONLY) => LFS_ERR_NOENT; + lfsr_file_open(&lfs, &file_, "gello", LFS_O_RDWR) => LFS_ERR_NOENT; + + // removed files can not be synced + lfsr_file_sync(&lfs, &file) => LFS_ERR_NOENT; + + // but we should still be able to read our file handle + lfsr_file_rewind(&lfs, &file) => 0; + prng = 42; + for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { + uint8_t wbuf[CHUNK]; + for (lfs_size_t j = 0; j < CHUNK; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + uint8_t rbuf[CHUNK]; + lfsr_file_read(&lfs, &file, rbuf, CHUNK) => CHUNK; + assert(memcmp(wbuf, rbuf, CHUNK) == 0); + } + + // close + lfsr_file_close(&lfs, &file) => 0; + + // because the file was removed, it should still not exist + // via stat + lfsr_stat(&lfs, "gello", &info) => LFS_ERR_NOENT; + // via readdir + 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) => LFS_ERR_NOENT; + lfsr_dir_close(&lfs, &dir) => 0; + // rdonly rejected + lfsr_file_open(&lfs, &file_, "gello", LFS_O_RDONLY) => LFS_ERR_NOENT; + // non-create rejected + lfsr_file_open(&lfs, &file_, "gello", LFS_O_WRONLY) => LFS_ERR_NOENT; + lfsr_file_open(&lfs, &file_, "gello", LFS_O_RDWR) => LFS_ERR_NOENT; + + // even after a remount + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + + // via stat + lfsr_stat(&lfs, "gello", &info) => LFS_ERR_NOENT; + // via readdir + 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) => LFS_ERR_NOENT; + lfsr_dir_close(&lfs, &dir) => 0; + // rdonly rejected + lfsr_file_open(&lfs, &file_, "gello", LFS_O_RDONLY) => LFS_ERR_NOENT; + // non-create rejected + lfsr_file_open(&lfs, &file_, "gello", LFS_O_WRONLY) => LFS_ERR_NOENT; + lfsr_file_open(&lfs, &file_, "gello", LFS_O_RDWR) => LFS_ERR_NOENT; + + lfsr_unmount(&lfs) => 0; +''' + +[cases.test_fscratch_zombie_posthumous] +defines.SIZE = '4*BLOCK_SIZE' +defines.CHUNK = 64 +code = ''' + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + + // create a file + lfsr_file_t file; + lfsr_file_open(&lfs, &file, "gello", + LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0; + + // need to sync so we can remove + lfsr_file_sync(&lfs, &file) => 0; + + // remove the file + lfsr_remove(&lfs, "gello") => 0; + + // write to the file + uint32_t prng = 42; + for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { + uint8_t wbuf[CHUNK]; + for (lfs_size_t j = 0; j < CHUNK; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + lfsr_file_write(&lfs, &file, wbuf, CHUNK) => CHUNK; + } + + // as far as the filesystem is concerned, the file does not exist yet + // via stat + struct lfs_info info; + lfsr_stat(&lfs, "gello", &info) => LFS_ERR_NOENT; + // via readdir + 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) => LFS_ERR_NOENT; + lfsr_dir_close(&lfs, &dir) => 0; + // rdonly rejected + lfsr_file_t file_; + lfsr_file_open(&lfs, &file_, "gello", LFS_O_RDONLY) => LFS_ERR_NOENT; + // non-create rejected + lfsr_file_open(&lfs, &file_, "gello", LFS_O_WRONLY) => LFS_ERR_NOENT; + lfsr_file_open(&lfs, &file_, "gello", LFS_O_RDWR) => LFS_ERR_NOENT; + + // removed files can not be synced + lfsr_file_sync(&lfs, &file) => LFS_ERR_NOENT; + + // but we should still be able to read our file handle + lfsr_file_rewind(&lfs, &file) => 0; + prng = 42; + for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { + uint8_t wbuf[CHUNK]; + for (lfs_size_t j = 0; j < CHUNK; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + uint8_t rbuf[CHUNK]; + lfsr_file_read(&lfs, &file, rbuf, CHUNK) => CHUNK; + assert(memcmp(wbuf, rbuf, CHUNK) == 0); + } + + // close + lfsr_file_close(&lfs, &file) => 0; + + // because the file was removed, it should still not exist + // via stat + lfsr_stat(&lfs, "gello", &info) => LFS_ERR_NOENT; + // via readdir + 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) => LFS_ERR_NOENT; + lfsr_dir_close(&lfs, &dir) => 0; + // rdonly rejected + lfsr_file_open(&lfs, &file_, "gello", LFS_O_RDONLY) => LFS_ERR_NOENT; + // non-create rejected + lfsr_file_open(&lfs, &file_, "gello", LFS_O_WRONLY) => LFS_ERR_NOENT; + lfsr_file_open(&lfs, &file_, "gello", LFS_O_RDWR) => LFS_ERR_NOENT; + + // even after a remount + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + + // via stat + lfsr_stat(&lfs, "gello", &info) => LFS_ERR_NOENT; + // via readdir + 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) => LFS_ERR_NOENT; + lfsr_dir_close(&lfs, &dir) => 0; + // rdonly rejected + lfsr_file_open(&lfs, &file_, "gello", LFS_O_RDONLY) => LFS_ERR_NOENT; + // non-create rejected + lfsr_file_open(&lfs, &file_, "gello", LFS_O_WRONLY) => LFS_ERR_NOENT; + lfsr_file_open(&lfs, &file_, "gello", LFS_O_RDWR) => LFS_ERR_NOENT; + + lfsr_unmount(&lfs) => 0; +''' + +[cases.test_fscratch_zombie_rwrw] +defines.SIZE = '4*BLOCK_SIZE' +defines.CHUNK = 64 +defines.SYNC = [false, true] +code = ''' + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + + // create a file + lfsr_file_t file; + lfsr_file_open(&lfs, &file, "gello", + LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0; + + // write to the file + uint32_t prng = 42; + for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { + uint8_t wbuf[CHUNK]; + for (lfs_size_t j = 0; j < CHUNK; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + lfsr_file_write(&lfs, &file, wbuf, CHUNK) => CHUNK; + } + + // need to sync so we can remove + lfsr_file_sync(&lfs, &file) => 0; + + // remove the file + lfsr_remove(&lfs, "gello") => 0; + + // create a second file + lfsr_file_t file__; + lfsr_file_open(&lfs, &file__, "gello", + LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0; + + // write to the second file + prng = 42+1; + for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { + uint8_t wbuf[CHUNK]; + for (lfs_size_t j = 0; j < CHUNK; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + lfsr_file_write(&lfs, &file__, wbuf, CHUNK) => CHUNK; + } + + // as far as the filesystem is concerned, the file does not exist yet + // via stat + struct lfs_info info; + lfsr_stat(&lfs, "gello", &info) => LFS_ERR_NOENT; + // via readdir + 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) => LFS_ERR_NOENT; + lfsr_dir_close(&lfs, &dir) => 0; + // rdonly rejected + lfsr_file_t file_; + lfsr_file_open(&lfs, &file_, "gello", LFS_O_RDONLY) => LFS_ERR_NOENT; + // non-create rejected + lfsr_file_open(&lfs, &file_, "gello", LFS_O_WRONLY) => LFS_ERR_NOENT; + lfsr_file_open(&lfs, &file_, "gello", LFS_O_RDWR) => LFS_ERR_NOENT; + + // removed files can not be synced + lfsr_file_sync(&lfs, &file) => LFS_ERR_NOENT; + // but we should be able to sync our second file just fine + lfsr_file_sync(&lfs, &file__) => 0; + + // second file should appear on disk now + // via stat + lfsr_stat(&lfs, "gello", &info) => 0; + assert(strcmp(info.name, "gello") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == SIZE); + // via readdir + 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, "gello") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == SIZE); + lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; + lfsr_dir_close(&lfs, &dir) => 0; + // via open + lfsr_file_open(&lfs, &file_, "gello", LFS_O_RDONLY) => 0; + prng = 42+1; + for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { + uint8_t wbuf[CHUNK]; + for (lfs_size_t j = 0; j < CHUNK; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + uint8_t rbuf[CHUNK]; + lfsr_file_read(&lfs, &file_, rbuf, CHUNK) => CHUNK; + assert(memcmp(wbuf, rbuf, CHUNK) == 0); + } + lfsr_file_close(&lfs, &file_) => 0; + + // the perhaps surprising thing is we should still be able + // to read both file handles + lfsr_file_rewind(&lfs, &file) => 0; + prng = 42; + for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { + uint8_t wbuf[CHUNK]; + for (lfs_size_t j = 0; j < CHUNK; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + uint8_t rbuf[CHUNK]; + lfsr_file_read(&lfs, &file, rbuf, CHUNK) => CHUNK; + assert(memcmp(wbuf, rbuf, CHUNK) == 0); + } + + lfsr_file_rewind(&lfs, &file__) => 0; + prng = 42+1; + for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { + uint8_t wbuf[CHUNK]; + for (lfs_size_t j = 0; j < CHUNK; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + uint8_t rbuf[CHUNK]; + lfsr_file_read(&lfs, &file__, rbuf, CHUNK) => CHUNK; + assert(memcmp(wbuf, rbuf, CHUNK) == 0); + } + + // close + lfsr_file_close(&lfs, &file) => 0; + lfsr_file_close(&lfs, &file__) => 0; + + // check disk again + // via stat + lfsr_stat(&lfs, "gello", &info) => 0; + assert(strcmp(info.name, "gello") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == SIZE); + // via readdir + 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, "gello") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == SIZE); + lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; + lfsr_dir_close(&lfs, &dir) => 0; + // via open + lfsr_file_open(&lfs, &file_, "gello", LFS_O_RDONLY) => 0; + prng = 42+1; + for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { + uint8_t wbuf[CHUNK]; + for (lfs_size_t j = 0; j < CHUNK; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + uint8_t rbuf[CHUNK]; + lfsr_file_read(&lfs, &file_, rbuf, CHUNK) => CHUNK; + assert(memcmp(wbuf, rbuf, CHUNK) == 0); + } + lfsr_file_close(&lfs, &file_) => 0; + + // remount + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + + // check disk again again + // via stat + lfsr_stat(&lfs, "gello", &info) => 0; + assert(strcmp(info.name, "gello") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == SIZE); + // via readdir + 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, "gello") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == SIZE); + lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; + lfsr_dir_close(&lfs, &dir) => 0; + // via open + lfsr_file_open(&lfs, &file_, "gello", LFS_O_RDONLY) => 0; + prng = 42+1; + for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { + uint8_t wbuf[CHUNK]; + for (lfs_size_t j = 0; j < CHUNK; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + uint8_t rbuf[CHUNK]; + lfsr_file_read(&lfs, &file_, rbuf, CHUNK) => CHUNK; + assert(memcmp(wbuf, rbuf, CHUNK) == 0); + } + lfsr_file_close(&lfs, &file_) => 0; + + lfsr_unmount(&lfs) => 0; +''' + +[cases.test_fscratch_zombie_rwrw_posthumous] +defines.SIZE = '4*BLOCK_SIZE' +defines.CHUNK = 64 +defines.SYNC = [false, true] +code = ''' + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + + // create a file + lfsr_file_t file; + lfsr_file_open(&lfs, &file, "gello", + LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0; + + // need to sync so we can remove + lfsr_file_sync(&lfs, &file) => 0; + + // remove the file + lfsr_remove(&lfs, "gello") => 0; + + // create a second file + lfsr_file_t file__; + lfsr_file_open(&lfs, &file__, "gello", + LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0; + + // write to the second file + uint32_t prng = 42+1; + for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { + uint8_t wbuf[CHUNK]; + for (lfs_size_t j = 0; j < CHUNK; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + lfsr_file_write(&lfs, &file__, wbuf, CHUNK) => CHUNK; + } + + // write to the first file + prng = 42; + for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { + uint8_t wbuf[CHUNK]; + for (lfs_size_t j = 0; j < CHUNK; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + lfsr_file_write(&lfs, &file, wbuf, CHUNK) => CHUNK; + } + + // as far as the filesystem is concerned, the file does not exist yet + // via stat + struct lfs_info info; + lfsr_stat(&lfs, "gello", &info) => LFS_ERR_NOENT; + // via readdir + 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) => LFS_ERR_NOENT; + lfsr_dir_close(&lfs, &dir) => 0; + // rdonly rejected + lfsr_file_t file_; + lfsr_file_open(&lfs, &file_, "gello", LFS_O_RDONLY) => LFS_ERR_NOENT; + // non-create rejected + lfsr_file_open(&lfs, &file_, "gello", LFS_O_WRONLY) => LFS_ERR_NOENT; + lfsr_file_open(&lfs, &file_, "gello", LFS_O_RDWR) => LFS_ERR_NOENT; + + // removed files can not be synced + lfsr_file_sync(&lfs, &file) => LFS_ERR_NOENT; + // but we should be able to sync our second file just fine + lfsr_file_sync(&lfs, &file__) => 0; + + // second file should appear on disk now + // via stat + lfsr_stat(&lfs, "gello", &info) => 0; + assert(strcmp(info.name, "gello") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == SIZE); + // via readdir + 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, "gello") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == SIZE); + lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; + lfsr_dir_close(&lfs, &dir) => 0; + // via open + lfsr_file_open(&lfs, &file_, "gello", LFS_O_RDONLY) => 0; + prng = 42+1; + for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { + uint8_t wbuf[CHUNK]; + for (lfs_size_t j = 0; j < CHUNK; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + uint8_t rbuf[CHUNK]; + lfsr_file_read(&lfs, &file_, rbuf, CHUNK) => CHUNK; + assert(memcmp(wbuf, rbuf, CHUNK) == 0); + } + lfsr_file_close(&lfs, &file_) => 0; + + // the perhaps surprising thing is we should still be able + // to read both file handles + lfsr_file_rewind(&lfs, &file) => 0; + prng = 42; + for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { + uint8_t wbuf[CHUNK]; + for (lfs_size_t j = 0; j < CHUNK; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + uint8_t rbuf[CHUNK]; + lfsr_file_read(&lfs, &file, rbuf, CHUNK) => CHUNK; + assert(memcmp(wbuf, rbuf, CHUNK) == 0); + } + + lfsr_file_rewind(&lfs, &file__) => 0; + prng = 42+1; + for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { + uint8_t wbuf[CHUNK]; + for (lfs_size_t j = 0; j < CHUNK; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + uint8_t rbuf[CHUNK]; + lfsr_file_read(&lfs, &file__, rbuf, CHUNK) => CHUNK; + assert(memcmp(wbuf, rbuf, CHUNK) == 0); + } + + // close + lfsr_file_close(&lfs, &file) => 0; + lfsr_file_close(&lfs, &file__) => 0; + + // check disk again + // via stat + lfsr_stat(&lfs, "gello", &info) => 0; + assert(strcmp(info.name, "gello") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == SIZE); + // via readdir + 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, "gello") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == SIZE); + lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; + lfsr_dir_close(&lfs, &dir) => 0; + // via open + lfsr_file_open(&lfs, &file_, "gello", LFS_O_RDONLY) => 0; + prng = 42+1; + for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { + uint8_t wbuf[CHUNK]; + for (lfs_size_t j = 0; j < CHUNK; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + uint8_t rbuf[CHUNK]; + lfsr_file_read(&lfs, &file_, rbuf, CHUNK) => CHUNK; + assert(memcmp(wbuf, rbuf, CHUNK) == 0); + } + lfsr_file_close(&lfs, &file_) => 0; + + // remount + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + + // check disk again again + // via stat + lfsr_stat(&lfs, "gello", &info) => 0; + assert(strcmp(info.name, "gello") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == SIZE); + // via readdir + 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, "gello") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == SIZE); + lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; + lfsr_dir_close(&lfs, &dir) => 0; + // via open + lfsr_file_open(&lfs, &file_, "gello", LFS_O_RDONLY) => 0; + prng = 42+1; + for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { + uint8_t wbuf[CHUNK]; + for (lfs_size_t j = 0; j < CHUNK; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + uint8_t rbuf[CHUNK]; + lfsr_file_read(&lfs, &file_, rbuf, CHUNK) => CHUNK; + assert(memcmp(wbuf, rbuf, CHUNK) == 0); + } + lfsr_file_close(&lfs, &file_) => 0; + + lfsr_unmount(&lfs) => 0; +''' + +[cases.test_fscratch_zombie_zombie_rwrwrw] +defines.SIZE = '4*BLOCK_SIZE' +defines.CHUNK = 64 +defines.SYNC = [false, true] +code = ''' + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + + // create a file + lfsr_file_t file; + lfsr_file_open(&lfs, &file, "gello", + LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0; + + // write to the file + uint32_t prng = 42; + for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { + uint8_t wbuf[CHUNK]; + for (lfs_size_t j = 0; j < CHUNK; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + lfsr_file_write(&lfs, &file, wbuf, CHUNK) => CHUNK; + } + + // need to sync so we can remove + lfsr_file_sync(&lfs, &file) => 0; + + // remove the file + lfsr_remove(&lfs, "gello") => 0; + + // create a second file + lfsr_file_t file__; + lfsr_file_open(&lfs, &file__, "gello", + LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0; + + // write to the second file + prng = 42+1; + for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { + uint8_t wbuf[CHUNK]; + for (lfs_size_t j = 0; j < CHUNK; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + lfsr_file_write(&lfs, &file__, wbuf, CHUNK) => CHUNK; + } + + // need to sync so we can remove + lfsr_file_sync(&lfs, &file__) => 0; + + // remove the file + lfsr_remove(&lfs, "gello") => 0; + + // create a third file + lfsr_file_t file___; + lfsr_file_open(&lfs, &file___, "gello", + LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0; + + // write to the third file + prng = 42+2; + for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { + uint8_t wbuf[CHUNK]; + for (lfs_size_t j = 0; j < CHUNK; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + lfsr_file_write(&lfs, &file___, wbuf, CHUNK) => CHUNK; + } + + // as far as the filesystem is concerned, the file does not exist yet + // via stat + struct lfs_info info; + lfsr_stat(&lfs, "gello", &info) => LFS_ERR_NOENT; + // via readdir + 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) => LFS_ERR_NOENT; + lfsr_dir_close(&lfs, &dir) => 0; + // rdonly rejected + lfsr_file_t file_; + lfsr_file_open(&lfs, &file_, "gello", LFS_O_RDONLY) => LFS_ERR_NOENT; + // non-create rejected + lfsr_file_open(&lfs, &file_, "gello", LFS_O_WRONLY) => LFS_ERR_NOENT; + lfsr_file_open(&lfs, &file_, "gello", LFS_O_RDWR) => LFS_ERR_NOENT; + + // removed files can not be synced + lfsr_file_sync(&lfs, &file) => LFS_ERR_NOENT; + lfsr_file_sync(&lfs, &file__) => LFS_ERR_NOENT; + // but we should be able to sync our third file just fine + lfsr_file_sync(&lfs, &file___) => 0; + + // third file should appear on disk now + // via stat + lfsr_stat(&lfs, "gello", &info) => 0; + assert(strcmp(info.name, "gello") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == SIZE); + // via readdir + 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, "gello") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == SIZE); + lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; + lfsr_dir_close(&lfs, &dir) => 0; + // via open + lfsr_file_open(&lfs, &file_, "gello", LFS_O_RDONLY) => 0; + prng = 42+2; + for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { + uint8_t wbuf[CHUNK]; + for (lfs_size_t j = 0; j < CHUNK; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + uint8_t rbuf[CHUNK]; + lfsr_file_read(&lfs, &file_, rbuf, CHUNK) => CHUNK; + assert(memcmp(wbuf, rbuf, CHUNK) == 0); + } + lfsr_file_close(&lfs, &file_) => 0; + + // the perhaps surprising thing is we should still be able + // to read all file handles + lfsr_file_rewind(&lfs, &file) => 0; + prng = 42; + for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { + uint8_t wbuf[CHUNK]; + for (lfs_size_t j = 0; j < CHUNK; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + uint8_t rbuf[CHUNK]; + lfsr_file_read(&lfs, &file, rbuf, CHUNK) => CHUNK; + assert(memcmp(wbuf, rbuf, CHUNK) == 0); + } + + lfsr_file_rewind(&lfs, &file__) => 0; + prng = 42+1; + for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { + uint8_t wbuf[CHUNK]; + for (lfs_size_t j = 0; j < CHUNK; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + uint8_t rbuf[CHUNK]; + lfsr_file_read(&lfs, &file__, rbuf, CHUNK) => CHUNK; + assert(memcmp(wbuf, rbuf, CHUNK) == 0); + } + + lfsr_file_rewind(&lfs, &file___) => 0; + prng = 42+2; + for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { + uint8_t wbuf[CHUNK]; + for (lfs_size_t j = 0; j < CHUNK; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + uint8_t rbuf[CHUNK]; + lfsr_file_read(&lfs, &file___, rbuf, CHUNK) => CHUNK; + assert(memcmp(wbuf, rbuf, CHUNK) == 0); + } + + // close + lfsr_file_close(&lfs, &file) => 0; + lfsr_file_close(&lfs, &file__) => 0; + lfsr_file_close(&lfs, &file___) => 0; + + // check disk again + // via stat + lfsr_stat(&lfs, "gello", &info) => 0; + assert(strcmp(info.name, "gello") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == SIZE); + // via readdir + 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, "gello") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == SIZE); + lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; + lfsr_dir_close(&lfs, &dir) => 0; + // via open + lfsr_file_open(&lfs, &file_, "gello", LFS_O_RDONLY) => 0; + prng = 42+2; + for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { + uint8_t wbuf[CHUNK]; + for (lfs_size_t j = 0; j < CHUNK; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + uint8_t rbuf[CHUNK]; + lfsr_file_read(&lfs, &file_, rbuf, CHUNK) => CHUNK; + assert(memcmp(wbuf, rbuf, CHUNK) == 0); + } + lfsr_file_close(&lfs, &file_) => 0; + + // remount + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + + // check disk again again + // via stat + lfsr_stat(&lfs, "gello", &info) => 0; + assert(strcmp(info.name, "gello") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == SIZE); + // via readdir + 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, "gello") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == SIZE); + lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; + lfsr_dir_close(&lfs, &dir) => 0; + // via open + lfsr_file_open(&lfs, &file_, "gello", LFS_O_RDONLY) => 0; + prng = 42+2; + for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { + uint8_t wbuf[CHUNK]; + for (lfs_size_t j = 0; j < CHUNK; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + uint8_t rbuf[CHUNK]; + lfsr_file_read(&lfs, &file_, rbuf, CHUNK) => CHUNK; + assert(memcmp(wbuf, rbuf, CHUNK) == 0); + } + lfsr_file_close(&lfs, &file_) => 0; + + lfsr_unmount(&lfs) => 0; +''' + +[cases.test_fscratch_zombie_zombie_rwrwrw_posthumous] +defines.SIZE = '4*BLOCK_SIZE' +defines.CHUNK = 64 +defines.SYNC = [false, true] +code = ''' + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + + // create a file + lfsr_file_t file; + lfsr_file_open(&lfs, &file, "gello", + LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0; + + // need to sync so we can remove + lfsr_file_sync(&lfs, &file) => 0; + + // remove the file + lfsr_remove(&lfs, "gello") => 0; + + // create a second file + lfsr_file_t file__; + lfsr_file_open(&lfs, &file__, "gello", + LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0; + + // need to sync so we can remove + lfsr_file_sync(&lfs, &file__) => 0; + + // remove the file + lfsr_remove(&lfs, "gello") => 0; + + // create a third file + lfsr_file_t file___; + lfsr_file_open(&lfs, &file___, "gello", + LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0; + + // write to the third file + uint32_t prng = 42+2; + for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { + uint8_t wbuf[CHUNK]; + for (lfs_size_t j = 0; j < CHUNK; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + lfsr_file_write(&lfs, &file___, wbuf, CHUNK) => CHUNK; + } + + // write to the second file + prng = 42+1; + for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { + uint8_t wbuf[CHUNK]; + for (lfs_size_t j = 0; j < CHUNK; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + lfsr_file_write(&lfs, &file__, wbuf, CHUNK) => CHUNK; + } + + // write to the first file + prng = 42; + for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { + uint8_t wbuf[CHUNK]; + for (lfs_size_t j = 0; j < CHUNK; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + lfsr_file_write(&lfs, &file, wbuf, CHUNK) => CHUNK; + } + + // as far as the filesystem is concerned, the file does not exist yet + // via stat + struct lfs_info info; + lfsr_stat(&lfs, "gello", &info) => LFS_ERR_NOENT; + // via readdir + 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) => LFS_ERR_NOENT; + lfsr_dir_close(&lfs, &dir) => 0; + // rdonly rejected + lfsr_file_t file_; + lfsr_file_open(&lfs, &file_, "gello", LFS_O_RDONLY) => LFS_ERR_NOENT; + // non-create rejected + lfsr_file_open(&lfs, &file_, "gello", LFS_O_WRONLY) => LFS_ERR_NOENT; + lfsr_file_open(&lfs, &file_, "gello", LFS_O_RDWR) => LFS_ERR_NOENT; + + // removed files can not be synced + lfsr_file_sync(&lfs, &file) => LFS_ERR_NOENT; + lfsr_file_sync(&lfs, &file__) => LFS_ERR_NOENT; + // but we should be able to sync our third file just fine + lfsr_file_sync(&lfs, &file___) => 0; + + // third file should appear on disk now + // via stat + lfsr_stat(&lfs, "gello", &info) => 0; + assert(strcmp(info.name, "gello") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == SIZE); + // via readdir + 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, "gello") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == SIZE); + lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; + lfsr_dir_close(&lfs, &dir) => 0; + // via open + lfsr_file_open(&lfs, &file_, "gello", LFS_O_RDONLY) => 0; + prng = 42+2; + for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { + uint8_t wbuf[CHUNK]; + for (lfs_size_t j = 0; j < CHUNK; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + uint8_t rbuf[CHUNK]; + lfsr_file_read(&lfs, &file_, rbuf, CHUNK) => CHUNK; + assert(memcmp(wbuf, rbuf, CHUNK) == 0); + } + lfsr_file_close(&lfs, &file_) => 0; + + // the perhaps surprising thing is we should still be able + // to read all file handles + lfsr_file_rewind(&lfs, &file) => 0; + prng = 42; + for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { + uint8_t wbuf[CHUNK]; + for (lfs_size_t j = 0; j < CHUNK; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + uint8_t rbuf[CHUNK]; + lfsr_file_read(&lfs, &file, rbuf, CHUNK) => CHUNK; + assert(memcmp(wbuf, rbuf, CHUNK) == 0); + } + + lfsr_file_rewind(&lfs, &file__) => 0; + prng = 42+1; + for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { + uint8_t wbuf[CHUNK]; + for (lfs_size_t j = 0; j < CHUNK; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + uint8_t rbuf[CHUNK]; + lfsr_file_read(&lfs, &file__, rbuf, CHUNK) => CHUNK; + assert(memcmp(wbuf, rbuf, CHUNK) == 0); + } + + lfsr_file_rewind(&lfs, &file___) => 0; + prng = 42+2; + for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { + uint8_t wbuf[CHUNK]; + for (lfs_size_t j = 0; j < CHUNK; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + uint8_t rbuf[CHUNK]; + lfsr_file_read(&lfs, &file___, rbuf, CHUNK) => CHUNK; + assert(memcmp(wbuf, rbuf, CHUNK) == 0); + } + + // close + lfsr_file_close(&lfs, &file) => 0; + lfsr_file_close(&lfs, &file__) => 0; + lfsr_file_close(&lfs, &file___) => 0; + + // check disk again + // via stat + lfsr_stat(&lfs, "gello", &info) => 0; + assert(strcmp(info.name, "gello") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == SIZE); + // via readdir + 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, "gello") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == SIZE); + lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; + lfsr_dir_close(&lfs, &dir) => 0; + // via open + lfsr_file_open(&lfs, &file_, "gello", LFS_O_RDONLY) => 0; + prng = 42+2; + for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { + uint8_t wbuf[CHUNK]; + for (lfs_size_t j = 0; j < CHUNK; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + uint8_t rbuf[CHUNK]; + lfsr_file_read(&lfs, &file_, rbuf, CHUNK) => CHUNK; + assert(memcmp(wbuf, rbuf, CHUNK) == 0); + } + lfsr_file_close(&lfs, &file_) => 0; + + // remount + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + + // check disk again again + // via stat + lfsr_stat(&lfs, "gello", &info) => 0; + assert(strcmp(info.name, "gello") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == SIZE); + // via readdir + 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, "gello") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == SIZE); + lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; + lfsr_dir_close(&lfs, &dir) => 0; + // via open + lfsr_file_open(&lfs, &file_, "gello", LFS_O_RDONLY) => 0; + prng = 42+2; + for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { + uint8_t wbuf[CHUNK]; + for (lfs_size_t j = 0; j < CHUNK; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + uint8_t rbuf[CHUNK]; + lfsr_file_read(&lfs, &file_, rbuf, CHUNK) => CHUNK; + assert(memcmp(wbuf, rbuf, CHUNK) == 0); + } + lfsr_file_close(&lfs, &file_) => 0; + + lfsr_unmount(&lfs) => 0; +''' + +[cases.test_fscratch_zombie_open] +# 0 => don't close (before end of test) +# 1 => close after op +# 2 => close before op +defines.CLOSE = [0, 1, 2] +# 0 => don't remount +# 1 => remount after op +# 2 => remount before op +defines.REMOUNT = [0, 1, 2] +defines.POSTHUMOUS = [false, true] +if = 'REMOUNT <= CLOSE' +code = ''' + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + + // create a zombie + lfsr_file_t zombie; + lfsr_file_open(&lfs, &zombie, "gello", + LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0; + if (!POSTHUMOUS) { + lfsr_file_write(&lfs, &zombie, + "WoOoOoOoOoO", strlen("WoOoOoOoOoO")) + => strlen("WoOoOoOoOoO"); + } + lfsr_file_sync(&lfs, &zombie) => 0; + lfsr_remove(&lfs, "gello") => 0; + + if (CLOSE == 2) { + lfsr_file_close(&lfs, &zombie) => 0; + } + if (REMOUNT == 2) { + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + } + + // create a new file over the zombie + lfsr_file_t file; + lfsr_file_open(&lfs, &file, "gello", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + lfsr_file_write(&lfs, &file, "hello!", strlen("hello!")) + => strlen("hello!"); + lfsr_file_close(&lfs, &file) => 0; + + if (CLOSE <= 1 && REMOUNT <= 1) { + if (POSTHUMOUS) { + lfsr_file_write(&lfs, &zombie, + "WoOoOoOoOoO", strlen("WoOoOoOoOoO")) + => strlen("WoOoOoOoOoO"); + } + + // we should still be able to read our zombie + lfsr_file_rewind(&lfs, &zombie) => 0; + uint8_t rbuf[256]; + lfsr_file_read(&lfs, &zombie, rbuf, sizeof(rbuf)) + => strlen("WoOoOoOoOoO"); + assert(memcmp(rbuf, "WoOoOoOoOoO", strlen("WoOoOoOoOoO")) == 0); + + if (CLOSE == 1) { + lfsr_file_close(&lfs, &zombie) => 0; + } + if (REMOUNT == 1) { + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + } + } + + // make sure the new file is readable + // via stat + struct lfs_info info; + lfsr_stat(&lfs, "gello", &info) => 0; + assert(strcmp(info.name, "gello") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == strlen("hello!")); + // via readdir + 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, "gello") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == strlen("hello!")); + lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; + lfsr_dir_close(&lfs, &dir) => 0; + // via open + lfsr_file_t file_; + lfsr_file_open(&lfs, &file_, "gello", LFS_O_RDONLY) => 0; + uint8_t rbuf[256]; + lfsr_file_read(&lfs, &file_, rbuf, sizeof(rbuf)) => strlen("hello!"); + assert(memcmp(rbuf, "hello!", strlen("hello!")) == 0); + lfsr_file_close(&lfs, &file_) => 0; + + if (CLOSE == 0) { + lfsr_file_close(&lfs, &zombie) => 0; + } + lfsr_unmount(&lfs) => 0; +''' + +[cases.test_fscratch_zombie_mkdir] +# 0 => don't close (before end of test) +# 1 => close after op +# 2 => close before op +defines.CLOSE = [0, 1, 2] +# 0 => don't remount +# 1 => remount after op +# 2 => remount before op +defines.REMOUNT = [0, 1, 2] +defines.POSTHUMOUS = [false, true] +if = 'REMOUNT <= CLOSE' +code = ''' + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + + // create a zombie + lfsr_file_t zombie; + lfsr_file_open(&lfs, &zombie, "gello", + LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0; + if (!POSTHUMOUS) { + lfsr_file_write(&lfs, &zombie, + "WoOoOoOoOoO", strlen("WoOoOoOoOoO")) + => strlen("WoOoOoOoOoO"); + } + lfsr_file_sync(&lfs, &zombie) => 0; + lfsr_remove(&lfs, "gello") => 0; + + if (CLOSE == 2) { + lfsr_file_close(&lfs, &zombie) => 0; + } + if (REMOUNT == 2) { + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + } + + // create a new dir over the zombie + lfsr_mkdir(&lfs, "gello") => 0; + + if (CLOSE <= 1 && REMOUNT <= 1) { + if (POSTHUMOUS) { + lfsr_file_write(&lfs, &zombie, + "WoOoOoOoOoO", strlen("WoOoOoOoOoO")) + => strlen("WoOoOoOoOoO"); + } + + // we should still be able to read our zombie + lfsr_file_rewind(&lfs, &zombie) => 0; + uint8_t rbuf[256]; + lfsr_file_read(&lfs, &zombie, rbuf, sizeof(rbuf)) + => strlen("WoOoOoOoOoO"); + assert(memcmp(rbuf, "WoOoOoOoOoO", strlen("WoOoOoOoOoO")) == 0); + + if (CLOSE == 1) { + lfsr_file_close(&lfs, &zombie) => 0; + } + if (REMOUNT == 1) { + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + } + } + + // make sure the new dir is readable + // via stat + struct lfs_info info; + lfsr_stat(&lfs, "gello", &info) => 0; + assert(strcmp(info.name, "gello") == 0); + assert(info.type == LFS_TYPE_DIR); + // via readdir + 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, "gello") == 0); + assert(info.type == LFS_TYPE_DIR); + lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; + lfsr_dir_close(&lfs, &dir) => 0; + + if (CLOSE == 0) { + lfsr_file_close(&lfs, &zombie) => 0; + } + lfsr_unmount(&lfs) => 0; +''' + +[cases.test_fscratch_zombie_remove] +# 0 => don't close (before end of test) +# 1 => close after op +# 2 => close before op +defines.CLOSE = [0, 1, 2] +# 0 => don't remount +# 1 => remount after op +# 2 => remount before op +defines.REMOUNT = [0, 1, 2] +defines.POSTHUMOUS = [false, true] +if = 'REMOUNT <= CLOSE' +code = ''' + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + + // create a zombie + lfsr_file_t zombie; + lfsr_file_open(&lfs, &zombie, "gello", + LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0; + if (!POSTHUMOUS) { + lfsr_file_write(&lfs, &zombie, + "WoOoOoOoOoO", strlen("WoOoOoOoOoO")) + => strlen("WoOoOoOoOoO"); + } + lfsr_file_sync(&lfs, &zombie) => 0; + lfsr_remove(&lfs, "gello") => 0; + + if (CLOSE == 2) { + lfsr_file_close(&lfs, &zombie) => 0; + } + if (REMOUNT == 2) { + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + } + + // zombies aren't real, so remove should fail + lfsr_remove(&lfs, "gello") => LFS_ERR_NOENT; + + if (CLOSE <= 1 && REMOUNT <= 1) { + if (POSTHUMOUS) { + lfsr_file_write(&lfs, &zombie, + "WoOoOoOoOoO", strlen("WoOoOoOoOoO")) + => strlen("WoOoOoOoOoO"); + } + + // we should still be able to read our zombie + lfsr_file_rewind(&lfs, &zombie) => 0; + uint8_t rbuf[256]; + lfsr_file_read(&lfs, &zombie, rbuf, sizeof(rbuf)) + => strlen("WoOoOoOoOoO"); + assert(memcmp(rbuf, "WoOoOoOoOoO", strlen("WoOoOoOoOoO")) == 0); + + if (CLOSE == 1) { + lfsr_file_close(&lfs, &zombie) => 0; + } + if (REMOUNT == 1) { + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + } + } + + // just make sure things look ok + // via stat + struct lfs_info info; + lfsr_stat(&lfs, "gello", &info) => LFS_ERR_NOENT; + // via readdir + 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) => LFS_ERR_NOENT; + lfsr_dir_close(&lfs, &dir) => 0; + + if (CLOSE == 0) { + lfsr_file_close(&lfs, &zombie) => 0; + } + lfsr_unmount(&lfs) => 0; +''' + +[cases.test_fscratch_zombie_rename_dst] +defines.DIR = [false, true] +defines.INTERDIR = [false, true] +defines.DISTANCE = [0, 1, 100] +# 0 => don't close (before end of test) +# 1 => close after op +# 2 => close before op +defines.CLOSE = [0, 1, 2] +# 0 => don't remount +# 1 => remount after op +# 2 => remount before op +defines.REMOUNT = [0, 1, 2] +defines.POSTHUMOUS = [false, true] +if = 'REMOUNT <= CLOSE' +code = ''' + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + + // create an interesting directory structure + if (INTERDIR) { + lfsr_mkdir(&lfs, "a") => 0; + lfsr_mkdir(&lfs, "b") => 0; + lfsr_mkdir(&lfs, "c") => 0; + } + for (lfs_size_t i = 0; i < DISTANCE; i++) { + char name[256]; + sprintf(name, (INTERDIR) ? "b/hello%03x" : "hello%03x", i); + lfsr_file_t file; + lfsr_file_open(&lfs, &file, name, + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + lfsr_file_close(&lfs, &file) => 0; + } + + // make our src file before zombies, just in case + if (DIR) { + lfsr_mkdir(&lfs, (INTERDIR) ? "c/iello" : "iello") => 0; + } else { + lfsr_file_t file; + lfsr_file_open(&lfs, &file, (INTERDIR) ? "c/iello" : "iello", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + lfsr_file_write(&lfs, &file, "hello!", strlen("hello!")) + => strlen("hello!"); + lfsr_file_close(&lfs, &file) => 0; + } + + // create a zombie + lfsr_file_t zombie; + lfsr_file_open(&lfs, &zombie, (INTERDIR) ? "a/gello" : "gello", + LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0; + if (!POSTHUMOUS) { + lfsr_file_write(&lfs, &zombie, + "WoOoOoOoOoO", strlen("WoOoOoOoOoO")) + => strlen("WoOoOoOoOoO"); + } + lfsr_file_sync(&lfs, &zombie) => 0; + lfsr_remove(&lfs, (INTERDIR) ? "a/gello" : "gello") => 0; + + if (CLOSE == 2) { + lfsr_file_close(&lfs, &zombie) => 0; + } + if (REMOUNT == 2) { + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + } + + // rename onto zombie + lfsr_rename(&lfs, + (INTERDIR) ? "c/iello" : "iello", + (INTERDIR) ? "a/gello" : "gello") => 0; + + if (CLOSE <= 1 && REMOUNT <= 1) { + if (POSTHUMOUS) { + lfsr_file_write(&lfs, &zombie, + "WoOoOoOoOoO", strlen("WoOoOoOoOoO")) + => strlen("WoOoOoOoOoO"); + } + + // we should still be able to read our zombie + lfsr_file_rewind(&lfs, &zombie) => 0; + uint8_t rbuf[256]; + lfsr_file_read(&lfs, &zombie, rbuf, sizeof(rbuf)) + => strlen("WoOoOoOoOoO"); + assert(memcmp(rbuf, "WoOoOoOoOoO", strlen("WoOoOoOoOoO")) == 0); + + if (CLOSE == 1) { + lfsr_file_close(&lfs, &zombie) => 0; + } + if (REMOUNT == 1) { + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + } + } + + // make sure the new file is readable + // via stat + struct lfs_info info; + lfsr_stat(&lfs, (INTERDIR) ? "a/gello" : "gello", &info) => 0; + assert(strcmp(info.name, "gello") == 0); + if (DIR) { + assert(info.type == LFS_TYPE_DIR); + } else { + assert(info.type == LFS_TYPE_REG); + assert(info.size == strlen("hello!")); + } + // via readdir + lfsr_dir_t dir; + lfsr_dir_open(&lfs, &dir, (INTERDIR) ? "a" : "/") => 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, "gello") == 0); + if (DIR) { + assert(info.type == LFS_TYPE_DIR); + } else { + assert(info.type == LFS_TYPE_REG); + assert(info.size == strlen("hello!")); + } + if (!INTERDIR) { + for (lfs_size_t i = 0; i < DISTANCE; i++) { + char name[256]; + sprintf(name, "hello%03x", i); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, name) == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == 0); + } + } + lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; + lfsr_dir_close(&lfs, &dir) => 0; + // via open + if (DIR) { + lfsr_file_t file_; + lfsr_file_open(&lfs, &file_, (INTERDIR) ? "a/gello" : "gello", + LFS_O_RDONLY) => LFS_ERR_ISDIR; + } else { + lfsr_file_t file_; + lfsr_file_open(&lfs, &file_, (INTERDIR) ? "a/gello" : "gello", + LFS_O_RDONLY) => 0; + uint8_t rbuf[256]; + lfsr_file_read(&lfs, &file_, rbuf, sizeof(rbuf)) => strlen("hello!"); + assert(memcmp(rbuf, "hello!", strlen("hello!")) == 0); + lfsr_file_close(&lfs, &file_) => 0; + } + + if (CLOSE == 0) { + lfsr_file_close(&lfs, &zombie) => 0; + } + lfsr_unmount(&lfs) => 0; +''' + +[cases.test_fscratch_zombie_rename_src] +# 0 => don't close (before end of test) +# 1 => close after op +# 2 => close before op +defines.CLOSE = [0, 1, 2] +# 0 => don't remount +# 1 => remount after op +# 2 => remount before op +defines.REMOUNT = [0, 1, 2] +defines.POSTHUMOUS = [false, true] +if = 'REMOUNT <= CLOSE' +code = ''' + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + + // create a zombie + lfsr_file_t zombie; + lfsr_file_open(&lfs, &zombie, "gello", + LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0; + if (!POSTHUMOUS) { + lfsr_file_write(&lfs, &zombie, + "WoOoOoOoOoO", strlen("WoOoOoOoOoO")) + => strlen("WoOoOoOoOoO"); + } + lfsr_file_sync(&lfs, &zombie) => 0; + lfsr_remove(&lfs, "gello") => 0; + + if (CLOSE == 2) { + lfsr_file_close(&lfs, &zombie) => 0; + } + if (REMOUNT == 2) { + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + } + + // zombies aren't real, so rename should fail + lfsr_rename(&lfs, "gello", "hello") => LFS_ERR_NOENT; + + if (CLOSE <= 1 && REMOUNT <= 1) { + if (POSTHUMOUS) { + lfsr_file_write(&lfs, &zombie, + "WoOoOoOoOoO", strlen("WoOoOoOoOoO")) + => strlen("WoOoOoOoOoO"); + } + + // we should still be able to read our zombie + lfsr_file_rewind(&lfs, &zombie) => 0; + uint8_t rbuf[256]; + lfsr_file_read(&lfs, &zombie, rbuf, sizeof(rbuf)) + => strlen("WoOoOoOoOoO"); + assert(memcmp(rbuf, "WoOoOoOoOoO", strlen("WoOoOoOoOoO")) == 0); + + if (CLOSE == 1) { + lfsr_file_close(&lfs, &zombie) => 0; + } + if (REMOUNT == 1) { + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + } + } + + // just make sure things look ok + // via stat + struct lfs_info info; + lfsr_stat(&lfs, "gello", &info) => LFS_ERR_NOENT; + // via readdir + 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) => LFS_ERR_NOENT; + lfsr_dir_close(&lfs, &dir) => 0; + + if (CLOSE == 0) { + lfsr_file_close(&lfs, &zombie) => 0; + } + lfsr_unmount(&lfs) => 0; +''' + +[cases.test_fscratch_zombify_mkdir] +defines.CLOSE = [false, true] +defines.REMOUNT = [false, true] +defines.POSTHUMOUS = [false, true] +if = 'REMOUNT <= CLOSE' +code = ''' + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + + // create a file, not a zombie yet + lfsr_file_t zombie; + lfsr_file_open(&lfs, &zombie, "gello", + LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0; + if (!POSTHUMOUS) { + lfsr_file_write(&lfs, &zombie, + "WoOoOoOoOoO", strlen("WoOoOoOoOoO")) + => strlen("WoOoOoOoOoO"); + } + + // use mkdir on the same name, the file hasn't been created yet, + // so this shouldn't fail, but because we have an open file handle + // we create a zombie + lfsr_mkdir(&lfs, "gello") => 0; + + if (POSTHUMOUS) { + lfsr_file_write(&lfs, &zombie, + "WoOoOoOoOoO", strlen("WoOoOoOoOoO")) + => strlen("WoOoOoOoOoO"); + } + + // we should still be able to read our zombie + lfsr_file_rewind(&lfs, &zombie) => 0; + uint8_t rbuf[256]; + lfsr_file_read(&lfs, &zombie, rbuf, sizeof(rbuf)) + => strlen("WoOoOoOoOoO"); + assert(memcmp(rbuf, "WoOoOoOoOoO", strlen("WoOoOoOoOoO")) == 0); + + if (CLOSE) { + lfsr_file_close(&lfs, &zombie) => 0; + } + if (REMOUNT) { + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + } + + // make sure the new dir is readable + // via stat + struct lfs_info info; + lfsr_stat(&lfs, "gello", &info) => 0; + assert(strcmp(info.name, "gello") == 0); + assert(info.type == LFS_TYPE_DIR); + // via readdir + 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, "gello") == 0); + assert(info.type == LFS_TYPE_DIR); + lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; + lfsr_dir_close(&lfs, &dir) => 0; + + if (!CLOSE) { + lfsr_file_close(&lfs, &zombie) => 0; + } + lfsr_unmount(&lfs) => 0; +''' + +[cases.test_fscratch_zombify_rename_dst] +defines.ORPHAN = [false, true] +defines.DIR = [false, true] +defines.INTERDIR = [false, true] +defines.DISTANCE = [0, 1, 100] +defines.CLOSE = [false, true] +defines.REMOUNT = [false, true] +defines.POSTHUMOUS = [false, true] +if = [ + 'REMOUNT <= CLOSE', + '!DIR || ORPHAN', +] +code = ''' + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + + // create an interesting directory structure + if (INTERDIR) { + lfsr_mkdir(&lfs, "a") => 0; + lfsr_mkdir(&lfs, "b") => 0; + lfsr_mkdir(&lfs, "c") => 0; + } + for (lfs_size_t i = 0; i < DISTANCE; i++) { + char name[256]; + sprintf(name, (INTERDIR) ? "b/hello%03x" : "hello%03x", i); + lfsr_file_t file; + lfsr_file_open(&lfs, &file, name, + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + lfsr_file_close(&lfs, &file) => 0; + } + + // make our src file before zombies, just in case + if (DIR) { + lfsr_mkdir(&lfs, (INTERDIR) ? "c/iello" : "iello") => 0; + } else { + lfsr_file_t file; + lfsr_file_open(&lfs, &file, (INTERDIR) ? "c/iello" : "iello", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + lfsr_file_write(&lfs, &file, "hello!", strlen("hello!")) + => strlen("hello!"); + lfsr_file_close(&lfs, &file) => 0; + } + + // create a file, not a zombie yet + lfsr_file_t zombie; + lfsr_file_open(&lfs, &zombie, (INTERDIR) ? "a/gello" : "gello", + LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0; + if (!POSTHUMOUS) { + lfsr_file_write(&lfs, &zombie, + "WoOoOoOoOoO", strlen("WoOoOoOoOoO")) + => strlen("WoOoOoOoOoO"); + } + if (!ORPHAN) { + lfsr_file_sync(&lfs, &zombie) => 0; + } + + // rename onto the same name, this shouldn't fail (note the test + // conditions), but because we have an open file handle we create + // a zombie + lfsr_rename(&lfs, + (INTERDIR) ? "c/iello" : "iello", + (INTERDIR) ? "a/gello" : "gello") => 0; + + if (POSTHUMOUS) { + lfsr_file_write(&lfs, &zombie, + "WoOoOoOoOoO", strlen("WoOoOoOoOoO")) + => strlen("WoOoOoOoOoO"); + } + + // we should still be able to read our zombie + lfsr_file_rewind(&lfs, &zombie) => 0; + uint8_t rbuf[256]; + lfsr_file_read(&lfs, &zombie, rbuf, sizeof(rbuf)) + => strlen("WoOoOoOoOoO"); + assert(memcmp(rbuf, "WoOoOoOoOoO", strlen("WoOoOoOoOoO")) == 0); + + if (CLOSE) { + lfsr_file_close(&lfs, &zombie) => 0; + } + if (REMOUNT) { + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + } + + // make sure the new file is readable + // via stat + struct lfs_info info; + lfsr_stat(&lfs, (INTERDIR) ? "a/gello" : "gello", &info) => 0; + assert(strcmp(info.name, "gello") == 0); + if (DIR) { + assert(info.type == LFS_TYPE_DIR); + } else { + assert(info.type == LFS_TYPE_REG); + assert(info.size == strlen("hello!")); + } + // via readdir + lfsr_dir_t dir; + lfsr_dir_open(&lfs, &dir, (INTERDIR) ? "a" : "/") => 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, "gello") == 0); + if (DIR) { + assert(info.type == LFS_TYPE_DIR); + } else { + assert(info.type == LFS_TYPE_REG); + assert(info.size == strlen("hello!")); + } + if (!INTERDIR) { + for (lfs_size_t i = 0; i < DISTANCE; i++) { + char name[256]; + sprintf(name, "hello%03x", i); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, name) == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == 0); + } + } + lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; + lfsr_dir_close(&lfs, &dir) => 0; + // via open + if (DIR) { + lfsr_file_t file_; + lfsr_file_open(&lfs, &file_, (INTERDIR) ? "a/gello" : "gello", + LFS_O_RDONLY) => LFS_ERR_ISDIR; + } else { + lfsr_file_t file_; + lfsr_file_open(&lfs, &file_, (INTERDIR) ? "a/gello" : "gello", + LFS_O_RDONLY) => 0; + uint8_t rbuf[256]; + lfsr_file_read(&lfs, &file_, rbuf, sizeof(rbuf)) => strlen("hello!"); + assert(memcmp(rbuf, "hello!", strlen("hello!")) == 0); + lfsr_file_close(&lfs, &file_) => 0; + } + + if (!CLOSE) { + lfsr_file_close(&lfs, &zombie) => 0; + } + lfsr_unmount(&lfs) => 0; +''' + +[cases.test_fscratch_file_on_zombie_remove] +defines.DIR = [false, true] +# 0 => don't close (before end of test) +# 1 => close after op +# 2 => close before op +defines.CLOSE = [0, 1, 2] +# 0 => don't remount +# 1 => remount after op +# 2 => remount before op +defines.REMOUNT = [0, 1, 2] +defines.POSTHUMOUS = [false, true] +if = 'REMOUNT <= CLOSE' +code = ''' + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + + // create a zombie + lfsr_file_t zombie; + lfsr_file_open(&lfs, &zombie, "gello", + LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0; + if (!POSTHUMOUS) { + lfsr_file_write(&lfs, &zombie, + "WoOoOoOoOoO", strlen("WoOoOoOoOoO")) + => strlen("WoOoOoOoOoO"); + } + lfsr_file_sync(&lfs, &zombie) => 0; + lfsr_remove(&lfs, "gello") => 0; + + // create a file on top of the zombie + if (DIR) { + lfsr_mkdir(&lfs, "gello") => 0; + } else { + lfsr_file_t file; + lfsr_file_open(&lfs, &file, "gello", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + lfsr_file_write(&lfs, &file, "hmmmmmmmm", strlen("hmmmmmmmm")) + => strlen("hmmmmmmmm"); + lfsr_file_close(&lfs, &file) => 0; + } + + if (CLOSE == 2) { + lfsr_file_close(&lfs, &zombie) => 0; + } + if (REMOUNT == 2) { + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + } + + // remove the file + lfsr_remove(&lfs, "gello") => 0; + + if (CLOSE <= 1 && REMOUNT <= 1) { + if (POSTHUMOUS) { + lfsr_file_write(&lfs, &zombie, + "WoOoOoOoOoO", strlen("WoOoOoOoOoO")) + => strlen("WoOoOoOoOoO"); + } + + // we should still be able to read our zombie + lfsr_file_rewind(&lfs, &zombie) => 0; + uint8_t rbuf[256]; + lfsr_file_read(&lfs, &zombie, rbuf, sizeof(rbuf)) + => strlen("WoOoOoOoOoO"); + assert(memcmp(rbuf, "WoOoOoOoOoO", strlen("WoOoOoOoOoO")) == 0); + + if (CLOSE == 1) { + lfsr_file_close(&lfs, &zombie) => 0; + } + if (REMOUNT == 1) { + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + } + } + + // just make sure things look ok + // via stat + struct lfs_info info; + lfsr_stat(&lfs, "gello", &info) => LFS_ERR_NOENT; + // via readdir + 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) => LFS_ERR_NOENT; + lfsr_dir_close(&lfs, &dir) => 0; + + if (CLOSE == 0) { + lfsr_file_close(&lfs, &zombie) => 0; + } + lfsr_unmount(&lfs) => 0; +''' + +[cases.test_fscratch_file_on_zombie_rename_dst] +defines.DIR = [false, true] +defines.INTERDIR = [false, true] +defines.DISTANCE = [0, 1, 100] +# 0 => don't close (before end of test) +# 1 => close after op +# 2 => close before op +defines.CLOSE = [0, 1, 2] +# 0 => don't remount +# 1 => remount after op +# 2 => remount before op +defines.REMOUNT = [0, 1, 2] +defines.POSTHUMOUS = [false, true] +if = 'REMOUNT <= CLOSE' +code = ''' + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + + // create an interesting directory structure + if (INTERDIR) { + lfsr_mkdir(&lfs, "a") => 0; + lfsr_mkdir(&lfs, "b") => 0; + lfsr_mkdir(&lfs, "c") => 0; + } + for (lfs_size_t i = 0; i < DISTANCE; i++) { + char name[256]; + sprintf(name, (INTERDIR) ? "b/hello%03x" : "hello%03x", i); + lfsr_file_t file; + lfsr_file_open(&lfs, &file, name, + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + lfsr_file_close(&lfs, &file) => 0; + } + + // make our src file before zombies, just in case + if (DIR) { + lfsr_mkdir(&lfs, (INTERDIR) ? "c/iello" : "iello") => 0; + } else { + lfsr_file_t file; + lfsr_file_open(&lfs, &file, (INTERDIR) ? "c/iello" : "iello", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + lfsr_file_write(&lfs, &file, "hello!", strlen("hello!")) + => strlen("hello!"); + lfsr_file_close(&lfs, &file) => 0; + } + + // create a zombie + lfsr_file_t zombie; + lfsr_file_open(&lfs, &zombie, (INTERDIR) ? "a/gello" : "gello", + LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0; + if (!POSTHUMOUS) { + lfsr_file_write(&lfs, &zombie, + "WoOoOoOoOoO", strlen("WoOoOoOoOoO")) + => strlen("WoOoOoOoOoO"); + } + lfsr_file_sync(&lfs, &zombie) => 0; + lfsr_remove(&lfs, (INTERDIR) ? "a/gello" : "gello") => 0; + + // create a file on top of the zombie + if (DIR) { + lfsr_mkdir(&lfs, (INTERDIR) ? "a/gello" : "gello") => 0; + } else { + lfsr_file_t file; + lfsr_file_open(&lfs, &file, (INTERDIR) ? "a/gello" : "gello", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + lfsr_file_write(&lfs, &file, "hmmmmmmmm", strlen("hmmmmmmmm")) + => strlen("hmmmmmmmm"); + lfsr_file_close(&lfs, &file) => 0; + } + + if (CLOSE == 2) { + lfsr_file_close(&lfs, &zombie) => 0; + } + if (REMOUNT == 2) { + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + } + + // rename onto the file + lfsr_rename(&lfs, + (INTERDIR) ? "c/iello" : "iello", + (INTERDIR) ? "a/gello" : "gello") => 0; + + if (CLOSE <= 1 && REMOUNT <= 1) { + if (POSTHUMOUS) { + lfsr_file_write(&lfs, &zombie, + "WoOoOoOoOoO", strlen("WoOoOoOoOoO")) + => strlen("WoOoOoOoOoO"); + } + + // we should still be able to read our zombie + lfsr_file_rewind(&lfs, &zombie) => 0; + uint8_t rbuf[256]; + lfsr_file_read(&lfs, &zombie, rbuf, sizeof(rbuf)) + => strlen("WoOoOoOoOoO"); + assert(memcmp(rbuf, "WoOoOoOoOoO", strlen("WoOoOoOoOoO")) == 0); + + if (CLOSE == 1) { + lfsr_file_close(&lfs, &zombie) => 0; + } + if (REMOUNT == 1) { + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + } + } + + // make sure the new file is readable + // via stat + struct lfs_info info; + lfsr_stat(&lfs, (INTERDIR) ? "a/gello" : "gello", &info) => 0; + assert(strcmp(info.name, "gello") == 0); + if (DIR) { + assert(info.type == LFS_TYPE_DIR); + } else { + assert(info.type == LFS_TYPE_REG); + assert(info.size == strlen("hello!")); + } + // via readdir + lfsr_dir_t dir; + lfsr_dir_open(&lfs, &dir, (INTERDIR) ? "a" : "/") => 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, "gello") == 0); + if (DIR) { + assert(info.type == LFS_TYPE_DIR); + } else { + assert(info.type == LFS_TYPE_REG); + assert(info.size == strlen("hello!")); + } + if (!INTERDIR) { + for (lfs_size_t i = 0; i < DISTANCE; i++) { + char name[256]; + sprintf(name, (INTERDIR) ? "a/hello%03x" : "hello%03x", i); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, name) == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == 0); + } + } + lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; + lfsr_dir_close(&lfs, &dir) => 0; + // via open + if (DIR) { + lfsr_file_t file_; + lfsr_file_open(&lfs, &file_, (INTERDIR) ? "a/gello" : "gello", + LFS_O_RDONLY) => LFS_ERR_ISDIR; + } else { + lfsr_file_t file_; + lfsr_file_open(&lfs, &file_, (INTERDIR) ? "a/gello" : "gello", + LFS_O_RDONLY) => 0; + uint8_t rbuf[256]; + lfsr_file_read(&lfs, &file_, rbuf, sizeof(rbuf)) => strlen("hello!"); + assert(memcmp(rbuf, "hello!", strlen("hello!")) == 0); + lfsr_file_close(&lfs, &file_) => 0; + } + + if (CLOSE == 0) { + lfsr_file_close(&lfs, &zombie) => 0; + } + lfsr_unmount(&lfs) => 0; +''' + +# this doesn't really involve scratch files, but we might as well test +# this here +[cases.test_fscratch_rename_src] +defines.EXISTS = [false, true] +defines.INTERDIR = [false, true] +defines.DISTANCE = [0, 1, 100] +defines.SYNC = [false, true] +# 0 => don't close (before end of test) +# 1 => close after op +# 2 => close before op +defines.CLOSE = [0, 1, 2] +# 0 => don't remount +# 1 => remount after op +# 2 => remount before op +defines.REMOUNT = [0, 1, 2] +defines.POSTRENAME = [false, true] +if = 'REMOUNT <= CLOSE' +code = ''' + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + + // create an interesting directory structure + if (INTERDIR) { + lfsr_mkdir(&lfs, "a") => 0; + lfsr_mkdir(&lfs, "b") => 0; + lfsr_mkdir(&lfs, "c") => 0; + } + for (lfs_size_t i = 0; i < DISTANCE; i++) { + char name[256]; + sprintf(name, (INTERDIR) ? "b/hello%03x" : "hello%03x", i); + lfsr_file_t file; + lfsr_file_open(&lfs, &file, name, + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + lfsr_file_close(&lfs, &file) => 0; + } + + // create our destination first, just in case + if (EXISTS) { + lfsr_file_t file; + lfsr_file_open(&lfs, &file, (INTERDIR) ? "a/gello" : "gello", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + lfsr_file_write(&lfs, &file, "hmmmmmmmm", strlen("hmmmmmmmm")) + => strlen("hmmmmmmmm"); + lfsr_file_close(&lfs, &file) => 0; + } + + // create a file + lfsr_file_t file; + lfsr_file_open(&lfs, &file, (INTERDIR) ? "c/iello" : "iello", + LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0; + lfsr_file_sync(&lfs, &file) => 0; + if (!POSTRENAME) { + lfsr_file_write(&lfs, &file, + "hello!", strlen("hello!")) + => strlen("hello!"); + if (SYNC) { + lfsr_file_sync(&lfs, &file) => 0; + } + } + + if (CLOSE == 2) { + lfsr_file_close(&lfs, &file) => 0; + } + if (REMOUNT == 2) { + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + } + + // rename while open + lfsr_rename(&lfs, + (INTERDIR) ? "c/iello" : "iello", + (INTERDIR) ? "a/gello" : "gello") => 0; + + if (CLOSE <= 1 && REMOUNT <= 1) { + if (POSTRENAME) { + lfsr_file_write(&lfs, &file, + "hello!", strlen("hello!")) + => strlen("hello!"); + if (SYNC) { + lfsr_file_sync(&lfs, &file) => 0; + } + } + + // we should still be able to read our file + lfsr_file_rewind(&lfs, &file) => 0; + uint8_t rbuf[256]; + lfsr_file_read(&lfs, &file, rbuf, sizeof(rbuf)) + => strlen("hello!"); + assert(memcmp(rbuf, "hello!", strlen("hello!")) == 0); + + if (CLOSE == 1) { + lfsr_file_close(&lfs, &file) => 0; + } + if (REMOUNT == 1) { + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + } + } + + // make sure the new file is readable + // via stat + struct lfs_info info; + lfsr_stat(&lfs, (INTERDIR) ? "a/gello" : "gello", &info) => 0; + assert(strcmp(info.name, "gello") == 0); + assert(info.type == LFS_TYPE_REG); + if ((SYNC || CLOSE >= 1) && !(POSTRENAME && CLOSE >= 2)) { + assert(info.size == strlen("hello!")); + } else { + assert(info.size == 0); + } + // via readdir + lfsr_dir_t dir; + lfsr_dir_open(&lfs, &dir, (INTERDIR) ? "a" : "/") => 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, "gello") == 0); + assert(info.type == LFS_TYPE_REG); + if ((SYNC || CLOSE >= 1) && !(POSTRENAME && CLOSE >= 2)) { + assert(info.size == strlen("hello!")); + } else { + assert(info.size == 0); + } + if (!INTERDIR) { + for (lfs_size_t i = 0; i < DISTANCE; i++) { + char name[256]; + sprintf(name, (INTERDIR) ? "a/hello%03x" : "hello%03x", i); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, name) == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == 0); + } + } + lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; + lfsr_dir_close(&lfs, &dir) => 0; + // via open + lfsr_file_t file_; + lfsr_file_open(&lfs, &file_, (INTERDIR) ? "a/gello" : "gello", + LFS_O_RDONLY) => 0; + uint8_t rbuf[256]; + if ((SYNC || CLOSE >= 1) && !(POSTRENAME && CLOSE >= 2)) { + lfsr_file_read(&lfs, &file_, rbuf, sizeof(rbuf)) => strlen("hello!"); + assert(memcmp(rbuf, "hello!", strlen("hello!")) == 0); + } else { + lfsr_file_read(&lfs, &file_, rbuf, sizeof(rbuf)) => 0; + } + lfsr_file_close(&lfs, &file_) => 0; + + if (CLOSE == 0) { + lfsr_file_close(&lfs, &file) => 0; + } + lfsr_unmount(&lfs) => 0; +''' + +[cases.test_fscratch_file_on_zombie_rename_src] +defines.DIR = [false, true] +defines.EXISTS = [false, true] +defines.INTERDIR = [false, true] +defines.DISTANCE = [0, 1, 100] +# 0 => don't close (before end of test) +# 1 => close after op +# 2 => close before op +defines.CLOSE = [0, 1, 2] +# 0 => don't remount +# 1 => remount after op +# 2 => remount before op +defines.REMOUNT = [0, 1, 2] +defines.POSTHUMOUS = [false, true] +if = 'REMOUNT <= CLOSE' +code = ''' + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + + // create an interesting directory structure + if (INTERDIR) { + lfsr_mkdir(&lfs, "a") => 0; + lfsr_mkdir(&lfs, "b") => 0; + lfsr_mkdir(&lfs, "c") => 0; + } + for (lfs_size_t i = 0; i < DISTANCE; i++) { + char name[256]; + sprintf(name, (INTERDIR) ? "b/hello%03x" : "hello%03x", i); + lfsr_file_t file; + lfsr_file_open(&lfs, &file, name, + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + lfsr_file_close(&lfs, &file) => 0; + } + + // create our destination first, just in case + if (EXISTS) { + if (DIR) { + lfsr_mkdir(&lfs, (INTERDIR) ? "a/gello" : "gello") => 0; + } else { + lfsr_file_t file; + lfsr_file_open(&lfs, &file, (INTERDIR) ? "a/gello" : "gello", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + lfsr_file_write(&lfs, &file, "hmmmmmmmm", strlen("hmmmmmmmm")) + => strlen("hmmmmmmmm"); + lfsr_file_close(&lfs, &file) => 0; + } + } + + // create a zombie + lfsr_file_t zombie; + lfsr_file_open(&lfs, &zombie, (INTERDIR) ? "c/iello" : "iello", + LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0; + if (!POSTHUMOUS) { + lfsr_file_write(&lfs, &zombie, + "WoOoOoOoOoO", strlen("WoOoOoOoOoO")) + => strlen("WoOoOoOoOoO"); + } + lfsr_file_sync(&lfs, &zombie) => 0; + lfsr_remove(&lfs, (INTERDIR) ? "c/iello" : "iello") => 0; + + // create a file on top of the zombie + if (DIR) { + lfsr_mkdir(&lfs, (INTERDIR) ? "c/iello" : "iello") => 0; + } else { + lfsr_file_t file; + lfsr_file_open(&lfs, &file, (INTERDIR) ? "c/iello" : "iello", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + lfsr_file_write(&lfs, &file, "hello!", strlen("hello!")) + => strlen("hello!"); + lfsr_file_close(&lfs, &file) => 0; + } + + if (CLOSE == 2) { + lfsr_file_close(&lfs, &zombie) => 0; + } + if (REMOUNT == 2) { + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + } + + // rename while open + lfsr_rename(&lfs, + (INTERDIR) ? "c/iello" : "iello", + (INTERDIR) ? "a/gello" : "gello") => 0; + + if (CLOSE <= 1 && REMOUNT <= 1) { + if (POSTHUMOUS) { + lfsr_file_write(&lfs, &zombie, + "WoOoOoOoOoO", strlen("WoOoOoOoOoO")) + => strlen("WoOoOoOoOoO"); + } + + // we should still be able to read our file + lfsr_file_rewind(&lfs, &zombie) => 0; + uint8_t rbuf[256]; + lfsr_file_read(&lfs, &zombie, rbuf, sizeof(rbuf)) + => strlen("WoOoOoOoOoO"); + assert(memcmp(rbuf, "WoOoOoOoOoO", strlen("WoOoOoOoOoO")) == 0); + + if (CLOSE == 1) { + lfsr_file_close(&lfs, &zombie) => 0; + } + if (REMOUNT == 1) { + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + } + } + + // make sure the new file is readable + // via stat + struct lfs_info info; + lfsr_stat(&lfs, (INTERDIR) ? "a/gello" : "gello", &info) => 0; + assert(strcmp(info.name, "gello") == 0); + if (DIR) { + assert(info.type == LFS_TYPE_DIR); + } else { + assert(info.type == LFS_TYPE_REG); + assert(info.size == strlen("hello!")); + } + // via readdir + lfsr_dir_t dir; + lfsr_dir_open(&lfs, &dir, (INTERDIR) ? "a" : "/") => 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, "gello") == 0); + if (DIR) { + assert(info.type == LFS_TYPE_DIR); + } else { + assert(info.type == LFS_TYPE_REG); + assert(info.size == strlen("hello!")); + } + if (!INTERDIR) { + for (lfs_size_t i = 0; i < DISTANCE; i++) { + char name[256]; + sprintf(name, (INTERDIR) ? "a/hello%03x" : "hello%03x", i); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, name) == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == 0); + } + } + lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; + lfsr_dir_close(&lfs, &dir) => 0; + // via open + if (DIR) { + lfsr_file_t file_; + lfsr_file_open(&lfs, &file_, (INTERDIR) ? "a/gello" : "gello", + LFS_O_RDONLY) => LFS_ERR_ISDIR; + } else { + lfsr_file_t file_; + lfsr_file_open(&lfs, &file_, (INTERDIR) ? "a/gello" : "gello", + LFS_O_RDONLY) => 0; + uint8_t rbuf[256]; + lfsr_file_read(&lfs, &file_, rbuf, sizeof(rbuf)) => strlen("hello!"); + assert(memcmp(rbuf, "hello!", strlen("hello!")) == 0); + lfsr_file_close(&lfs, &file_) => 0; + } + + if (CLOSE == 0) { + lfsr_file_close(&lfs, &zombie) => 0; + } + lfsr_unmount(&lfs) => 0; +'''