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; +'''