From a75537faff4ff05ede1ea40a4f1feebac5b064e5 Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Mon, 9 Jun 2025 13:31:46 -0500 Subject: [PATCH] kv: Implemented a simple key-value API This adds a couple functions that treat files as simple key-value pairs: - lfs3_get - Read a file - lfs3_size - Get the size of a file - lfs3_set - Write a file - lfs3_remove - Remove a file (this one already exists!) The idea is the only real difference between a filesystem and key-value store in the microcontroller space is the API, and the key-value API _is_ much easier to use. It also opens the door to making the file API opt-out in the future to trade code cost for feature set. littlefs will probably never be competitive with other microcontroller-scale key-value stores, but it may be interesting for systems already using littlefs for other storage. And don't worry, these are still files, so they can always be opened with the full file API when more advanced operations are needed. These APIs also matches the custom attribute APIs, which makes sense because they're both key-values. Any mismatch should be considered an API bug, because the best user interface is a consistent one. This new API is tested in tests/test_kv.toml. --- At the moment the implementation is naive, just sitting on top of the file API. This works remarkably well thanks to littlefs's cache bypassing logic, but does have some downsides: - lfs3_set always writes two commits: one for the stickynote and one for the file sync. Unfortunately this is a fundamental limitation of littlefs's file API. One nice benefit of lfs3_set is in theory we can bypass this limitation, but not if we just sit on top of the file API. - There may be code savings from more tightly integrating the key-value code. This also highlighted an awkward corner case with per-file cache configuration in which the buffer needs to be non-null even if zero. Not the end of the world, but just a bit awkward. Maybe this deserves revisiting in the config API rework? --- Code changes were relatively minimal given that this is a whole new API, unfortunately the stack took quite a hit: code stack ctx before: 37352 2280 636 after: 37644 (+0.8%) 2448 (+7.4%) 636 (+0.0%) The stack surprised me, but in hindsight it makes sense. In sitting on top of the reset of the codebase, the key-value API adds very little code, but every stack allocation in these functions add to the stack hot-path. This isn't the end of the world, and it's actually probably a good thing to have an lfs3_file_t allocated in the stack hot-path. lfs3_file_t's size has been a bit difficult to track thanks to struct lfs3_info dominating ctx measurements... --- lfs3.c | 161 ++++- lfs3.h | 23 +- tests/test_attrs.toml | 20 - tests/test_kv.toml | 1421 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 1574 insertions(+), 51 deletions(-) create mode 100644 tests/test_kv.toml diff --git a/lfs3.c b/lfs3.c index b4cdb87b..2795fad2 100644 --- a/lfs3.c +++ b/lfs3.c @@ -11316,7 +11316,7 @@ static inline void lfs3_file_discardbshrub(lfs3_file_t *file) { static inline lfs3_size_t lfs3_file_cachesize(lfs3_t *lfs3, const lfs3_file_t *file) { - return (file->cfg->cache_size) + return (file->cfg->cache_buffer || file->cfg->cache_size) ? file->cfg->cache_size : lfs3->cfg->file_cache_size; } @@ -11337,7 +11337,16 @@ static inline lfs3_off_t lfs3_file_size_(const lfs3_file_t *file) { // file operations -static int lfs3_file_fetch(lfs3_t *lfs3, lfs3_file_t *file, bool trunc) { +static void lfs3_file_init(lfs3_file_t *file, uint32_t flags, + const struct lfs3_file_config *cfg) { + file->cfg = cfg; + file->b.o.flags = lfs3_o_typeflags(LFS3_TYPE_REG) | flags; + file->pos = 0; + // default to no cache + file->cache.size = 0; +} + +static int lfs3_file_fetch(lfs3_t *lfs3, lfs3_file_t *file, uint32_t flags) { // default data state lfs3_file_discardbshrub(file); // discard the current cache @@ -11346,7 +11355,11 @@ static int lfs3_file_fetch(lfs3_t *lfs3, lfs3_file_t *file, bool trunc) { lfs3_file_discardleaf(file); // don't bother reading disk if we're not created or truncating - if (!lfs3_o_isuncreat(file->b.o.flags) && !trunc) { + if (lfs3_o_isuncreat(flags) || lfs3_o_istrunc(flags)) { + // but do mark as unsync + file->b.o.flags |= LFS3_o_UNSYNC; + + } else { // lookup the file struct, if there is one lfs3_tag_t tag; lfs3_data_t data; @@ -11399,7 +11412,7 @@ static int lfs3_file_fetch(lfs3_t *lfs3, lfs3_file_t *file, bool trunc) { } // don't bother reading disk if we're not created yet - if (lfs3_o_isuncreat(file->b.o.flags)) { + if (lfs3_o_isuncreat(flags)) { if (file->cfg->attrs[i].size) { *file->cfg->attrs[i].size = LFS3_ERR_NOATTR; } @@ -11479,9 +11492,6 @@ int lfs3_file_opencfg(lfs3_t *lfs3, lfs3_file_t *file, || !lfs3_o_isexcl(cfg->attrs[i].flags)); } - // mounted with LFS3_M_FLUSH/SYNC? implies LFS3_O_FLUSH/SYNC - flags |= lfs3->flags & (LFS3_M_FLUSH | LFS3_M_SYNC); - if (!lfs3_o_isrdonly(flags)) { // prepare our filesystem for writing #ifndef LFS3_RDONLY @@ -11493,12 +11503,10 @@ int lfs3_file_opencfg(lfs3_t *lfs3, lfs3_file_t *file, } // setup file state - file->cfg = cfg; - file->b.o.flags = flags - | lfs3_o_typeflags(LFS3_TYPE_REG) - // default to unsynced for uncreated/truncated files - | LFS3_o_UNSYNC; - file->pos = 0; + lfs3_file_init(file, + // mounted with LFS3_M_FLUSH/SYNC? implies LFS3_O_FLUSH/SYNC + flags | (lfs3->flags & (LFS3_M_FLUSH | LFS3_M_SYNC)), + cfg); // lookup our parent lfs3_tag_t tag; @@ -11508,14 +11516,14 @@ int lfs3_file_opencfg(lfs3_t *lfs3, lfs3_file_t *file, if (err && !(err == LFS3_ERR_NOENT && lfs3_path_islast(path))) { return err; } - bool exists = err != LFS3_ERR_NOENT; + bool exists = (err != LFS3_ERR_NOENT); // creating a new entry? if (!exists || tag == LFS3_TAG_ORPHAN) { - if (!lfs3_o_iscreat(flags)) { + if (!lfs3_o_iscreat(file->b.o.flags)) { return LFS3_ERR_NOENT; } - LFS3_ASSERT(!lfs3_o_isrdonly(flags)); + LFS3_ASSERT(!lfs3_o_isrdonly(file->b.o.flags)); #ifndef LFS3_RDONLY // we're a file, don't allow trailing slashes @@ -11550,10 +11558,13 @@ int lfs3_file_opencfg(lfs3_t *lfs3, lfs3_file_t *file, } } } + + // mark as uncreated + file->b.o.flags |= LFS3_o_UNCREAT; #endif } else { // wanted to create a new entry? - if (lfs3_o_isexcl(flags)) { + if (lfs3_o_isexcl(file->b.o.flags)) { return LFS3_ERR_EXIST; } @@ -11564,14 +11575,13 @@ int lfs3_file_opencfg(lfs3_t *lfs3, lfs3_file_t *file, if (tag == LFS3_TAG_UNKNOWN) { return LFS3_ERR_NOTSUP; } - } - // if stickynote, mark as uncreated, we need to convert to reg file - // on first sync - if (!exists - || tag == LFS3_TAG_STICKYNOTE - || tag == LFS3_TAG_ORPHAN) { - file->b.o.flags |= LFS3_o_UNCREAT; + #ifndef LFS3_RDONLY + // if stickynote, mark as uncreated + if (tag == LFS3_TAG_STICKYNOTE) { + file->b.o.flags |= LFS3_o_UNCREAT; + } + #endif } // allocate cache if necessary @@ -11585,14 +11595,15 @@ int lfs3_file_opencfg(lfs3_t *lfs3, lfs3_file_t *file, } // fetch the file struct and custom attrs - err = lfs3_file_fetch(lfs3, file, lfs3_o_istrunc(flags)); + err = lfs3_file_fetch(lfs3, file, file->b.o.flags); if (err) { goto failed; } // check metadata/data for errors? - if (lfs3_t_isckmeta(flags) || lfs3_t_isckdata(flags)) { - err = lfs3_file_ck(lfs3, file, flags); + if (lfs3_t_isckmeta(file->b.o.flags) + || lfs3_t_isckdata(file->b.o.flags)) { + err = lfs3_file_ck(lfs3, file, file->b.o.flags); if (err) { goto failed; } @@ -11609,11 +11620,12 @@ failed:; } // default file config -static const struct lfs3_file_config lfs3_file_defaults = {0}; +static const struct lfs3_file_config lfs3_file_defaultcfg = {0}; int lfs3_file_open(lfs3_t *lfs3, lfs3_file_t *file, const char *path, uint32_t flags) { - return lfs3_file_opencfg(lfs3, file, path, flags, &lfs3_file_defaults); + return lfs3_file_opencfg(lfs3, file, path, flags, + &lfs3_file_defaultcfg); } // clean up resources @@ -13456,7 +13468,9 @@ int lfs3_file_resync(lfs3_t *lfs3, lfs3_file_t *file) { // do nothing if already in-sync if (lfs3_o_isunsync(file->b.o.flags)) { // refetch the file struct from disk - err = lfs3_file_fetch(lfs3, file, false); + err = lfs3_file_fetch(lfs3, file, + // don't truncate again! + file->b.o.flags & ~LFS3_O_TRUNC); if (err) { goto failed; } @@ -13840,6 +13854,93 @@ int lfs3_file_ckdata(lfs3_t *lfs3, lfs3_file_t *file) { +/// Simple key-value API /// + +// a simple key-value API is easier to use if your file fits in RAM, and +// if that's all you need you can potentially compile-out the more +// advanced file operations + +// kv file config, we need to explicitly disable the file cache +static const struct lfs3_file_config lfs3_file_kvconfig = { + // TODO is this the best way to do this? + .cache_buffer = (uint8_t*)true, + .cache_size = 0, +}; + +lfs3_ssize_t lfs3_get(lfs3_t *lfs3, const char *path, + void *buffer, lfs3_size_t size) { + // we just use the file API here, but with no cache so all reads + // bypass the cache + lfs3_file_t file; + int err = lfs3_file_opencfg(lfs3, &file, path, LFS3_O_RDONLY, + &lfs3_file_kvconfig); + if (err) { + return err; + } + + lfs3_ssize_t size_ = lfs3_file_read(lfs3, &file, buffer, size); + + // unconditionally close + err = lfs3_file_close(lfs3, &file); + // we didn't allocate anything, so this can't fail + LFS3_ASSERT(!err); + + return size_; +} + +lfs3_ssize_t lfs3_size(lfs3_t *lfs3, const char *path) { + // we just use the file API here, but with no cache so all reads + // bypass the cache + lfs3_file_t file; + int err = lfs3_file_opencfg(lfs3, &file, path, LFS3_O_RDONLY, + &lfs3_file_kvconfig); + if (err) { + return err; + } + + lfs3_ssize_t size_ = lfs3_file_size_(&file); + + // unconditionally close + err = lfs3_file_close(lfs3, &file); + // we didn't allocate anything, so this can't fail + LFS3_ASSERT(!err); + + return size_; +} + +#ifndef LFS3_RDONLY +int lfs3_set(lfs3_t *lfs3, const char *path, + const void *buffer, lfs3_size_t size) { + // we just use the file API here, but with no cache so all writes + // bypass the cache + lfs3_file_t file; + int err = lfs3_file_opencfg(lfs3, &file, path, + LFS3_O_WRONLY | LFS3_O_CREAT | LFS3_O_TRUNC, + &lfs3_file_kvconfig); + if (err) { + return err; + } + + lfs3_ssize_t size_ = lfs3_file_write(lfs3, &file, buffer, size); + if (size_ < 0) { + err = size_; + } + + // unconditionally close + int err_ = lfs3_file_close(lfs3, &file); + if (err_) { + // we didn't allocate anything, and write failing would set the + // desync flag, so only one of write/close can fail + LFS3_ASSERT(!err); + err = err_; + } + + return err; +} +#endif + + + /// High-level filesystem operations /// diff --git a/lfs3.h b/lfs3.h index cbf3a19a..1fdd0895 100644 --- a/lfs3.h +++ b/lfs3.h @@ -619,7 +619,7 @@ struct lfs3_file_config { // Size of the file cache in bytes. In addition to filesystem-wide // read/prog caches, each file gets its own cache to reduce disk - // accesses. Defaults to file_cache_size. + // accesses. Defaults to file_cache_size if cache_buffer is NULL. lfs3_size_t cache_size; // Optional list of custom attributes attached to the file. If readable, @@ -916,6 +916,27 @@ int lfs3_unmount(lfs3_t *lfs3); /// General operations /// +// Get the value of a file +// +// Returns the number of bytes read, or a negative error code on failure. +// Note this may be less than the on-disk file size if the buffer is not +// large enough. +lfs3_ssize_t lfs3_get(lfs3_t *lfs3, const char *path, + void *buffer, lfs3_size_t size); + +// Get a file's size +// +// Returns the size of the file, or a negative error code on failure. +lfs3_ssize_t lfs3_size(lfs3_t *lfs3, const char *path); + +// Set the value of a file +// +// Returns a negative error code on failure. +#ifndef LFS3_RDONLY +int lfs3_set(lfs3_t *lfs3, const char *path, + const void *buffer, lfs3_size_t size); +#endif + // Removes a file or directory // // If removing a directory, the directory must be empty. diff --git a/tests/test_attrs.toml b/tests/test_attrs.toml index c059f1a6..d40bd9da 100644 --- a/tests/test_attrs.toml +++ b/tests/test_attrs.toml @@ -223,26 +223,6 @@ code = ''' lfs3_file_close(&lfs3, &file) => 0; } - for (int remount = 0; remount < 2; remount++) { - // remount? - if (remount) { - lfs3_unmount(&lfs3) => 0; - lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0; - } - - // make sure setattr didn't quietly create attrs - - // try getting the attr sizes - lfs3_sizeattr(&lfs3, path, 'a') => LFS3_ERR_NOATTR; - lfs3_sizeattr(&lfs3, path, 'b') => LFS3_ERR_NOATTR; - lfs3_sizeattr(&lfs3, path, 'c') => LFS3_ERR_NOATTR; - // try reading attrs - uint8_t rbuf[256]; - lfs3_getattr(&lfs3, path, 'a', rbuf, sizeof(rbuf)) => LFS3_ERR_NOATTR; - lfs3_getattr(&lfs3, path, 'b', rbuf, sizeof(rbuf)) => LFS3_ERR_NOATTR; - lfs3_getattr(&lfs3, path, 'c', rbuf, sizeof(rbuf)) => LFS3_ERR_NOATTR; - } - lfs3_unmount(&lfs3) => 0; ''' diff --git a/tests/test_kv.toml b/tests/test_kv.toml new file mode 100644 index 00000000..ba1b581f --- /dev/null +++ b/tests/test_kv.toml @@ -0,0 +1,1421 @@ +# Tests over the simple key-value API +after = ['test_files', 'test_fsync', 'test_stickynotes'] + + +## General set/get tests + +# test some simple kv operations +[cases.test_kv_set] +code = ''' + lfs3_t lfs3; + lfs3_format(&lfs3, LFS3_F_RDWR, CFG) => 0; + lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0; + + // create some kv files + const char *a = "One 18.25 ounce package chocolate cake mix."; + lfs3_set(&lfs3, "a", a, strlen(a)) => 0; + const char *b = "One can prepared coconut pecan frosting."; + lfs3_set(&lfs3, "b", b, strlen(b)) => 0; + const char *c = "Three slash four cup vegetable oil."; + lfs3_set(&lfs3, "c", c, strlen(c)) => 0; + + for (int remount = 0; remount < 2; remount++) { + // remount? + if (remount) { + lfs3_unmount(&lfs3) => 0; + lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0; + } + + // try getting the file sizes + lfs3_size(&lfs3, "a") => strlen(a); + lfs3_size(&lfs3, "b") => strlen(b); + lfs3_size(&lfs3, "c") => strlen(c); + // try reading the files + uint8_t rbuf[256]; + lfs3_get(&lfs3, "a", rbuf, sizeof(rbuf)) => strlen(a); + assert(memcmp(rbuf, a, strlen(a)) == 0); + lfs3_get(&lfs3, "b", rbuf, sizeof(rbuf)) => strlen(b); + assert(memcmp(rbuf, b, strlen(b)) == 0); + lfs3_get(&lfs3, "c", rbuf, sizeof(rbuf)) => strlen(c); + assert(memcmp(rbuf, c, strlen(c)) == 0); + } + + lfs3_unmount(&lfs3) => 0; +''' + +# test truncated get calls still work +[cases.test_kv_set_trunc] +defines.BUFSIZE = [1, 4, 7] +code = ''' + lfs3_t lfs3; + lfs3_format(&lfs3, LFS3_F_RDWR, CFG) => 0; + lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0; + + // create some kv files + const char *a = "One 18.25 ounce package chocolate cake mix."; + lfs3_set(&lfs3, "a", a, strlen(a)) => 0; + const char *b = "One can prepared coconut pecan frosting."; + lfs3_set(&lfs3, "b", b, strlen(b)) => 0; + const char *c = "Three slash four cup vegetable oil."; + lfs3_set(&lfs3, "c", c, strlen(c)) => 0; + + for (int remount = 0; remount < 2; remount++) { + // remount? + if (remount) { + lfs3_unmount(&lfs3) => 0; + lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0; + } + + // try getting the file sizes + lfs3_size(&lfs3, "a") => strlen(a); + lfs3_size(&lfs3, "b") => strlen(b); + lfs3_size(&lfs3, "c") => strlen(c); + + // mark rbuf so we can detect overflow + uint8_t rbuf[256]; + rbuf[BUFSIZE] = '!'; + // try reading truncated files + lfs3_get(&lfs3, "a", rbuf, BUFSIZE) => BUFSIZE; + assert(memcmp(rbuf, a, BUFSIZE) == 0); + assert(rbuf[BUFSIZE] == '!'); + lfs3_get(&lfs3, "b", rbuf, BUFSIZE) => BUFSIZE; + assert(memcmp(rbuf, b, BUFSIZE) == 0); + assert(rbuf[BUFSIZE] == '!'); + lfs3_get(&lfs3, "c", rbuf, BUFSIZE) => BUFSIZE; + assert(memcmp(rbuf, c, BUFSIZE) == 0); + assert(rbuf[BUFSIZE] == '!'); + + // try reading the full files + lfs3_get(&lfs3, "a", rbuf, sizeof(rbuf)) => strlen(a); + assert(memcmp(rbuf, a, strlen(a)) == 0); + lfs3_get(&lfs3, "b", rbuf, sizeof(rbuf)) => strlen(b); + assert(memcmp(rbuf, b, strlen(b)) == 0); + lfs3_get(&lfs3, "c", rbuf, sizeof(rbuf)) => strlen(c); + assert(memcmp(rbuf, c, strlen(c)) == 0); + } + + lfs3_unmount(&lfs3) => 0; +''' + +# test ENOENT works +[cases.test_kv_set_noent] +code = ''' + lfs3_t lfs3; + lfs3_format(&lfs3, LFS3_F_RDWR, CFG) => 0; + lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0; + + // try getting the file sizes + lfs3_size(&lfs3, "a") => LFS3_ERR_NOENT; + lfs3_size(&lfs3, "b") => LFS3_ERR_NOENT; + lfs3_size(&lfs3, "c") => LFS3_ERR_NOENT; + + // try reading files + uint8_t rbuf[256]; + lfs3_get(&lfs3, "a", rbuf, sizeof(rbuf)) => LFS3_ERR_NOENT; + lfs3_get(&lfs3, "b", rbuf, sizeof(rbuf)) => LFS3_ERR_NOENT; + lfs3_get(&lfs3, "c", rbuf, sizeof(rbuf)) => LFS3_ERR_NOENT; + + lfs3_unmount(&lfs3) => 0; +''' + +# test that updating kv files works +[cases.test_kv_set_update] +# update based on this mask +defines.MASK = 'range(0x8)' +code = ''' + lfs3_t lfs3; + lfs3_format(&lfs3, LFS3_F_RDWR, CFG) => 0; + lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0; + + // create some kv files + const char *a = "One 18.25 ounce package chocolate cake mix."; + lfs3_set(&lfs3, "a", a, strlen(a)) => 0; + const char *b = "One can prepared coconut pecan frosting."; + lfs3_set(&lfs3, "b", b, strlen(b)) => 0; + const char *c = "Three slash four cup vegetable oil."; + lfs3_set(&lfs3, "c", c, strlen(c)) => 0; + + // rewrite some kv files + const char *a_ = "Four large eggs. One cup semi-sweet chocolate chips."; + if (MASK & 0x1) { + lfs3_set(&lfs3, "a", a_, strlen(a_)) => 0; + } + const char *b_ = "Three slash four cup butter or margarine."; + if (MASK & 0x2) { + lfs3_set(&lfs3, "b", b_, strlen(b_)) => 0; + } + const char *c_ = "One and two third cups granulated sugar."; + if (MASK & 0x4) { + lfs3_set(&lfs3, "c", c_, strlen(c_)) => 0; + } + + for (int remount = 0; remount < 2; remount++) { + // remount? + if (remount) { + lfs3_unmount(&lfs3) => 0; + lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0; + } + + // try getting the file sizes + if (MASK & 0x1) { + lfs3_size(&lfs3, "a") => strlen(a_); + } else { + lfs3_size(&lfs3, "a") => strlen(a); + } + if (MASK & 0x2) { + lfs3_size(&lfs3, "b") => strlen(b_); + } else { + lfs3_size(&lfs3, "b") => strlen(b); + } + if (MASK & 0x4) { + lfs3_size(&lfs3, "c") => strlen(c_); + } else { + lfs3_size(&lfs3, "c") => strlen(c); + } + + // try reading files + uint8_t rbuf[256]; + if (MASK & 0x1) { + lfs3_get(&lfs3, "a", rbuf, sizeof(rbuf)) => strlen(a_); + assert(memcmp(rbuf, a_, strlen(a_)) == 0); + } else { + lfs3_get(&lfs3, "a", rbuf, sizeof(rbuf)) => strlen(a); + assert(memcmp(rbuf, a, strlen(a)) == 0); + } + if (MASK & 0x2) { + lfs3_get(&lfs3, "b", rbuf, sizeof(rbuf)) => strlen(b_); + assert(memcmp(rbuf, b_, strlen(b_)) == 0); + } else { + lfs3_get(&lfs3, "b", rbuf, sizeof(rbuf)) => strlen(b); + assert(memcmp(rbuf, b, strlen(b)) == 0); + } + if (MASK & 0x4) { + lfs3_get(&lfs3, "c", rbuf, sizeof(rbuf)) => strlen(c_); + assert(memcmp(rbuf, c_, strlen(c_)) == 0); + } else { + lfs3_get(&lfs3, "c", rbuf, sizeof(rbuf)) => strlen(c); + assert(memcmp(rbuf, c, strlen(c)) == 0); + } + } + + lfs3_unmount(&lfs3) => 0; +''' + +# test removing simple kv files +# +# surprise! this is the normal file remove function +# +[cases.test_kv_remove] +# remove based on this mask +defines.MASK = 'range(0x8)' +code = ''' + lfs3_t lfs3; + lfs3_format(&lfs3, LFS3_F_RDWR, CFG) => 0; + lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0; + + // create some kv files + const char *a = "One 18.25 ounce package chocolate cake mix."; + lfs3_set(&lfs3, "a", a, strlen(a)) => 0; + const char *b = "One can prepared coconut pecan frosting."; + lfs3_set(&lfs3, "b", b, strlen(b)) => 0; + const char *c = "Three slash four cup vegetable oil."; + lfs3_set(&lfs3, "c", c, strlen(c)) => 0; + + // remove some kv files + if (MASK & 0x1) { + lfs3_remove(&lfs3, "a") => 0; + } + if (MASK & 0x2) { + lfs3_remove(&lfs3, "b") => 0; + } + if (MASK & 0x4) { + lfs3_remove(&lfs3, "c") => 0; + } + + for (int remount = 0; remount < 2; remount++) { + // remount? + if (remount) { + lfs3_unmount(&lfs3) => 0; + lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0; + } + + // try getting the file sizes + if (MASK & 0x1) { + lfs3_size(&lfs3, "a") => LFS3_ERR_NOENT; + } else { + lfs3_size(&lfs3, "a") => strlen(a); + } + if (MASK & 0x2) { + lfs3_size(&lfs3, "b") => LFS3_ERR_NOENT; + } else { + lfs3_size(&lfs3, "b") => strlen(b); + } + if (MASK & 0x4) { + lfs3_size(&lfs3, "c") => LFS3_ERR_NOENT; + } else { + lfs3_size(&lfs3, "c") => strlen(c); + } + + // try reading the full files + uint8_t rbuf[256]; + if (MASK & 0x1) { + lfs3_get(&lfs3, "a", rbuf, sizeof(rbuf)) + => LFS3_ERR_NOENT; + } else { + lfs3_get(&lfs3, "a", rbuf, sizeof(rbuf)) + => strlen(a); + assert(memcmp(rbuf, a, strlen(a)) == 0); + } + if (MASK & 0x2) { + lfs3_get(&lfs3, "b", rbuf, sizeof(rbuf)) + => LFS3_ERR_NOENT; + } else { + lfs3_get(&lfs3, "b", rbuf, sizeof(rbuf)) + => strlen(b); + assert(memcmp(rbuf, b, strlen(b)) == 0); + } + if (MASK & 0x4) { + lfs3_get(&lfs3, "c", rbuf, sizeof(rbuf)) + => LFS3_ERR_NOENT; + } else { + lfs3_get(&lfs3, "c", rbuf, sizeof(rbuf)) + => strlen(c); + assert(memcmp(rbuf, c, strlen(c)) == 0); + } + } + + lfs3_unmount(&lfs3) => 0; +''' + +# test creating a bunch of kv files +[cases.test_kv_many] +defines.N = [40, 400] +defines.SIZE = 4 +defines.COMPACT = [false, true] +defines.GC_FLAGS = 'LFS3_GC_COMPACT' +defines.GC_STEPS = -1 +defines.GC_COMPACT_THRESH = 'BLOCK_SIZE/2' +if = 'LFS3_IFDEF_GC(true, !COMPACT)' +code = ''' + lfs3_t lfs3; + lfs3_format(&lfs3, LFS3_F_RDWR, CFG) => 0; + lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0; + + // create N kv files + uint32_t prng = 42; + for (lfs3_size_t i = 0; i < N; i++) { + char name[256]; + sprintf(name, "key%03x", i); + + uint8_t wbuf[SIZE]; + for (lfs3_size_t j = 0; j < SIZE; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + + lfs3_set(&lfs3, name, wbuf, SIZE) => 0; + } + + // try compacting? + #ifdef LFS3_GC + if (COMPACT) { + lfs3_fs_gc(&lfs3) => 0; + } + #endif + + for (int remount = 0; remount < 2; remount++) { + // remount? + if (remount) { + lfs3_unmount(&lfs3) => 0; + lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0; + } + + // try getting the file sizes + for (lfs3_size_t i = 0; i < N; i++) { + char name[256]; + sprintf(name, "key%03x", i); + lfs3_size(&lfs3, name) => SIZE; + } + // try reading the files + prng = 42; + for (lfs3_size_t i = 0; i < N; i++) { + char name[256]; + sprintf(name, "key%03x", i); + uint8_t wbuf[SIZE]; + for (lfs3_size_t j = 0; j < SIZE; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + uint8_t rbuf[256]; + lfs3_get(&lfs3, name, rbuf, sizeof(rbuf)) => SIZE; + assert(memcmp(rbuf, wbuf, SIZE) == 0); + } + } + + lfs3_unmount(&lfs3) => 0; +''' + +# fuzz kv files +[cases.test_kv_fuzz] +defines.N = 10 +defines.SIZE = 4 +defines.OPS = ['4*N', '40*N'] +defines.SEED = 'range(20)' +fuzz = 'SEED' +code = ''' + lfs3_t lfs3; + lfs3_format(&lfs3, LFS3_F_RDWR, CFG) => 0; + lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0; + + // set up a simulation to compare against + uint32_t *sim_prngs = malloc(N*sizeof(uint32_t)); + memset(sim_prngs, 0, N*sizeof(uint32_t)); + + uint32_t prng = SEED; + for (lfs3_size_t i = 0; i < OPS; i++) { + // choose which operation to do + uint8_t op = TEST_PRNG(&prng) % 2; + + // create a kv file? + if (op == 0) { + // choose a kv file + lfs3_size_t x = TEST_PRNG(&prng) % N; + // choose a prng + uint32_t wprng = TEST_PRNG(&prng); + + // create the file + char name[256]; + sprintf(name, "key%03x", x); + uint32_t wprng_ = wprng; + uint8_t wbuf[SIZE]; + for (lfs3_size_t j = 0; j < SIZE; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&wprng_) % 26); + } + lfs3_set(&lfs3, name, wbuf, SIZE) => 0; + + // update our sim + sim_prngs[x] = wprng; + + // remove a kv file? + } else if (op == 1) { + // choose a kv file + lfs3_size_t x = TEST_PRNG(&prng) % N; + + // remove the file + char name[256]; + sprintf(name, "key%03x", x); + if (sim_prngs[x]) { + lfs3_remove(&lfs3, name) => 0; + } else { + lfs3_remove(&lfs3, name) => LFS3_ERR_NOENT; + } + + // update our sim + sim_prngs[x] = 0; + } + } + + for (int remount = 0; remount < 2; remount++) { + // remount? + if (remount) { + lfs3_unmount(&lfs3) => 0; + lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0; + } + + // try getting each file size + for (lfs3_size_t j = 0; j < N; j++) { + char name[256]; + sprintf(name, "key%03x", j); + if (sim_prngs[j]) { + lfs3_size(&lfs3, name) => SIZE; + } else { + lfs3_size(&lfs3, name) => LFS3_ERR_NOENT; + } + } + // try reading each file + for (lfs3_size_t j = 0; j < N; j++) { + char name[256]; + sprintf(name, "key%03x", j); + if (sim_prngs[j]) { + uint32_t wprng_ = sim_prngs[j]; + uint8_t wbuf[SIZE]; + for (lfs3_size_t i = 0; i < SIZE; i++) { + wbuf[i] = 'a' + (TEST_PRNG(&wprng_) % 26); + } + uint8_t rbuf[256]; + lfs3_get(&lfs3, name, rbuf, sizeof(rbuf)) => SIZE; + assert(memcmp(rbuf, wbuf, SIZE) == 0); + } else { + uint8_t rbuf[256]; + lfs3_get(&lfs3, name, rbuf, sizeof(rbuf)) + => LFS3_ERR_NOENT; + } + } + } + + // clean up sim/lfs3 + free(sim_prngs); + lfs3_unmount(&lfs3) => 0; +''' + +# test creating a bunch of kv files +[cases.test_kv_many_big] +defines.N = 40 +# size is more an upper limit here +defines.SIZE = 40000 +defines.COMPACT = [false, true] +defines.GC_FLAGS = 'LFS3_GC_COMPACT' +defines.GC_STEPS = -1 +defines.GC_COMPACT_THRESH = 'BLOCK_SIZE/2' +if = 'LFS3_IFDEF_GC(true, !COMPACT)' +code = ''' + lfs3_t lfs3; + lfs3_format(&lfs3, LFS3_F_RDWR, CFG) => 0; + lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0; + + // create N kv files + uint32_t prng = 42; + for (lfs3_size_t i = 0; i < N; i++) { + char name[256]; + sprintf(name, "key%03x", i); + + // choose a random size, bias towards zero + lfs3_off_t size = lfs3_abs( + (TEST_PRNG(&prng) % SIZE) + + (TEST_PRNG(&prng) % SIZE) + - SIZE); + uint8_t wbuf[SIZE]; + for (lfs3_size_t j = 0; j < size; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + + lfs3_set(&lfs3, name, wbuf, size) => 0; + } + + // try compacting? + #ifdef LFS3_GC + if (COMPACT) { + lfs3_fs_gc(&lfs3) => 0; + } + #endif + + for (int remount = 0; remount < 2; remount++) { + // remount? + if (remount) { + lfs3_unmount(&lfs3) => 0; + lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0; + } + + // try getting the file sizes + prng = 42; + for (lfs3_size_t i = 0; i < N; i++) { + char name[256]; + sprintf(name, "key%03x", i); + lfs3_off_t size = lfs3_abs( + (TEST_PRNG(&prng) % SIZE) + + (TEST_PRNG(&prng) % SIZE) + - SIZE); + for (lfs3_size_t j = 0; j < size; j++) { + TEST_PRNG(&prng); + } + lfs3_size(&lfs3, name) => size; + } + // try reading the files + prng = 42; + for (lfs3_size_t i = 0; i < N; i++) { + char name[256]; + sprintf(name, "key%03x", i); + lfs3_off_t size = lfs3_abs( + (TEST_PRNG(&prng) % SIZE) + + (TEST_PRNG(&prng) % SIZE) + - SIZE); + uint8_t wbuf[SIZE]; + for (lfs3_size_t j = 0; j < size; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + uint8_t rbuf[SIZE]; + lfs3_get(&lfs3, name, rbuf, sizeof(rbuf)) => size; + assert(memcmp(rbuf, wbuf, size) == 0); + } + } + + lfs3_unmount(&lfs3) => 0; +''' + +# fuzz kv files +[cases.test_kv_fuzz_big] +defines.N = 10 +# size is more an upper limit here +defines.SIZE = 40000 +defines.OPS = ['4*N', '40*N'] +defines.SEED = 'range(20)' +fuzz = 'SEED' +code = ''' + lfs3_t lfs3; + lfs3_format(&lfs3, LFS3_F_RDWR, CFG) => 0; + lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0; + + // set up a simulation to compare against + lfs3_off_t *sim_sizes = malloc(N*sizeof(lfs3_off_t)); + memset(sim_sizes, 0, N*sizeof(lfs3_off_t)); + uint32_t *sim_prngs = malloc(N*sizeof(uint32_t)); + memset(sim_prngs, 0, N*sizeof(uint32_t)); + + uint32_t prng = SEED; + for (lfs3_size_t i = 0; i < OPS; i++) { + // choose which operation to do + uint8_t op = TEST_PRNG(&prng) % 2; + + // create a kv file? + if (op == 0) { + // choose a kv file + lfs3_size_t x = TEST_PRNG(&prng) % N; + // choose a size, bias towards zero + lfs3_off_t size = lfs3_abs( + (TEST_PRNG(&prng) % SIZE) + + (TEST_PRNG(&prng) % SIZE) + - SIZE); + // choose a prng + uint32_t wprng = TEST_PRNG(&prng); + + // create the file + char name[256]; + sprintf(name, "key%03x", x); + uint32_t wprng_ = wprng; + uint8_t wbuf[SIZE]; + for (lfs3_size_t j = 0; j < size; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&wprng_) % 26); + } + lfs3_set(&lfs3, name, wbuf, size) => 0; + + // update our sim + sim_sizes[x] = size; + sim_prngs[x] = wprng; + + // remove a kv file? + } else if (op == 1) { + // choose a kv file + lfs3_size_t x = TEST_PRNG(&prng) % N; + + // remove the file + char name[256]; + sprintf(name, "key%03x", x); + if (sim_prngs[x]) { + lfs3_remove(&lfs3, name) => 0; + } else { + lfs3_remove(&lfs3, name) => LFS3_ERR_NOENT; + } + + // update our sim + sim_prngs[x] = 0; + } + } + + for (int remount = 0; remount < 2; remount++) { + // remount? + if (remount) { + lfs3_unmount(&lfs3) => 0; + lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0; + } + + // try getting each file size + for (lfs3_size_t j = 0; j < N; j++) { + char name[256]; + sprintf(name, "key%03x", j); + if (sim_prngs[j]) { + lfs3_size(&lfs3, name) => sim_sizes[j]; + } else { + lfs3_size(&lfs3, name) => LFS3_ERR_NOENT; + } + } + // try reading each file + for (lfs3_size_t j = 0; j < N; j++) { + char name[256]; + sprintf(name, "key%03x", j); + if (sim_prngs[j]) { + uint32_t wprng_ = sim_prngs[j]; + uint8_t wbuf[SIZE]; + for (lfs3_size_t i = 0; i < sim_sizes[j]; i++) { + wbuf[i] = 'a' + (TEST_PRNG(&wprng_) % 26); + } + uint8_t rbuf[SIZE]; + lfs3_get(&lfs3, name, rbuf, sizeof(rbuf)) => sim_sizes[j]; + assert(memcmp(rbuf, wbuf, sim_sizes[j]) == 0); + } else { + uint8_t rbuf[SIZE]; + lfs3_get(&lfs3, name, rbuf, sizeof(rbuf)) + => LFS3_ERR_NOENT; + } + } + } + + // clean up sim/lfs3 + free(sim_sizes); + free(sim_prngs); + lfs3_unmount(&lfs3) => 0; +''' + +# test kv files can be read as normal files +[cases.test_kv_interop_reads] +code = ''' + lfs3_t lfs3; + lfs3_format(&lfs3, LFS3_F_RDWR, CFG) => 0; + lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0; + + // create some kv files + const char *a = "One 18.25 ounce package chocolate cake mix."; + lfs3_set(&lfs3, "a", a, strlen(a)) => 0; + const char *b = "One can prepared coconut pecan frosting."; + lfs3_set(&lfs3, "b", b, strlen(b)) => 0; + const char *c = "Three slash four cup vegetable oil."; + lfs3_set(&lfs3, "c", c, strlen(c)) => 0; + + for (int remount = 0; remount < 2; remount++) { + // remount? + if (remount) { + lfs3_unmount(&lfs3) => 0; + lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0; + } + + // try getting the file sizes + lfs3_size(&lfs3, "a") => strlen(a); + lfs3_size(&lfs3, "b") => strlen(b); + lfs3_size(&lfs3, "c") => strlen(c); + // try reading the files + uint8_t rbuf[256]; + lfs3_get(&lfs3, "a", rbuf, sizeof(rbuf)) => strlen(a); + assert(memcmp(rbuf, a, strlen(a)) == 0); + lfs3_get(&lfs3, "b", rbuf, sizeof(rbuf)) => strlen(b); + assert(memcmp(rbuf, b, strlen(b)) == 0); + lfs3_get(&lfs3, "c", rbuf, sizeof(rbuf)) => strlen(c); + assert(memcmp(rbuf, c, strlen(c)) == 0); + + // check our files with stat + struct lfs3_info info; + lfs3_stat(&lfs3, "a", &info) => 0; + assert(strcmp(info.name, "a") == 0); + assert(info.type == LFS3_TYPE_REG); + assert(info.size == strlen(a)); + lfs3_stat(&lfs3, "b", &info) => 0; + assert(strcmp(info.name, "b") == 0); + assert(info.type == LFS3_TYPE_REG); + assert(info.size == strlen(b)); + lfs3_stat(&lfs3, "c", &info) => 0; + assert(strcmp(info.name, "c") == 0); + assert(info.type == LFS3_TYPE_REG); + assert(info.size == strlen(c)); + + // and with dir read + lfs3_dir_t dir; + lfs3_dir_open(&lfs3, &dir, "/") => 0; + lfs3_dir_read(&lfs3, &dir, &info) => 0; + assert(strcmp(info.name, ".") == 0); + assert(info.type == LFS3_TYPE_DIR); + assert(info.size == 0); + lfs3_dir_read(&lfs3, &dir, &info) => 0; + assert(strcmp(info.name, "..") == 0); + assert(info.type == LFS3_TYPE_DIR); + assert(info.size == 0); + lfs3_dir_read(&lfs3, &dir, &info) => 0; + assert(strcmp(info.name, "a") == 0); + assert(info.type == LFS3_TYPE_REG); + assert(info.size == strlen(a)); + lfs3_dir_read(&lfs3, &dir, &info) => 0; + assert(strcmp(info.name, "b") == 0); + assert(info.type == LFS3_TYPE_REG); + assert(info.size == strlen(b)); + lfs3_dir_read(&lfs3, &dir, &info) => 0; + assert(strcmp(info.name, "c") == 0); + assert(info.type == LFS3_TYPE_REG); + assert(info.size == strlen(c)); + lfs3_dir_read(&lfs3, &dir, &info) => LFS3_ERR_NOENT; + lfs3_dir_close(&lfs3, &dir) => 0; + + // try reading our files + lfs3_file_t file_a; + lfs3_file_t file_b; + lfs3_file_t file_c; + lfs3_file_open(&lfs3, &file_a, "a", LFS3_O_RDONLY) => 0; + lfs3_file_open(&lfs3, &file_b, "b", LFS3_O_RDONLY) => 0; + lfs3_file_open(&lfs3, &file_c, "c", LFS3_O_RDONLY) => 0; + // is size correct? + lfs3_file_size(&lfs3, &file_a) => strlen(a); + lfs3_file_size(&lfs3, &file_b) => strlen(b); + lfs3_file_size(&lfs3, &file_c) => strlen(c); + // try reading + lfs3_file_read(&lfs3, &file_a, rbuf, sizeof(rbuf)) => strlen(a); + assert(memcmp(rbuf, a, strlen(a)) == 0); + lfs3_file_read(&lfs3, &file_b, rbuf, sizeof(rbuf)) => strlen(b); + assert(memcmp(rbuf, b, strlen(b)) == 0); + lfs3_file_read(&lfs3, &file_c, rbuf, sizeof(rbuf)) => strlen(c); + assert(memcmp(rbuf, c, strlen(c)) == 0); + lfs3_file_close(&lfs3, &file_a) => 0; + lfs3_file_close(&lfs3, &file_b) => 0; + lfs3_file_close(&lfs3, &file_c) => 0; + } + + lfs3_unmount(&lfs3) => 0; +''' + +# test normal files can be read a kv files +[cases.test_kv_interop_writes] +code = ''' + lfs3_t lfs3; + lfs3_format(&lfs3, LFS3_F_RDWR, CFG) => 0; + lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0; + + // create some kv files + const char *a = "One 18.25 ounce package chocolate cake mix."; + lfs3_file_t file_a; + lfs3_file_open(&lfs3, &file_a, "a", + LFS3_O_WRONLY | LFS3_O_CREAT | LFS3_O_EXCL) => 0; + lfs3_file_write(&lfs3, &file_a, a, strlen(a)) => strlen(a); + lfs3_file_close(&lfs3, &file_a) => 0; + const char *b = "One can prepared coconut pecan frosting."; + lfs3_file_t file_b; + lfs3_file_open(&lfs3, &file_b, "b", + LFS3_O_WRONLY | LFS3_O_CREAT | LFS3_O_EXCL) => 0; + lfs3_file_write(&lfs3, &file_b, b, strlen(b)) => strlen(b); + lfs3_file_close(&lfs3, &file_b) => 0; + const char *c = "Three slash four cup vegetable oil."; + lfs3_file_t file_c; + lfs3_file_open(&lfs3, &file_c, "c", + LFS3_O_WRONLY | LFS3_O_CREAT | LFS3_O_EXCL) => 0; + lfs3_file_write(&lfs3, &file_c, c, strlen(c)) => strlen(c); + lfs3_file_close(&lfs3, &file_c) => 0; + + for (int remount = 0; remount < 2; remount++) { + // remount? + if (remount) { + lfs3_unmount(&lfs3) => 0; + lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0; + } + + // try getting the file sizes + lfs3_size(&lfs3, "a") => strlen(a); + lfs3_size(&lfs3, "b") => strlen(b); + lfs3_size(&lfs3, "c") => strlen(c); + // try reading the files + uint8_t rbuf[256]; + lfs3_get(&lfs3, "a", rbuf, sizeof(rbuf)) => strlen(a); + assert(memcmp(rbuf, a, strlen(a)) == 0); + lfs3_get(&lfs3, "b", rbuf, sizeof(rbuf)) => strlen(b); + assert(memcmp(rbuf, b, strlen(b)) == 0); + lfs3_get(&lfs3, "c", rbuf, sizeof(rbuf)) => strlen(c); + assert(memcmp(rbuf, c, strlen(c)) == 0); + } + + lfs3_unmount(&lfs3) => 0; +''' + +# test kv files broadcast sync updates +[cases.test_kv_interop_sync] +defines.STICKYNOTES = [false, true] +code = ''' + lfs3_t lfs3; + lfs3_format(&lfs3, LFS3_F_RDWR, CFG) => 0; + lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0; + + // open some files + const char *a = "One 18.25 ounce package chocolate cake mix."; + lfs3_file_t file_a; + lfs3_file_open(&lfs3, &file_a, "a", + LFS3_O_RDWR | LFS3_O_CREAT | LFS3_O_EXCL) => 0; + lfs3_file_write(&lfs3, &file_a, a, strlen(a)) => strlen(a); + const char *b = "One can prepared coconut pecan frosting."; + lfs3_file_t file_b; + lfs3_file_open(&lfs3, &file_b, "b", + LFS3_O_RDWR | LFS3_O_CREAT | LFS3_O_EXCL) => 0; + lfs3_file_write(&lfs3, &file_b, b, strlen(b)) => strlen(b); + const char *c = "Three slash four cup vegetable oil."; + lfs3_file_t file_c; + lfs3_file_open(&lfs3, &file_c, "c", + LFS3_O_RDWR | LFS3_O_CREAT | LFS3_O_EXCL) => 0; + lfs3_file_write(&lfs3, &file_c, c, strlen(c)) => strlen(c); + + // sync to create if creating + if (!STICKYNOTES) { + lfs3_file_sync(&lfs3, &file_a) => 0; + lfs3_file_sync(&lfs3, &file_b) => 0; + lfs3_file_sync(&lfs3, &file_c) => 0; + } + + // set the new values + const char *a_ = "Four large eggs. One cup semi-sweet chocolate chips."; + lfs3_set(&lfs3, "a", a_, strlen(a_)) => 0; + const char *b_ = "Three slash four cup butter or margarine."; + lfs3_set(&lfs3, "b", b_, strlen(b_)) => 0; + const char *c_ = "One and two third cups granulated sugar."; + lfs3_set(&lfs3, "c", c_, strlen(c_)) => 0; + + // files should have been created + struct lfs3_info info; + lfs3_stat(&lfs3, "a", &info) => 0; + assert(strcmp(info.name, "a") == 0); + assert(info.type == LFS3_TYPE_REG); + assert(info.size == strlen(a_)); + lfs3_stat(&lfs3, "b", &info) => 0; + assert(strcmp(info.name, "b") == 0); + assert(info.type == LFS3_TYPE_REG); + assert(info.size == strlen(b_)); + lfs3_stat(&lfs3, "c", &info) => 0; + assert(strcmp(info.name, "c") == 0); + assert(info.type == LFS3_TYPE_REG); + assert(info.size == strlen(c_)); + + // try getting the file sizes + lfs3_size(&lfs3, "a") => strlen(a_); + lfs3_size(&lfs3, "b") => strlen(b_); + lfs3_size(&lfs3, "c") => strlen(c_); + + // try reading files + uint8_t rbuf[256]; + lfs3_get(&lfs3, "a", rbuf, sizeof(rbuf)) => strlen(a_); + assert(memcmp(rbuf, a_, strlen(a_)) == 0); + lfs3_get(&lfs3, "b", rbuf, sizeof(rbuf)) => strlen(b_); + assert(memcmp(rbuf, b_, strlen(b_)) == 0); + lfs3_get(&lfs3, "c", rbuf, sizeof(rbuf)) => strlen(c_); + assert(memcmp(rbuf, c_, strlen(c_)) == 0); + + // opened files should be updated + lfs3_file_rewind(&lfs3, &file_a) => 0; + lfs3_file_read(&lfs3, &file_a, rbuf, sizeof(rbuf)) => strlen(a_); + assert(memcmp(rbuf, a_, strlen(a_)) == 0); + lfs3_file_rewind(&lfs3, &file_b) => 0; + lfs3_file_read(&lfs3, &file_b, rbuf, sizeof(rbuf)) => strlen(b_); + assert(memcmp(rbuf, b_, strlen(b_)) == 0); + lfs3_file_rewind(&lfs3, &file_c) => 0; + lfs3_file_read(&lfs3, &file_c, rbuf, sizeof(rbuf)) => strlen(c_); + assert(memcmp(rbuf, c_, strlen(c_)) == 0); + + lfs3_file_close(&lfs3, &file_a) => 0; + lfs3_file_close(&lfs3, &file_b) => 0; + lfs3_file_close(&lfs3, &file_c) => 0; + + lfs3_unmount(&lfs3) => 0; +''' + +# test kv files don't interfere with desync files +[cases.test_kv_interop_desync] +defines.STICKYNOTES = [false, true] +code = ''' + lfs3_t lfs3; + lfs3_format(&lfs3, LFS3_F_RDWR, CFG) => 0; + lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0; + + // open some files desync + const char *a = "One 18.25 ounce package chocolate cake mix."; + lfs3_file_t file_a; + lfs3_file_open(&lfs3, &file_a, "a", + LFS3_O_RDWR | LFS3_O_CREAT | LFS3_O_EXCL | LFS3_O_DESYNC) => 0; + lfs3_file_write(&lfs3, &file_a, a, strlen(a)) => strlen(a); + const char *b = "One can prepared coconut pecan frosting."; + lfs3_file_t file_b; + lfs3_file_open(&lfs3, &file_b, "b", + LFS3_O_RDWR | LFS3_O_CREAT | LFS3_O_EXCL | LFS3_O_DESYNC) => 0; + lfs3_file_write(&lfs3, &file_b, b, strlen(b)) => strlen(b); + const char *c = "Three slash four cup vegetable oil."; + lfs3_file_t file_c; + lfs3_file_open(&lfs3, &file_c, "c", + LFS3_O_RDWR | LFS3_O_CREAT | LFS3_O_EXCL | LFS3_O_DESYNC) => 0; + lfs3_file_write(&lfs3, &file_c, c, strlen(c)) => strlen(c); + + // sync to create if creating + if (!STICKYNOTES) { + lfs3_file_sync(&lfs3, &file_a) => 0; + lfs3_file_sync(&lfs3, &file_b) => 0; + lfs3_file_sync(&lfs3, &file_c) => 0; + // but then desync again! + lfs3_file_desync(&lfs3, &file_a) => 0; + lfs3_file_desync(&lfs3, &file_b) => 0; + lfs3_file_desync(&lfs3, &file_c) => 0; + } + + // set the new values + const char *a_ = "Four large eggs. One cup semi-sweet chocolate chips."; + lfs3_set(&lfs3, "a", a_, strlen(a_)) => 0; + const char *b_ = "Three slash four cup butter or margarine."; + lfs3_set(&lfs3, "b", b_, strlen(b_)) => 0; + const char *c_ = "One and two third cups granulated sugar."; + lfs3_set(&lfs3, "c", c_, strlen(c_)) => 0; + + // files should have been created + struct lfs3_info info; + lfs3_stat(&lfs3, "a", &info) => 0; + assert(strcmp(info.name, "a") == 0); + assert(info.type == LFS3_TYPE_REG); + assert(info.size == strlen(a_)); + lfs3_stat(&lfs3, "b", &info) => 0; + assert(strcmp(info.name, "b") == 0); + assert(info.type == LFS3_TYPE_REG); + assert(info.size == strlen(b_)); + lfs3_stat(&lfs3, "c", &info) => 0; + assert(strcmp(info.name, "c") == 0); + assert(info.type == LFS3_TYPE_REG); + assert(info.size == strlen(c_)); + + // try getting the file sizes + lfs3_size(&lfs3, "a") => strlen(a_); + lfs3_size(&lfs3, "b") => strlen(b_); + lfs3_size(&lfs3, "c") => strlen(c_); + + // try reading files + uint8_t rbuf[256]; + lfs3_get(&lfs3, "a", rbuf, sizeof(rbuf)) => strlen(a_); + assert(memcmp(rbuf, a_, strlen(a_)) == 0); + lfs3_get(&lfs3, "b", rbuf, sizeof(rbuf)) => strlen(b_); + assert(memcmp(rbuf, b_, strlen(b_)) == 0); + lfs3_get(&lfs3, "c", rbuf, sizeof(rbuf)) => strlen(c_); + assert(memcmp(rbuf, c_, strlen(c_)) == 0); + + // opened files should _not_ be updated + lfs3_file_rewind(&lfs3, &file_a) => 0; + lfs3_file_read(&lfs3, &file_a, rbuf, sizeof(rbuf)) => strlen(a); + assert(memcmp(rbuf, a, strlen(a)) == 0); + lfs3_file_rewind(&lfs3, &file_b) => 0; + lfs3_file_read(&lfs3, &file_b, rbuf, sizeof(rbuf)) => strlen(b); + assert(memcmp(rbuf, b, strlen(b)) == 0); + lfs3_file_rewind(&lfs3, &file_c) => 0; + lfs3_file_read(&lfs3, &file_c, rbuf, sizeof(rbuf)) => strlen(c); + assert(memcmp(rbuf, c, strlen(c)) == 0); + + lfs3_file_close(&lfs3, &file_a) => 0; + lfs3_file_close(&lfs3, &file_b) => 0; + lfs3_file_close(&lfs3, &file_c) => 0; + + lfs3_unmount(&lfs3) => 0; +''' + +# test kv files work with resyncing files +[cases.test_kv_interop_resync] +defines.STICKYNOTES = [false, true] +code = ''' + lfs3_t lfs3; + lfs3_format(&lfs3, LFS3_F_RDWR, CFG) => 0; + lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0; + + // open some files desync + const char *a = "One 18.25 ounce package chocolate cake mix."; + lfs3_file_t file_a; + lfs3_file_open(&lfs3, &file_a, "a", + LFS3_O_RDWR | LFS3_O_CREAT | LFS3_O_EXCL | LFS3_O_DESYNC) => 0; + lfs3_file_write(&lfs3, &file_a, a, strlen(a)) => strlen(a); + const char *b = "One can prepared coconut pecan frosting."; + lfs3_file_t file_b; + lfs3_file_open(&lfs3, &file_b, "b", + LFS3_O_RDWR | LFS3_O_CREAT | LFS3_O_EXCL | LFS3_O_DESYNC) => 0; + lfs3_file_write(&lfs3, &file_b, b, strlen(b)) => strlen(b); + const char *c = "Three slash four cup vegetable oil."; + lfs3_file_t file_c; + lfs3_file_open(&lfs3, &file_c, "c", + LFS3_O_RDWR | LFS3_O_CREAT | LFS3_O_EXCL | LFS3_O_DESYNC) => 0; + lfs3_file_write(&lfs3, &file_c, c, strlen(c)) => strlen(c); + + // sync to create if creating + if (!STICKYNOTES) { + lfs3_file_sync(&lfs3, &file_a) => 0; + lfs3_file_sync(&lfs3, &file_b) => 0; + lfs3_file_sync(&lfs3, &file_c) => 0; + // but then desync again! + lfs3_file_desync(&lfs3, &file_a) => 0; + lfs3_file_desync(&lfs3, &file_b) => 0; + lfs3_file_desync(&lfs3, &file_c) => 0; + } + + // set the new values + const char *a_ = "Four large eggs. One cup semi-sweet chocolate chips."; + lfs3_set(&lfs3, "a", a_, strlen(a_)) => 0; + const char *b_ = "Three slash four cup butter or margarine."; + lfs3_set(&lfs3, "b", b_, strlen(b_)) => 0; + const char *c_ = "One and two third cups granulated sugar."; + lfs3_set(&lfs3, "c", c_, strlen(c_)) => 0; + + // files should have been created + struct lfs3_info info; + lfs3_stat(&lfs3, "a", &info) => 0; + assert(strcmp(info.name, "a") == 0); + assert(info.type == LFS3_TYPE_REG); + assert(info.size == strlen(a_)); + lfs3_stat(&lfs3, "b", &info) => 0; + assert(strcmp(info.name, "b") == 0); + assert(info.type == LFS3_TYPE_REG); + assert(info.size == strlen(b_)); + lfs3_stat(&lfs3, "c", &info) => 0; + assert(strcmp(info.name, "c") == 0); + assert(info.type == LFS3_TYPE_REG); + assert(info.size == strlen(c_)); + + // try getting the file sizes + lfs3_size(&lfs3, "a") => strlen(a_); + lfs3_size(&lfs3, "b") => strlen(b_); + lfs3_size(&lfs3, "c") => strlen(c_); + + // try reading files + uint8_t rbuf[256]; + lfs3_get(&lfs3, "a", rbuf, sizeof(rbuf)) => strlen(a_); + assert(memcmp(rbuf, a_, strlen(a_)) == 0); + lfs3_get(&lfs3, "b", rbuf, sizeof(rbuf)) => strlen(b_); + assert(memcmp(rbuf, b_, strlen(b_)) == 0); + lfs3_get(&lfs3, "c", rbuf, sizeof(rbuf)) => strlen(c_); + assert(memcmp(rbuf, c_, strlen(c_)) == 0); + + // opened files should _not_ be updated + lfs3_file_rewind(&lfs3, &file_a) => 0; + lfs3_file_read(&lfs3, &file_a, rbuf, sizeof(rbuf)) => strlen(a); + assert(memcmp(rbuf, a, strlen(a)) == 0); + lfs3_file_rewind(&lfs3, &file_b) => 0; + lfs3_file_read(&lfs3, &file_b, rbuf, sizeof(rbuf)) => strlen(b); + assert(memcmp(rbuf, b, strlen(b)) == 0); + lfs3_file_rewind(&lfs3, &file_c) => 0; + lfs3_file_read(&lfs3, &file_c, rbuf, sizeof(rbuf)) => strlen(c); + assert(memcmp(rbuf, c, strlen(c)) == 0); + + // but if we resync they should be updated + lfs3_file_resync(&lfs3, &file_a) => 0; + lfs3_file_resync(&lfs3, &file_b) => 0; + lfs3_file_resync(&lfs3, &file_c) => 0; + lfs3_file_rewind(&lfs3, &file_a) => 0; + lfs3_file_read(&lfs3, &file_a, rbuf, sizeof(rbuf)) => strlen(a_); + assert(memcmp(rbuf, a_, strlen(a_)) == 0); + lfs3_file_rewind(&lfs3, &file_b) => 0; + lfs3_file_read(&lfs3, &file_b, rbuf, sizeof(rbuf)) => strlen(b_); + assert(memcmp(rbuf, b_, strlen(b_)) == 0); + lfs3_file_rewind(&lfs3, &file_c) => 0; + lfs3_file_read(&lfs3, &file_c, rbuf, sizeof(rbuf)) => strlen(c_); + assert(memcmp(rbuf, c_, strlen(c_)) == 0); + + lfs3_file_close(&lfs3, &file_a) => 0; + lfs3_file_close(&lfs3, &file_b) => 0; + lfs3_file_close(&lfs3, &file_c) => 0; + + lfs3_unmount(&lfs3) => 0; +''' + +# fuzz kv files and normal files +[cases.test_kv_interop_fuzz] +defines.STICKYNOTES = [false, true] +defines.N = 10 +defines.SIZE = 4 +defines.OPS = ['4*N', '40*N'] +defines.SEED = 'range(20)' +fuzz = 'SEED' +code = ''' + lfs3_t lfs3; + lfs3_format(&lfs3, LFS3_F_RDWR, CFG) => 0; + lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0; + + // set up a simulation to compare against + uint32_t *sim_prngs = malloc(N*sizeof(uint32_t)); + memset(sim_prngs, 0, N*sizeof(uint32_t)); + // keep track of opened files + lfs3_file_t **sim_files = malloc(N*sizeof(lfs3_file_t*)); + memset(sim_files, 0, N*sizeof(lfs3_file_t*)); + + uint32_t prng = SEED; + for (lfs3_size_t i = 0; i < OPS; i++) { + // choose which operation to do + uint8_t op = TEST_PRNG(&prng) % 3; + + // create a kv file? + if (op == 0) { + // choose a kv file + lfs3_size_t x = TEST_PRNG(&prng) % N; + // choose a prng + uint32_t wprng = TEST_PRNG(&prng); + + // create the file + char name[256]; + sprintf(name, "key%03x", x); + uint32_t wprng_ = wprng; + uint8_t wbuf[SIZE]; + for (lfs3_size_t j = 0; j < SIZE; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&wprng_) % 26); + } + lfs3_set(&lfs3, name, wbuf, SIZE) => 0; + + // update our sim + sim_prngs[x] = wprng; + + // remove a kv file? + } else if (op == 1) { + // choose a kv file + lfs3_size_t x = TEST_PRNG(&prng) % N; + + // remove the file + char name[256]; + sprintf(name, "key%03x", x); + if (sim_prngs[x]) { + lfs3_remove(&lfs3, name) => 0; + } else { + lfs3_remove(&lfs3, name) => LFS3_ERR_NOENT; + } + + // update our sim + sim_prngs[x] = 0; + if (sim_files[x]) { + lfs3_file_close(&lfs3, sim_files[x]) => 0; + free(sim_files[x]); + } + sim_files[x] = NULL; + + // open a normal file? + } else if (op == 2) { + // choose a file + lfs3_size_t x = TEST_PRNG(&prng) % N; + // choose a prng + uint32_t wprng = TEST_PRNG(&prng); + + // close if open + if (sim_files[x]) { + lfs3_file_close(&lfs3, sim_files[x]) => 0; + free(sim_files[x]); + } + + // create/open the file + sim_files[x] = malloc(sizeof(lfs3_file_t)); + char name[256]; + sprintf(name, "key%03x", x); + uint32_t wprng_ = wprng; + uint8_t wbuf[SIZE]; + for (lfs3_size_t j = 0; j < SIZE; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&wprng_) % 26); + } + lfs3_file_open(&lfs3, sim_files[x], name, + LFS3_O_RDWR | LFS3_O_CREAT) => 0; + lfs3_file_write(&lfs3, sim_files[x], wbuf, SIZE) => SIZE; + lfs3_file_sync(&lfs3, sim_files[x]) => 0; + + // update our sim + sim_prngs[x] = wprng; + } + } + + // try getting each file size + for (lfs3_size_t j = 0; j < N; j++) { + char name[256]; + sprintf(name, "key%03x", j); + if (sim_prngs[j]) { + lfs3_size(&lfs3, name) => SIZE; + } else { + lfs3_size(&lfs3, name) => LFS3_ERR_NOENT; + } + } + + // try reading each file + for (lfs3_size_t j = 0; j < N; j++) { + char name[256]; + sprintf(name, "key%03x", j); + if (sim_prngs[j]) { + uint32_t wprng_ = sim_prngs[j]; + uint8_t wbuf[SIZE]; + for (lfs3_size_t i = 0; i < SIZE; i++) { + wbuf[i] = 'a' + (TEST_PRNG(&wprng_) % 26); + } + uint8_t rbuf[256]; + lfs3_get(&lfs3, name, rbuf, sizeof(rbuf)) => SIZE; + assert(memcmp(rbuf, wbuf, SIZE) == 0); + } else { + uint8_t rbuf[256]; + lfs3_get(&lfs3, name, rbuf, sizeof(rbuf)) + => LFS3_ERR_NOENT; + } + } + + // try reading the opened file handles + for (lfs3_size_t j = 0; j < N; j++) { + if (sim_files[j]) { + lfs3_file_rewind(&lfs3, sim_files[j]) => 0; + uint32_t wprng_ = sim_prngs[j]; + uint8_t wbuf[SIZE]; + for (lfs3_size_t i = 0; i < SIZE; i++) { + wbuf[i] = 'a' + (TEST_PRNG(&wprng_) % 26); + } + uint8_t rbuf[256]; + lfs3_file_read(&lfs3, sim_files[j], rbuf, sizeof(rbuf)) => SIZE; + assert(memcmp(rbuf, wbuf, SIZE) == 0); + } + } + + // clean up sim/files + free(sim_prngs); + for (lfs3_size_t j = 0; j < N; j++) { + if (sim_files[j]) { + lfs3_file_close(&lfs3, sim_files[j]) => 0; + free(sim_files[j]); + } + } + free(sim_files); + lfs3_unmount(&lfs3) => 0; +''' + +# fuzz kv files and normal files +[cases.test_kv_interop_fuzz_big] +defines.STICKYNOTES = [false, true] +defines.N = 10 +# size is more an upper limit here +defines.SIZE = 40000 +defines.OPS = ['4*N', '40*N'] +defines.SEED = 'range(20)' +fuzz = 'SEED' +code = ''' + lfs3_t lfs3; + lfs3_format(&lfs3, LFS3_F_RDWR, CFG) => 0; + lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0; + + // set up a simulation to compare against + lfs3_off_t *sim_sizes = malloc(N*sizeof(lfs3_off_t)); + memset(sim_sizes, 0, N*sizeof(lfs3_off_t)); + uint32_t *sim_prngs = malloc(N*sizeof(uint32_t)); + memset(sim_prngs, 0, N*sizeof(uint32_t)); + // keep track of opened files + lfs3_file_t **sim_files = malloc(N*sizeof(lfs3_file_t*)); + memset(sim_files, 0, N*sizeof(lfs3_file_t*)); + + uint32_t prng = SEED; + for (lfs3_size_t i = 0; i < OPS; i++) { + // choose which operation to do + uint8_t op = TEST_PRNG(&prng) % 3; + + // create a kv file? + if (op == 0) { + // choose a kv file + lfs3_size_t x = TEST_PRNG(&prng) % N; + // choose a size, bias towards zero + lfs3_off_t size = lfs3_abs( + (TEST_PRNG(&prng) % SIZE) + + (TEST_PRNG(&prng) % SIZE) + - SIZE); + // choose a prng + uint32_t wprng = TEST_PRNG(&prng); + + // create the file + char name[256]; + sprintf(name, "key%03x", x); + uint32_t wprng_ = wprng; + uint8_t wbuf[SIZE]; + for (lfs3_size_t j = 0; j < size; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&wprng_) % 26); + } + lfs3_set(&lfs3, name, wbuf, size) => 0; + + // update our sim + sim_sizes[x] = size; + sim_prngs[x] = wprng; + + // remove a kv file? + } else if (op == 1) { + // choose a kv file + lfs3_size_t x = TEST_PRNG(&prng) % N; + + // remove the file + char name[256]; + sprintf(name, "key%03x", x); + if (sim_prngs[x]) { + lfs3_remove(&lfs3, name) => 0; + } else { + lfs3_remove(&lfs3, name) => LFS3_ERR_NOENT; + } + + // update our sim + sim_prngs[x] = 0; + if (sim_files[x]) { + lfs3_file_close(&lfs3, sim_files[x]) => 0; + free(sim_files[x]); + } + sim_files[x] = NULL; + + // open a normal file? + } else if (op == 2) { + // choose a file + lfs3_size_t x = TEST_PRNG(&prng) % N; + // choose a size, bias towards zero + lfs3_off_t size = lfs3_abs( + (TEST_PRNG(&prng) % SIZE) + + (TEST_PRNG(&prng) % SIZE) + - SIZE); + // choose a prng + uint32_t wprng = TEST_PRNG(&prng); + + // close if open + if (sim_files[x]) { + lfs3_file_close(&lfs3, sim_files[x]) => 0; + free(sim_files[x]); + } + + // create/open the file + sim_files[x] = malloc(sizeof(lfs3_file_t)); + char name[256]; + sprintf(name, "key%03x", x); + uint32_t wprng_ = wprng; + uint8_t wbuf[SIZE]; + for (lfs3_size_t j = 0; j < size; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&wprng_) % 26); + } + lfs3_file_open(&lfs3, sim_files[x], name, + LFS3_O_RDWR | LFS3_O_CREAT | LFS3_O_TRUNC) => 0; + lfs3_file_write(&lfs3, sim_files[x], wbuf, size) => size; + lfs3_file_sync(&lfs3, sim_files[x]) => 0; + + // update our sim + sim_sizes[x] = size; + sim_prngs[x] = wprng; + } + } + + // try getting each file size + for (lfs3_size_t j = 0; j < N; j++) { + char name[256]; + sprintf(name, "key%03x", j); + if (sim_prngs[j]) { + lfs3_size(&lfs3, name) => sim_sizes[j]; + } else { + lfs3_size(&lfs3, name) => LFS3_ERR_NOENT; + } + } + + // try reading each file + for (lfs3_size_t j = 0; j < N; j++) { + char name[256]; + sprintf(name, "key%03x", j); + if (sim_prngs[j]) { + uint32_t wprng_ = sim_prngs[j]; + uint8_t wbuf[SIZE]; + for (lfs3_size_t i = 0; i < sim_sizes[j]; i++) { + wbuf[i] = 'a' + (TEST_PRNG(&wprng_) % 26); + } + uint8_t rbuf[SIZE]; + lfs3_get(&lfs3, name, rbuf, sizeof(rbuf)) => sim_sizes[j]; + assert(memcmp(rbuf, wbuf, sim_sizes[j]) == 0); + } else { + uint8_t rbuf[SIZE]; + lfs3_get(&lfs3, name, rbuf, sizeof(rbuf)) + => LFS3_ERR_NOENT; + } + } + + // try reading the opened file handles + for (lfs3_size_t j = 0; j < N; j++) { + if (sim_files[j]) { + lfs3_file_rewind(&lfs3, sim_files[j]) => 0; + uint32_t wprng_ = sim_prngs[j]; + uint8_t wbuf[SIZE]; + for (lfs3_size_t i = 0; i < sim_sizes[j]; i++) { + wbuf[i] = 'a' + (TEST_PRNG(&wprng_) % 26); + } + uint8_t rbuf[SIZE]; + lfs3_file_read(&lfs3, sim_files[j], rbuf, sizeof(rbuf)) + => sim_sizes[j]; + assert(memcmp(rbuf, wbuf, sim_sizes[j]) == 0); + } + } + + // clean up sim/files + free(sim_prngs); + for (lfs3_size_t j = 0; j < N; j++) { + if (sim_files[j]) { + lfs3_file_close(&lfs3, sim_files[j]) => 0; + free(sim_files[j]); + } + } + free(sim_files); + lfs3_unmount(&lfs3) => 0; +''' +