From c74ec1c133b2cd8ed3c7b2236388b52f78e501ff Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Sun, 17 Sep 2023 11:02:30 -0500 Subject: [PATCH] Initial commit of basic file creation Currently limited to inlined files and only simpler truncate-writes. But still this lets us test file creation/deletion. This is also enough logic to make it clear that, even though we have some powerful high-level primitives, mapping file operations onto these is still going to be non-trivial. --- lfs.c | 534 ++++++++++++++++----- lfs.h | 70 ++- runners/bench_runner.c | 2 + runners/bench_runner.h | 20 +- runners/test_runner.c | 10 + runners/test_runner.h | 20 +- tests/{test_files.toml => test_ftree.toml} | 432 +++++++++++++++++ 7 files changed, 941 insertions(+), 147 deletions(-) rename tests/{test_files.toml => test_ftree.toml} (57%) diff --git a/lfs.c b/lfs.c index f5b75e18..66a1bb46 100644 --- a/lfs.c +++ b/lfs.c @@ -1000,32 +1000,6 @@ static lfs_ssize_t lfsr_bd_progtag(lfs_t *lfs, /// lfsr_data_t stuff /// -// either an on-disk or in-device data pointer -typedef struct lfsr_data { - union { - // The sign-bit of the size field indicates if the data is in-device - // or on-disk. - // - // After removing the sign bit, the size always encodes the resulting - // size on-disk. - // - lfs_ssize_t size; - struct { - lfs_ssize_t size; - // This leb128 field is a bit of a hack that allows a single leb128 - // to be injected into lfsr_bd_progdata. Outside of - // lfsr_bd_progdata, this field is invalid! - int32_t leb128; - const uint8_t *buffer; - } b; - struct { - lfs_ssize_t size; - lfs_size_t off; - lfs_block_t block; - } d; - } u; -} lfsr_data_t; - // the most common use is to pass a buffer with explicit size #define LFSR_DATA(_buffer, _size) \ ((lfsr_data_t){ \ @@ -1764,10 +1738,6 @@ static int lfsr_data_readgrm(lfs_t *lfs, lfsr_data_t *data, static int lfs_alloc(lfs_t *lfs, lfs_block_t *block); static void lfs_alloc_ack(lfs_t *lfs); -// and our main "fix everything before writing" function -static int lfsr_fs_preparemutation(lfs_t *lfs); -static int lfsr_fs_fixgrm(lfs_t *lfs); - /// Red-black-yellow Dhara tree operations /// @@ -6891,6 +6861,75 @@ static int lfs_alloc(lfs_t *lfs, lfs_block_t *block) { } +/// Prepare the filesystem for mutation /// + +static int lfsr_fs_fixgrm(lfs_t *lfs) { + while (lfsr_grm_hasrm(&lfs->grm)) { + // find our mdir + lfsr_mdir_t mdir; + LFS_ASSERT(lfs->grm.rms[0] < lfs_smax32( + lfsr_mtree_weight(lfs), + lfsr_mleafweight(lfs))); + int err = lfsr_mtree_lookup(lfs, lfs->grm.rms[0], &mdir); + if (err) { + return err; + } + + // mark grm as taken care of + lfsr_grm_t grm = lfs->grm; + lfsr_grm_poprm(&grm); + + // make sure to adjust any remaining grms + if ((grm.rms[0] & lfsr_midbmask(lfs)) + == (mdir.mid & lfsr_midbmask(lfs)) + && grm.rms[0] >= mdir.mid) { + LFS_ASSERT(grm.rms[0] != mdir.mid); + grm.rms[0] -= 1; + } + + // remove the rid while also updating our grm + LFS_ASSERT((lfs->grm.rms[0] & lfsr_midrmask(lfs)) < mdir.u.m.weight); + err = lfsr_mdir_commit(lfs, &mdir, LFSR_ATTRS( + LFSR_ATTR(mdir.mid, RM, -1, NULL), + LFSR_ATTR(-1, GRM, 0, GRM(&grm)))); + } + + return 0; +} + +static int lfsr_fs_preparemutation(lfs_t *lfs) { + // checkpoint the allocator + lfs_alloc_ack(lfs); + + // fix pending grms + if (lfsr_grm_hasrm(&lfs->grm)) { + if (lfsr_grm_count(&lfs->grm) == 2) { + LFS_DEBUG("Fixing pending grm " + "%"PRId32".%"PRId32" %"PRId32".%"PRId32, + lfs->grm.rms[0] >> lfs->mleaf_bits, + lfs->grm.rms[0] & lfsr_midrmask(lfs), + lfs->grm.rms[1] >> lfs->mleaf_bits, + lfs->grm.rms[1] & lfsr_midrmask(lfs)); + } else if (lfsr_grm_count(&lfs->grm) == 1) { + LFS_DEBUG("Fixing pending grm %"PRId32".%"PRId32, + lfs->grm.rms[0] >> lfs->mleaf_bits, + lfs->grm.rms[0] & lfsr_midrmask(lfs)); + } + + int err = lfsr_fs_fixgrm(lfs); + if (err) { + return err; + } + + // checkpoint the allocator again since our fixgrm completed some + // work + lfs_alloc_ack(lfs); + } + + return 0; +} + + /// Directory operations /// int lfsr_mkdir(lfs_t *lfs, const char *path) { @@ -7238,9 +7277,66 @@ int lfsr_rename(lfs_t *lfs, const char *old_path, const char *new_path) { return lfsr_fs_fixgrm(lfs); } -int lfsr_stat(lfs_t *lfs, const char *path, struct lfs_info *info) { - memset(info, 0, sizeof(struct lfs_info)); +// common stat function once we have an mdir +static int lfsr_mdir_stat(lfs_t *lfs, lfsr_mdir_t *mdir, lfsr_mid_t mid, + lfsr_sdid_t did, struct lfs_info *info) { + // lookup our name tag + lfsr_tag_t tag; + lfsr_data_t data; + int err = lfsr_mdir_lookup(lfs, mdir, mid, LFSR_TAG_WIDE(NAME), + &tag, &data); + if (err) { + return err; + } + // get our did + lfsr_did_t did_; + err = lfsr_data_readleb128(lfs, &data, (int32_t*)&did_); + if (err) { + return err; + } + + // did mismatch? this terminates dir reads + if (did != -1 && did_ != (lfsr_did_t)did) { + return LFS_ERR_NOENT; + } + + // get file type from the tag + info->type = lfsr_tag_filetype(tag); + + // get file name from the name entry + LFS_ASSERT(lfsr_data_size(&data) <= LFS_NAME_MAX); + lfs_ssize_t name_size = lfsr_data_read(lfs, &data, + info->name, LFS_NAME_MAX); + if (name_size < 0) { + return name_size; + } + info->name[name_size] = '\0'; + + // get file size if we're a regular file, this gets a bit messy + // because of the different file representations + info->size = 0; + if (tag == LFSR_TAG_REG) { + err = lfsr_mdir_lookup(lfs, mdir, mid, LFSR_TAG_WIDE(STRUCT), + &tag, &data); + if (err && err != LFS_ERR_NOENT) { + return err; + } + + if (err != LFS_ERR_NOENT) { + if (tag == LFSR_TAG_INLINED) { + info->size = lfsr_data_size(&data); + } else { + // TODO + LFS_ASSERT(false); + } + } + } + + return 0; +} + +int lfsr_stat(lfs_t *lfs, const char *path, struct lfs_info *info) { // lookup our entry lfsr_mdir_t mdir; lfsr_tag_t tag; @@ -7261,15 +7357,7 @@ int lfsr_stat(lfs_t *lfs, const char *path, struct lfs_info *info) { } // fill out our info struct - info->type = lfsr_tag_filetype(tag); - - LFS_ASSERT(name_size <= LFS_NAME_MAX); - memcpy(info->name, name, name_size); - info->name[name_size] = '\0'; - - // TODO size once we have actual files - - return 0; + return lfsr_mdir_stat(lfs, &mdir, mdir.mid, -1, info); } int lfsr_dir_open(lfs_t *lfs, lfsr_dir_t *dir, const char *path) { @@ -7313,13 +7401,13 @@ int lfsr_dir_open(lfs_t *lfs, lfsr_dir_t *dir, const char *path) { } // add to tracked mdirs - lfsr_mdir_addopened(lfs, LFS_TYPE_DIR, (lfsr_openedmdir_t*)dir); + lfsr_mdir_addopened(lfs, LFS_TYPE_DIR, &dir->m); return 0; } int lfsr_dir_close(lfs_t *lfs, lfsr_dir_t *dir) { // remove from tracked mdirs - lfsr_mdir_removeopened(lfs, LFS_TYPE_DIR, (lfsr_openedmdir_t*)dir); + lfsr_mdir_removeopened(lfs, LFS_TYPE_DIR, &dir->m); return 0; } @@ -7348,42 +7436,14 @@ int lfsr_dir_read(lfs_t *lfs, lfsr_dir_t *dir, struct lfs_info *info) { return err; } - // lookup our name tag - lfsr_tag_t tag; - lfsr_data_t data; - err = lfsr_mdir_lookup(lfs, &dir->m.mdir, - dir->m.mdir.mid, LFSR_TAG_WIDE(NAME), - &tag, &data); + // fill out our info struct + // + // this will return LFS_ERR_NOENT if our dids mismatch + err = lfsr_mdir_stat(lfs, &dir->m.mdir, dir->m.mdir.mid, dir->did, info); if (err) { return err; } - // get our did - lfsr_did_t did; - err = lfsr_data_readleb128(lfs, &data, (int32_t*)&did); - if (err) { - return err; - } - - // did mismatch? we must be done - if (did != dir->did) { - return LFS_ERR_NOENT; - } - - // get file name from the name entry - LFS_ASSERT(lfsr_data_size(&data) <= LFS_NAME_MAX); - lfs_ssize_t name_size = lfsr_data_read(lfs, &data, - info->name, LFS_NAME_MAX); - if (name_size < 0) { - return name_size; - } - info->name[name_size] = '\0'; - - // get file type from the tag - info->type = lfsr_tag_filetype(tag); - - // TODO get size once we actually have regular files - // eagerly look up the next entry err = lfsr_mtree_seek(lfs, &dir->m.mdir, 1); if (err && err != LFS_ERR_NOENT) { @@ -7456,74 +7516,300 @@ int lfsr_dir_rewind(lfs_t *lfs, lfsr_dir_t *dir) { } -/// Prepare the filesystem for mutation /// +/// File operations /// -static int lfsr_fs_fixgrm(lfs_t *lfs) { - while (lfsr_grm_hasrm(&lfs->grm)) { - // find our mdir - lfsr_mdir_t mdir; - LFS_ASSERT(lfs->grm.rms[0] < lfs_smax32( - lfsr_mtree_weight(lfs), - lfsr_mleafweight(lfs))); - int err = lfsr_mtree_lookup(lfs, lfs->grm.rms[0], &mdir); +static bool lfsr_file_isreadable(uint32_t flags) { + return (flags & LFS_O_RDONLY) == LFS_O_RDONLY; +} + +static bool lfsr_file_iswriteable(uint32_t flags) { + return (flags & LFS_O_WRONLY) == LFS_O_WRONLY; +} + +int lfsr_file_sync(lfs_t *lfs, lfsr_file_t *file); + +int lfsr_file_opencfg(lfs_t *lfs, lfsr_file_t *file, + const char *path, uint32_t flags, + const struct lfs_file_config *cfg) { + if (lfsr_file_iswriteable(flags)) { + // prepare our filesystem for writing + int err = lfsr_fs_preparemutation(lfs); if (err) { return err; } - - // mark grm as taken care of - lfsr_grm_t grm = lfs->grm; - lfsr_grm_poprm(&grm); - - // make sure to adjust any remaining grms - if ((grm.rms[0] & lfsr_midbmask(lfs)) - == (mdir.mid & lfsr_midbmask(lfs)) - && grm.rms[0] >= mdir.mid) { - LFS_ASSERT(grm.rms[0] != mdir.mid); - grm.rms[0] -= 1; - } - - // remove the rid while also updating our grm - LFS_ASSERT((lfs->grm.rms[0] & lfsr_midrmask(lfs)) < mdir.u.m.weight); - err = lfsr_mdir_commit(lfs, &mdir, LFSR_ATTRS( - LFSR_ATTR(mdir.mid, RM, -1, NULL), - LFSR_ATTR(-1, GRM, 0, GRM(&grm)))); } - return 0; -} + // default inlined state + file->flags = flags; + file->cfg = cfg; + file->pos = 0; + file->inlined_pos = 0; + file->inlined = LFSR_DATA_NULL; -static int lfsr_fs_preparemutation(lfs_t *lfs) { - // checkpoint the allocator - lfs_alloc_ack(lfs); + // lookup our parent + lfsr_tag_t tag; + lfsr_did_t did; + const char *name; + lfs_size_t name_size; + int err = lfsr_mtree_pathlookup(lfs, path, + &file->m.mdir, &tag, + &did, &name, &name_size); + if (err && err != LFS_ERR_NOENT) { + return err; + } - // fix pending grms - if (lfsr_grm_hasrm(&lfs->grm)) { - if (lfsr_grm_count(&lfs->grm) == 2) { - LFS_DEBUG("Fixing pending grm " - "%"PRId32".%"PRId32" %"PRId32".%"PRId32, - lfs->grm.rms[0] >> lfs->mleaf_bits, - lfs->grm.rms[0] & lfsr_midrmask(lfs), - lfs->grm.rms[1] >> lfs->mleaf_bits, - lfs->grm.rms[1] & lfsr_midrmask(lfs)); - } else if (lfsr_grm_count(&lfs->grm) == 1) { - LFS_DEBUG("Fixing pending grm %"PRId32".%"PRId32, - lfs->grm.rms[0] >> lfs->mleaf_bits, - lfs->grm.rms[0] & lfsr_midrmask(lfs)); + // creating a new entry? + if (err == LFS_ERR_NOENT) { + if (!(flags & LFS_O_CREAT)) { + return LFS_ERR_NOENT; + } + LFS_ASSERT(lfsr_file_iswriteable(flags)); + + // check that name fits + if (name_size > lfs->name_limit) { + return LFS_ERR_NAMETOOLONG; } - int err = lfsr_fs_fixgrm(lfs); + // create our entry + // + // note this risks creating a zero-length file if we lose power here, + // but it's the only way for us to save the file name. + // + // TODO or is it? ;) + err = lfsr_mdir_commit(lfs, &file->m.mdir, LFSR_ATTRS( + LFSR_ATTR(file->m.mdir.mid, + REG, +1, NAME(did, name, name_size)))); if (err) { return err; } + } else { + if (flags & LFS_O_EXCL) { + // oh, we really wanted to create a new entry + return LFS_ERR_EXIST; + } - // checkpoint the allocator again since our fixgrm completed some - // work - lfs_alloc_ack(lfs); + // wrong type? + if (tag != LFSR_TAG_REG) { + return LFS_ERR_ISDIR; + } + + // if we're truncating don't bother to read any state, we're + // just going to truncate after all + if (!(flags & LFS_O_TRUNC)) { + // read the inline state + err = lfsr_mdir_lookup(lfs, &file->m.mdir, + file->m.mdir.mid, LFSR_TAG_INLINED, + NULL, &file->inlined); + if (err && err != LFS_ERR_NOENT) { + return err; + } + } + } + + // TODO common function for this? + // figure out the total size + file->size = lfsr_data_size(&file->inlined); + + // allocate buffer if necessary + if (file->cfg->buffer) { + file->buffer = file->cfg->buffer; + } else { + file->buffer = lfs_malloc(lfs->cfg->cache_size); + if (!file->buffer) { + return LFS_ERR_NOMEM; + } + } + file->buffer_pos = 0; + file->buffer_size = 0; + + // add to tracked mdirs + lfsr_mdir_addopened(lfs, LFS_TYPE_REG, &file->m); + return 0; +} + +// default file config +static const struct lfs_file_config lfsr_file_defaults = {0}; + +int lfsr_file_open(lfs_t *lfs, lfsr_file_t *file, + const char *path, uint32_t flags) { + return lfsr_file_opencfg(lfs, file, path, flags, &lfsr_file_defaults); +} + +int lfsr_file_close(lfs_t *lfs, lfsr_file_t *file) { + int err = lfsr_file_sync(lfs, file); + + // remove from tracked mdirs + lfsr_mdir_removeopened(lfs, LFS_TYPE_REG, &file->m); + + // clean up memory + if (!file->cfg->buffer) { + lfs_free(file->buffer); + } + + return err; +} + +lfs_ssize_t lfsr_file_read(lfs_t *lfs, lfsr_file_t *file, + void *buffer, lfs_size_t size) { + LFS_ASSERT(lfsr_file_isreadable(file->flags)); + LFS_ASSERT(size <= 0x7fffffff); + + lfs_off_t pos = file->pos; + uint8_t *buffer_ = buffer; + while (size > 0) { + lfs_ssize_t d = size; + + // is the data in our write buffer? + if (pos < file->buffer_pos + file->buffer_size) { + if (pos >= file->buffer_pos) { + d = lfs_min32( + size, + file->buffer_size - (pos - file->buffer_pos)); + memcpy(buffer_, &file->buffer[pos - file->buffer_pos], d); + + pos += d; + buffer_ += d; + size -= d; + continue; + } + + // buffered data takes priority + d = lfs_min32(d, file->buffer_pos - pos); + } + + // is the data inlined? + if (pos < file->inlined_pos + lfsr_data_size(&file->inlined)) { + if (pos >= file->inlined_pos) { + lfsr_data_t inlined = file->inlined; + lfsr_data_add(&inlined, pos - file->inlined_pos); + d = lfsr_data_read(lfs, &inlined, buffer_, d); + if (d < 0) { + return d; + } + + pos += d; + buffer_ += d; + size -= d; + continue; + } + + // inlined data takes priority + d = lfs_min32(d, file->inlined_pos - pos); + } + + // TODO + // no more data? + break; + } + + lfs_size_t read = pos - file->pos; + file->pos = pos; + return read; +} + +lfs_ssize_t lfsr_file_write(lfs_t *lfs, lfsr_file_t *file, + const void *buffer, lfs_size_t size) { + LFS_ASSERT(lfsr_file_iswriteable(file->flags)); + LFS_ASSERT(size <= 0x7fffffff); + + lfs_off_t pos = file->pos; + const uint8_t *buffer_ = buffer; + int err; + while (size > 0) { + // try to fill our write buffer + if (pos >= file->buffer_pos + && pos <= file->buffer_pos + file->buffer_size + && pos < file->buffer_pos + lfs->cfg->cache_size) { + lfs_size_t d = lfs_min32( + size, + lfs->cfg->cache_size - (pos - file->buffer_pos)); + memcpy(&file->buffer[pos - file->buffer_pos], buffer_, d); + file->buffer_size = lfs_max32( + file->buffer_size, + pos+d - file->buffer_pos); + + pos += d; + buffer_ += d; + size -= d; + file->flags |= LFS_F_UNSYNCED; + continue; + } + + // TODO + LFS_ASSERT(false); + +// // flush our write buffer to make it useable, first condition can +// // no longer fail +// err = lfsr_file_flush(lfs, file); +// if (err) { +// return err; +// } + } + + lfs_size_t written = pos - file->pos; + file->pos = pos; + file->size = lfs_max32(file->size, pos); + return written; + +failed:; + file->flags |= LFS_F_ERRORED; + return err; +} + +int lfsr_file_flush(lfs_t *lfs, lfsr_file_t *file) { + // TODO + return 0; +} + +int lfsr_file_sync(lfs_t *lfs, lfsr_file_t *file) { + if (file->flags & LFS_F_ERRORED) { + // it's not safe to do anything if our file errored + return 0; + } + + // do nothing if our file has been removed + if (file->m.mdir.mid == -1) { + return 0; + } + + // write out any data we need to + int err = lfsr_file_flush(lfs, file); + if (err) { + goto failed; + } + + if (file->flags & LFS_F_UNSYNCED) { + // TODO + // TODO what if buffer_size > inlined_size? + LFS_ASSERT(file->buffer_pos == 0); + // commit our file's metadata + err = lfsr_mdir_commit(lfs, &file->m.mdir, LFSR_ATTRS( + LFSR_ATTR(file->m.mdir.mid, WIDE(RM), 0, NULL), + (file->buffer_size > 0 + ? LFSR_ATTR(file->m.mdir.mid, + WIDE(INLINED), 0, BUF( + file->buffer, file->buffer_size)) + : LFSR_ATTR_NOOP))); + if (err) { + goto failed; + } + + file->flags &= ~LFS_F_UNSYNCED; } return 0; + +failed:; + file->flags |= LFS_F_ERRORED; + return err; } +lfs_soff_t lfsr_file_size(lfs_t *lfs, lfsr_file_t *file) { + (void)lfs; + return file->size; +} + + diff --git a/lfs.h b/lfs.h index 1d439a8e..9439d1f4 100644 --- a/lfs.h +++ b/lfs.h @@ -152,15 +152,9 @@ enum lfs_open_flags { #endif // internally used flags -#ifndef LFS_READONLY - LFS_F_DIRTY = 0x010000, // File does not match storage - LFS_F_WRITING = 0x020000, // File has been written since last flush -#endif - LFS_F_READING = 0x040000, // File has been read since last flush -#ifndef LFS_READONLY - LFS_F_ERRED = 0x080000, // An error occurred during write -#endif - LFS_F_INLINE = 0x100000, // Currently inlined in directory entry + LFS_F_UNFLUSHED = 0x010000, // File's has data that needs to be written + LFS_F_UNSYNCED = 0x020000, // File's metadata does not match storage + LFS_F_ERRORED = 0x040000, // An error occurred during write }; // File seek flags @@ -280,6 +274,10 @@ struct lfs_config { // // can help bound the metadata compaction time. Must be <= block_size. // // Defaults to block_size when zero. // lfs_size_t metadata_max; + + // TODO document + lfs_size_t inline_size; + lfs_size_t bud_size; }; // File info structure @@ -427,6 +425,32 @@ typedef struct lfs_mdir { lfs_block_t tail[2]; } lfs_mdir_t; +// either an on-disk or in-device data pointer +typedef struct lfsr_data { + union { + // The sign-bit of the size field indicates if the data is in-device + // or on-disk. + // + // After removing the sign bit, the size always encodes the resulting + // size on-disk. + // + lfs_ssize_t size; + struct { + lfs_ssize_t size; + // This leb128 field is a bit of a hack that allows a single leb128 + // to be injected into lfsr_bd_progdata. Outside of + // lfsr_bd_progdata, this field is invalid! + int32_t leb128; + const uint8_t *buffer; + } b; + struct { + lfs_ssize_t size; + lfs_size_t off; + lfs_block_t block; + } d; + } u; +} lfsr_data_t; + // littlefs directory type typedef struct lfs_dir { struct lfs_dir *next; @@ -466,6 +490,22 @@ typedef struct lfs_file { const struct lfs_file_config *cfg; } lfs_file_t; +typedef struct lfsr_file { + lfsr_openedmdir_t m; + uint32_t flags; + lfs_off_t pos; + lfs_off_t size; + + lfs_off_t buffer_pos; + uint8_t *buffer; + lfs_size_t buffer_size; + + lfs_off_t inlined_pos; + lfsr_data_t inlined; + + const struct lfs_file_config *cfg; +} lfsr_file_t; + typedef struct lfs_superblock { uint32_t version; lfs_size_t block_size; @@ -638,6 +678,8 @@ int lfs_removeattr(lfs_t *lfs, const char *path, uint8_t type); // Returns a negative error code on failure. int lfs_file_open(lfs_t *lfs, lfs_file_t *file, const char *path, int flags); +int lfsr_file_open(lfs_t *lfs, lfsr_file_t *file, + const char *path, uint32_t flags); // if LFS_NO_MALLOC is defined, lfs_file_open() will fail with LFS_ERR_NOMEM // thus use lfs_file_opencfg() with config.buffer set. @@ -656,6 +698,9 @@ int lfs_file_open(lfs_t *lfs, lfs_file_t *file, int lfs_file_opencfg(lfs_t *lfs, lfs_file_t *file, const char *path, int flags, const struct lfs_file_config *config); +int lfsr_file_opencfg(lfs_t *lfs, lfsr_file_t *file, + const char *path, uint32_t flags, + const struct lfs_file_config *config); // Close a file // @@ -664,12 +709,14 @@ int lfs_file_opencfg(lfs_t *lfs, lfs_file_t *file, // // Returns a negative error code on failure. int lfs_file_close(lfs_t *lfs, lfs_file_t *file); +int lfsr_file_close(lfs_t *lfs, lfsr_file_t *file); // Synchronize a file on storage // // Any pending writes are written out to storage. // Returns a negative error code on failure. int lfs_file_sync(lfs_t *lfs, lfs_file_t *file); +int lfsr_file_sync(lfs_t *lfs, lfsr_file_t *file); // Read data from file // @@ -677,6 +724,8 @@ int lfs_file_sync(lfs_t *lfs, lfs_file_t *file); // Returns the number of bytes read, or a negative error code on failure. lfs_ssize_t lfs_file_read(lfs_t *lfs, lfs_file_t *file, void *buffer, lfs_size_t size); +lfs_ssize_t lfsr_file_read(lfs_t *lfs, lfsr_file_t *file, + void *buffer, lfs_size_t size); #ifndef LFS_READONLY // Write data to file @@ -687,6 +736,8 @@ lfs_ssize_t lfs_file_read(lfs_t *lfs, lfs_file_t *file, // Returns the number of bytes written, or a negative error code on failure. lfs_ssize_t lfs_file_write(lfs_t *lfs, lfs_file_t *file, const void *buffer, lfs_size_t size); +lfs_ssize_t lfsr_file_write(lfs_t *lfs, lfsr_file_t *file, + const void *buffer, lfs_size_t size); #endif // Change the position of the file @@ -720,6 +771,7 @@ int lfs_file_rewind(lfs_t *lfs, lfs_file_t *file); // Similar to lfs_file_seek(lfs, file, 0, LFS_SEEK_END) // Returns the size of the file, or a negative error code on failure. lfs_soff_t lfs_file_size(lfs_t *lfs, lfs_file_t *file); +lfs_soff_t lfsr_file_size(lfs_t *lfs, lfsr_file_t *file); /// Directory operations /// diff --git a/runners/bench_runner.c b/runners/bench_runner.c index 3c4ec505..b54e877a 100644 --- a/runners/bench_runner.c +++ b/runners/bench_runner.c @@ -1387,6 +1387,8 @@ void perm_run( .block_count = BLOCK_COUNT, .block_cycles = BLOCK_CYCLES, .cache_size = CACHE_SIZE, + .inline_size = INLINE_SIZE, + .bud_size = BUD_SIZE, .lookahead_size = LOOKAHEAD_SIZE, }; diff --git a/runners/bench_runner.h b/runners/bench_runner.h index 3421066a..e88ee641 100644 --- a/runners/bench_runner.h +++ b/runners/bench_runner.h @@ -98,7 +98,7 @@ intmax_t bench_define(size_t define); // a few preconfigured defines that control how benches run -#define BENCH_IMPLICIT_DEFINE_COUNT 12 +#define BENCH_IMPLICIT_DEFINE_COUNT 14 #define BENCH_GEOMETRY_DEFINE_COUNT 3 #define READ_SIZE_i 0 @@ -107,12 +107,14 @@ intmax_t bench_define(size_t define); #define BLOCK_COUNT_i 3 #define DISK_SIZE_i 4 #define CACHE_SIZE_i 5 -#define LOOKAHEAD_SIZE_i 6 -#define BLOCK_CYCLES_i 7 -#define ERASE_VALUE_i 8 -#define ERASE_CYCLES_i 9 -#define BADBLOCK_BEHAVIOR_i 10 -#define POWERLOSS_BEHAVIOR_i 11 +#define INLINE_SIZE_i 6 +#define BUD_SIZE_i 7 +#define LOOKAHEAD_SIZE_i 8 +#define BLOCK_CYCLES_i 9 +#define ERASE_VALUE_i 10 +#define ERASE_CYCLES_i 11 +#define BADBLOCK_BEHAVIOR_i 12 +#define POWERLOSS_BEHAVIOR_i 13 #define READ_SIZE bench_define(READ_SIZE_i) #define PROG_SIZE bench_define(PROG_SIZE_i) @@ -120,6 +122,8 @@ intmax_t bench_define(size_t define); #define BLOCK_COUNT bench_define(BLOCK_COUNT_i) #define DISK_SIZE bench_define(DISK_SIZE_i) #define CACHE_SIZE bench_define(CACHE_SIZE_i) +#define INLINE_SIZE bench_define(INLINE_SIZE_i) +#define BUD_SIZE bench_define(BUD_SIZE_i) #define LOOKAHEAD_SIZE bench_define(LOOKAHEAD_SIZE_i) #define BLOCK_CYCLES bench_define(BLOCK_CYCLES_i) #define ERASE_VALUE bench_define(ERASE_VALUE_i) @@ -135,6 +139,8 @@ intmax_t bench_define(size_t define); BENCH_DEF(BLOCK_COUNT, DISK_SIZE/BLOCK_SIZE ) \ BENCH_DEF(DISK_SIZE, 1024*1024 ) \ BENCH_DEF(CACHE_SIZE, lfs_max(64, lfs_max(READ_SIZE, PROG_SIZE))) \ + BENCH_DEF(INLINE_SIZE, BLOCK_SIZE/4 ) \ + BENCH_DEF(BUD_SIZE, BLOCK_SIZE/4 ) \ BENCH_DEF(LOOKAHEAD_SIZE, 16 ) \ BENCH_DEF(BLOCK_CYCLES, -1 ) \ BENCH_DEF(ERASE_VALUE, 0xff ) \ diff --git a/runners/test_runner.c b/runners/test_runner.c index e8798e63..dfa60805 100644 --- a/runners/test_runner.c +++ b/runners/test_runner.c @@ -1418,6 +1418,8 @@ static void run_powerloss_none( .block_count = BLOCK_COUNT, .block_cycles = BLOCK_CYCLES, .cache_size = CACHE_SIZE, + .inline_size = INLINE_SIZE, + .bud_size = BUD_SIZE, .lookahead_size = LOOKAHEAD_SIZE, }; @@ -1487,6 +1489,8 @@ static void run_powerloss_linear( .block_count = BLOCK_COUNT, .block_cycles = BLOCK_CYCLES, .cache_size = CACHE_SIZE, + .inline_size = INLINE_SIZE, + .bud_size = BUD_SIZE, .lookahead_size = LOOKAHEAD_SIZE, }; @@ -1573,6 +1577,8 @@ static void run_powerloss_log( .block_count = BLOCK_COUNT, .block_cycles = BLOCK_CYCLES, .cache_size = CACHE_SIZE, + .inline_size = INLINE_SIZE, + .bud_size = BUD_SIZE, .lookahead_size = LOOKAHEAD_SIZE, }; @@ -1657,6 +1663,8 @@ static void run_powerloss_cycles( .block_count = BLOCK_COUNT, .block_cycles = BLOCK_CYCLES, .cache_size = CACHE_SIZE, + .inline_size = INLINE_SIZE, + .bud_size = BUD_SIZE, .lookahead_size = LOOKAHEAD_SIZE, }; @@ -1839,6 +1847,8 @@ static void run_powerloss_exhaustive( .block_count = BLOCK_COUNT, .block_cycles = BLOCK_CYCLES, .cache_size = CACHE_SIZE, + .inline_size = INLINE_SIZE, + .bud_size = BUD_SIZE, .lookahead_size = LOOKAHEAD_SIZE, }; diff --git a/runners/test_runner.h b/runners/test_runner.h index 908b525f..cd9df993 100644 --- a/runners/test_runner.h +++ b/runners/test_runner.h @@ -97,7 +97,7 @@ intmax_t test_define(size_t define); // a few preconfigured defines that control how tests run -#define TEST_IMPLICIT_DEFINE_COUNT 12 +#define TEST_IMPLICIT_DEFINE_COUNT 14 #define TEST_GEOMETRY_DEFINE_COUNT 3 #define READ_SIZE_i 0 @@ -106,12 +106,14 @@ intmax_t test_define(size_t define); #define BLOCK_COUNT_i 3 #define DISK_SIZE_i 4 #define CACHE_SIZE_i 5 -#define LOOKAHEAD_SIZE_i 6 -#define BLOCK_CYCLES_i 7 -#define ERASE_VALUE_i 8 -#define ERASE_CYCLES_i 9 -#define BADBLOCK_BEHAVIOR_i 10 -#define POWERLOSS_BEHAVIOR_i 11 +#define INLINE_SIZE_i 6 +#define BUD_SIZE_i 7 +#define LOOKAHEAD_SIZE_i 8 +#define BLOCK_CYCLES_i 9 +#define ERASE_VALUE_i 10 +#define ERASE_CYCLES_i 11 +#define BADBLOCK_BEHAVIOR_i 12 +#define POWERLOSS_BEHAVIOR_i 13 #define READ_SIZE TEST_DEFINE(READ_SIZE_i) #define PROG_SIZE TEST_DEFINE(PROG_SIZE_i) @@ -119,6 +121,8 @@ intmax_t test_define(size_t define); #define BLOCK_COUNT TEST_DEFINE(BLOCK_COUNT_i) #define DISK_SIZE TEST_DEFINE(DISK_SIZE_i) #define CACHE_SIZE TEST_DEFINE(CACHE_SIZE_i) +#define INLINE_SIZE TEST_DEFINE(INLINE_SIZE_i) +#define BUD_SIZE TEST_DEFINE(BUD_SIZE_i) #define LOOKAHEAD_SIZE TEST_DEFINE(LOOKAHEAD_SIZE_i) #define BLOCK_CYCLES TEST_DEFINE(BLOCK_CYCLES_i) #define ERASE_VALUE TEST_DEFINE(ERASE_VALUE_i) @@ -134,6 +138,8 @@ intmax_t test_define(size_t define); TEST_DEF(BLOCK_COUNT, DISK_SIZE/BLOCK_SIZE ) \ TEST_DEF(DISK_SIZE, 1024*1024 ) \ TEST_DEF(CACHE_SIZE, lfs_max(64, lfs_max(READ_SIZE, PROG_SIZE)) ) \ + TEST_DEF(INLINE_SIZE, BLOCK_SIZE/4 ) \ + TEST_DEF(BUD_SIZE, BLOCK_SIZE/4 ) \ TEST_DEF(LOOKAHEAD_SIZE, 16 ) \ TEST_DEF(BLOCK_CYCLES, -1 ) \ TEST_DEF(ERASE_VALUE, 0xff ) \ diff --git a/tests/test_files.toml b/tests/test_ftree.toml similarity index 57% rename from tests/test_files.toml rename to tests/test_ftree.toml index e70817da..b79d7fa3 100644 --- a/tests/test_files.toml +++ b/tests/test_ftree.toml @@ -1,3 +1,435 @@ +# Test basic file operations +after = ['test_dtree'] + + +# test creation/deletion +[cases.test_ftree_create] +defines.REMOUNT = [false, true] +reentrant = true +code = ''' + // format once per test + lfs_t lfs; + int err = lfsr_mount(&lfs, CFG); + if (err) { + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + } + + // create a file + lfsr_file_t file; + lfsr_file_open(&lfs, &file, "hello", LFS_O_WRONLY | LFS_O_CREAT) => 0; + lfsr_file_close(&lfs, &file) => 0; + + // remount? + if (REMOUNT) { + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + } + + // check our file with stat + struct lfs_info info; + lfsr_stat(&lfs, "hello", &info) => 0; + assert(strcmp(info.name, "hello") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == 0); + + // and with dir read + lfsr_dir_t dir; + lfsr_dir_open(&lfs, &dir, "/") => 0; + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, ".") == 0); + assert(info.type == LFS_TYPE_DIR); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "..") == 0); + assert(info.type == LFS_TYPE_DIR); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "hello") == 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; + + // try reading our file + lfsr_file_open(&lfs, &file, "hello", LFS_O_RDONLY) => 0; + // is size correct? + lfsr_file_size(&lfs, &file) => 0; + // try reading + uint8_t buf[8192]; + lfsr_file_read(&lfs, &file, buf, sizeof(buf)) => 0; + lfsr_file_close(&lfs, &file) => 0; + + lfsr_unmount(&lfs) => 0; +''' + +# test we can write some data, should be inlined +[cases.test_ftree_hello] +defines.REMOUNT = [false, true] +reentrant = true +code = ''' + // format once per test + lfs_t lfs; + int err = lfsr_mount(&lfs, CFG); + if (err) { + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + } + + // create a file + lfsr_file_t file; + lfsr_file_open(&lfs, &file, "hello", LFS_O_WRONLY | LFS_O_CREAT) => 0; + uint8_t wbuf[8192]; + strcpy((char*)wbuf, "Hello World!"); + lfs_size_t wsize = strlen((const char*)wbuf); + lfsr_file_write(&lfs, &file, wbuf, wsize) => wsize; + lfsr_file_close(&lfs, &file) => 0; + + // remount? + if (REMOUNT) { + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + } + + // check our file with stat + struct lfs_info info; + lfsr_stat(&lfs, "hello", &info) => 0; + assert(strcmp(info.name, "hello") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == wsize); + + // and with dir read + lfsr_dir_t dir; + lfsr_dir_open(&lfs, &dir, "/") => 0; + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, ".") == 0); + assert(info.type == LFS_TYPE_DIR); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "..") == 0); + assert(info.type == LFS_TYPE_DIR); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "hello") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == wsize); + lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; + lfsr_dir_close(&lfs, &dir) => 0; + + // try reading our file + lfsr_file_open(&lfs, &file, "hello", LFS_O_RDONLY) => 0; + // is size correct? + lfsr_file_size(&lfs, &file) => wsize; + // try reading + uint8_t rbuf[8192]; + lfsr_file_read(&lfs, &file, rbuf, sizeof(rbuf)) => wsize; + assert(memcmp(rbuf, wbuf, wsize) == 0); + lfsr_file_close(&lfs, &file) => 0; + + lfsr_unmount(&lfs) => 0; +''' + +# test we can rewrite a file +[cases.test_ftree_trunc] +defines.REMOUNT = [false, true] +reentrant = true +code = ''' + // format once per test + lfs_t lfs; + int err = lfsr_mount(&lfs, CFG); + if (err) { + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + } + + // create a file + lfsr_file_t file; + lfsr_file_open(&lfs, &file, "hello", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0; + uint8_t wbuf[8192]; + strcpy((char*)wbuf, "Oh no!"); + lfs_size_t wsize = strlen((const char*)wbuf); + lfsr_file_write(&lfs, &file, wbuf, wsize) => wsize; + lfsr_file_close(&lfs, &file) => 0; + + // remount? + if (REMOUNT) { + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + } + + // rewrite the file + lfsr_file_open(&lfs, &file, "hello", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0; + strcpy((char*)wbuf, "Hello World!"); + wsize = strlen((const char*)wbuf); + lfsr_file_write(&lfs, &file, wbuf, wsize) => wsize; + lfsr_file_close(&lfs, &file) => 0; + + // check our file with stat + struct lfs_info info; + lfsr_stat(&lfs, "hello", &info) => 0; + assert(strcmp(info.name, "hello") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == wsize); + + // and with dir read + lfsr_dir_t dir; + lfsr_dir_open(&lfs, &dir, "/") => 0; + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, ".") == 0); + assert(info.type == LFS_TYPE_DIR); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "..") == 0); + assert(info.type == LFS_TYPE_DIR); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "hello") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == wsize); + lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; + lfsr_dir_close(&lfs, &dir) => 0; + + // try reading our file + lfsr_file_open(&lfs, &file, "hello", LFS_O_RDONLY) => 0; + // is size correct? + lfsr_file_size(&lfs, &file) => wsize; + // try reading + uint8_t rbuf[8192]; + lfsr_file_read(&lfs, &file, rbuf, sizeof(rbuf)) => wsize; + assert(memcmp(rbuf, wbuf, wsize) == 0); + lfsr_file_close(&lfs, &file) => 0; + + lfsr_unmount(&lfs) => 0; +''' + +# check for LFS_F_EXCL errors +[cases.test_ftree_excl] +defines.REMOUNT = [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, "hello", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + uint8_t wbuf[8192]; + strcpy((char*)wbuf, "Hello World!"); + lfs_size_t wsize = strlen((const char*)wbuf); + lfsr_file_write(&lfs, &file, wbuf, wsize) => wsize; + lfsr_file_close(&lfs, &file) => 0; + + // remount? + if (REMOUNT) { + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + } + + // try to recreate file, this should error + lfsr_file_open(&lfs, &file, "hello", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST; + + // remount? + if (REMOUNT) { + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + } + + // check our file with stat + struct lfs_info info; + lfsr_stat(&lfs, "hello", &info) => 0; + assert(strcmp(info.name, "hello") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == wsize); + + // and with dir read + lfsr_dir_t dir; + lfsr_dir_open(&lfs, &dir, "/") => 0; + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, ".") == 0); + assert(info.type == LFS_TYPE_DIR); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "..") == 0); + assert(info.type == LFS_TYPE_DIR); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "hello") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == wsize); + lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; + lfsr_dir_close(&lfs, &dir) => 0; + + // try reading our file + lfsr_file_open(&lfs, &file, "hello", LFS_O_RDONLY) => 0; + // is size correct? + lfsr_file_size(&lfs, &file) => wsize; + // try reading + uint8_t buf[8192]; + lfsr_file_read(&lfs, &file, buf, sizeof(buf)) => wsize; + lfsr_file_close(&lfs, &file) => 0; + + lfsr_unmount(&lfs) => 0; +''' + +# a file is not a directory +[cases.test_ftree_file_not_dir] +defines.REMOUNT = [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, "hello", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + uint8_t wbuf[8192]; + strcpy((char*)wbuf, "Hello World!"); + lfs_size_t wsize = strlen((const char*)wbuf); + lfsr_file_write(&lfs, &file, wbuf, wsize) => wsize; + lfsr_file_close(&lfs, &file) => 0; + + // remount? + if (REMOUNT) { + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + } + + // try to open our file as a directory + lfsr_dir_t dir; + lfsr_dir_open(&lfs, &dir, "hello") => LFS_ERR_NOTDIR; + + // try to create a directory on top of our file + lfsr_mkdir(&lfs, "hello") => LFS_ERR_EXIST; + + // try to rename a directory onto our file + lfsr_mkdir(&lfs, "not_hello") => 0; + lfsr_rename(&lfs, "not_hello", "hello") => LFS_ERR_ISDIR; + + // remount? + if (REMOUNT) { + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + } + + // check our file with stat + struct lfs_info info; + lfsr_stat(&lfs, "hello", &info) => 0; + assert(strcmp(info.name, "hello") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == wsize); + + // and with dir read + lfsr_dir_open(&lfs, &dir, "/") => 0; + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, ".") == 0); + assert(info.type == LFS_TYPE_DIR); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "..") == 0); + assert(info.type == LFS_TYPE_DIR); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "hello") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == wsize); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "not_hello") == 0); + assert(info.type == LFS_TYPE_DIR); + lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; + lfsr_dir_close(&lfs, &dir) => 0; + + // try reading our file + lfsr_file_open(&lfs, &file, "hello", LFS_O_RDONLY) => 0; + // is size correct? + lfsr_file_size(&lfs, &file) => wsize; + // try reading + uint8_t buf[8192]; + lfsr_file_read(&lfs, &file, buf, sizeof(buf)) => wsize; + lfsr_file_close(&lfs, &file) => 0; + + lfsr_unmount(&lfs) => 0; +''' + +# a directory is not a file +[cases.test_ftree_dir_not_file] +defines.REMOUNT = [false, true] +code = ''' + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + + // create a directory + lfsr_mkdir(&lfs, "hello") => 0; + + // try reading our directory as a file + lfsr_file_t file; + lfsr_file_open(&lfs, &file, "hello", LFS_O_RDONLY) => LFS_ERR_ISDIR; + + // try writing our directory as a file + lfsr_file_open(&lfs, &file, "hello", LFS_O_WRONLY) => LFS_ERR_ISDIR; + lfsr_file_open(&lfs, &file, "hello", + LFS_O_WRONLY | LFS_O_TRUNC) => LFS_ERR_ISDIR; + lfsr_file_open(&lfs, &file, "hello", + LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR; + lfsr_file_open(&lfs, &file, "hello", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => LFS_ERR_ISDIR; + + // try rename a file on top of our directory + lfsr_file_open(&lfs, &file, "not_hello", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + uint8_t wbuf[8192]; + strcpy((char*)wbuf, "Hello World!"); + lfs_size_t wsize = strlen((const char*)wbuf); + lfsr_file_write(&lfs, &file, wbuf, wsize) => wsize; + lfsr_file_close(&lfs, &file) => 0; + + lfsr_rename(&lfs, "not_hello", "hello") => LFS_ERR_ISDIR; + + // remount? + if (REMOUNT) { + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + } + + // check our dir with stat + struct lfs_info info; + lfsr_stat(&lfs, "hello", &info) => 0; + assert(strcmp(info.name, "hello") == 0); + assert(info.type == LFS_TYPE_DIR); + + // and with dir read + lfsr_dir_t dir; + lfsr_dir_open(&lfs, &dir, "/") => 0; + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, ".") == 0); + assert(info.type == LFS_TYPE_DIR); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "..") == 0); + assert(info.type == LFS_TYPE_DIR); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "hello") == 0); + assert(info.type == LFS_TYPE_DIR); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "not_hello") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == wsize); + lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; + lfsr_dir_close(&lfs, &dir) => 0; + + // did we corrupt our renaming file? + // try reading our file + lfsr_file_open(&lfs, &file, "not_hello", LFS_O_RDONLY) => 0; + // is size correct? + lfsr_file_size(&lfs, &file) => wsize; + // try reading + uint8_t rbuf[8192]; + lfsr_file_read(&lfs, &file, rbuf, sizeof(rbuf)) => wsize; + assert(memcmp(rbuf, wbuf, wsize) == 0); + lfsr_file_close(&lfs, &file) => 0; + + lfsr_unmount(&lfs) => 0; +''' + + + + + + # #[cases.test_files_simple] #code = '''