diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..a49c75c --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,109 @@ +name: release +on: + workflow_run: + workflows: [test] + branches: [master] + types: [completed] + +defaults: + run: + shell: bash -euv -o pipefail {0} + +jobs: + release: + runs-on: ubuntu-22.04 + + # need to manually check for a couple things + # - tests passed? + # - we are the most recent commit on master? + if: ${{github.event.workflow_run.conclusion == 'success' && + github.event.workflow_run.head_sha == github.sha}} + + steps: + - uses: actions/checkout@v2 + with: + ref: ${{github.event.workflow_run.head_sha}} + # need workflow access since we push branches + # containing workflows + token: ${{secrets.BOT_TOKEN}} + # need all tags + fetch-depth: 0 + + - name: find-version + run: | + # rip version from lfs_fuse.c + LFS_FUSE_VERSION="$( \ + grep -o '^#define LFS_FUSE_VERSION .*$' lfs_fuse.c \ + | awk '{print $3}')" + LFS_FUSE_VERSION_MAJOR="$((0xffff & ($LFS_FUSE_VERSION >> 16)))" + LFS_FUSE_VERSION_MINOR="$((0xffff & ($LFS_FUSE_VERSION >> 0)))" + + # find a new patch version based on what we find in our tags + LFS_FUSE_VERSION_PATCH="$( \ + ( git describe --tags --abbrev=0 \ + --match="v$LFS_FUSE_VERSION_MAJOR.$LFS_FUSE_VERSION_MINOR.*" \ + || echo 'v0.0.-1' ) \ + | awk -F '.' '{print $3+1}')" + + # found new version + LFS_FUSE_VERSION="v$LFS_FUSE_VERSION_MAJOR` + `.$LFS_FUSE_VERSION_MINOR` + `.$LFS_FUSE_VERSION_PATCH" + echo "LFS_FUSE_VERSION=$LFS_FUSE_VERSION" + echo "LFS_FUSE_VERSION=$LFS_FUSE_VERSION" >> $GITHUB_ENV + echo "LFS_FUSE_VERSION_MAJOR=$LFS_FUSE_VERSION_MAJOR" >> $GITHUB_ENV + echo "LFS_FUSE_VERSION_MINOR=$LFS_FUSE_VERSION_MINOR" >> $GITHUB_ENV + echo "LFS_FUSE_VERSION_PATCH=$LFS_FUSE_VERSION_PATCH" >> $GITHUB_ENV + + # try to find previous version? + - name: find-prev-version + continue-on-error: true + run: | + LFS_FUSE_PREV_VERSION="$( \ + git describe --tags --abbrev=0 --match 'v*' \ + || true)" + echo "LFS_FUSE_PREV_VERSION=$LFS_FUSE_PREV_VERSION" + echo "LFS_FUSE_PREV_VERSION=$LFS_FUSE_PREV_VERSION" >> $GITHUB_ENV + + # find changes from history + - name: create-changes + run: | + [ -n "$LFS_FUSE_PREV_VERSION" ] || exit 0 + # use explicit link to github commit so that release notes can + # be copied elsewhere + git log "$LFS_FUSE_PREV_VERSION.." \ + --grep='^Merge' --invert-grep \ + --format="format:[\`%h\`](` + `https://github.com/$GITHUB_REPOSITORY/commit/%h) %s" \ + > changes.txt + echo "CHANGES:" + cat changes.txt + + # create and update major branches (vN) + - name: create-major-branches + run: | + # create major branch + git branch "v$LFS_FUSE_VERSION_MAJOR" HEAD + + # push! + git push --atomic origin "v$LFS_FUSE_VERSION_MAJOR" + + # build release notes + - name: create-release + run: | + # create release and patch version tag (vN.N.N) + # only draft if not a patch release + touch release.txt + [ -e changes.txt ] && cat changes.txt >> release.txt + cat release.txt + + curl -sS -X POST -H "authorization: token ${{secrets.BOT_TOKEN}}" \ + "$GITHUB_API_URL/repos/$GITHUB_REPOSITORY/releases" \ + -d "$(jq -n --rawfile release release.txt '{ + tag_name: env.LFS_FUSE_VERSION, + name: env.LFS_FUSE_VERSION | rtrimstr(".0"), + target_commitish: "${{github.event.workflow_run.head_sha}}", + draft: env.LFS_FUSE_VERSION | endswith(".0"), + body: $release, + }' | tee /dev/stderr)" + diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..3931a48 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,119 @@ +name: test +on: [push, pull_request] + +defaults: + run: + shell: bash -euv -o pipefail {0} + +env: + CFLAGS: -Werror + MAKEFLAGS: -j + +jobs: + # self-host test with littlefs-fuse + test: + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v2 + - name: install + run: | + # need a few things + sudo apt-get update -qq + sudo apt-get install -qq gcc python3 python3-pip libfuse-dev + sudo pip3 install toml + gcc --version + python3 --version + fusermount -V + - name: setup + run: | + # setup disk for littlefs-fuse + mkdir mount + LOOP=$(sudo losetup -f) + sudo chmod a+rw $LOOP + dd if=/dev/zero bs=512 count=128K of=disk + losetup $LOOP disk + echo "LOOP=$LOOP" >> $GITHUB_ENV + - name: test + run: | + # self-host test + make + + ./lfs --format $LOOP + ./lfs --stat $LOOP + ./lfs $LOOP mount + + ls mount + cp -r littlefs mount/littlefs + cd mount/littlefs + stat . + ls -flh + make -B test-runner + make -B test + + cd ../.. + umount mount + ./lfs --stat $LOOP + + # test older versions + test-lfs2_0: + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v2 + - name: install + run: | + # need a few things + sudo apt-get update -qq + sudo apt-get install -qq gcc python3 python3-pip libfuse-dev + sudo pip3 install toml + gcc --version + python3 --version + fusermount -V + - name: setup + run: | + # setup disk for littlefs-fuse + mkdir mount + LOOP=$(sudo losetup -f) + sudo chmod a+rw $LOOP + dd if=/dev/zero bs=512 count=128K of=disk + losetup $LOOP disk + echo "LOOP=$LOOP" >> $GITHUB_ENV + - name: test + run: | + # self-host test + make + + ./lfs -d=lfs2.0 --format $LOOP + ./lfs --stat $LOOP + ./lfs -d=lfs2.0 $LOOP mount + + ls mount + cp -r littlefs mount/littlefs + cd mount/littlefs + stat . + ls -flh + make -B test-runner + make -B test + + cd ../.. + umount mount + ./lfs --stat $LOOP + - name: test-migrate + run: | + # self-host test, this time migrating to current version + make + + ./lfs -d=lfs2.0 --format $LOOP + ./lfs --stat $LOOP + ./lfs $LOOP mount + + ls mount + cp -r littlefs mount/littlefs + cd mount/littlefs + stat . + ls -flh + make -B test-runner + make -B test + + cd ../.. + umount mount + ./lfs --stat $LOOP diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 93c107e..0000000 --- a/.travis.yml +++ /dev/null @@ -1,90 +0,0 @@ -# Environment variables -env: - global: - - CFLAGS=-Werror - -cache: - pip: true - directories: - - $HOME/.cache/apt - -# CI Jobs -jobs: - include: - # Test stage - - stage: test - env: - - STAGE=test - install: - - sudo apt-get install python3 python3-pip - - sudo pip3 install toml - - sudo apt-get install libfuse-dev - - fusermount -V - - gcc --version - before_script: - - mkdir mount - - sudo chmod a+rw /dev/loop0 - - dd if=/dev/zero bs=512 count=128K of=disk - - losetup /dev/loop0 disk - script: - # Build - - make - # Format and mount - - ./lfs --format /dev/loop0 - - ./lfs /dev/loop0 mount - # Some simple operations than self hosting test - - ls mount - - cp -r littlefs mount/littlefs - - cd mount/littlefs - - stat . - - ls -flh - - make -B test - - # Deploy stage for updating versions and tags - - stage: deploy - env: - - STAGE=deploy - script: - - | - bash << 'SCRIPT' - set -ev - # Find version defined in lfs.h - LFS_VERSION=$(grep -ox '#define LFS_VERSION .*' littlefs/lfs.h | cut -d ' ' -f3) - LFS_VERSION_MAJOR=$((0xffff & ($LFS_VERSION >> 16))) - LFS_VERSION_MINOR=$((0xffff & ($LFS_VERSION >> 0))) - # Grab latest patch from repo tags, default to 0, needs finagling - # to get past GitHub's pagination API - PREV_URL=https://api.github.com/repos/$TRAVIS_REPO_SLUG/git/refs/tags/v$LFS_VERSION_MAJOR.$LFS_VERSION_MINOR. - PREV_URL=$(curl -u "$GEKY_BOT_RELEASES" "$PREV_URL" -I \ - | sed -n '/^Link/{s/.*<\(.*\)>; rel="last"/\1/;p;q0};$q1' \ - || echo $PREV_URL) - LFS_VERSION_PATCH=$(curl -u "$GEKY_BOT_RELEASES" "$PREV_URL" \ - | jq 'map(.ref | match("\\bv.*\\..*\\.(.*)$";"g") - .captures[].string | tonumber) | max + 1' \ - || echo 0) - # We have our new version - LFS_VERSION="v$LFS_VERSION_MAJOR.$LFS_VERSION_MINOR.$LFS_VERSION_PATCH" - echo "VERSION $LFS_VERSION" - # Check that we're the most recent commit - CURRENT_COMMIT=$(curl -f -u "$GEKY_BOT_RELEASES" \ - https://api.github.com/repos/$TRAVIS_REPO_SLUG/commits/master \ - | jq -re '.sha') - [ "$TRAVIS_COMMIT" == "$CURRENT_COMMIT" ] || exit 0 - # Create major branch (vN) - git branch v$LFS_VERSION_MAJOR HEAD - git push https://$GEKY_BOT_RELEASES@github.com/$TRAVIS_REPO_SLUG.git \ - v$LFS_VERSION_MAJOR - # Create patch version tag (vN.N.N) - curl -f -u "$GEKY_BOT_RELEASES" -X POST \ - https://api.github.com/repos/$TRAVIS_REPO_SLUG/git/refs \ - -d "{ - \"ref\": \"refs/tags/$LFS_VERSION\", - \"sha\": \"$TRAVIS_COMMIT\" - }" - SCRIPT - -# Job control -stages: - - name: test - - name: deploy - if: branch = master AND type = push diff --git a/LICENSE.md b/LICENSE.md index ed69bea..3c97553 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,3 +1,4 @@ +Copyright (c) 2022, the littlefs authors. Copyright (c) 2017, Arm Limited. All rights reserved. Redistribution and use in source and binary forms, with or without modification, diff --git a/Makefile b/Makefile index ca73029..c6725a1 100644 --- a/Makefile +++ b/Makefile @@ -23,6 +23,9 @@ override CFLAGS += -I. -Ilittlefs override CFLAGS += -std=c99 -Wall -pedantic override CFLAGS += -D_FILE_OFFSET_BITS=64 override CFLAGS += -D_XOPEN_SOURCE=700 +# enable multiversion support in littlefs +override CFLAGS += -DLFS_MULTIVERSION +# enable migrate support in littlefs override CFLAGS += -DLFS_MIGRATE override LFLAGS += -lfuse diff --git a/lfs_fuse.c b/lfs_fuse.c index 3c87dad..7aceb2b 100644 --- a/lfs_fuse.c +++ b/lfs_fuse.c @@ -1,6 +1,7 @@ /* * FUSE wrapper for the littlefs * + * Copyright (c) 2022, the littlefs authors. * Copyright (c) 2017, Arm Limited. All rights reserved. * SPDX-License-Identifier: BSD-3-Clause */ @@ -25,9 +26,22 @@ #include +// littefs-fuse version +// +// Note this is different from the littlefs core version, and littlefs +// on-disk version +// +// Major (top-nibble), incremented on backwards incompatible changes +// Minor (bottom-nibble), incremented on feature additions +#define LFS_FUSE_VERSION 0x00020007 +#define LFS_FUSE_VERSION_MAJOR (0xffff & (LFS_FUSE_VERSION >> 16)) +#define LFS_FUSE_VERSION_MINOR (0xffff & (LFS_FUSE_VERSION >> 0)) + + // config and other state static struct lfs_config config = {0}; static const char *device = NULL; +static bool stat_ = false; static bool format = false; static bool migrate = false; static lfs_t lfs; @@ -70,6 +84,59 @@ void *lfs_fuse_init(struct fuse_conn_info *conn) { return 0; } +int lfs_fuse_stat(void) { + int err = lfs_fuse_bd_create(&config, device); + if (err) { + return err; + } + + lfs_fuse_defaults(&config); + + err = lfs_mount(&lfs, &config); + if (err) { + goto failed; + } + + // get on-disk info + struct lfs_fsinfo fsinfo; + err = lfs_fs_stat(&lfs, &fsinfo); + if (err) { + goto failed; + } + + // get block usage + lfs_ssize_t in_use = lfs_fs_size(&lfs); + if (in_use < 0) { + err = in_use; + goto failed; + } + + // print to stdout + printf("disk_version: lfs%d.%d\n", + 0xffff & (fsinfo.disk_version >> 16), + 0xffff & (fsinfo.disk_version >> 0)); + printf("block_size: %d\n", config.block_size); + printf("block_count: %d\n", config.block_count); + printf(" used: %d/%d (%.1f%%)\n", + in_use, + config.block_count, + 100.0f * (float)in_use / (float)config.block_count); + printf(" free: %d/%d (%.1f%%)\n", + config.block_count-in_use, + config.block_count, + 100.0f * (float)(config.block_count-in_use) + / (float)config.block_count); + printf("name_max: %d\n", fsinfo.name_max); + printf("file_max: %d\n", fsinfo.file_max); + printf("attr_max: %d\n", fsinfo.attr_max); + + err = lfs_unmount(&lfs); + +failed: + lfs_fuse_bd_destroy(&config); + return err; +} + int lfs_fuse_format(void) { int err = lfs_fuse_bd_create(&config, device); if (err) { @@ -117,6 +184,14 @@ void lfs_fuse_destroy(void *eh) { int lfs_fuse_statfs(const char *path, struct statvfs *s) { memset(s, 0, sizeof(struct statvfs)); + // get the on-disk name_max from littlefs + struct lfs_fsinfo fsinfo; + int err = lfs_fs_stat(&lfs, &fsinfo); + if (err) { + return err; + } + + // get the filesystem block usage from littlefs lfs_ssize_t in_use = lfs_fs_size(&lfs); if (in_use < 0) { return in_use; @@ -127,7 +202,7 @@ int lfs_fuse_statfs(const char *path, struct statvfs *s) { s->f_blocks = config.block_count; s->f_bfree = config.block_count - in_use; s->f_bavail = config.block_count - in_use; - s->f_namemax = config.name_max; + s->f_namemax = fsinfo.name_max; return 0; } @@ -391,14 +466,19 @@ static struct fuse_operations lfs_fuse_ops = { enum lfs_fuse_keys { KEY_HELP, KEY_VERSION, + KEY_STAT, KEY_FORMAT, KEY_MIGRATE, + KEY_DISK_VERSION, }; #define OPT(t, p) { t, offsetof(struct lfs_config, p), 0} static struct fuse_opt lfs_fuse_opts[] = { + FUSE_OPT_KEY("--stat", KEY_STAT), FUSE_OPT_KEY("--format", KEY_FORMAT), FUSE_OPT_KEY("--migrate", KEY_MIGRATE), + {"-d=", -1U, KEY_DISK_VERSION}, + {"--disk_version=", -1U, KEY_DISK_VERSION}, OPT("-b=%" SCNu32, block_size), OPT("--block_size=%" SCNu32, block_size), OPT("--block_count=%" SCNu32, block_count), @@ -426,8 +506,10 @@ static const char help_text[] = " -V --version print version\n" "\n" "littlefs options:\n" +" --stat print filesystem info instead of mounting\n" " --format format instead of mounting\n" " --migrate migrate previous version instead of mounting\n" +" -d --disk_version attempt to use this on-disk version of littlefs\n" " -b --block_size logical block size, overrides the block device\n" " --block_count block count, overrides the block device\n" " --block_cycles number of erase cycles before eviction (512)\n" @@ -452,6 +534,10 @@ int lfs_fuse_opt_proc(void *data, const char *arg, } break; + case KEY_STAT: + stat_ = true; + return 0; + case KEY_FORMAT: format = true; return 0; @@ -467,13 +553,76 @@ int lfs_fuse_opt_proc(void *data, const char *arg, exit(1); case KEY_VERSION: + fprintf(stderr, "littlefs-fuse version: v%d.%d\n", + LFS_FUSE_VERSION_MAJOR, LFS_FUSE_VERSION_MINOR); fprintf(stderr, "littlefs version: v%d.%d\n", - LFS_VERSION_MAJOR, LFS_VERSION_MINOR); - fprintf(stderr, "littlefs disk version: v%d.%d\n", - LFS_DISK_VERSION_MAJOR, LFS_DISK_VERSION_MINOR); + LFS_VERSION_MAJOR, LFS_VERSION_MINOR); + fprintf(stderr, "littlefs disk version: lfs%d.%d\n", + LFS_DISK_VERSION_MAJOR, LFS_DISK_VERSION_MINOR); fuse_opt_add_arg(args, "--version"); fuse_main(args->argc, args->argv, &lfs_fuse_ops, NULL); exit(0); + + case KEY_DISK_VERSION: { + // skip opt prefix + const char *arg_ = strchr(arg, '='); + if (arg_) { + arg = arg_ + 1; + } + + // parse out the requested disk version + // supported formats: + // - no-prefix - 2.1 + // - v-prefix - v2.1 + // - lfs-prefix - lfs2.1 + // - littlefs-prefix - littlefs2.1 + const char *orig_arg = arg; + if (strlen(arg) >= strlen("v") + && memcmp(arg, "v", strlen("v")) == 0) { + arg += strlen("v"); + } else if (strlen(arg) >= strlen("lfs") + && memcmp(arg, "lfs", strlen("lfs")) == 0) { + arg += strlen("lfs"); + } else if (strlen(arg) >= strlen("littlefs") + && memcmp(arg, "littlefs", strlen("littlefs")) == 0) { + arg += strlen("littlefs"); + } + + char *parsed; + uintmax_t major = strtoumax(arg, &parsed, 0); + if (parsed == arg) { + goto invalid_version; + } + arg = parsed; + + if (arg[0] != '.') { + goto invalid_version; + } + arg += 1; + + uintmax_t minor = strtoumax(arg, &parsed, 0); + if (parsed == arg) { + goto invalid_version; + } + arg = parsed; + + if (arg[0] != '\0') { + goto invalid_version; + } + + if (major > 0xffff || minor > 0xffff) { + goto invalid_version; + } + + config.disk_version + = ((major & 0xffff) << 16) + | ((minor & 0xffff) << 0); + return 0; + + invalid_version: + fprintf(stderr, "invalid disk version: \"%s\"\n", orig_arg); + exit(1); + } } return 1; @@ -488,6 +637,16 @@ int main(int argc, char *argv[]) { exit(1); } + if (stat_) { + // stat time, no mount + int err = lfs_fuse_stat(); + if (err) { + LFS_ERROR("%s", strerror(-err)); + exit(-err); + } + exit(0); + } + if (format) { // format time, no mount int err = lfs_fuse_format(); diff --git a/lfs_fuse_bd.c b/lfs_fuse_bd.c index 9859c32..d01a21f 100644 --- a/lfs_fuse_bd.c +++ b/lfs_fuse_bd.c @@ -1,6 +1,7 @@ /* * Linux user-space block device wrapper * + * Copyright (c) 2022, the littlefs authors. * Copyright (c) 2017, Arm Limited. All rights reserved. * SPDX-License-Identifier: BSD-3-Clause */ @@ -41,12 +42,12 @@ int lfs_fuse_bd_create(struct lfs_config *cfg, const char *path) { // get size in sectors if (!cfg->block_count) { - long size; - int err = ioctl(fd, BLKGETSIZE, &size); + uint64_t size; + int err = ioctl(fd, BLKGETSIZE64, &size); if (err) { return -errno; } - cfg->block_count = size; + cfg->block_count = size / cfg->block_size; } // setup function pointers @@ -66,6 +67,7 @@ void lfs_fuse_bd_destroy(const struct lfs_config *cfg) { int lfs_fuse_bd_read(const struct lfs_config *cfg, lfs_block_t block, lfs_off_t off, void *buffer, lfs_size_t size) { int fd = (intptr_t)cfg->context; + uint8_t *buffer_ = buffer; // check if read is valid assert(block < cfg->block_count); @@ -77,9 +79,14 @@ int lfs_fuse_bd_read(const struct lfs_config *cfg, lfs_block_t block, } // read block - ssize_t res = read(fd, buffer, (size_t)size); - if (res < 0) { - return -errno; + while (size > 0) { + ssize_t res = read(fd, buffer_, (size_t)size); + if (res < 0) { + return -errno; + } + + buffer_ += res; + size -= res; } return 0; @@ -88,6 +95,7 @@ int lfs_fuse_bd_read(const struct lfs_config *cfg, lfs_block_t block, int lfs_fuse_bd_prog(const struct lfs_config *cfg, lfs_block_t block, lfs_off_t off, const void *buffer, lfs_size_t size) { int fd = (intptr_t)cfg->context; + const uint8_t *buffer_ = buffer; // check if write is valid assert(block < cfg->block_count); @@ -99,9 +107,14 @@ int lfs_fuse_bd_prog(const struct lfs_config *cfg, lfs_block_t block, } // write block - ssize_t res = write(fd, buffer, (size_t)size); - if (res < 0) { - return -errno; + while (size > 0) { + ssize_t res = write(fd, buffer_, (size_t)size); + if (res < 0) { + return -errno; + } + + buffer_ += res; + size -= res; } return 0; diff --git a/lfs_fuse_bd.h b/lfs_fuse_bd.h index 07197b2..5465c84 100644 --- a/lfs_fuse_bd.h +++ b/lfs_fuse_bd.h @@ -1,6 +1,7 @@ /* * Linux user-space block device wrapper * + * Copyright (c) 2022, the littlefs authors. * Copyright (c) 2017, Arm Limited. All rights reserved. * SPDX-License-Identifier: BSD-3-Clause */ diff --git a/littlefs/.github/workflows/release.yml b/littlefs/.github/workflows/release.yml index b2ead2e..c0d446d 100644 --- a/littlefs/.github/workflows/release.yml +++ b/littlefs/.github/workflows/release.yml @@ -102,7 +102,7 @@ jobs: # sizes table i=0 j=0 - for c in "" readonly threadsafe migrate error-asserts + for c in "" readonly threadsafe multiversion migrate error-asserts do # per-config results c_or_default=${c:-default} diff --git a/littlefs/.github/workflows/test.yml b/littlefs/.github/workflows/test.yml index 2cee352..ccb08fe 100644 --- a/littlefs/.github/workflows/test.yml +++ b/littlefs/.github/workflows/test.yml @@ -170,6 +170,27 @@ jobs: cp lfs.data.csv sizes/${{matrix.arch}}-threadsafe.data.csv cp lfs.stack.csv sizes/${{matrix.arch}}-threadsafe.stack.csv cp lfs.structs.csv sizes/${{matrix.arch}}-threadsafe.structs.csv + - name: sizes-multiversion + run: | + make clean + CFLAGS="$CFLAGS \ + -DLFS_NO_ASSERT \ + -DLFS_NO_DEBUG \ + -DLFS_NO_WARN \ + -DLFS_NO_ERROR \ + -DLFS_MULTIVERSION" \ + make lfs.code.csv lfs.data.csv lfs.stack.csv lfs.structs.csv + ./scripts/structs.py -u lfs.structs.csv + ./scripts/summary.py lfs.code.csv lfs.data.csv lfs.stack.csv \ + -bfunction \ + -fcode=code_size \ + -fdata=data_size \ + -fstack=stack_limit --max=stack_limit + mkdir -p sizes + cp lfs.code.csv sizes/${{matrix.arch}}-multiversion.code.csv + cp lfs.data.csv sizes/${{matrix.arch}}-multiversion.data.csv + cp lfs.stack.csv sizes/${{matrix.arch}}-multiversion.stack.csv + cp lfs.structs.csv sizes/${{matrix.arch}}-multiversion.structs.csv - name: sizes-migrate run: | make clean @@ -353,6 +374,42 @@ jobs: run: | CFLAGS="$CFLAGS -DLFS_NO_INTRINSICS" make test + # run LFS_MULTIVERSION tests + test-multiversion: + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v2 + - 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-multiversion + run: | + CFLAGS="$CFLAGS -DLFS_MULTIVERSION" make test + + # run tests on the older version lfs2.0 + test-lfs2_0: + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v2 + - 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-lfs2_0 + run: | + CFLAGS="$CFLAGS -DLFS_MULTIVERSION" \ + TESTFLAGS="$TESTFLAGS -DDISK_VERSION=0x00020000" \ + make test + # run under Valgrind to check for memory errors test-valgrind: runs-on: ubuntu-22.04 @@ -371,7 +428,8 @@ jobs: # on one geometry - name: test-valgrind run: | - TESTFLAGS="$TESTFLAGS --valgrind -Gdefault -Pnone" make test + 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 @@ -684,7 +742,7 @@ jobs: # sizes table i=0 j=0 - for c in "" readonly threadsafe migrate error-asserts + for c in "" readonly threadsafe multiversion migrate error-asserts do # per-config results c_or_default=${c:-default} diff --git a/littlefs/README.md b/littlefs/README.md index 3afddfd..df7ee00 100644 --- a/littlefs/README.md +++ b/littlefs/README.md @@ -250,6 +250,10 @@ License Identifiers that are here available: http://spdx.org/licenses/ MCUs. It offers static wear-leveling and power-resilience with only a fixed _O(|address|)_ pointer structure stored on each block and in RAM. +- [ChaN's FatFs] - A lightweight reimplementation of the infamous FAT filesystem + for microcontroller-scale devices. Due to limitations of FAT it can't provide + power-loss resilience, but it does allow easy interop with PCs. + - [chamelon] - A pure-OCaml implementation of (most of) littlefs, designed for use with the MirageOS library operating system project. It is interoperable with the reference implementation, with some caveats. @@ -266,6 +270,7 @@ License Identifiers that are here available: http://spdx.org/licenses/ [LittleFileSystem]: https://os.mbed.com/docs/mbed-os/latest/apis/littlefilesystem.html [SPIFFS]: https://github.com/pellepl/spiffs [Dhara]: https://github.com/dlbeer/dhara +[ChaN's FatFs]: http://elm-chan.org/fsw/ff/00index_e.html [littlefs-python]: https://pypi.org/project/littlefs-python/ [littlefs2-rust]: https://crates.io/crates/littlefs2 [chamelon]: https://github.com/yomimono/chamelon diff --git a/littlefs/lfs.c b/littlefs/lfs.c index f0daf54..38b825d 100644 --- a/littlefs/lfs.c +++ b/littlefs/lfs.c @@ -415,11 +415,11 @@ static inline uint8_t lfs_gstate_getorphans(const lfs_gstate_t *a) { static inline bool lfs_gstate_hasmove(const lfs_gstate_t *a) { return lfs_tag_type1(a->tag); } +#endif static inline bool lfs_gstate_needssuperblock(const lfs_gstate_t *a) { return lfs_tag_size(a->tag) >> 9; } -#endif static inline bool lfs_gstate_hasmovehere(const lfs_gstate_t *a, const lfs_block_t *pair) { @@ -518,6 +518,28 @@ static void lfs_mlist_append(lfs_t *lfs, struct lfs_mlist *mlist) { lfs->mlist = mlist; } +// some other filesystem operations +static uint32_t lfs_fs_disk_version(lfs_t *lfs) { + (void)lfs; +#ifdef LFS_MULTIVERSION + if (lfs->cfg->disk_version) { + return lfs->cfg->disk_version; + } else +#endif + { + return LFS_DISK_VERSION; + } +} + +static uint16_t lfs_fs_disk_version_major(lfs_t *lfs) { + return 0xffff & (lfs_fs_disk_version(lfs) >> 16); + +} + +static uint16_t lfs_fs_disk_version_minor(lfs_t *lfs) { + return 0xffff & (lfs_fs_disk_version(lfs) >> 0); +} + /// Internal operations predeclared here /// #ifndef LFS_READONLY @@ -535,7 +557,6 @@ static int lfs_file_outline(lfs_t *lfs, lfs_file_t *file); static int lfs_file_flush(lfs_t *lfs, lfs_file_t *file); static int lfs_fs_deorphan(lfs_t *lfs, bool powerloss); -static void lfs_fs_prepsuperblock(lfs_t *lfs, bool needssuperblock); static int lfs_fs_preporphans(lfs_t *lfs, int8_t orphans); static void lfs_fs_prepmove(lfs_t *lfs, uint16_t id, const lfs_block_t pair[2]); @@ -546,6 +567,8 @@ static lfs_stag_t lfs_fs_parent(lfs_t *lfs, const lfs_block_t dir[2], static int lfs_fs_forceconsistency(lfs_t *lfs); #endif +static void lfs_fs_prepsuperblock(lfs_t *lfs, bool needssuperblock); + #ifdef LFS_MIGRATE static int lfs1_traverse(lfs_t *lfs, int (*cb)(void*, lfs_block_t), void *data); @@ -1110,7 +1133,8 @@ static lfs_stag_t lfs_dir_fetchmatch(lfs_t *lfs, // next commit not yet programmed? if (!lfs_tag_isvalid(tag)) { - maybeerased = true; + // we only might be erased if the last tag was a crc + maybeerased = (lfs_tag_type2(ptag) == LFS_TYPE_CCRC); break; // out of range? } else if (off + lfs_tag_dsize(tag) > lfs->cfg->block_size) { @@ -1155,14 +1179,11 @@ static lfs_stag_t lfs_dir_fetchmatch(lfs_t *lfs, dir->tail[1] = temptail[1]; dir->split = tempsplit; - // reset crc + // reset crc, hasfcrc crc = 0xffffffff; continue; } - // fcrc is only valid when last tag was a crc - hasfcrc = false; - // crc the entry first, hopefully leaving it in the cache err = lfs_bd_crc(lfs, NULL, &lfs->rcache, lfs->cfg->block_size, @@ -1256,20 +1277,33 @@ static lfs_stag_t lfs_dir_fetchmatch(lfs_t *lfs, // did we end on a valid commit? we may have an erased block dir->erased = false; - if (maybeerased && hasfcrc && dir->off % lfs->cfg->prog_size == 0) { - // check for an fcrc matching the next prog's erased state, if - // this failed most likely a previous prog was interrupted, we - // need a new erase - uint32_t fcrc_ = 0xffffffff; - int err = lfs_bd_crc(lfs, - NULL, &lfs->rcache, lfs->cfg->block_size, - dir->pair[0], dir->off, fcrc.size, &fcrc_); - if (err && err != LFS_ERR_CORRUPT) { - return err; - } + if (maybeerased && dir->off % lfs->cfg->prog_size == 0) { + #ifdef LFS_MULTIVERSION + // note versions < lfs2.1 did not have fcrc tags, if + // we're < lfs2.1 treat missing fcrc as erased data + // + // we don't strictly need to do this, but otherwise writing + // to lfs2.0 disks becomes very inefficient + if (lfs_fs_disk_version(lfs) < 0x00020001) { + dir->erased = true; + + } else + #endif + if (hasfcrc) { + // check for an fcrc matching the next prog's erased state, if + // this failed most likely a previous prog was interrupted, we + // need a new erase + uint32_t fcrc_ = 0xffffffff; + int err = lfs_bd_crc(lfs, + NULL, &lfs->rcache, lfs->cfg->block_size, + dir->pair[0], dir->off, fcrc.size, &fcrc_); + if (err && err != LFS_ERR_CORRUPT) { + return err; + } - // found beginning of erased part? - dir->erased = (fcrc_ == fcrc.crc); + // found beginning of erased part? + dir->erased = (fcrc_ == fcrc.crc); + } } // synthetic move @@ -1605,22 +1639,34 @@ static int lfs_dir_commitcrc(lfs_t *lfs, struct lfs_commit *commit) { return err; } - // find the expected fcrc, don't bother avoiding a reread - // of the eperturb, it should still be in our cache - struct lfs_fcrc fcrc = {.size=lfs->cfg->prog_size, .crc=0xffffffff}; - err = lfs_bd_crc(lfs, - NULL, &lfs->rcache, lfs->cfg->prog_size, - commit->block, noff, fcrc.size, &fcrc.crc); - if (err && err != LFS_ERR_CORRUPT) { - return err; - } + #ifdef LFS_MULTIVERSION + // unfortunately fcrcs break mdir fetching < lfs2.1, so only write + // these if we're a >= lfs2.1 filesystem + if (lfs_fs_disk_version(lfs) <= 0x00020000) { + // don't write fcrc + } else + #endif + { + // find the expected fcrc, don't bother avoiding a reread + // of the eperturb, it should still be in our cache + struct lfs_fcrc fcrc = { + .size = lfs->cfg->prog_size, + .crc = 0xffffffff + }; + err = lfs_bd_crc(lfs, + NULL, &lfs->rcache, lfs->cfg->prog_size, + commit->block, noff, fcrc.size, &fcrc.crc); + if (err && err != LFS_ERR_CORRUPT) { + return err; + } - lfs_fcrc_tole32(&fcrc); - err = lfs_dir_commitattr(lfs, commit, - LFS_MKTAG(LFS_TYPE_FCRC, 0x3ff, sizeof(struct lfs_fcrc)), - &fcrc); - if (err) { - return err; + lfs_fcrc_tole32(&fcrc); + err = lfs_dir_commitattr(lfs, commit, + LFS_MKTAG(LFS_TYPE_FCRC, 0x3ff, sizeof(struct lfs_fcrc)), + &fcrc); + if (err) { + return err; + } } } @@ -4051,6 +4097,15 @@ static int lfs_init(lfs_t *lfs, const struct lfs_config *cfg) { lfs->cfg = cfg; int err = 0; +#ifdef LFS_MULTIVERSION + // this driver only supports minor version < current minor version + LFS_ASSERT(!lfs->cfg->disk_version || ( + (0xffff & (lfs->cfg->disk_version >> 16)) + == LFS_DISK_VERSION_MAJOR + && (0xffff & (lfs->cfg->disk_version >> 0)) + <= LFS_DISK_VERSION_MINOR)); +#endif + // check that bool is a truthy-preserving type // // note the most common reason for this failure is a before-c99 compiler, @@ -4208,7 +4263,7 @@ static int lfs_rawformat(lfs_t *lfs, const struct lfs_config *cfg) { // write one superblock lfs_superblock_t superblock = { - .version = LFS_DISK_VERSION, + .version = lfs_fs_disk_version(lfs), .block_size = lfs->cfg->block_size, .block_count = lfs->cfg->block_count, .name_max = lfs->name_max, @@ -4306,12 +4361,14 @@ static int lfs_rawmount(lfs_t *lfs, const struct lfs_config *cfg) { // check version uint16_t major_version = (0xffff & (superblock.version >> 16)); uint16_t minor_version = (0xffff & (superblock.version >> 0)); - if ((major_version != LFS_DISK_VERSION_MAJOR || - minor_version > LFS_DISK_VERSION_MINOR)) { + if (major_version != lfs_fs_disk_version_major(lfs) + || minor_version > lfs_fs_disk_version_minor(lfs)) { LFS_ERROR("Invalid version " "v%"PRIu16".%"PRIu16" != v%"PRIu16".%"PRIu16, - major_version, minor_version, - LFS_DISK_VERSION_MAJOR, LFS_DISK_VERSION_MINOR); + major_version, + minor_version, + lfs_fs_disk_version_major(lfs), + lfs_fs_disk_version_minor(lfs)); err = LFS_ERR_INVAL; goto cleanup; } @@ -4319,16 +4376,16 @@ static int lfs_rawmount(lfs_t *lfs, const struct lfs_config *cfg) { // found older minor version? set an in-device only bit in the // gstate so we know we need to rewrite the superblock before // the first write - if (minor_version < LFS_DISK_VERSION_MINOR) { + if (minor_version < lfs_fs_disk_version_minor(lfs)) { LFS_DEBUG("Found older minor version " "v%"PRIu16".%"PRIu16" < v%"PRIu16".%"PRIu16, - major_version, minor_version, - LFS_DISK_VERSION_MAJOR, LFS_DISK_VERSION_MINOR); - #ifndef LFS_READONLY + major_version, + minor_version, + lfs_fs_disk_version_major(lfs), + lfs_fs_disk_version_minor(lfs)); // note this bit is reserved on disk, so fetching more gstate // will not interfere here lfs_fs_prepsuperblock(lfs, true); - #endif } // check superblock configuration @@ -4421,6 +4478,42 @@ static int lfs_rawunmount(lfs_t *lfs) { /// Filesystem filesystem operations /// +static int lfs_fs_rawstat(lfs_t *lfs, struct lfs_fsinfo *fsinfo) { + // if the superblock is up-to-date, we must be on the most recent + // minor version of littlefs + if (!lfs_gstate_needssuperblock(&lfs->gstate)) { + fsinfo->disk_version = lfs_fs_disk_version(lfs); + + // otherwise we need to read the minor version on disk + } else { + // fetch the superblock + lfs_mdir_t dir; + int err = lfs_dir_fetch(lfs, &dir, lfs->root); + if (err) { + return err; + } + + lfs_superblock_t superblock; + lfs_stag_t tag = lfs_dir_get(lfs, &dir, LFS_MKTAG(0x7ff, 0x3ff, 0), + LFS_MKTAG(LFS_TYPE_INLINESTRUCT, 0, sizeof(superblock)), + &superblock); + if (tag < 0) { + return tag; + } + lfs_superblock_fromle32(&superblock); + + // read the on-disk version + fsinfo->disk_version = superblock.version; + } + + // other on-disk configuration, we cache all of these for internal use + fsinfo->name_max = lfs->name_max; + fsinfo->file_max = lfs->file_max; + fsinfo->attr_max = lfs->attr_max; + + return 0; +} + int lfs_fs_rawtraverse(lfs_t *lfs, int (*cb)(void *data, lfs_block_t block), void *data, bool includeorphans) { @@ -4631,12 +4724,10 @@ static lfs_stag_t lfs_fs_parent(lfs_t *lfs, const lfs_block_t pair[2], } #endif -#ifndef LFS_READONLY static void lfs_fs_prepsuperblock(lfs_t *lfs, bool needssuperblock) { lfs->gstate.tag = (lfs->gstate.tag & ~LFS_MKTAG(0, 0, 0x200)) | (uint32_t)needssuperblock << 9; } -#endif #ifndef LFS_READONLY static int lfs_fs_preporphans(lfs_t *lfs, int8_t orphans) { @@ -4678,7 +4769,7 @@ static int lfs_fs_desuperblock(lfs_t *lfs) { // write a new superblock lfs_superblock_t superblock = { - .version = LFS_DISK_VERSION, + .version = lfs_fs_disk_version(lfs), .block_size = lfs->cfg->block_size, .block_count = lfs->cfg->block_count, .name_max = lfs->name_max, @@ -4934,6 +5025,7 @@ static lfs_ssize_t lfs_fs_rawsize(lfs_t *lfs) { return size; } + #ifdef LFS_MIGRATE ////// Migration from littelfs v1 below this ////// @@ -6053,6 +6145,20 @@ int lfs_dir_rewind(lfs_t *lfs, lfs_dir_t *dir) { return err; } +int lfs_fs_stat(lfs_t *lfs, struct lfs_fsinfo *fsinfo) { + int err = LFS_LOCK(lfs->cfg); + if (err) { + return err; + } + LFS_TRACE("lfs_fs_stat(%p, %p)", (void*)lfs, (void*)fsinfo); + + err = lfs_fs_rawstat(lfs, fsinfo); + + LFS_TRACE("lfs_fs_stat -> %d", err); + LFS_UNLOCK(lfs->cfg); + return err; +} + lfs_ssize_t lfs_fs_size(lfs_t *lfs) { int err = LFS_LOCK(lfs->cfg); if (err) { diff --git a/littlefs/lfs.h b/littlefs/lfs.h index eb5c355..1081735 100644 --- a/littlefs/lfs.h +++ b/littlefs/lfs.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 LFS_VERSION 0x00020006 +#define LFS_VERSION 0x00020007 #define LFS_VERSION_MAJOR (0xffff & (LFS_VERSION >> 16)) #define LFS_VERSION_MINOR (0xffff & (LFS_VERSION >> 0)) @@ -263,6 +263,14 @@ struct lfs_config { // can help bound the metadata compaction time. Must be <= block_size. // Defaults to block_size when zero. lfs_size_t metadata_max; + +#ifdef LFS_MULTIVERSION + // On-disk version to use when writing in the form of 16-bit major version + // + 16-bit minor version. This limiting metadata to what is supported by + // older minor versions. Note that some features will be lost. Defaults to + // to the most recent minor version when zero. + uint32_t disk_version; +#endif }; // File info structure @@ -280,6 +288,21 @@ struct lfs_info { char name[LFS_NAME_MAX+1]; }; +// Filesystem info structure +struct lfs_fsinfo { + // On-disk version. + uint32_t disk_version; + + // Upper limit on the length of file names in bytes. + lfs_size_t name_max; + + // Upper limit on the size of files in bytes. + lfs_size_t file_max; + + // Upper limit on the size of custom attributes in bytes. + lfs_size_t attr_max; +}; + // Custom attribute structure, used to describe custom attributes // committed atomically during file writes. struct lfs_attr { @@ -659,6 +682,12 @@ int lfs_dir_rewind(lfs_t *lfs, lfs_dir_t *dir); /// Filesystem-level filesystem operations +// Find on-disk info about the filesystem +// +// Fills out the fsinfo structure based on the filesystem found on-disk. +// Returns a negative error code on failure. +int lfs_fs_stat(lfs_t *lfs, struct lfs_fsinfo *fsinfo); + // Finds the current size of the filesystem // // Note: Result is best effort. If files share COW structures, the returned diff --git a/littlefs/runners/test_runner.c b/littlefs/runners/test_runner.c index abc867c..27f8524 100644 --- a/littlefs/runners/test_runner.c +++ b/littlefs/runners/test_runner.c @@ -1346,6 +1346,9 @@ static void run_powerloss_none( .block_cycles = BLOCK_CYCLES, .cache_size = CACHE_SIZE, .lookahead_size = LOOKAHEAD_SIZE, + #ifdef LFS_MULTIVERSION + .disk_version = DISK_VERSION, + #endif }; struct lfs_emubd_config bdcfg = { @@ -1415,6 +1418,9 @@ static void run_powerloss_linear( .block_cycles = BLOCK_CYCLES, .cache_size = CACHE_SIZE, .lookahead_size = LOOKAHEAD_SIZE, + #ifdef LFS_MULTIVERSION + .disk_version = DISK_VERSION, + #endif }; struct lfs_emubd_config bdcfg = { @@ -1501,6 +1507,9 @@ static void run_powerloss_log( .block_cycles = BLOCK_CYCLES, .cache_size = CACHE_SIZE, .lookahead_size = LOOKAHEAD_SIZE, + #ifdef LFS_MULTIVERSION + .disk_version = DISK_VERSION, + #endif }; struct lfs_emubd_config bdcfg = { @@ -1585,6 +1594,9 @@ static void run_powerloss_cycles( .block_cycles = BLOCK_CYCLES, .cache_size = CACHE_SIZE, .lookahead_size = LOOKAHEAD_SIZE, + #ifdef LFS_MULTIVERSION + .disk_version = DISK_VERSION, + #endif }; struct lfs_emubd_config bdcfg = { @@ -1767,6 +1779,9 @@ static void run_powerloss_exhaustive( .block_cycles = BLOCK_CYCLES, .cache_size = CACHE_SIZE, .lookahead_size = LOOKAHEAD_SIZE, + #ifdef LFS_MULTIVERSION + .disk_version = DISK_VERSION, + #endif }; struct lfs_emubd_config bdcfg = { diff --git a/littlefs/runners/test_runner.h b/littlefs/runners/test_runner.h index 9ff1f79..e30d492 100644 --- a/littlefs/runners/test_runner.h +++ b/littlefs/runners/test_runner.h @@ -91,6 +91,7 @@ intmax_t test_define(size_t define); #define ERASE_CYCLES_i 8 #define BADBLOCK_BEHAVIOR_i 9 #define POWERLOSS_BEHAVIOR_i 10 +#define DISK_VERSION_i 11 #define READ_SIZE TEST_DEFINE(READ_SIZE_i) #define PROG_SIZE TEST_DEFINE(PROG_SIZE_i) @@ -103,6 +104,7 @@ intmax_t test_define(size_t define); #define ERASE_CYCLES TEST_DEFINE(ERASE_CYCLES_i) #define BADBLOCK_BEHAVIOR TEST_DEFINE(BADBLOCK_BEHAVIOR_i) #define POWERLOSS_BEHAVIOR TEST_DEFINE(POWERLOSS_BEHAVIOR_i) +#define DISK_VERSION TEST_DEFINE(DISK_VERSION_i) #define TEST_IMPLICIT_DEFINES \ TEST_DEF(READ_SIZE, PROG_SIZE) \ @@ -115,9 +117,10 @@ intmax_t test_define(size_t define); TEST_DEF(ERASE_VALUE, 0xff) \ TEST_DEF(ERASE_CYCLES, 0) \ TEST_DEF(BADBLOCK_BEHAVIOR, LFS_EMUBD_BADBLOCK_PROGERROR) \ - TEST_DEF(POWERLOSS_BEHAVIOR, LFS_EMUBD_POWERLOSS_NOOP) + TEST_DEF(POWERLOSS_BEHAVIOR, LFS_EMUBD_POWERLOSS_NOOP) \ + TEST_DEF(DISK_VERSION, 0) -#define TEST_IMPLICIT_DEFINE_COUNT 11 +#define TEST_IMPLICIT_DEFINE_COUNT 12 #define TEST_GEOMETRY_DEFINE_COUNT 4 diff --git a/littlefs/tests/test_compat.toml b/littlefs/tests/test_compat.toml index a36c38a..ba44714 100644 --- a/littlefs/tests/test_compat.toml +++ b/littlefs/tests/test_compat.toml @@ -22,14 +22,16 @@ code = ''' #define STRINGIZE_(x) #x #include STRINGIZE(LFSP) #else -#define LFSP_VERSION LFS_VERSION -#define LFSP_VERSION_MAJOR LFS_VERSION_MAJOR -#define LFSP_VERSION_MINOR LFS_VERSION_MINOR +#define LFSP_DISK_VERSION LFS_DISK_VERSION +#define LFSP_DISK_VERSION_MAJOR LFS_DISK_VERSION_MAJOR +#define LFSP_DISK_VERSION_MINOR LFS_DISK_VERSION_MINOR #define lfsp_t lfs_t #define lfsp_config lfs_config #define lfsp_format lfs_format #define lfsp_mount lfs_mount #define lfsp_unmount lfs_unmount +#define lfsp_fsinfo lfs_fsinfo +#define lfsp_fs_stat lfs_fs_stat #define lfsp_dir_t lfs_dir_t #define lfsp_info lfs_info #define LFSP_TYPE_REG LFS_TYPE_REG @@ -58,7 +60,10 @@ code = ''' # test we can mount in a new version [cases.test_compat_forward_mount] -if = 'LFS_VERSION_MAJOR == LFSP_VERSION_MAJOR' +if = ''' + LFS_DISK_VERSION_MAJOR == LFSP_DISK_VERSION_MAJOR + && DISK_VERSION == 0 +''' code = ''' // create the previous version struct lfsp_config cfgp; @@ -74,13 +79,22 @@ code = ''' // now test the new mount lfs_t lfs; lfs_mount(&lfs, cfg) => 0; + + // we should be able to read the version using lfs_fs_stat + struct lfs_fsinfo fsinfo; + lfs_fs_stat(&lfs, &fsinfo) => 0; + assert(fsinfo.disk_version == LFSP_DISK_VERSION); + lfs_unmount(&lfs) => 0; ''' # test we can read dirs in a new version [cases.test_compat_forward_read_dirs] defines.COUNT = 5 -if = 'LFS_VERSION_MAJOR == LFSP_VERSION_MAJOR' +if = ''' + LFS_DISK_VERSION_MAJOR == LFSP_DISK_VERSION_MAJOR + && DISK_VERSION == 0 +''' code = ''' // create the previous version struct lfsp_config cfgp; @@ -102,6 +116,11 @@ code = ''' lfs_t lfs; lfs_mount(&lfs, cfg) => 0; + // we should be able to read the version using lfs_fs_stat + struct lfs_fsinfo fsinfo; + lfs_fs_stat(&lfs, &fsinfo) => 0; + assert(fsinfo.disk_version == LFSP_DISK_VERSION); + // can we list the directories? lfs_dir_t dir; lfs_dir_open(&lfs, &dir, "/") => 0; @@ -132,7 +151,10 @@ code = ''' defines.COUNT = 5 defines.SIZE = [4, 32, 512, 8192] defines.CHUNK = 4 -if = 'LFS_VERSION_MAJOR == LFSP_VERSION_MAJOR' +if = ''' + LFS_DISK_VERSION_MAJOR == LFSP_DISK_VERSION_MAJOR + && DISK_VERSION == 0 +''' code = ''' // create the previous version struct lfsp_config cfgp; @@ -166,6 +188,11 @@ code = ''' lfs_t lfs; lfs_mount(&lfs, cfg) => 0; + // we should be able to read the version using lfs_fs_stat + struct lfs_fsinfo fsinfo; + lfs_fs_stat(&lfs, &fsinfo) => 0; + assert(fsinfo.disk_version == LFSP_DISK_VERSION); + // can we list the files? lfs_dir_t dir; lfs_dir_open(&lfs, &dir, "/") => 0; @@ -214,7 +241,10 @@ code = ''' defines.COUNT = 5 defines.SIZE = [4, 32, 512, 8192] defines.CHUNK = 4 -if = 'LFS_VERSION_MAJOR == LFSP_VERSION_MAJOR' +if = ''' + LFS_DISK_VERSION_MAJOR == LFSP_DISK_VERSION_MAJOR + && DISK_VERSION == 0 +''' code = ''' // create the previous version struct lfsp_config cfgp; @@ -251,6 +281,11 @@ code = ''' lfs_t lfs; lfs_mount(&lfs, cfg) => 0; + // we should be able to read the version using lfs_fs_stat + struct lfs_fsinfo fsinfo; + lfs_fs_stat(&lfs, &fsinfo) => 0; + assert(fsinfo.disk_version == LFSP_DISK_VERSION); + // can we list the directories? lfs_dir_t dir; lfs_dir_open(&lfs, &dir, "/") => 0; @@ -321,7 +356,10 @@ code = ''' # test we can write dirs in a new version [cases.test_compat_forward_write_dirs] defines.COUNT = 10 -if = 'LFS_VERSION_MAJOR == LFSP_VERSION_MAJOR' +if = ''' + LFS_DISK_VERSION_MAJOR == LFSP_DISK_VERSION_MAJOR + && DISK_VERSION == 0 +''' code = ''' // create the previous version struct lfsp_config cfgp; @@ -343,6 +381,11 @@ code = ''' lfs_t lfs; lfs_mount(&lfs, cfg) => 0; + // we should be able to read the version using lfs_fs_stat + struct lfs_fsinfo fsinfo; + lfs_fs_stat(&lfs, &fsinfo) => 0; + assert(fsinfo.disk_version == LFSP_DISK_VERSION); + // write another COUNT/2 dirs for (lfs_size_t i = COUNT/2; i < COUNT; i++) { char name[8]; @@ -380,7 +423,10 @@ code = ''' defines.COUNT = 5 defines.SIZE = [4, 32, 512, 8192] defines.CHUNK = 2 -if = 'LFS_VERSION_MAJOR == LFSP_VERSION_MAJOR' +if = ''' + LFS_DISK_VERSION_MAJOR == LFSP_DISK_VERSION_MAJOR + && DISK_VERSION == 0 +''' code = ''' // create the previous version struct lfsp_config cfgp; @@ -420,6 +466,11 @@ code = ''' lfs_t lfs; lfs_mount(&lfs, cfg) => 0; + // we should be able to read the version using lfs_fs_stat + struct lfs_fsinfo fsinfo; + lfs_fs_stat(&lfs, &fsinfo) => 0; + assert(fsinfo.disk_version == LFSP_DISK_VERSION); + // write half COUNT files prng = 42; for (lfs_size_t i = 0; i < COUNT; i++) { @@ -494,7 +545,10 @@ code = ''' defines.COUNT = 5 defines.SIZE = [4, 32, 512, 8192] defines.CHUNK = 2 -if = 'LFS_VERSION_MAJOR == LFSP_VERSION_MAJOR' +if = ''' + LFS_DISK_VERSION_MAJOR == LFSP_DISK_VERSION_MAJOR + && DISK_VERSION == 0 +''' code = ''' // create the previous version struct lfsp_config cfgp; @@ -537,6 +591,11 @@ code = ''' lfs_t lfs; lfs_mount(&lfs, cfg) => 0; + // we should be able to read the version using lfs_fs_stat + struct lfs_fsinfo fsinfo; + lfs_fs_stat(&lfs, &fsinfo) => 0; + assert(fsinfo.disk_version == LFSP_DISK_VERSION); + // write half COUNT files prng = 42; for (lfs_size_t i = 0; i < COUNT; i++) { @@ -636,7 +695,10 @@ code = ''' # test we can mount in an old version [cases.test_compat_backward_mount] -if = 'LFS_VERSION == LFSP_VERSION' +if = ''' + LFS_DISK_VERSION == LFSP_DISK_VERSION + && DISK_VERSION == 0 +''' code = ''' // create the new version lfs_t lfs; @@ -651,13 +713,17 @@ code = ''' memcpy(&cfgp, cfg, sizeof(cfgp)); lfsp_t lfsp; lfsp_mount(&lfsp, &cfgp) => 0; + lfsp_unmount(&lfsp) => 0; ''' # test we can read dirs in an old version [cases.test_compat_backward_read_dirs] defines.COUNT = 5 -if = 'LFS_VERSION == LFSP_VERSION' +if = ''' + LFS_DISK_VERSION == LFSP_DISK_VERSION + && DISK_VERSION == 0 +''' code = ''' // create the new version lfs_t lfs; @@ -709,7 +775,10 @@ code = ''' defines.COUNT = 5 defines.SIZE = [4, 32, 512, 8192] defines.CHUNK = 4 -if = 'LFS_VERSION == LFSP_VERSION' +if = ''' + LFS_DISK_VERSION == LFSP_DISK_VERSION + && DISK_VERSION == 0 +''' code = ''' // create the new version lfs_t lfs; @@ -791,7 +860,10 @@ code = ''' defines.COUNT = 5 defines.SIZE = [4, 32, 512, 8192] defines.CHUNK = 4 -if = 'LFS_VERSION == LFSP_VERSION' +if = ''' + LFS_DISK_VERSION == LFSP_DISK_VERSION + && DISK_VERSION == 0 +''' code = ''' // create the new version lfs_t lfs; @@ -898,7 +970,10 @@ code = ''' # test we can write dirs in an old version [cases.test_compat_backward_write_dirs] defines.COUNT = 10 -if = 'LFS_VERSION == LFSP_VERSION' +if = ''' + LFS_DISK_VERSION == LFSP_DISK_VERSION + && DISK_VERSION == 0 +''' code = ''' // create the new version lfs_t lfs; @@ -957,7 +1032,10 @@ code = ''' defines.COUNT = 5 defines.SIZE = [4, 32, 512, 8192] defines.CHUNK = 2 -if = 'LFS_VERSION == LFSP_VERSION' +if = ''' + LFS_DISK_VERSION == LFSP_DISK_VERSION + && DISK_VERSION == 0 +''' code = ''' // create the previous version lfs_t lfs; @@ -1071,7 +1149,10 @@ code = ''' defines.COUNT = 5 defines.SIZE = [4, 32, 512, 8192] defines.CHUNK = 2 -if = 'LFS_VERSION == LFSP_VERSION' +if = ''' + LFS_DISK_VERSION == LFSP_DISK_VERSION + && DISK_VERSION == 0 +''' code = ''' // create the previous version lfs_t lfs; @@ -1280,7 +1361,10 @@ code = ''' # test that we correctly bump the minor version [cases.test_compat_minor_bump] in = 'lfs.c' -if = 'LFS_DISK_VERSION_MINOR > 0' +if = ''' + LFS_DISK_VERSION_MINOR > 0 + && DISK_VERSION == 0 +''' code = ''' // create a superblock lfs_t lfs; @@ -1316,45 +1400,54 @@ code = ''' // mount should still work lfs_mount(&lfs, cfg) => 0; + + struct lfs_fsinfo fsinfo; + lfs_fs_stat(&lfs, &fsinfo) => 0; + assert(fsinfo.disk_version == LFS_DISK_VERSION-1); + lfs_file_open(&lfs, &file, "test", LFS_O_RDONLY) => 0; uint8_t buffer[8]; lfs_file_read(&lfs, &file, buffer, 8) => 8; assert(memcmp(buffer, "testtest", 8) == 0); lfs_file_close(&lfs, &file) => 0; + + // minor version should be unchanged + lfs_fs_stat(&lfs, &fsinfo) => 0; + assert(fsinfo.disk_version == LFS_DISK_VERSION-1); + lfs_unmount(&lfs) => 0; // if we write, we need to bump the minor version lfs_mount(&lfs, cfg) => 0; + + lfs_fs_stat(&lfs, &fsinfo) => 0; + assert(fsinfo.disk_version == LFS_DISK_VERSION-1); + lfs_file_open(&lfs, &file, "test", LFS_O_WRONLY | LFS_O_TRUNC) => 0; lfs_file_write(&lfs, &file, "teeeeest", 8) => 8; lfs_file_close(&lfs, &file) => 0; - // minor version should have changed - lfs_dir_fetch(&lfs, &mdir, (lfs_block_t[2]){0, 1}) => 0; - lfs_dir_get(&lfs, &mdir, LFS_MKTAG(0x7ff, 0x3ff, 0), - LFS_MKTAG(LFS_TYPE_INLINESTRUCT, 0, sizeof(superblock)), - &superblock) - => LFS_MKTAG(LFS_TYPE_INLINESTRUCT, 0, sizeof(superblock)); - lfs_superblock_fromle32(&superblock); - assert((superblock.version >> 16) & 0xffff == LFS_DISK_VERSION_MAJOR); - assert((superblock.version >> 0) & 0xffff == LFS_DISK_VERSION_MINOR); + // minor version should be changed + lfs_fs_stat(&lfs, &fsinfo) => 0; + assert(fsinfo.disk_version == LFS_DISK_VERSION); + lfs_unmount(&lfs) => 0; // and of course mount should still work lfs_mount(&lfs, cfg) => 0; + + // minor version should have changed + lfs_fs_stat(&lfs, &fsinfo) => 0; + assert(fsinfo.disk_version == LFS_DISK_VERSION); + lfs_file_open(&lfs, &file, "test", LFS_O_RDONLY) => 0; lfs_file_read(&lfs, &file, buffer, 8) => 8; assert(memcmp(buffer, "teeeeest", 8) == 0); lfs_file_close(&lfs, &file) => 0; - // minor version should have changed - lfs_dir_fetch(&lfs, &mdir, (lfs_block_t[2]){0, 1}) => 0; - lfs_dir_get(&lfs, &mdir, LFS_MKTAG(0x7ff, 0x3ff, 0), - LFS_MKTAG(LFS_TYPE_INLINESTRUCT, 0, sizeof(superblock)), - &superblock) - => LFS_MKTAG(LFS_TYPE_INLINESTRUCT, 0, sizeof(superblock)); - lfs_superblock_fromle32(&superblock); - assert((superblock.version >> 16) & 0xffff == LFS_DISK_VERSION_MAJOR); - assert((superblock.version >> 0) & 0xffff == LFS_DISK_VERSION_MINOR); + // yep, still changed + lfs_fs_stat(&lfs, &fsinfo) => 0; + assert(fsinfo.disk_version == LFS_DISK_VERSION); + lfs_unmount(&lfs) => 0; ''' diff --git a/littlefs/tests/test_powerloss.toml b/littlefs/tests/test_powerloss.toml index 06f8661..92c323b 100644 --- a/littlefs/tests/test_powerloss.toml +++ b/littlefs/tests/test_powerloss.toml @@ -90,7 +90,10 @@ code = ''' # partial prog, may not be byte in order! [cases.test_powerloss_partial_prog] -if = "PROG_SIZE < BLOCK_SIZE" +if = ''' + PROG_SIZE < BLOCK_SIZE + && (DISK_VERSION == 0 || DISK_VERSION >= 0x00020001) +''' defines.BYTE_OFF = ["0", "PROG_SIZE-1", "PROG_SIZE/2"] defines.BYTE_VALUE = [0x33, 0xcc] in = "lfs.c" diff --git a/littlefs/tests/test_superblocks.toml b/littlefs/tests/test_superblocks.toml index 689bbcd..0aff84b 100644 --- a/littlefs/tests/test_superblocks.toml +++ b/littlefs/tests/test_superblocks.toml @@ -34,6 +34,54 @@ code = ''' lfs_mount(&lfs, cfg) => LFS_ERR_CORRUPT; ''' +# test we can read superblock info through lfs_fs_stat +[cases.test_superblocks_stat] +if = 'DISK_VERSION == 0' +code = ''' + lfs_t lfs; + lfs_format(&lfs, cfg) => 0; + + // test we can mount and read fsinfo + lfs_mount(&lfs, cfg) => 0; + + struct lfs_fsinfo fsinfo; + lfs_fs_stat(&lfs, &fsinfo) => 0; + assert(fsinfo.disk_version == LFS_DISK_VERSION); + assert(fsinfo.name_max == LFS_NAME_MAX); + assert(fsinfo.file_max == LFS_FILE_MAX); + assert(fsinfo.attr_max == LFS_ATTR_MAX); + + lfs_unmount(&lfs) => 0; +''' + +[cases.test_superblocks_stat_tweaked] +if = 'DISK_VERSION == 0' +defines.TWEAKED_NAME_MAX = 63 +defines.TWEAKED_FILE_MAX = '(1 << 16)-1' +defines.TWEAKED_ATTR_MAX = 512 +code = ''' + // create filesystem with tweaked params + struct lfs_config tweaked_cfg = *cfg; + tweaked_cfg.name_max = TWEAKED_NAME_MAX; + tweaked_cfg.file_max = TWEAKED_FILE_MAX; + tweaked_cfg.attr_max = TWEAKED_ATTR_MAX; + + lfs_t lfs; + lfs_format(&lfs, &tweaked_cfg) => 0; + + // test we can mount and read these params with the original config + lfs_mount(&lfs, cfg) => 0; + + struct lfs_fsinfo fsinfo; + lfs_fs_stat(&lfs, &fsinfo) => 0; + assert(fsinfo.disk_version == LFS_DISK_VERSION); + assert(fsinfo.name_max == TWEAKED_NAME_MAX); + assert(fsinfo.file_max == TWEAKED_FILE_MAX); + assert(fsinfo.attr_max == TWEAKED_ATTR_MAX); + + lfs_unmount(&lfs) => 0; +''' + # expanding superblock [cases.test_superblocks_expand] defines.BLOCK_CYCLES = [32, 33, 1]