diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 46e57e5b..07f34a3a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -374,6 +374,22 @@ jobs: run: | CFLAGS="$CFLAGS -DLFS2_NO_INTRINSICS" make test + test-shrink: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: install + run: | + # need a few things + sudo apt-get update -qq + sudo apt-get install -qq gcc python3 python3-pip + pip3 install toml + gcc --version + python3 --version + - name: test-no-intrinsics + run: | + CFLAGS="$CFLAGS -DLFS2_SHRINKNONRELOCATING" make test + # run with all trace options enabled to at least make sure these # all compile test-yes-trace: @@ -454,8 +470,7 @@ jobs: TESTFLAGS="$TESTFLAGS --valgrind --context=1024 -Gdefault -Pnone" \ make test - # test that compilation is warning free under clang - # run with Clang, mostly to check for Clang-specific warnings + # compile/run with Clang, mostly to check for Clang-specific warnings test-clang: runs-on: ubuntu-latest steps: @@ -469,12 +484,8 @@ jobs: python3 --version - name: test-clang run: | - # override CFLAGS since Clang does not support -fcallgraph-info - # and -ftrack-macro-expansions - make \ - CC=clang \ - CFLAGS="$CFLAGS -MMD -g3 -I. -std=c99 -Wall -Wextra -pedantic" \ - test + CC=clang \ + make test # run benchmarks # diff --git a/Makefile b/Makefile index 39069ac9..307a2ea3 100644 --- a/Makefile +++ b/Makefile @@ -18,6 +18,12 @@ VALGRIND ?= valgrind GDB ?= gdb PERF ?= perf +# guess clang or gcc (clang sometimes masquerades as gcc because of +# course it does) +ifneq ($(shell $(CC) --version | grep clang),) +NO_GCC = 1 +endif + SRC ?= $(filter-out $(wildcard *.t.* *.b.*),$(wildcard *.c)) OBJ := $(SRC:%.c=$(BUILDDIR)/%.o) DEP := $(SRC:%.c=$(BUILDDIR)/%.d) @@ -59,12 +65,15 @@ BENCH_PERF := $(BENCH_RUNNER:%=%.perf) BENCH_TRACE := $(BENCH_RUNNER:%=%.trace) BENCH_CSV := $(BENCH_RUNNER:%=%.csv) -CFLAGS += -fcallgraph-info=su CFLAGS += -g3 CFLAGS += -I. CFLAGS += -std=c99 -Wall -Wextra -pedantic CFLAGS += -Wmissing-prototypes +ifndef NO_GCC +CFLAGS += -fcallgraph-info=su CFLAGS += -ftrack-macro-expansion=0 +endif + ifdef DEBUG CFLAGS += -O0 else diff --git a/lfs2.c b/lfs2.c index f9ce2cf2..abdec19d 100644 --- a/lfs2.c +++ b/lfs2.c @@ -3932,7 +3932,9 @@ static int lfs2_remove_(lfs2_t *lfs2, const char *path) { } lfs2->mlist = dir.next; - if (lfs2_tag_type3(tag) == LFS2_TYPE_DIR) { + if (lfs2_gstate_hasorphans(&lfs2->gstate)) { + LFS2_ASSERT(lfs2_tag_type3(tag) == LFS2_TYPE_DIR); + // fix orphan err = lfs2_fs_preporphans(lfs2, -1); if (err) { @@ -4076,8 +4078,10 @@ static int lfs2_rename_(lfs2_t *lfs2, const char *oldpath, const char *newpath) } lfs2->mlist = prevdir.next; - if (prevtag != LFS2_ERR_NOENT - && lfs2_tag_type3(prevtag) == LFS2_TYPE_DIR) { + if (lfs2_gstate_hasorphans(&lfs2->gstate)) { + LFS2_ASSERT(prevtag != LFS2_ERR_NOENT + && lfs2_tag_type3(prevtag) == LFS2_TYPE_DIR); + // fix orphan err = lfs2_fs_preporphans(lfs2, -1); if (err) { @@ -5233,40 +5237,64 @@ static int lfs2_fs_gc_(lfs2_t *lfs2) { #endif #ifndef LFS2_READONLY +#ifdef LFS2_SHRINKNONRELOCATING +static int lfs2_shrink_checkblock(void *data, lfs2_block_t block) { + lfs2_size_t threshold = *((lfs2_size_t*)data); + if (block >= threshold) { + return LFS2_ERR_NOTEMPTY; + } + return 0; +} +#endif + static int lfs2_fs_grow_(lfs2_t *lfs2, lfs2_size_t block_count) { + int err; + + if (block_count == lfs2->block_count) { + return 0; + } + + +#ifndef LFS2_SHRINKNONRELOCATING // shrinking is not supported LFS2_ASSERT(block_count >= lfs2->block_count); - - if (block_count > lfs2->block_count) { - lfs2->block_count = block_count; - - // fetch the root - lfs2_mdir_t root; - int err = lfs2_dir_fetch(lfs2, &root, lfs2->root); - if (err) { - return err; - } - - // update the superblock - lfs2_superblock_t superblock; - lfs2_stag_t tag = lfs2_dir_get(lfs2, &root, LFS2_MKTAG(0x7ff, 0x3ff, 0), - LFS2_MKTAG(LFS2_TYPE_INLINESTRUCT, 0, sizeof(superblock)), - &superblock); - if (tag < 0) { - return tag; - } - lfs2_superblock_fromle32(&superblock); - - superblock.block_count = lfs2->block_count; - - lfs2_superblock_tole32(&superblock); - err = lfs2_dir_commit(lfs2, &root, LFS2_MKATTRS( - {tag, &superblock})); +#endif +#ifdef LFS2_SHRINKNONRELOCATING + if (block_count < lfs2->block_count) { + err = lfs2_fs_traverse_(lfs2, lfs2_shrink_checkblock, &block_count, true); if (err) { return err; } } +#endif + lfs2->block_count = block_count; + + // fetch the root + lfs2_mdir_t root; + err = lfs2_dir_fetch(lfs2, &root, lfs2->root); + if (err) { + return err; + } + + // update the superblock + lfs2_superblock_t superblock; + lfs2_stag_t tag = lfs2_dir_get(lfs2, &root, LFS2_MKTAG(0x7ff, 0x3ff, 0), + LFS2_MKTAG(LFS2_TYPE_INLINESTRUCT, 0, sizeof(superblock)), + &superblock); + if (tag < 0) { + return tag; + } + lfs2_superblock_fromle32(&superblock); + + superblock.block_count = lfs2->block_count; + + lfs2_superblock_tole32(&superblock); + err = lfs2_dir_commit(lfs2, &root, LFS2_MKATTRS( + {tag, &superblock})); + if (err) { + return err; + } return 0; } #endif diff --git a/lfs2.h b/lfs2.h index f503fd00..77477aaf 100644 --- a/lfs2.h +++ b/lfs2.h @@ -21,7 +21,7 @@ extern "C" // Software library version // Major (top-nibble), incremented on backwards incompatible changes // Minor (bottom-nibble), incremented on feature additions -#define LFS2_VERSION 0x0002000a +#define LFS2_VERSION 0x0002000b #define LFS2_VERSION_MAJOR (0xffff & (LFS2_VERSION >> 16)) #define LFS2_VERSION_MINOR (0xffff & (LFS2_VERSION >> 0)) @@ -766,7 +766,11 @@ int lfs2_fs_gc(lfs2_t *lfs2); // Grows the filesystem to a new size, updating the superblock with the new // block count. // -// Note: This is irreversible. +// If LFS2_SHRINKNONRELOCATING is defined, this function will also accept +// block_counts smaller than the current configuration, after checking +// that none of the blocks that are being removed are in use. +// Note that littlefs's pseudorandom block allocation means that +// this is very unlikely to work in the general case. // // Returns a negative error code on failure. int lfs2_fs_grow(lfs2_t *lfs2, lfs2_size_t block_count); diff --git a/lfs2_util.h b/lfs2_util.h index 48d9f4c5..12c82a63 100644 --- a/lfs2_util.h +++ b/lfs2_util.h @@ -195,10 +195,10 @@ static inline uint32_t lfs2_fromle32(uint32_t a) { (defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)) return __builtin_bswap32(a); #else - return (((uint8_t*)&a)[0] << 0) | - (((uint8_t*)&a)[1] << 8) | - (((uint8_t*)&a)[2] << 16) | - (((uint8_t*)&a)[3] << 24); + return ((uint32_t)((uint8_t*)&a)[0] << 0) | + ((uint32_t)((uint8_t*)&a)[1] << 8) | + ((uint32_t)((uint8_t*)&a)[2] << 16) | + ((uint32_t)((uint8_t*)&a)[3] << 24); #endif } @@ -218,10 +218,10 @@ static inline uint32_t lfs2_frombe32(uint32_t a) { (defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) return a; #else - return (((uint8_t*)&a)[0] << 24) | - (((uint8_t*)&a)[1] << 16) | - (((uint8_t*)&a)[2] << 8) | - (((uint8_t*)&a)[3] << 0); + return ((uint32_t)((uint8_t*)&a)[0] << 24) | + ((uint32_t)((uint8_t*)&a)[1] << 16) | + ((uint32_t)((uint8_t*)&a)[2] << 8) | + ((uint32_t)((uint8_t*)&a)[3] << 0); #endif } @@ -231,8 +231,8 @@ static inline uint32_t lfs2_tobe32(uint32_t a) { // Calculate CRC-32 with polynomial = 0x04c11db7 #ifdef LFS2_CRC -uint32_t lfs2_crc(uint32_t crc, const void *buffer, size_t size) { - return LFS2_CRC(crc, buffer, size) +static inline uint32_t lfs2_crc(uint32_t crc, const void *buffer, size_t size) { + return LFS2_CRC(crc, buffer, size); } #else uint32_t lfs2_crc(uint32_t crc, const void *buffer, size_t size); diff --git a/runners/bench_runner.c b/runners/bench_runner.c index c959629d..04a387a6 100644 --- a/runners/bench_runner.c +++ b/runners/bench_runner.c @@ -123,8 +123,13 @@ typedef struct bench_id { // bench suites are linked into a custom ld section +#if defined(__APPLE__) +extern struct bench_suite __start__bench_suites __asm("section$start$__DATA$_bench_suites"); +extern struct bench_suite __stop__bench_suites __asm("section$end$__DATA$_bench_suites"); +#else extern struct bench_suite __start__bench_suites; extern struct bench_suite __stop__bench_suites; +#endif const struct bench_suite *bench_suites = &__start__bench_suites; #define BENCH_SUITE_COUNT \ diff --git a/runners/test_runner.c b/runners/test_runner.c index e565d484..58e3e22f 100644 --- a/runners/test_runner.c +++ b/runners/test_runner.c @@ -136,8 +136,13 @@ typedef struct test_id { // test suites are linked into a custom ld section +#if defined(__APPLE__) +extern struct test_suite __start__test_suites __asm("section$start$__DATA$_test_suites"); +extern struct test_suite __stop__test_suites __asm("section$end$__DATA$_test_suites"); +#else extern struct test_suite __start__test_suites; extern struct test_suite __stop__test_suites; +#endif const struct test_suite *test_suites = &__start__test_suites; #define TEST_SUITE_COUNT \ diff --git a/scripts/bench.py b/scripts/bench.py index f9123f82..6c0b3453 100755 --- a/scripts/bench.py +++ b/scripts/bench.py @@ -404,12 +404,15 @@ def compile(bench_paths, **args): f.writeln() # create suite struct - # + f.writeln('#if defined(__APPLE__)') + f.writeln('__attribute__((section("__DATA,_bench_suites")))') + f.writeln('#else') # note we place this in the custom bench_suites section with # minimum alignment, otherwise GCC ups the alignment to # 32-bytes for some reason f.writeln('__attribute__((section("_bench_suites"), ' 'aligned(1)))') + f.writeln('#endif') f.writeln('const struct bench_suite __bench__%s__suite = {' % suite.name) f.writeln(4*' '+'.name = "%s",' % suite.name) diff --git a/scripts/changeprefix.py b/scripts/changeprefix.py index 35f07614..4135755c 100755 --- a/scripts/changeprefix.py +++ b/scripts/changeprefix.py @@ -73,7 +73,7 @@ def changefile(from_prefix, to_prefix, from_path, to_path, *, shutil.copystat(from_path, to_path) if to_path_temp: - os.rename(to_path, from_path) + shutil.move(to_path, from_path) elif from_path != '-': os.remove(from_path) diff --git a/scripts/test.py b/scripts/test.py index 19f9a9e8..8646f2d1 100755 --- a/scripts/test.py +++ b/scripts/test.py @@ -412,12 +412,15 @@ def compile(test_paths, **args): f.writeln() # create suite struct - # + f.writeln('#if defined(__APPLE__)') + f.writeln('__attribute__((section("__DATA,_test_suites")))') + f.writeln('#else') # note we place this in the custom test_suites section with # minimum alignment, otherwise GCC ups the alignment to # 32-bytes for some reason f.writeln('__attribute__((section("_test_suites"), ' 'aligned(1)))') + f.writeln('#endif') f.writeln('const struct test_suite __test__%s__suite = {' % suite.name) f.writeln(4*' '+'.name = "%s",' % suite.name) diff --git a/tests/test_orphans.toml b/tests/test_orphans.toml index ae6d4ddc..e297731e 100644 --- a/tests/test_orphans.toml +++ b/tests/test_orphans.toml @@ -207,7 +207,8 @@ code = ''' [cases.test_orphans_reentrant] reentrant = true # TODO fix this case, caused by non-DAG trees -if = '!(DEPTH == 3 && CACHE_SIZE != 64)' +# NOTE the second condition is required +if = '!(DEPTH == 3 && CACHE_SIZE != 64) && 2*FILES < BLOCK_COUNT' defines = [ {FILES=6, DEPTH=1, CYCLES=20}, {FILES=26, DEPTH=1, CYCLES=20}, @@ -271,3 +272,69 @@ code = ''' lfs2_unmount(&lfs2) => 0; ''' +# non-reentrant testing for orphans, this is the same as reentrant +# testing, but we test way more states than we could under powerloss +[cases.test_orphans_nonreentrant] +# TODO fix this case, caused by non-DAG trees +# NOTE the second condition is required +if = '!(DEPTH == 3 && CACHE_SIZE != 64) && 2*FILES < BLOCK_COUNT' +defines = [ + {FILES=6, DEPTH=1, CYCLES=2000}, + {FILES=26, DEPTH=1, CYCLES=2000}, + {FILES=3, DEPTH=3, CYCLES=2000}, +] +code = ''' + lfs2_t lfs2; + lfs2_format(&lfs2, cfg) => 0; + lfs2_mount(&lfs2, cfg) => 0; + + uint32_t prng = 1; + const char alpha[] = "abcdefghijklmnopqrstuvwxyz"; + for (unsigned i = 0; i < CYCLES; i++) { + // create random path + char full_path[256]; + for (unsigned d = 0; d < DEPTH; d++) { + sprintf(&full_path[2*d], "/%c", alpha[TEST_PRNG(&prng) % FILES]); + } + + // if it does not exist, we create it, else we destroy + struct lfs2_info info; + int res = lfs2_stat(&lfs2, full_path, &info); + if (res == LFS2_ERR_NOENT) { + // create each directory in turn, ignore if dir already exists + for (unsigned d = 0; d < DEPTH; d++) { + char path[1024]; + strcpy(path, full_path); + path[2*d+2] = '\0'; + int err = lfs2_mkdir(&lfs2, path); + assert(!err || err == LFS2_ERR_EXIST); + } + + for (unsigned d = 0; d < DEPTH; d++) { + char path[1024]; + strcpy(path, full_path); + path[2*d+2] = '\0'; + lfs2_stat(&lfs2, path, &info) => 0; + assert(strcmp(info.name, &path[2*d+1]) == 0); + assert(info.type == LFS2_TYPE_DIR); + } + } else { + // is valid dir? + assert(strcmp(info.name, &full_path[2*(DEPTH-1)+1]) == 0); + assert(info.type == LFS2_TYPE_DIR); + + // try to delete path in reverse order, ignore if dir is not empty + for (int d = DEPTH-1; d >= 0; d--) { + char path[1024]; + strcpy(path, full_path); + path[2*d+2] = '\0'; + int err = lfs2_remove(&lfs2, path); + assert(!err || err == LFS2_ERR_NOTEMPTY); + } + + lfs2_stat(&lfs2, full_path, &info) => LFS2_ERR_NOENT; + } + } + lfs2_unmount(&lfs2) => 0; +''' + diff --git a/tests/test_relocations.toml b/tests/test_relocations.toml index 60dfd62c..6e3af92c 100644 --- a/tests/test_relocations.toml +++ b/tests/test_relocations.toml @@ -341,3 +341,171 @@ code = ''' } lfs2_unmount(&lfs2) => 0; ''' + +# non-reentrant testing for orphans, this is the same as reentrant +# testing, but we test way more states than we could under powerloss +[cases.test_relocations_nonreentrant] +# TODO fix this case, caused by non-DAG trees +# NOTE the second condition is required +if = '!(DEPTH == 3 && CACHE_SIZE != 64) && 2*FILES < BLOCK_COUNT' +defines = [ + {FILES=6, DEPTH=1, CYCLES=2000, BLOCK_CYCLES=1}, + {FILES=26, DEPTH=1, CYCLES=2000, BLOCK_CYCLES=1}, + {FILES=3, DEPTH=3, CYCLES=2000, BLOCK_CYCLES=1}, +] +code = ''' + lfs2_t lfs2; + lfs2_format(&lfs2, cfg) => 0; + lfs2_mount(&lfs2, cfg) => 0; + + uint32_t prng = 1; + const char alpha[] = "abcdefghijklmnopqrstuvwxyz"; + for (unsigned i = 0; i < CYCLES; i++) { + // create random path + char full_path[256]; + for (unsigned d = 0; d < DEPTH; d++) { + sprintf(&full_path[2*d], "/%c", alpha[TEST_PRNG(&prng) % FILES]); + } + + // if it does not exist, we create it, else we destroy + struct lfs2_info info; + int res = lfs2_stat(&lfs2, full_path, &info); + if (res == LFS2_ERR_NOENT) { + // create each directory in turn, ignore if dir already exists + for (unsigned d = 0; d < DEPTH; d++) { + char path[1024]; + strcpy(path, full_path); + path[2*d+2] = '\0'; + int err = lfs2_mkdir(&lfs2, path); + assert(!err || err == LFS2_ERR_EXIST); + } + + for (unsigned d = 0; d < DEPTH; d++) { + char path[1024]; + strcpy(path, full_path); + path[2*d+2] = '\0'; + lfs2_stat(&lfs2, path, &info) => 0; + assert(strcmp(info.name, &path[2*d+1]) == 0); + assert(info.type == LFS2_TYPE_DIR); + } + } else { + // is valid dir? + assert(strcmp(info.name, &full_path[2*(DEPTH-1)+1]) == 0); + assert(info.type == LFS2_TYPE_DIR); + + // try to delete path in reverse order, ignore if dir is not empty + for (unsigned d = DEPTH-1; d+1 > 0; d--) { + char path[1024]; + strcpy(path, full_path); + path[2*d+2] = '\0'; + int err = lfs2_remove(&lfs2, path); + assert(!err || err == LFS2_ERR_NOTEMPTY); + } + + lfs2_stat(&lfs2, full_path, &info) => LFS2_ERR_NOENT; + } + } + lfs2_unmount(&lfs2) => 0; +''' + +# non-reentrant testing for relocations, but now with random renames! +[cases.test_relocations_nonreentrant_renames] +# TODO fix this case, caused by non-DAG trees +# NOTE the second condition is required +if = '!(DEPTH == 3 && CACHE_SIZE != 64) && 2*FILES < BLOCK_COUNT' +defines = [ + {FILES=6, DEPTH=1, CYCLES=2000, BLOCK_CYCLES=1}, + {FILES=26, DEPTH=1, CYCLES=2000, BLOCK_CYCLES=1}, + {FILES=3, DEPTH=3, CYCLES=2000, BLOCK_CYCLES=1}, +] +code = ''' + lfs2_t lfs2; + lfs2_format(&lfs2, cfg) => 0; + lfs2_mount(&lfs2, cfg) => 0; + + uint32_t prng = 1; + const char alpha[] = "abcdefghijklmnopqrstuvwxyz"; + for (unsigned i = 0; i < CYCLES; i++) { + // create random path + char full_path[256]; + for (unsigned d = 0; d < DEPTH; d++) { + sprintf(&full_path[2*d], "/%c", alpha[TEST_PRNG(&prng) % FILES]); + } + + // if it does not exist, we create it, else we destroy + struct lfs2_info info; + int res = lfs2_stat(&lfs2, full_path, &info); + assert(!res || res == LFS2_ERR_NOENT); + if (res == LFS2_ERR_NOENT) { + // create each directory in turn, ignore if dir already exists + for (unsigned d = 0; d < DEPTH; d++) { + char path[1024]; + strcpy(path, full_path); + path[2*d+2] = '\0'; + int err = lfs2_mkdir(&lfs2, path); + assert(!err || err == LFS2_ERR_EXIST); + } + + for (unsigned d = 0; d < DEPTH; d++) { + char path[1024]; + strcpy(path, full_path); + path[2*d+2] = '\0'; + lfs2_stat(&lfs2, path, &info) => 0; + assert(strcmp(info.name, &path[2*d+1]) == 0); + assert(info.type == LFS2_TYPE_DIR); + } + } else { + assert(strcmp(info.name, &full_path[2*(DEPTH-1)+1]) == 0); + assert(info.type == LFS2_TYPE_DIR); + + // create new random path + char new_path[256]; + for (unsigned d = 0; d < DEPTH; d++) { + sprintf(&new_path[2*d], "/%c", alpha[TEST_PRNG(&prng) % FILES]); + } + + // if new path does not exist, rename, otherwise destroy + res = lfs2_stat(&lfs2, new_path, &info); + assert(!res || res == LFS2_ERR_NOENT); + if (res == LFS2_ERR_NOENT) { + // stop once some dir is renamed + for (unsigned d = 0; d < DEPTH; d++) { + char path[1024]; + strcpy(&path[2*d], &full_path[2*d]); + path[2*d+2] = '\0'; + strcpy(&path[128+2*d], &new_path[2*d]); + path[128+2*d+2] = '\0'; + int err = lfs2_rename(&lfs2, path, path+128); + assert(!err || err == LFS2_ERR_NOTEMPTY); + if (!err) { + strcpy(path, path+128); + } + } + + for (unsigned d = 0; d < DEPTH; d++) { + char path[1024]; + strcpy(path, new_path); + path[2*d+2] = '\0'; + lfs2_stat(&lfs2, path, &info) => 0; + assert(strcmp(info.name, &path[2*d+1]) == 0); + assert(info.type == LFS2_TYPE_DIR); + } + + lfs2_stat(&lfs2, full_path, &info) => LFS2_ERR_NOENT; + } else { + // try to delete path in reverse order, + // ignore if dir is not empty + for (unsigned d = DEPTH-1; d+1 > 0; d--) { + char path[1024]; + strcpy(path, full_path); + path[2*d+2] = '\0'; + int err = lfs2_remove(&lfs2, path); + assert(!err || err == LFS2_ERR_NOTEMPTY); + } + + lfs2_stat(&lfs2, full_path, &info) => LFS2_ERR_NOENT; + } + } + } + lfs2_unmount(&lfs2) => 0; +''' diff --git a/tests/test_shrink.toml b/tests/test_shrink.toml new file mode 100644 index 00000000..efdc8be0 --- /dev/null +++ b/tests/test_shrink.toml @@ -0,0 +1,109 @@ +# simple shrink +[cases.test_shrink_simple] +defines.BLOCK_COUNT = [10, 15, 20] +defines.AFTER_BLOCK_COUNT = [5, 10, 15, 19] + +if = "AFTER_BLOCK_COUNT <= BLOCK_COUNT" +code = ''' +#ifdef LFS2_SHRINKNONRELOCATING + lfs2_t lfs2; + lfs2_format(&lfs2, cfg) => 0; + lfs2_mount(&lfs2, cfg) => 0; + lfs2_fs_grow(&lfs2, AFTER_BLOCK_COUNT) => 0; + lfs2_unmount(&lfs2); + if (BLOCK_COUNT != AFTER_BLOCK_COUNT) { + lfs2_mount(&lfs2, cfg) => LFS2_ERR_INVAL; + } + lfs2_t lfs22 = lfs2; + struct lfs2_config cfg2 = *cfg; + cfg2.block_count = AFTER_BLOCK_COUNT; + lfs22.cfg = &cfg2; + lfs2_mount(&lfs22, &cfg2) => 0; + lfs2_unmount(&lfs22) => 0; +#endif +''' + +# shrinking full +[cases.test_shrink_full] +defines.BLOCK_COUNT = [10, 15, 20] +defines.AFTER_BLOCK_COUNT = [5, 7, 10, 12, 15, 17, 20] +defines.FILES_COUNT = [7, 8, 9, 10] +if = "AFTER_BLOCK_COUNT <= BLOCK_COUNT && FILES_COUNT + 2 < BLOCK_COUNT" +code = ''' +#ifdef LFS2_SHRINKNONRELOCATING + lfs2_t lfs2; + lfs2_format(&lfs2, cfg) => 0; + // create FILES_COUNT files of BLOCK_SIZE - 50 bytes (to avoid inlining) + lfs2_mount(&lfs2, cfg) => 0; + for (int i = 0; i < FILES_COUNT + 1; i++) { + lfs2_file_t file; + char path[1024]; + sprintf(path, "file_%03d", i); + lfs2_file_open(&lfs2, &file, path, + LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_EXCL) => 0; + char wbuffer[BLOCK_SIZE]; + memset(wbuffer, 'b', BLOCK_SIZE); + // Ensure one block is taken per file, but that files are not inlined. + lfs2_size_t size = BLOCK_SIZE - 0x40; + sprintf(wbuffer, "Hi %03d", i); + lfs2_file_write(&lfs2, &file, wbuffer, size) => size; + lfs2_file_close(&lfs2, &file) => 0; + } + + int err = lfs2_fs_grow(&lfs2, AFTER_BLOCK_COUNT); + if (err == 0) { + for (int i = 0; i < FILES_COUNT + 1; i++) { + lfs2_file_t file; + char path[1024]; + sprintf(path, "file_%03d", i); + lfs2_file_open(&lfs2, &file, path, + LFS2_O_RDONLY ) => 0; + lfs2_size_t size = BLOCK_SIZE - 0x40; + char wbuffer[size]; + char wbuffer_ref[size]; + // Ensure one block is taken per file, but that files are not inlined. + memset(wbuffer_ref, 'b', size); + sprintf(wbuffer_ref, "Hi %03d", i); + lfs2_file_read(&lfs2, &file, wbuffer, BLOCK_SIZE) => size; + lfs2_file_close(&lfs2, &file) => 0; + for (lfs2_size_t j = 0; j < size; j++) { + wbuffer[j] => wbuffer_ref[j]; + } + } + } else { + assert(err == LFS2_ERR_NOTEMPTY); + } + + lfs2_unmount(&lfs2) => 0; + if (err == 0 ) { + if ( AFTER_BLOCK_COUNT != BLOCK_COUNT ) { + lfs2_mount(&lfs2, cfg) => LFS2_ERR_INVAL; + } + + lfs2_t lfs22 = lfs2; + struct lfs2_config cfg2 = *cfg; + cfg2.block_count = AFTER_BLOCK_COUNT; + lfs22.cfg = &cfg2; + lfs2_mount(&lfs22, &cfg2) => 0; + for (int i = 0; i < FILES_COUNT + 1; i++) { + lfs2_file_t file; + char path[1024]; + sprintf(path, "file_%03d", i); + lfs2_file_open(&lfs22, &file, path, + LFS2_O_RDONLY ) => 0; + lfs2_size_t size = BLOCK_SIZE - 0x40; + char wbuffer[size]; + char wbuffer_ref[size]; + // Ensure one block is taken per file, but that files are not inlined. + memset(wbuffer_ref, 'b', size); + sprintf(wbuffer_ref, "Hi %03d", i); + lfs2_file_read(&lfs22, &file, wbuffer, BLOCK_SIZE) => size; + lfs2_file_close(&lfs22, &file) => 0; + for (lfs2_size_t j = 0; j < size; j++) { + wbuffer[j] => wbuffer_ref[j]; + } + } + lfs2_unmount(&lfs22); + } +#endif +''' diff --git a/tests/test_superblocks.toml b/tests/test_superblocks.toml index 9cba80c2..7b92af49 100644 --- a/tests/test_superblocks.toml +++ b/tests/test_superblocks.toml @@ -524,6 +524,114 @@ code = ''' lfs2_unmount(&lfs2) => 0; ''' + +# mount and grow the filesystem +[cases.test_superblocks_shrink] +defines.BLOCK_COUNT = 'ERASE_COUNT' +defines.BLOCK_COUNT_2 = ['ERASE_COUNT/2', 'ERASE_COUNT/4', '2'] +defines.KNOWN_BLOCK_COUNT = [true, false] +code = ''' +#ifdef LFS2_SHRINKNONRELOCATING + lfs2_t lfs2; + lfs2_format(&lfs2, cfg) => 0; + + if (KNOWN_BLOCK_COUNT) { + cfg->block_count = BLOCK_COUNT; + } else { + cfg->block_count = 0; + } + + // mount with block_size < erase_size + lfs2_mount(&lfs2, cfg) => 0; + struct lfs2_fsinfo fsinfo; + lfs2_fs_stat(&lfs2, &fsinfo) => 0; + assert(fsinfo.block_size == BLOCK_SIZE); + assert(fsinfo.block_count == BLOCK_COUNT); + lfs2_unmount(&lfs2) => 0; + + // same size is a noop + lfs2_mount(&lfs2, cfg) => 0; + lfs2_fs_grow(&lfs2, BLOCK_COUNT) => 0; + lfs2_fs_stat(&lfs2, &fsinfo) => 0; + assert(fsinfo.block_size == BLOCK_SIZE); + assert(fsinfo.block_count == BLOCK_COUNT); + lfs2_unmount(&lfs2) => 0; + + lfs2_mount(&lfs2, cfg) => 0; + lfs2_fs_stat(&lfs2, &fsinfo) => 0; + assert(fsinfo.block_size == BLOCK_SIZE); + assert(fsinfo.block_count == BLOCK_COUNT); + lfs2_unmount(&lfs2) => 0; + + // grow to new size + lfs2_mount(&lfs2, cfg) => 0; + lfs2_fs_grow(&lfs2, BLOCK_COUNT_2) => 0; + lfs2_fs_stat(&lfs2, &fsinfo) => 0; + assert(fsinfo.block_size == BLOCK_SIZE); + assert(fsinfo.block_count == BLOCK_COUNT_2); + lfs2_unmount(&lfs2) => 0; + + if (KNOWN_BLOCK_COUNT) { + cfg->block_count = BLOCK_COUNT_2; + } else { + cfg->block_count = 0; + } + + lfs2_mount(&lfs2, cfg) => 0; + lfs2_fs_stat(&lfs2, &fsinfo) => 0; + assert(fsinfo.block_size == BLOCK_SIZE); + assert(fsinfo.block_count == BLOCK_COUNT_2); + lfs2_unmount(&lfs2) => 0; + + // mounting with the previous size should fail + cfg->block_count = BLOCK_COUNT; + lfs2_mount(&lfs2, cfg) => LFS2_ERR_INVAL; + + if (KNOWN_BLOCK_COUNT) { + cfg->block_count = BLOCK_COUNT_2; + } else { + cfg->block_count = 0; + } + + // same size is a noop + lfs2_mount(&lfs2, cfg) => 0; + lfs2_fs_grow(&lfs2, BLOCK_COUNT_2) => 0; + lfs2_fs_stat(&lfs2, &fsinfo) => 0; + assert(fsinfo.block_size == BLOCK_SIZE); + assert(fsinfo.block_count == BLOCK_COUNT_2); + lfs2_unmount(&lfs2) => 0; + + lfs2_mount(&lfs2, cfg) => 0; + lfs2_fs_stat(&lfs2, &fsinfo) => 0; + assert(fsinfo.block_size == BLOCK_SIZE); + assert(fsinfo.block_count == BLOCK_COUNT_2); + lfs2_unmount(&lfs2) => 0; + + // do some work + lfs2_mount(&lfs2, cfg) => 0; + lfs2_fs_stat(&lfs2, &fsinfo) => 0; + assert(fsinfo.block_size == BLOCK_SIZE); + assert(fsinfo.block_count == BLOCK_COUNT_2); + lfs2_file_t file; + lfs2_file_open(&lfs2, &file, "test", + LFS2_O_CREAT | LFS2_O_EXCL | LFS2_O_WRONLY) => 0; + lfs2_file_write(&lfs2, &file, "hello!", 6) => 6; + lfs2_file_close(&lfs2, &file) => 0; + lfs2_unmount(&lfs2) => 0; + + lfs2_mount(&lfs2, cfg) => 0; + lfs2_fs_stat(&lfs2, &fsinfo) => 0; + assert(fsinfo.block_size == BLOCK_SIZE); + assert(fsinfo.block_count == BLOCK_COUNT_2); + lfs2_file_open(&lfs2, &file, "test", LFS2_O_RDONLY) => 0; + uint8_t buffer[256]; + lfs2_file_read(&lfs2, &file, buffer, sizeof(buffer)) => 6; + lfs2_file_close(&lfs2, &file) => 0; + assert(memcmp(buffer, "hello!", 6) == 0); + lfs2_unmount(&lfs2) => 0; +#endif +''' + # test that metadata_max does not cause problems for superblock compaction [cases.test_superblocks_metadata_max] defines.METADATA_MAX = [