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