diff --git a/.gitignore b/.gitignore index 36f92cd8..a6ebc4c3 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,6 @@ blocks/ lfs test.c +tests/*.toml.* +scripts/__pycache__ +.gdb_history diff --git a/.travis.yml b/.travis.yml index 42e59dc5..78d964a4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,49 +1,70 @@ -# Environment variables +# environment variables env: global: - CFLAGS=-Werror + - MAKEFLAGS=-j -# Common test script -script: +# cache installation dirs +cache: + pip: true + directories: + - $HOME/.cache/apt + +# common installation +_: &install-common + # need toml, also pip3 isn't installed by default? + - sudo apt-get install python3 python3-pip + - sudo pip3 install toml + # setup a ram-backed disk to speed up reentrant tests + - mkdir disks + - sudo mount -t tmpfs -o size=100m tmpfs disks + - export TFLAGS="$TFLAGS --disk=disks/disk" + +# test cases +_: &test-example # make sure example can at least compile - - sed -n '/``` c/,/```/{/```/d; p;}' README.md > test.c && + - sed -n '/``` c/,/```/{/```/d; p}' README.md > test.c && make all CFLAGS+=" -Duser_provided_block_device_read=NULL -Duser_provided_block_device_prog=NULL -Duser_provided_block_device_erase=NULL -Duser_provided_block_device_sync=NULL -include stdio.h" +# default tests +_: &test-default + # normal+reentrant tests + - make test TFLAGS+="-nrk" +# common real-life geometries +_: &test-nor + # NOR flash: read/prog = 1 block = 4KiB + - make test TFLAGS+="-nrk -DLFS_READ_SIZE=1 -DLFS_BLOCK_SIZE=4096" +_: &test-emmc + # eMMC: read/prog = 512 block = 512 + - make test TFLAGS+="-nrk -DLFS_READ_SIZE=512 -DLFS_BLOCK_SIZE=512" +_: &test-nand + # NAND flash: read/prog = 4KiB block = 32KiB + - make test TFLAGS+="-nrk -DLFS_READ_SIZE=4096 -DLFS_BLOCK_SIZE=\(32*1024\)" +# other extreme geometries that are useful for testing various corner cases +_: &test-no-intrinsics + - make test TFLAGS+="-nrk -DLFS_NO_INTRINSICS" +_: &test-no-inline + - make test TFLAGS+="-nrk -DLFS_INLINE_MAX=0" +_: &test-byte-writes + - make test TFLAGS+="-nrk -DLFS_READ_SIZE=1 -DLFS_CACHE_SIZE=1" +_: &test-block-cycles + - make test TFLAGS+="-nrk -DLFS_BLOCK_CYCLES=1" +_: &test-odd-block-count + - make test TFLAGS+="-nrk -DLFS_BLOCK_COUNT=1023 -DLFS_LOOKAHEAD_SIZE=256" +_: &test-odd-block-size + - make test TFLAGS+="-nrk -DLFS_READ_SIZE=11 -DLFS_BLOCK_SIZE=704" - # run tests - - make test QUIET=1 - - # run tests with a few different configurations - - make test QUIET=1 CFLAGS+="-DLFS_READ_SIZE=1 -DLFS_CACHE_SIZE=4" - - make test QUIET=1 CFLAGS+="-DLFS_READ_SIZE=512 -DLFS_CACHE_SIZE=512 -DLFS_BLOCK_CYCLES=16" - - make test QUIET=1 CFLAGS+="-DLFS_READ_SIZE=8 -DLFS_CACHE_SIZE=16 -DLFS_BLOCK_CYCLES=2" - - make test QUIET=1 CFLAGS+="-DLFS_BLOCK_COUNT=1023 -DLFS_LOOKAHEAD_SIZE=256" - - - make clean test QUIET=1 CFLAGS+="-DLFS_INLINE_MAX=0" - - make clean test QUIET=1 CFLAGS+="-DLFS_EMUBD_ERASE_VALUE=0xff" - - make clean test QUIET=1 CFLAGS+="-DLFS_NO_INTRINSICS" - - # additional configurations that don't support all tests (this should be - # fixed but at the moment it is what it is) - - make test_files QUIET=1 - CFLAGS+="-DLFS_READ_SIZE=1 -DLFS_BLOCK_SIZE=4096" - - make test_files QUIET=1 - CFLAGS+="-DLFS_READ_SIZE=\(2*1024\) -DLFS_BLOCK_SIZE=\(64*1024\)" - - make test_files QUIET=1 - CFLAGS+="-DLFS_READ_SIZE=\(8*1024\) -DLFS_BLOCK_SIZE=\(64*1024\)" - - make test_files QUIET=1 - CFLAGS+="-DLFS_READ_SIZE=11 -DLFS_BLOCK_SIZE=704" - +# report size +_: &report-size # compile and find the code size with the smallest configuration - - make clean size - OBJ="$(ls lfs*.o | tr '\n' ' ')" + - make -j1 clean size + OBJ="$(ls lfs*.c | sed 's/\.c/\.o/' | tr '\n' ' ')" CFLAGS+="-DLFS_NO_ASSERT -DLFS_NO_DEBUG -DLFS_NO_WARN -DLFS_NO_ERROR" | tee sizes - # update status if we succeeded, compare with master if possible - | if [ "$TRAVIS_TEST_RESULT" -eq 0 ] @@ -51,10 +72,10 @@ script: CURR=$(tail -n1 sizes | awk '{print $1}') PREV=$(curl -u "$GEKY_BOT_STATUSES" https://api.github.com/repos/$TRAVIS_REPO_SLUG/status/master \ | jq -re "select(.sha != \"$TRAVIS_COMMIT\") - | .statuses[] | select(.context == \"$STAGE/$NAME\").description + | .statuses[] | select(.context == \"${TRAVIS_BUILD_STAGE_NAME,,}/$NAME\").description | capture(\"code size is (?[0-9]+)\").size" \ || echo 0) - + STATUS="Passed, code size is ${CURR}B" if [ "$PREV" -ne 0 ] then @@ -62,257 +83,347 @@ script: fi fi -# CI matrix +# stage control +stages: + - name: test + - name: deploy + if: branch = master AND type = push + +# job control jobs: - include: - # native testing - - stage: test - env: - - STAGE=test - - NAME=littlefs-x86 + # native testing + - &x86 + stage: test + env: + - NAME=littlefs-x86 + install: *install-common + script: [*test-example, *report-size] + - {<<: *x86, script: [*test-default, *report-size]} + - {<<: *x86, script: [*test-nor, *report-size]} + - {<<: *x86, script: [*test-emmc, *report-size]} + - {<<: *x86, script: [*test-nand, *report-size]} + - {<<: *x86, script: [*test-no-intrinsics, *report-size]} + - {<<: *x86, script: [*test-no-inline, *report-size]} + - {<<: *x86, script: [*test-byte-writes, *report-size]} + - {<<: *x86, script: [*test-block-cycles, *report-size]} + - {<<: *x86, script: [*test-odd-block-count, *report-size]} + - {<<: *x86, script: [*test-odd-block-size, *report-size]} + + # cross-compile with ARM (thumb mode) + - &arm + stage: test + env: + - NAME=littlefs-arm + - CC="arm-linux-gnueabi-gcc --static -mthumb" + - TFLAGS="$TFLAGS --exec=qemu-arm" + install: + - *install-common + - sudo apt-get install + gcc-arm-linux-gnueabi + libc6-dev-armel-cross + qemu-user + - arm-linux-gnueabi-gcc --version + - qemu-arm -version + script: [*test-example, *report-size] + - {<<: *arm, script: [*test-default, *report-size]} + - {<<: *arm, script: [*test-nor, *report-size]} + - {<<: *arm, script: [*test-emmc, *report-size]} + - {<<: *arm, script: [*test-nand, *report-size]} + - {<<: *arm, script: [*test-no-intrinsics, *report-size]} + - {<<: *arm, script: [*test-no-inline, *report-size]} + # it just takes way to long to run byte-level writes in qemu, + # note this is still tested in the native tests + #- {<<: *arm, script: [*test-byte-writes, *report-size]} + - {<<: *arm, script: [*test-block-cycles, *report-size]} + - {<<: *arm, script: [*test-odd-block-count, *report-size]} + - {<<: *arm, script: [*test-odd-block-size, *report-size]} + + # cross-compile with MIPS + - &mips + stage: test + env: + - NAME=littlefs-mips + - CC="mips-linux-gnu-gcc --static" + - TFLAGS="$TFLAGS --exec=qemu-mips" + install: + - *install-common + - sudo apt-get install + gcc-mips-linux-gnu + libc6-dev-mips-cross + qemu-user + - mips-linux-gnu-gcc --version + - qemu-mips -version + script: [*test-example, *report-size] + - {<<: *mips, script: [*test-default, *report-size]} + - {<<: *mips, script: [*test-nor, *report-size]} + - {<<: *mips, script: [*test-emmc, *report-size]} + - {<<: *mips, script: [*test-nand, *report-size]} + - {<<: *mips, script: [*test-no-intrinsics, *report-size]} + - {<<: *mips, script: [*test-no-inline, *report-size]} + # it just takes way to long to run byte-level writes in qemu, + # note this is still tested in the native tests + #- {<<: *mips, script: [*test-byte-writes, *report-size]} + - {<<: *mips, script: [*test-block-cycles, *report-size]} + - {<<: *mips, script: [*test-odd-block-count, *report-size]} + - {<<: *mips, script: [*test-odd-block-size, *report-size]} - # cross-compile with ARM (thumb mode) - - stage: test - env: - - STAGE=test - - NAME=littlefs-arm - - CC="arm-linux-gnueabi-gcc --static -mthumb" - - EXEC="qemu-arm" - install: - - sudo apt-get install - gcc-arm-linux-gnueabi - libc6-dev-armel-cross - qemu-user - - arm-linux-gnueabi-gcc --version - - qemu-arm -version + # cross-compile with PowerPC + - &powerpc + stage: test + env: + - NAME=littlefs-powerpc + - CC="powerpc-linux-gnu-gcc --static" + - TFLAGS="$TFLAGS --exec=qemu-ppc" + install: + - *install-common + - sudo apt-get install + gcc-powerpc-linux-gnu + libc6-dev-powerpc-cross + qemu-user + - powerpc-linux-gnu-gcc --version + - qemu-ppc -version + script: [*test-example, *report-size] + - {<<: *powerpc, script: [*test-default, *report-size]} + - {<<: *powerpc, script: [*test-nor, *report-size]} + - {<<: *powerpc, script: [*test-emmc, *report-size]} + - {<<: *powerpc, script: [*test-nand, *report-size]} + - {<<: *powerpc, script: [*test-no-intrinsics, *report-size]} + - {<<: *powerpc, script: [*test-no-inline, *report-size]} + # it just takes way to long to run byte-level writes in qemu, + # note this is still tested in the native tests + #- {<<: *powerpc, script: [*test-byte-writes, *report-size]} + - {<<: *powerpc, script: [*test-block-cycles, *report-size]} + - {<<: *powerpc, script: [*test-odd-block-count, *report-size]} + - {<<: *powerpc, script: [*test-odd-block-size, *report-size]} - # cross-compile with PowerPC - - stage: test - env: - - STAGE=test - - NAME=littlefs-powerpc - - CC="powerpc-linux-gnu-gcc --static" - - EXEC="qemu-ppc" - install: - - sudo apt-get install - gcc-powerpc-linux-gnu - libc6-dev-powerpc-cross - qemu-user - - powerpc-linux-gnu-gcc --version - - qemu-ppc -version + # test under valgrind, checking for memory errors + - &valgrind + stage: test + env: + - NAME=littlefs-valgrind + install: + - *install-common + - sudo apt-get install valgrind + - valgrind --version + script: + - make test TFLAGS+="-k --valgrind" - # cross-compile with MIPS - - stage: test - env: - - STAGE=test - - NAME=littlefs-mips - - CC="mips-linux-gnu-gcc --static" - - EXEC="qemu-mips" - install: - - sudo apt-get install - gcc-mips-linux-gnu - libc6-dev-mips-cross - qemu-user - - mips-linux-gnu-gcc --version - - qemu-mips -version + # self-host with littlefs-fuse for fuzz test + - stage: test + env: + - NAME=littlefs-fuse + if: branch !~ -prefix$ + install: + - *install-common + - sudo apt-get install libfuse-dev + - git clone --depth 1 https://github.com/geky/littlefs-fuse -b v2 + - fusermount -V + - gcc --version - # self-host with littlefs-fuse for fuzz test - - stage: test - env: - - STAGE=test - - NAME=littlefs-fuse - if: branch !~ -prefix$ - install: - - sudo apt-get install libfuse-dev - - git clone --depth 1 https://github.com/geky/littlefs-fuse -b v2 - - fusermount -V - - gcc --version - before_script: - # setup disk for littlefs-fuse - - rm -rf littlefs-fuse/littlefs/* - - cp -r $(git ls-tree --name-only HEAD) littlefs-fuse/littlefs + # setup disk for littlefs-fuse + - rm -rf littlefs-fuse/littlefs/* + - cp -r $(git ls-tree --name-only HEAD) littlefs-fuse/littlefs - - mkdir mount - - sudo chmod a+rw /dev/loop0 - - dd if=/dev/zero bs=512 count=4096 of=disk - - losetup /dev/loop0 disk - script: - # self-host test - - make -C littlefs-fuse + - mkdir mount + - sudo chmod a+rw /dev/loop0 + - dd if=/dev/zero bs=512 count=128K of=disk + - losetup /dev/loop0 disk + script: + # self-host test + - make -C littlefs-fuse - - littlefs-fuse/lfs --format /dev/loop0 - - littlefs-fuse/lfs /dev/loop0 mount + - littlefs-fuse/lfs --format /dev/loop0 + - littlefs-fuse/lfs /dev/loop0 mount - - ls mount - - mkdir mount/littlefs - - cp -r $(git ls-tree --name-only HEAD) mount/littlefs - - cd mount/littlefs - - stat . - - ls -flh - - make -B test_dirs test_files QUIET=1 + - ls mount + - mkdir mount/littlefs + - cp -r $(git ls-tree --name-only HEAD) mount/littlefs + - cd mount/littlefs + - stat . + - ls -flh + - make -B test - # self-host with littlefs-fuse for fuzz test - - stage: test - env: - - STAGE=test - - NAME=littlefs-migration - if: branch !~ -prefix$ - install: - - sudo apt-get install libfuse-dev - - git clone --depth 1 https://github.com/geky/littlefs-fuse -b v2 v2 - - git clone --depth 1 https://github.com/geky/littlefs-fuse -b v1 v1 - - fusermount -V - - gcc --version - before_script: - # setup disk for littlefs-fuse - - rm -rf v2/littlefs/* - - cp -r $(git ls-tree --name-only HEAD) v2/littlefs + # test migration using littlefs-fuse + - stage: test + env: + - NAME=littlefs-migration + if: branch !~ -prefix$ + install: + - *install-common + - sudo apt-get install libfuse-dev + - git clone --depth 1 https://github.com/geky/littlefs-fuse -b v2 v2 + - git clone --depth 1 https://github.com/geky/littlefs-fuse -b v1 v1 + - fusermount -V + - gcc --version - - mkdir mount - - sudo chmod a+rw /dev/loop0 - - dd if=/dev/zero bs=512 count=4096 of=disk - - losetup /dev/loop0 disk - script: - # compile v1 and v2 - - make -C v1 - - make -C v2 + # setup disk for littlefs-fuse + - rm -rf v2/littlefs/* + - cp -r $(git ls-tree --name-only HEAD) v2/littlefs - # run self-host test with v1 - - v1/lfs --format /dev/loop0 - - v1/lfs /dev/loop0 mount + - mkdir mount + - sudo chmod a+rw /dev/loop0 + - dd if=/dev/zero bs=512 count=128K of=disk + - losetup /dev/loop0 disk + script: + # compile v1 and v2 + - make -C v1 + - make -C v2 - - ls mount - - mkdir mount/littlefs - - cp -r $(git ls-tree --name-only HEAD) mount/littlefs - - cd mount/littlefs - - stat . - - ls -flh - - make -B test_dirs test_files QUIET=1 + # run self-host test with v1 + - v1/lfs --format /dev/loop0 + - v1/lfs /dev/loop0 mount - # attempt to migrate - - cd ../.. - - fusermount -u mount + - ls mount + - mkdir mount/littlefs + - cp -r $(git ls-tree --name-only HEAD) mount/littlefs + - cd mount/littlefs + - stat . + - ls -flh + - make -B test - - v2/lfs --migrate /dev/loop0 - - v2/lfs /dev/loop0 mount + # attempt to migrate + - cd ../.. + - fusermount -u mount - # run self-host test with v2 right where we left off - - ls mount - - cd mount/littlefs - - stat . - - ls -flh - - make -B test_dirs test_files QUIET=1 + - v2/lfs --migrate /dev/loop0 + - v2/lfs /dev/loop0 mount - # Automatically create releases - - stage: deploy - env: - - STAGE=deploy - - NAME=deploy - script: - - | - bash << 'SCRIPT' - set -ev - # Find version defined in lfs.h - LFS_VERSION=$(grep -ox '#define LFS_VERSION .*' lfs.h | cut -d ' ' -f3) - LFS_VERSION_MAJOR=$((0xffff & ($LFS_VERSION >> 16))) - LFS_VERSION_MINOR=$((0xffff & ($LFS_VERSION >> 0))) - # Grab latests 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 - git branch v$LFS_VERSION_MAJOR HEAD - # Create major prefix branch - git config user.name "geky bot" - git config user.email "bot@geky.net" - git fetch https://github.com/$TRAVIS_REPO_SLUG.git \ - --depth=50 v$LFS_VERSION_MAJOR-prefix || true - ./scripts/prefix.py lfs$LFS_VERSION_MAJOR - git branch v$LFS_VERSION_MAJOR-prefix $( \ - git commit-tree $(git write-tree) \ - $(git rev-parse --verify -q FETCH_HEAD | sed -e 's/^/-p /') \ - -p HEAD \ - -m "Generated v$LFS_VERSION_MAJOR prefixes") - git reset --hard - # Update major version branches (vN and vN-prefix) - git push --atomic https://$GEKY_BOT_RELEASES@github.com/$TRAVIS_REPO_SLUG.git \ - v$LFS_VERSION_MAJOR \ - v$LFS_VERSION_MAJOR-prefix - # Build release notes - PREV=$(git tag --sort=-v:refname -l "v*" | head -1) - if [ ! -z "$PREV" ] - then - echo "PREV $PREV" - CHANGES=$(git log --oneline $PREV.. --grep='^Merge' --invert-grep) - printf "CHANGES\n%s\n\n" "$CHANGES" - fi - case ${GEKY_BOT_DRAFT:-minor} in - true) DRAFT=true ;; - minor) DRAFT=$(jq -R 'endswith(".0")' <<< "$LFS_VERSION") ;; - false) DRAFT=false ;; - esac - # Create the release and patch version tag (vN.N.N) - curl -f -u "$GEKY_BOT_RELEASES" -X POST \ - https://api.github.com/repos/$TRAVIS_REPO_SLUG/releases \ - -d "{ - \"tag_name\": \"$LFS_VERSION\", - \"name\": \"${LFS_VERSION%.0}\", - \"target_commitish\": \"$TRAVIS_COMMIT\", - \"draft\": $DRAFT, - \"body\": $(jq -sR '.' <<< "$CHANGES") - }" #" - SCRIPT + # run self-host test with v2 right where we left off + - ls mount + - cd mount/littlefs + - stat . + - ls -flh + - make -B test -# Manage statuses + # automatically create releases + - stage: deploy + env: + - NAME=deploy + script: + - | + bash << 'SCRIPT' + set -ev + # Find version defined in lfs.h + LFS_VERSION=$(grep -ox '#define LFS_VERSION .*' lfs.h | cut -d ' ' -f3) + LFS_VERSION_MAJOR=$((0xffff & ($LFS_VERSION >> 16))) + LFS_VERSION_MINOR=$((0xffff & ($LFS_VERSION >> 0))) + # Grab latests 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 + git branch v$LFS_VERSION_MAJOR HEAD + # Create major prefix branch + git config user.name "geky bot" + git config user.email "bot@geky.net" + git fetch https://github.com/$TRAVIS_REPO_SLUG.git \ + --depth=50 v$LFS_VERSION_MAJOR-prefix || true + ./scripts/prefix.py lfs$LFS_VERSION_MAJOR + git branch v$LFS_VERSION_MAJOR-prefix $( \ + git commit-tree $(git write-tree) \ + $(git rev-parse --verify -q FETCH_HEAD | sed -e 's/^/-p /') \ + -p HEAD \ + -m "Generated v$LFS_VERSION_MAJOR prefixes") + git reset --hard + # Update major version branches (vN and vN-prefix) + git push --atomic https://$GEKY_BOT_RELEASES@github.com/$TRAVIS_REPO_SLUG.git \ + v$LFS_VERSION_MAJOR \ + v$LFS_VERSION_MAJOR-prefix + # Build release notes + PREV=$(git tag --sort=-v:refname -l "v*" | head -1) + if [ ! -z "$PREV" ] + then + echo "PREV $PREV" + CHANGES=$(git log --oneline $PREV.. --grep='^Merge' --invert-grep) + printf "CHANGES\n%s\n\n" "$CHANGES" + fi + case ${GEKY_BOT_DRAFT:-minor} in + true) DRAFT=true ;; + minor) DRAFT=$(jq -R 'endswith(".0")' <<< "$LFS_VERSION") ;; + false) DRAFT=false ;; + esac + # Create the release and patch version tag (vN.N.N) + curl -f -u "$GEKY_BOT_RELEASES" -X POST \ + https://api.github.com/repos/$TRAVIS_REPO_SLUG/releases \ + -d "{ + \"tag_name\": \"$LFS_VERSION\", + \"name\": \"${LFS_VERSION%.0}\", + \"target_commitish\": \"$TRAVIS_COMMIT\", + \"draft\": $DRAFT, + \"body\": $(jq -sR '.' <<< "$CHANGES") + }" #" + SCRIPT + +# manage statuses before_install: - | - curl -u "$GEKY_BOT_STATUSES" -X POST \ - https://api.github.com/repos/$TRAVIS_REPO_SLUG/statuses/${TRAVIS_PULL_REQUEST_SHA:-$TRAVIS_COMMIT} \ - -d "{ - \"context\": \"$STAGE/$NAME\", - \"state\": \"pending\", - \"description\": \"${STATUS:-In progress}\", - \"target_url\": \"https://travis-ci.org/$TRAVIS_REPO_SLUG/jobs/$TRAVIS_JOB_ID\" - }" + # don't clobber other (not us) failures + if ! curl https://api.github.com/repos/$TRAVIS_REPO_SLUG/status/${TRAVIS_PULL_REQUEST_SHA:-$TRAVIS_COMMIT} \ + | jq -e ".statuses[] | select( + .context == \"${TRAVIS_BUILD_STAGE_NAME,,}/$NAME\" and + .state == \"failure\" and + (.target_url | endswith(\"$TRAVIS_JOB_NUMBER\") | not))" + then + curl -u "$GEKY_BOT_STATUSES" -X POST \ + https://api.github.com/repos/$TRAVIS_REPO_SLUG/statuses/${TRAVIS_PULL_REQUEST_SHA:-$TRAVIS_COMMIT} \ + -d "{ + \"context\": \"${TRAVIS_BUILD_STAGE_NAME,,}/$NAME\", + \"state\": \"pending\", + \"description\": \"${STATUS:-In progress}\", + \"target_url\": \"$TRAVIS_JOB_WEB_URL#$TRAVIS_JOB_NUMBER\" + }" + fi after_failure: - | - curl -u "$GEKY_BOT_STATUSES" -X POST \ - https://api.github.com/repos/$TRAVIS_REPO_SLUG/statuses/${TRAVIS_PULL_REQUEST_SHA:-$TRAVIS_COMMIT} \ - -d "{ - \"context\": \"$STAGE/$NAME\", - \"state\": \"failure\", - \"description\": \"${STATUS:-Failed}\", - \"target_url\": \"https://travis-ci.org/$TRAVIS_REPO_SLUG/jobs/$TRAVIS_JOB_ID\" - }" + # don't clobber other (not us) failures + if ! curl https://api.github.com/repos/$TRAVIS_REPO_SLUG/status/${TRAVIS_PULL_REQUEST_SHA:-$TRAVIS_COMMIT} \ + | jq -e ".statuses[] | select( + .context == \"${TRAVIS_BUILD_STAGE_NAME,,}/$NAME\" and + .state == \"failure\" and + (.target_url | endswith(\"$TRAVIS_JOB_NUMBER\") | not))" + then + curl -u "$GEKY_BOT_STATUSES" -X POST \ + https://api.github.com/repos/$TRAVIS_REPO_SLUG/statuses/${TRAVIS_PULL_REQUEST_SHA:-$TRAVIS_COMMIT} \ + -d "{ + \"context\": \"${TRAVIS_BUILD_STAGE_NAME,,}/$NAME\", + \"state\": \"failure\", + \"description\": \"${STATUS:-Failed}\", + \"target_url\": \"$TRAVIS_JOB_WEB_URL#$TRAVIS_JOB_NUMBER\" + }" + fi after_success: - | - curl -u "$GEKY_BOT_STATUSES" -X POST \ - https://api.github.com/repos/$TRAVIS_REPO_SLUG/statuses/${TRAVIS_PULL_REQUEST_SHA:-$TRAVIS_COMMIT} \ - -d "{ - \"context\": \"$STAGE/$NAME\", - \"state\": \"success\", - \"description\": \"${STATUS:-Passed}\", - \"target_url\": \"https://travis-ci.org/$TRAVIS_REPO_SLUG/jobs/$TRAVIS_JOB_ID\" - }" - -# Job control -stages: - - name: test - - name: deploy - if: branch = master AND type = push + # don't clobber other (not us) failures + # only update if we were last job to mark in progress, + # this isn't perfect but is probably good enough + if ! curl https://api.github.com/repos/$TRAVIS_REPO_SLUG/status/${TRAVIS_PULL_REQUEST_SHA:-$TRAVIS_COMMIT} \ + | jq -e ".statuses[] | select( + .context == \"${TRAVIS_BUILD_STAGE_NAME,,}/$NAME\" and + (.state == \"failure\" or .state == \"pending\") and + (.target_url | endswith(\"$TRAVIS_JOB_NUMBER\") | not))" + then + curl -u "$GEKY_BOT_STATUSES" -X POST \ + https://api.github.com/repos/$TRAVIS_REPO_SLUG/statuses/${TRAVIS_PULL_REQUEST_SHA:-$TRAVIS_COMMIT} \ + -d "{ + \"context\": \"${TRAVIS_BUILD_STAGE_NAME,,}/$NAME\", + \"state\": \"success\", + \"description\": \"${STATUS:-Passed}\", + \"target_url\": \"$TRAVIS_JOB_WEB_URL#$TRAVIS_JOB_NUMBER\" + }" + fi diff --git a/Makefile b/Makefile index 95c26dac..7cc73443 100644 --- a/Makefile +++ b/Makefile @@ -7,15 +7,11 @@ CC ?= gcc AR ?= ar SIZE ?= size -SRC += $(wildcard *.c emubd/*.c) +SRC += $(wildcard *.c bd/*.c) OBJ := $(SRC:.c=.o) DEP := $(SRC:.c=.d) ASM := $(SRC:.c=.s) -TEST := $(patsubst tests/%.sh,%,$(wildcard tests/test_*)) - -SHELL = /bin/bash -o pipefail - ifdef DEBUG override CFLAGS += -O0 -g3 else @@ -33,6 +29,10 @@ override CFLAGS += -Wextra -Wshadow -Wjump-misses-init -Wundef # Remove missing-field-initializers because of GCC bug override CFLAGS += -Wno-missing-field-initializers +ifdef VERBOSE +override TFLAGS += -v +endif + all: $(TARGET) @@ -41,30 +41,11 @@ asm: $(ASM) size: $(OBJ) $(SIZE) -t $^ -.SUFFIXES: -test: \ - test_format \ - test_dirs \ - test_files \ - test_seek \ - test_truncate \ - test_entries \ - test_interspersed \ - test_alloc \ - test_paths \ - test_attrs \ - test_move \ - test_orphan \ - test_relocations \ - test_corrupt - @rm test.c -test_%: tests/test_%.sh - -ifdef QUIET - @./$< | sed -nu '/^[-=]/p' -else - ./$< -endif +test: + ./scripts/test.py $(TFLAGS) +.SECONDEXPANSION: +test%: tests/test$$(firstword $$(subst \#, ,%)).toml + ./scripts/test.py $@ $(TFLAGS) -include $(DEP) @@ -85,3 +66,4 @@ clean: rm -f $(OBJ) rm -f $(DEP) rm -f $(ASM) + rm -f tests/*.toml.* diff --git a/bd/lfs_filebd.c b/bd/lfs_filebd.c new file mode 100644 index 00000000..2d36a42f --- /dev/null +++ b/bd/lfs_filebd.c @@ -0,0 +1,205 @@ +/* + * Block device emulated in a file + * + * Copyright (c) 2017, Arm Limited. All rights reserved. + * SPDX-License-Identifier: BSD-3-Clause + */ +#include "bd/lfs_filebd.h" + +#include +#include +#include + +int lfs_filebd_createcfg(const struct lfs_config *cfg, const char *path, + const struct lfs_filebd_config *bdcfg) { + LFS_FILEBD_TRACE("lfs_filebd_createcfg(%p {.context=%p, " + ".read=%p, .prog=%p, .erase=%p, .sync=%p, " + ".read_size=%"PRIu32", .prog_size=%"PRIu32", " + ".block_size=%"PRIu32", .block_count=%"PRIu32"}, " + "\"%s\", " + "%p {.erase_value=%"PRId32"})", + (void*)cfg, cfg->context, + (void*)(uintptr_t)cfg->read, (void*)(uintptr_t)cfg->prog, + (void*)(uintptr_t)cfg->erase, (void*)(uintptr_t)cfg->sync, + cfg->read_size, cfg->prog_size, cfg->block_size, cfg->block_count, + path, (void*)bdcfg, bdcfg->erase_value); + lfs_filebd_t *bd = cfg->context; + bd->cfg = bdcfg; + + // open file + bd->fd = open(path, O_RDWR | O_CREAT, 0666); + if (bd->fd < 0) { + int err = -errno; + LFS_FILEBD_TRACE("lfs_filebd_createcfg -> %d", err); + return err; + } + + LFS_FILEBD_TRACE("lfs_filebd_createcfg -> %d", 0); + return 0; +} + +int lfs_filebd_create(const struct lfs_config *cfg, const char *path) { + LFS_FILEBD_TRACE("lfs_filebd_create(%p {.context=%p, " + ".read=%p, .prog=%p, .erase=%p, .sync=%p, " + ".read_size=%"PRIu32", .prog_size=%"PRIu32", " + ".block_size=%"PRIu32", .block_count=%"PRIu32"}, " + "\"%s\")", + (void*)cfg, cfg->context, + (void*)(uintptr_t)cfg->read, (void*)(uintptr_t)cfg->prog, + (void*)(uintptr_t)cfg->erase, (void*)(uintptr_t)cfg->sync, + cfg->read_size, cfg->prog_size, cfg->block_size, cfg->block_count, + path); + static const struct lfs_filebd_config defaults = {.erase_value=-1}; + int err = lfs_filebd_createcfg(cfg, path, &defaults); + LFS_FILEBD_TRACE("lfs_filebd_create -> %d", err); + return err; +} + +int lfs_filebd_destroy(const struct lfs_config *cfg) { + LFS_FILEBD_TRACE("lfs_filebd_destroy(%p)", (void*)cfg); + lfs_filebd_t *bd = cfg->context; + int err = close(bd->fd); + if (err < 0) { + err = -errno; + LFS_FILEBD_TRACE("lfs_filebd_destroy -> %d", err); + return err; + } + LFS_FILEBD_TRACE("lfs_filebd_destroy -> %d", 0); + return 0; +} + +int lfs_filebd_read(const struct lfs_config *cfg, lfs_block_t block, + lfs_off_t off, void *buffer, lfs_size_t size) { + LFS_FILEBD_TRACE("lfs_filebd_read(%p, " + "0x%"PRIx32", %"PRIu32", %p, %"PRIu32")", + (void*)cfg, block, off, buffer, size); + lfs_filebd_t *bd = cfg->context; + + // check if read is valid + LFS_ASSERT(off % cfg->read_size == 0); + LFS_ASSERT(size % cfg->read_size == 0); + LFS_ASSERT(block < cfg->block_count); + + // zero for reproducability (in case file is truncated) + if (bd->cfg->erase_value != -1) { + memset(buffer, bd->cfg->erase_value, size); + } + + // read + off_t res1 = lseek(bd->fd, + (off_t)block*cfg->block_size + (off_t)off, SEEK_SET); + if (res1 < 0) { + int err = -errno; + LFS_FILEBD_TRACE("lfs_filebd_read -> %d", err); + return err; + } + + ssize_t res2 = read(bd->fd, buffer, size); + if (res2 < 0) { + int err = -errno; + LFS_FILEBD_TRACE("lfs_filebd_read -> %d", err); + return err; + } + + LFS_FILEBD_TRACE("lfs_filebd_read -> %d", 0); + return 0; +} + +int lfs_filebd_prog(const struct lfs_config *cfg, lfs_block_t block, + lfs_off_t off, const void *buffer, lfs_size_t size) { + LFS_FILEBD_TRACE("lfs_filebd_prog(%p, 0x%"PRIx32", %"PRIu32", %p, %"PRIu32")", + (void*)cfg, block, off, buffer, size); + lfs_filebd_t *bd = cfg->context; + + // check if write is valid + LFS_ASSERT(off % cfg->prog_size == 0); + LFS_ASSERT(size % cfg->prog_size == 0); + LFS_ASSERT(block < cfg->block_count); + + // check that data was erased? only needed for testing + if (bd->cfg->erase_value != -1) { + off_t res1 = lseek(bd->fd, + (off_t)block*cfg->block_size + (off_t)off, SEEK_SET); + if (res1 < 0) { + int err = -errno; + LFS_FILEBD_TRACE("lfs_filebd_prog -> %d", err); + return err; + } + + for (lfs_off_t i = 0; i < size; i++) { + uint8_t c; + ssize_t res2 = read(bd->fd, &c, 1); + if (res2 < 0) { + int err = -errno; + LFS_FILEBD_TRACE("lfs_filebd_prog -> %d", err); + return err; + } + + LFS_ASSERT(c == bd->cfg->erase_value); + } + } + + // program data + off_t res1 = lseek(bd->fd, + (off_t)block*cfg->block_size + (off_t)off, SEEK_SET); + if (res1 < 0) { + int err = -errno; + LFS_FILEBD_TRACE("lfs_filebd_prog -> %d", err); + return err; + } + + ssize_t res2 = write(bd->fd, buffer, size); + if (res2 < 0) { + int err = -errno; + LFS_FILEBD_TRACE("lfs_filebd_prog -> %d", err); + return err; + } + + LFS_FILEBD_TRACE("lfs_filebd_prog -> %d", 0); + return 0; +} + +int lfs_filebd_erase(const struct lfs_config *cfg, lfs_block_t block) { + LFS_FILEBD_TRACE("lfs_filebd_erase(%p, 0x%"PRIx32")", (void*)cfg, block); + lfs_filebd_t *bd = cfg->context; + + // check if erase is valid + LFS_ASSERT(block < cfg->block_count); + + // erase, only needed for testing + if (bd->cfg->erase_value != -1) { + off_t res1 = lseek(bd->fd, (off_t)block*cfg->block_size, SEEK_SET); + if (res1 < 0) { + int err = -errno; + LFS_FILEBD_TRACE("lfs_filebd_erase -> %d", err); + return err; + } + + for (lfs_off_t i = 0; i < cfg->block_size; i++) { + ssize_t res2 = write(bd->fd, &(uint8_t){bd->cfg->erase_value}, 1); + if (res2 < 0) { + int err = -errno; + LFS_FILEBD_TRACE("lfs_filebd_erase -> %d", err); + return err; + } + } + } + + LFS_FILEBD_TRACE("lfs_filebd_erase -> %d", 0); + return 0; +} + +int lfs_filebd_sync(const struct lfs_config *cfg) { + LFS_FILEBD_TRACE("lfs_filebd_sync(%p)", (void*)cfg); + // file sync + lfs_filebd_t *bd = cfg->context; + int err = fsync(bd->fd); + if (err) { + err = -errno; + LFS_FILEBD_TRACE("lfs_filebd_sync -> %d", 0); + return err; + } + + LFS_FILEBD_TRACE("lfs_filebd_sync -> %d", 0); + return 0; +} diff --git a/bd/lfs_filebd.h b/bd/lfs_filebd.h new file mode 100644 index 00000000..0d56434a --- /dev/null +++ b/bd/lfs_filebd.h @@ -0,0 +1,73 @@ +/* + * Block device emulated in a file + * + * Copyright (c) 2017, Arm Limited. All rights reserved. + * SPDX-License-Identifier: BSD-3-Clause + */ +#ifndef LFS_FILEBD_H +#define LFS_FILEBD_H + +#include "lfs.h" +#include "lfs_util.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + + +// Block device specific tracing +#ifdef LFS_FILEBD_YES_TRACE +#define LFS_FILEBD_TRACE(...) LFS_TRACE(__VA_ARGS__) +#else +#define LFS_FILEBD_TRACE(...) +#endif + +// filebd config (optional) +struct lfs_filebd_config { + // 8-bit erase value to use for simulating erases. -1 does not simulate + // erases, which can speed up testing by avoiding all the extra block-device + // operations to store the erase value. + int32_t erase_value; +}; + +// filebd state +typedef struct lfs_filebd { + int fd; + const struct lfs_filebd_config *cfg; +} lfs_filebd_t; + + +// Create a file block device using the geometry in lfs_config +int lfs_filebd_create(const struct lfs_config *cfg, const char *path); +int lfs_filebd_createcfg(const struct lfs_config *cfg, const char *path, + const struct lfs_filebd_config *bdcfg); + +// Clean up memory associated with block device +int lfs_filebd_destroy(const struct lfs_config *cfg); + +// Read a block +int lfs_filebd_read(const struct lfs_config *cfg, lfs_block_t block, + lfs_off_t off, void *buffer, lfs_size_t size); + +// Program a block +// +// The block must have previously been erased. +int lfs_filebd_prog(const struct lfs_config *cfg, lfs_block_t block, + lfs_off_t off, const void *buffer, lfs_size_t size); + +// Erase a block +// +// A block must be erased before being programmed. The +// state of an erased block is undefined. +int lfs_filebd_erase(const struct lfs_config *cfg, lfs_block_t block); + +// Sync the block device +int lfs_filebd_sync(const struct lfs_config *cfg); + + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif diff --git a/bd/lfs_rambd.c b/bd/lfs_rambd.c new file mode 100644 index 00000000..0a6b5cca --- /dev/null +++ b/bd/lfs_rambd.c @@ -0,0 +1,140 @@ +/* + * Block device emulated in RAM + * + * Copyright (c) 2017, Arm Limited. All rights reserved. + * SPDX-License-Identifier: BSD-3-Clause + */ +#include "bd/lfs_rambd.h" + +int lfs_rambd_createcfg(const struct lfs_config *cfg, + const struct lfs_rambd_config *bdcfg) { + LFS_RAMBD_TRACE("lfs_rambd_createcfg(%p {.context=%p, " + ".read=%p, .prog=%p, .erase=%p, .sync=%p, " + ".read_size=%"PRIu32", .prog_size=%"PRIu32", " + ".block_size=%"PRIu32", .block_count=%"PRIu32"}, " + "%p {.erase_value=%"PRId32", .buffer=%p})", + (void*)cfg, cfg->context, + (void*)(uintptr_t)cfg->read, (void*)(uintptr_t)cfg->prog, + (void*)(uintptr_t)cfg->erase, (void*)(uintptr_t)cfg->sync, + cfg->read_size, cfg->prog_size, cfg->block_size, cfg->block_count, + (void*)bdcfg, bdcfg->erase_value, bdcfg->buffer); + lfs_rambd_t *bd = cfg->context; + bd->cfg = bdcfg; + + // allocate buffer? + if (bd->cfg->buffer) { + bd->buffer = bd->cfg->buffer; + } else { + bd->buffer = lfs_malloc(cfg->block_size * cfg->block_count); + if (!bd->buffer) { + LFS_RAMBD_TRACE("lfs_rambd_createcfg -> %d", LFS_ERR_NOMEM); + return LFS_ERR_NOMEM; + } + } + + // zero for reproducability? + if (bd->cfg->erase_value != -1) { + memset(bd->buffer, bd->cfg->erase_value, + cfg->block_size * cfg->block_count); + } + + LFS_RAMBD_TRACE("lfs_rambd_createcfg -> %d", 0); + return 0; +} + +int lfs_rambd_create(const struct lfs_config *cfg) { + LFS_RAMBD_TRACE("lfs_rambd_create(%p {.context=%p, " + ".read=%p, .prog=%p, .erase=%p, .sync=%p, " + ".read_size=%"PRIu32", .prog_size=%"PRIu32", " + ".block_size=%"PRIu32", .block_count=%"PRIu32"})", + (void*)cfg, cfg->context, + (void*)(uintptr_t)cfg->read, (void*)(uintptr_t)cfg->prog, + (void*)(uintptr_t)cfg->erase, (void*)(uintptr_t)cfg->sync, + cfg->read_size, cfg->prog_size, cfg->block_size, cfg->block_count); + static const struct lfs_rambd_config defaults = {.erase_value=-1}; + int err = lfs_rambd_createcfg(cfg, &defaults); + LFS_RAMBD_TRACE("lfs_rambd_create -> %d", err); + return err; +} + +int lfs_rambd_destroy(const struct lfs_config *cfg) { + LFS_RAMBD_TRACE("lfs_rambd_destroy(%p)", (void*)cfg); + // clean up memory + lfs_rambd_t *bd = cfg->context; + if (!bd->cfg->buffer) { + lfs_free(bd->buffer); + } + LFS_RAMBD_TRACE("lfs_rambd_destroy -> %d", 0); + return 0; +} + +int lfs_rambd_read(const struct lfs_config *cfg, lfs_block_t block, + lfs_off_t off, void *buffer, lfs_size_t size) { + LFS_RAMBD_TRACE("lfs_rambd_read(%p, " + "0x%"PRIx32", %"PRIu32", %p, %"PRIu32")", + (void*)cfg, block, off, buffer, size); + lfs_rambd_t *bd = cfg->context; + + // check if read is valid + LFS_ASSERT(off % cfg->read_size == 0); + LFS_ASSERT(size % cfg->read_size == 0); + LFS_ASSERT(block < cfg->block_count); + + // read data + memcpy(buffer, &bd->buffer[block*cfg->block_size + off], size); + + LFS_RAMBD_TRACE("lfs_rambd_read -> %d", 0); + return 0; +} + +int lfs_rambd_prog(const struct lfs_config *cfg, lfs_block_t block, + lfs_off_t off, const void *buffer, lfs_size_t size) { + LFS_RAMBD_TRACE("lfs_rambd_prog(%p, " + "0x%"PRIx32", %"PRIu32", %p, %"PRIu32")", + (void*)cfg, block, off, buffer, size); + lfs_rambd_t *bd = cfg->context; + + // check if write is valid + LFS_ASSERT(off % cfg->prog_size == 0); + LFS_ASSERT(size % cfg->prog_size == 0); + LFS_ASSERT(block < cfg->block_count); + + // check that data was erased? only needed for testing + if (bd->cfg->erase_value != -1) { + for (lfs_off_t i = 0; i < size; i++) { + LFS_ASSERT(bd->buffer[block*cfg->block_size + off + i] == + bd->cfg->erase_value); + } + } + + // program data + memcpy(&bd->buffer[block*cfg->block_size + off], buffer, size); + + LFS_RAMBD_TRACE("lfs_rambd_prog -> %d", 0); + return 0; +} + +int lfs_rambd_erase(const struct lfs_config *cfg, lfs_block_t block) { + LFS_RAMBD_TRACE("lfs_rambd_erase(%p, 0x%"PRIx32")", (void*)cfg, block); + lfs_rambd_t *bd = cfg->context; + + // check if erase is valid + LFS_ASSERT(block < cfg->block_count); + + // erase, only needed for testing + if (bd->cfg->erase_value != -1) { + memset(&bd->buffer[block*cfg->block_size], + bd->cfg->erase_value, cfg->block_size); + } + + LFS_RAMBD_TRACE("lfs_rambd_erase -> %d", 0); + return 0; +} + +int lfs_rambd_sync(const struct lfs_config *cfg) { + LFS_RAMBD_TRACE("lfs_rambd_sync(%p)", (void*)cfg); + // sync does nothing because we aren't backed by anything real + (void)cfg; + LFS_RAMBD_TRACE("lfs_rambd_sync -> %d", 0); + return 0; +} diff --git a/bd/lfs_rambd.h b/bd/lfs_rambd.h new file mode 100644 index 00000000..56a45ce9 --- /dev/null +++ b/bd/lfs_rambd.h @@ -0,0 +1,75 @@ +/* + * Block device emulated in RAM + * + * Copyright (c) 2017, Arm Limited. All rights reserved. + * SPDX-License-Identifier: BSD-3-Clause + */ +#ifndef LFS_RAMBD_H +#define LFS_RAMBD_H + +#include "lfs.h" +#include "lfs_util.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + + +// Block device specific tracing +#ifdef LFS_RAMBD_YES_TRACE +#define LFS_RAMBD_TRACE(...) LFS_TRACE(__VA_ARGS__) +#else +#define LFS_RAMBD_TRACE(...) +#endif + +// rambd config (optional) +struct lfs_rambd_config { + // 8-bit erase value to simulate erasing with. -1 indicates no erase + // occurs, which is still a valid block device + int32_t erase_value; + + // Optional statically allocated buffer for the block device. + void *buffer; +}; + +// rambd state +typedef struct lfs_rambd { + uint8_t *buffer; + const struct lfs_rambd_config *cfg; +} lfs_rambd_t; + + +// Create a RAM block device using the geometry in lfs_config +int lfs_rambd_create(const struct lfs_config *cfg); +int lfs_rambd_createcfg(const struct lfs_config *cfg, + const struct lfs_rambd_config *bdcfg); + +// Clean up memory associated with block device +int lfs_rambd_destroy(const struct lfs_config *cfg); + +// Read a block +int lfs_rambd_read(const struct lfs_config *cfg, lfs_block_t block, + lfs_off_t off, void *buffer, lfs_size_t size); + +// Program a block +// +// The block must have previously been erased. +int lfs_rambd_prog(const struct lfs_config *cfg, lfs_block_t block, + lfs_off_t off, const void *buffer, lfs_size_t size); + +// Erase a block +// +// A block must be erased before being programmed. The +// state of an erased block is undefined. +int lfs_rambd_erase(const struct lfs_config *cfg, lfs_block_t block); + +// Sync the block device +int lfs_rambd_sync(const struct lfs_config *cfg); + + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif diff --git a/bd/lfs_testbd.c b/bd/lfs_testbd.c new file mode 100644 index 00000000..1ec6fb90 --- /dev/null +++ b/bd/lfs_testbd.c @@ -0,0 +1,302 @@ +/* + * Testing block device, wraps filebd and rambd while providing a bunch + * of hooks for testing littlefs in various conditions. + * + * Copyright (c) 2017, Arm Limited. All rights reserved. + * SPDX-License-Identifier: BSD-3-Clause + */ +#include "bd/lfs_testbd.h" + +#include + + +int lfs_testbd_createcfg(const struct lfs_config *cfg, const char *path, + const struct lfs_testbd_config *bdcfg) { + LFS_TESTBD_TRACE("lfs_testbd_createcfg(%p {.context=%p, " + ".read=%p, .prog=%p, .erase=%p, .sync=%p, " + ".read_size=%"PRIu32", .prog_size=%"PRIu32", " + ".block_size=%"PRIu32", .block_count=%"PRIu32"}, " + "\"%s\", " + "%p {.erase_value=%"PRId32", .erase_cycles=%"PRIu32", " + ".badblock_behavior=%"PRIu8", .power_cycles=%"PRIu32", " + ".buffer=%p, .wear_buffer=%p})", + (void*)cfg, cfg->context, + (void*)(uintptr_t)cfg->read, (void*)(uintptr_t)cfg->prog, + (void*)(uintptr_t)cfg->erase, (void*)(uintptr_t)cfg->sync, + cfg->read_size, cfg->prog_size, cfg->block_size, cfg->block_count, + path, (void*)bdcfg, bdcfg->erase_value, bdcfg->erase_cycles, + bdcfg->badblock_behavior, bdcfg->power_cycles, + bdcfg->buffer, bdcfg->wear_buffer); + lfs_testbd_t *bd = cfg->context; + bd->cfg = bdcfg; + + // setup testing things + bd->persist = path; + bd->power_cycles = bd->cfg->power_cycles; + + if (bd->cfg->erase_cycles) { + if (bd->cfg->wear_buffer) { + bd->wear = bd->cfg->wear_buffer; + } else { + bd->wear = lfs_malloc(sizeof(lfs_testbd_wear_t)*cfg->block_count); + if (!bd->wear) { + LFS_TESTBD_TRACE("lfs_testbd_createcfg -> %d", LFS_ERR_NOMEM); + return LFS_ERR_NOMEM; + } + } + + memset(bd->wear, 0, sizeof(lfs_testbd_wear_t) * cfg->block_count); + } + + // create underlying block device + if (bd->persist) { + bd->u.file.cfg = (struct lfs_filebd_config){ + .erase_value = bd->cfg->erase_value, + }; + int err = lfs_filebd_createcfg(cfg, path, &bd->u.file.cfg); + LFS_TESTBD_TRACE("lfs_testbd_createcfg -> %d", err); + return err; + } else { + bd->u.ram.cfg = (struct lfs_rambd_config){ + .erase_value = bd->cfg->erase_value, + .buffer = bd->cfg->buffer, + }; + int err = lfs_rambd_createcfg(cfg, &bd->u.ram.cfg); + LFS_TESTBD_TRACE("lfs_testbd_createcfg -> %d", err); + return err; + } +} + +int lfs_testbd_create(const struct lfs_config *cfg, const char *path) { + LFS_TESTBD_TRACE("lfs_testbd_create(%p {.context=%p, " + ".read=%p, .prog=%p, .erase=%p, .sync=%p, " + ".read_size=%"PRIu32", .prog_size=%"PRIu32", " + ".block_size=%"PRIu32", .block_count=%"PRIu32"}, " + "\"%s\")", + (void*)cfg, cfg->context, + (void*)(uintptr_t)cfg->read, (void*)(uintptr_t)cfg->prog, + (void*)(uintptr_t)cfg->erase, (void*)(uintptr_t)cfg->sync, + cfg->read_size, cfg->prog_size, cfg->block_size, cfg->block_count, + path); + static const struct lfs_testbd_config defaults = {.erase_value=-1}; + int err = lfs_testbd_createcfg(cfg, path, &defaults); + LFS_TESTBD_TRACE("lfs_testbd_create -> %d", err); + return err; +} + +int lfs_testbd_destroy(const struct lfs_config *cfg) { + LFS_TESTBD_TRACE("lfs_testbd_destroy(%p)", (void*)cfg); + lfs_testbd_t *bd = cfg->context; + if (bd->cfg->erase_cycles && !bd->cfg->wear_buffer) { + lfs_free(bd->wear); + } + + if (bd->persist) { + int err = lfs_filebd_destroy(cfg); + LFS_TESTBD_TRACE("lfs_testbd_destroy -> %d", err); + return err; + } else { + int err = lfs_rambd_destroy(cfg); + LFS_TESTBD_TRACE("lfs_testbd_destroy -> %d", err); + return err; + } +} + +/// Internal mapping to block devices /// +static int lfs_testbd_rawread(const struct lfs_config *cfg, lfs_block_t block, + lfs_off_t off, void *buffer, lfs_size_t size) { + lfs_testbd_t *bd = cfg->context; + if (bd->persist) { + return lfs_filebd_read(cfg, block, off, buffer, size); + } else { + return lfs_rambd_read(cfg, block, off, buffer, size); + } +} + +static int lfs_testbd_rawprog(const struct lfs_config *cfg, lfs_block_t block, + lfs_off_t off, const void *buffer, lfs_size_t size) { + lfs_testbd_t *bd = cfg->context; + if (bd->persist) { + return lfs_filebd_prog(cfg, block, off, buffer, size); + } else { + return lfs_rambd_prog(cfg, block, off, buffer, size); + } +} + +static int lfs_testbd_rawerase(const struct lfs_config *cfg, + lfs_block_t block) { + lfs_testbd_t *bd = cfg->context; + if (bd->persist) { + return lfs_filebd_erase(cfg, block); + } else { + return lfs_rambd_erase(cfg, block); + } +} + +static int lfs_testbd_rawsync(const struct lfs_config *cfg) { + lfs_testbd_t *bd = cfg->context; + if (bd->persist) { + return lfs_filebd_sync(cfg); + } else { + return lfs_rambd_sync(cfg); + } +} + +/// block device API /// +int lfs_testbd_read(const struct lfs_config *cfg, lfs_block_t block, + lfs_off_t off, void *buffer, lfs_size_t size) { + LFS_TESTBD_TRACE("lfs_testbd_read(%p, " + "0x%"PRIx32", %"PRIu32", %p, %"PRIu32")", + (void*)cfg, block, off, buffer, size); + lfs_testbd_t *bd = cfg->context; + + // check if read is valid + LFS_ASSERT(off % cfg->read_size == 0); + LFS_ASSERT(size % cfg->read_size == 0); + LFS_ASSERT(block < cfg->block_count); + + // block bad? + if (bd->cfg->erase_cycles && bd->wear[block] >= bd->cfg->erase_cycles && + bd->cfg->badblock_behavior == LFS_TESTBD_BADBLOCK_READERROR) { + LFS_TESTBD_TRACE("lfs_testbd_read -> %d", LFS_ERR_CORRUPT); + return LFS_ERR_CORRUPT; + } + + // read + int err = lfs_testbd_rawread(cfg, block, off, buffer, size); + LFS_TESTBD_TRACE("lfs_testbd_read -> %d", err); + return err; +} + +int lfs_testbd_prog(const struct lfs_config *cfg, lfs_block_t block, + lfs_off_t off, const void *buffer, lfs_size_t size) { + LFS_TESTBD_TRACE("lfs_testbd_prog(%p, " + "0x%"PRIx32", %"PRIu32", %p, %"PRIu32")", + (void*)cfg, block, off, buffer, size); + lfs_testbd_t *bd = cfg->context; + + // check if write is valid + LFS_ASSERT(off % cfg->prog_size == 0); + LFS_ASSERT(size % cfg->prog_size == 0); + LFS_ASSERT(block < cfg->block_count); + + // block bad? + if (bd->cfg->erase_cycles && bd->wear[block] >= bd->cfg->erase_cycles) { + if (bd->cfg->badblock_behavior == + LFS_TESTBD_BADBLOCK_PROGERROR) { + LFS_TESTBD_TRACE("lfs_testbd_prog -> %d", LFS_ERR_CORRUPT); + return LFS_ERR_CORRUPT; + } else if (bd->cfg->badblock_behavior == + LFS_TESTBD_BADBLOCK_PROGNOOP || + bd->cfg->badblock_behavior == + LFS_TESTBD_BADBLOCK_ERASENOOP) { + LFS_TESTBD_TRACE("lfs_testbd_prog -> %d", 0); + return 0; + } + } + + // prog + int err = lfs_testbd_rawprog(cfg, block, off, buffer, size); + if (err) { + LFS_TESTBD_TRACE("lfs_testbd_prog -> %d", err); + return err; + } + + // lose power? + if (bd->power_cycles > 0) { + bd->power_cycles -= 1; + if (bd->power_cycles == 0) { + // sync to make sure we persist the last changes + assert(lfs_testbd_rawsync(cfg) == 0); + // simulate power loss + exit(33); + } + } + + LFS_TESTBD_TRACE("lfs_testbd_prog -> %d", 0); + return 0; +} + +int lfs_testbd_erase(const struct lfs_config *cfg, lfs_block_t block) { + LFS_TESTBD_TRACE("lfs_testbd_erase(%p, 0x%"PRIx32")", (void*)cfg, block); + lfs_testbd_t *bd = cfg->context; + + // check if erase is valid + LFS_ASSERT(block < cfg->block_count); + + // block bad? + if (bd->cfg->erase_cycles) { + if (bd->wear[block] >= bd->cfg->erase_cycles) { + if (bd->cfg->badblock_behavior == + LFS_TESTBD_BADBLOCK_ERASEERROR) { + LFS_TESTBD_TRACE("lfs_testbd_erase -> %d", LFS_ERR_CORRUPT); + return LFS_ERR_CORRUPT; + } else if (bd->cfg->badblock_behavior == + LFS_TESTBD_BADBLOCK_ERASENOOP) { + LFS_TESTBD_TRACE("lfs_testbd_erase -> %d", 0); + return 0; + } + } else { + // mark wear + bd->wear[block] += 1; + } + } + + // erase + int err = lfs_testbd_rawerase(cfg, block); + if (err) { + LFS_TESTBD_TRACE("lfs_testbd_erase -> %d", err); + return err; + } + + // lose power? + if (bd->power_cycles > 0) { + bd->power_cycles -= 1; + if (bd->power_cycles == 0) { + // sync to make sure we persist the last changes + assert(lfs_testbd_rawsync(cfg) == 0); + // simulate power loss + exit(33); + } + } + + LFS_TESTBD_TRACE("lfs_testbd_prog -> %d", 0); + return 0; +} + +int lfs_testbd_sync(const struct lfs_config *cfg) { + LFS_TESTBD_TRACE("lfs_testbd_sync(%p)", (void*)cfg); + int err = lfs_testbd_rawsync(cfg); + LFS_TESTBD_TRACE("lfs_testbd_sync -> %d", err); + return err; +} + + +/// simulated wear operations /// +lfs_testbd_swear_t lfs_testbd_getwear(const struct lfs_config *cfg, + lfs_block_t block) { + LFS_TESTBD_TRACE("lfs_testbd_getwear(%p, %"PRIu32")", (void*)cfg, block); + lfs_testbd_t *bd = cfg->context; + + // check if block is valid + LFS_ASSERT(bd->cfg->erase_cycles); + LFS_ASSERT(block < cfg->block_count); + + LFS_TESTBD_TRACE("lfs_testbd_getwear -> %"PRIu32, bd->wear[block]); + return bd->wear[block]; +} + +int lfs_testbd_setwear(const struct lfs_config *cfg, + lfs_block_t block, lfs_testbd_wear_t wear) { + LFS_TESTBD_TRACE("lfs_testbd_setwear(%p, %"PRIu32")", (void*)cfg, block); + lfs_testbd_t *bd = cfg->context; + + // check if block is valid + LFS_ASSERT(bd->cfg->erase_cycles); + LFS_ASSERT(block < cfg->block_count); + + bd->wear[block] = wear; + + LFS_TESTBD_TRACE("lfs_testbd_setwear -> %d", 0); + return 0; +} diff --git a/bd/lfs_testbd.h b/bd/lfs_testbd.h new file mode 100644 index 00000000..b1fb2e92 --- /dev/null +++ b/bd/lfs_testbd.h @@ -0,0 +1,141 @@ +/* + * Testing block device, wraps filebd and rambd while providing a bunch + * of hooks for testing littlefs in various conditions. + * + * Copyright (c) 2017, Arm Limited. All rights reserved. + * SPDX-License-Identifier: BSD-3-Clause + */ +#ifndef LFS_TESTBD_H +#define LFS_TESTBD_H + +#include "lfs.h" +#include "lfs_util.h" +#include "bd/lfs_rambd.h" +#include "bd/lfs_filebd.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + + +// Block device specific tracing +#ifdef LFS_TESTBD_YES_TRACE +#define LFS_TESTBD_TRACE(...) LFS_TRACE(__VA_ARGS__) +#else +#define LFS_TESTBD_TRACE(...) +#endif + +// Mode determining how "bad blocks" behave during testing. This simulates +// some real-world circumstances such as progs not sticking (prog-noop), +// a readonly disk (erase-noop), and ECC failures (read-error). +// +// Not that read-noop is not allowed. Read _must_ return a consistent (but +// may be arbitrary) value on every read. +enum lfs_testbd_badblock_behavior { + LFS_TESTBD_BADBLOCK_PROGERROR, + LFS_TESTBD_BADBLOCK_ERASEERROR, + LFS_TESTBD_BADBLOCK_READERROR, + LFS_TESTBD_BADBLOCK_PROGNOOP, + LFS_TESTBD_BADBLOCK_ERASENOOP, +}; + +// Type for measuring wear +typedef uint32_t lfs_testbd_wear_t; +typedef int32_t lfs_testbd_swear_t; + +// testbd config, this is required for testing +struct lfs_testbd_config { + // 8-bit erase value to use for simulating erases. -1 does not simulate + // erases, which can speed up testing by avoiding all the extra block-device + // operations to store the erase value. + int32_t erase_value; + + // Number of erase cycles before a block becomes "bad". The exact behavior + // of bad blocks is controlled by the badblock_mode. + uint32_t erase_cycles; + + // The mode determining how bad blocks fail + uint8_t badblock_behavior; + + // Number of write operations (erase/prog) before forcefully killing + // the program with exit. Simulates power-loss. 0 disables. + uint32_t power_cycles; + + // Optional buffer for RAM block device. + void *buffer; + + // Optional buffer for wear + void *wear_buffer; +}; + +// testbd state +typedef struct lfs_testbd { + union { + struct { + lfs_filebd_t bd; + struct lfs_filebd_config cfg; + } file; + struct { + lfs_rambd_t bd; + struct lfs_rambd_config cfg; + } ram; + } u; + + bool persist; + uint32_t power_cycles; + lfs_testbd_wear_t *wear; + + const struct lfs_testbd_config *cfg; +} lfs_testbd_t; + + +/// Block device API /// + +// Create a test block device using the geometry in lfs_config +// +// Note that filebd is used if a path is provided, if path is NULL +// testbd will use rambd which can be much faster. +int lfs_testbd_create(const struct lfs_config *cfg, const char *path); +int lfs_testbd_createcfg(const struct lfs_config *cfg, const char *path, + const struct lfs_testbd_config *bdcfg); + +// Clean up memory associated with block device +int lfs_testbd_destroy(const struct lfs_config *cfg); + +// Read a block +int lfs_testbd_read(const struct lfs_config *cfg, lfs_block_t block, + lfs_off_t off, void *buffer, lfs_size_t size); + +// Program a block +// +// The block must have previously been erased. +int lfs_testbd_prog(const struct lfs_config *cfg, lfs_block_t block, + lfs_off_t off, const void *buffer, lfs_size_t size); + +// Erase a block +// +// A block must be erased before being programmed. The +// state of an erased block is undefined. +int lfs_testbd_erase(const struct lfs_config *cfg, lfs_block_t block); + +// Sync the block device +int lfs_testbd_sync(const struct lfs_config *cfg); + + +/// Additional extended API for driving test features /// + +// Get simulated wear on a given block +lfs_testbd_swear_t lfs_testbd_getwear(const struct lfs_config *cfg, + lfs_block_t block); + +// Manually set simulated wear on a given block +int lfs_testbd_setwear(const struct lfs_config *cfg, + lfs_block_t block, lfs_testbd_wear_t wear); + + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif diff --git a/emubd/lfs_emubd.c b/emubd/lfs_emubd.c deleted file mode 100644 index 60c3470d..00000000 --- a/emubd/lfs_emubd.c +++ /dev/null @@ -1,414 +0,0 @@ -/* - * Block device emulated on standard files - * - * Copyright (c) 2017, Arm Limited. All rights reserved. - * SPDX-License-Identifier: BSD-3-Clause - */ -#include "emubd/lfs_emubd.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - - -// Emulated block device utils -static inline void lfs_emubd_tole32(lfs_emubd_t *emu) { - emu->cfg.read_size = lfs_tole32(emu->cfg.read_size); - emu->cfg.prog_size = lfs_tole32(emu->cfg.prog_size); - emu->cfg.block_size = lfs_tole32(emu->cfg.block_size); - emu->cfg.block_count = lfs_tole32(emu->cfg.block_count); - - emu->stats.read_count = lfs_tole32(emu->stats.read_count); - emu->stats.prog_count = lfs_tole32(emu->stats.prog_count); - emu->stats.erase_count = lfs_tole32(emu->stats.erase_count); - - for (unsigned i = 0; i < sizeof(emu->history.blocks) / - sizeof(emu->history.blocks[0]); i++) { - emu->history.blocks[i] = lfs_tole32(emu->history.blocks[i]); - } -} - -static inline void lfs_emubd_fromle32(lfs_emubd_t *emu) { - emu->cfg.read_size = lfs_fromle32(emu->cfg.read_size); - emu->cfg.prog_size = lfs_fromle32(emu->cfg.prog_size); - emu->cfg.block_size = lfs_fromle32(emu->cfg.block_size); - emu->cfg.block_count = lfs_fromle32(emu->cfg.block_count); - - emu->stats.read_count = lfs_fromle32(emu->stats.read_count); - emu->stats.prog_count = lfs_fromle32(emu->stats.prog_count); - emu->stats.erase_count = lfs_fromle32(emu->stats.erase_count); - - for (unsigned i = 0; i < sizeof(emu->history.blocks) / - sizeof(emu->history.blocks[0]); i++) { - emu->history.blocks[i] = lfs_fromle32(emu->history.blocks[i]); - } -} - - -// Block device emulated on existing filesystem -int lfs_emubd_create(const struct lfs_config *cfg, const char *path) { - LFS_TRACE("lfs_emubd_create(%p {.context=%p, " - ".read=%p, .prog=%p, .erase=%p, .sync=%p, " - ".read_size=%"PRIu32", .prog_size=%"PRIu32", " - ".block_size=%"PRIu32", .block_count=%"PRIu32"}, \"%s\")", - (void*)cfg, cfg->context, - (void*)(uintptr_t)cfg->read, (void*)(uintptr_t)cfg->prog, - (void*)(uintptr_t)cfg->erase, (void*)(uintptr_t)cfg->sync, - cfg->read_size, cfg->prog_size, cfg->block_size, cfg->block_count, - path); - lfs_emubd_t *emu = cfg->context; - emu->cfg.read_size = cfg->read_size; - emu->cfg.prog_size = cfg->prog_size; - emu->cfg.block_size = cfg->block_size; - emu->cfg.block_count = cfg->block_count; - - // Allocate buffer for creating children files - size_t pathlen = strlen(path); - emu->path = malloc(pathlen + 1 + LFS_NAME_MAX + 1); - if (!emu->path) { - int err = -ENOMEM; - LFS_TRACE("lfs_emubd_create -> %"PRId32, err); - return err; - } - - strcpy(emu->path, path); - emu->path[pathlen] = '/'; - emu->child = &emu->path[pathlen+1]; - memset(emu->child, '\0', LFS_NAME_MAX+1); - - // Create directory if it doesn't exist - int err = mkdir(path, 0777); - if (err && errno != EEXIST) { - err = -errno; - LFS_TRACE("lfs_emubd_create -> %"PRId32, err); - return err; - } - - // Load stats to continue incrementing - snprintf(emu->child, LFS_NAME_MAX, ".stats"); - FILE *f = fopen(emu->path, "r"); - if (!f) { - memset(&emu->stats, LFS_EMUBD_ERASE_VALUE, sizeof(emu->stats)); - } else { - size_t res = fread(&emu->stats, sizeof(emu->stats), 1, f); - lfs_emubd_fromle32(emu); - if (res < 1) { - err = -errno; - LFS_TRACE("lfs_emubd_create -> %"PRId32, err); - fclose(f); - return err; - } - - err = fclose(f); - if (err) { - err = -errno; - LFS_TRACE("lfs_emubd_create -> %"PRId32, err); - return err; - } - } - - // Load history - snprintf(emu->child, LFS_NAME_MAX, ".history"); - f = fopen(emu->path, "r"); - if (!f) { - memset(&emu->history, 0, sizeof(emu->history)); - } else { - size_t res = fread(&emu->history, sizeof(emu->history), 1, f); - lfs_emubd_fromle32(emu); - if (res < 1) { - err = -errno; - LFS_TRACE("lfs_emubd_create -> %"PRId32, err); - fclose(f); - return err; - } - - err = fclose(f); - if (err) { - err = -errno; - LFS_TRACE("lfs_emubd_create -> %"PRId32, err); - return err; - } - } - - LFS_TRACE("lfs_emubd_create -> %"PRId32, 0); - return 0; -} - -void lfs_emubd_destroy(const struct lfs_config *cfg) { - LFS_TRACE("lfs_emubd_destroy(%p)", (void*)cfg); - lfs_emubd_sync(cfg); - - lfs_emubd_t *emu = cfg->context; - free(emu->path); - LFS_TRACE("lfs_emubd_destroy -> %s", "void"); -} - -int lfs_emubd_read(const struct lfs_config *cfg, lfs_block_t block, - lfs_off_t off, void *buffer, lfs_size_t size) { - LFS_TRACE("lfs_emubd_read(%p, 0x%"PRIx32", %"PRIu32", %p, %"PRIu32")", - (void*)cfg, block, off, buffer, size); - lfs_emubd_t *emu = cfg->context; - uint8_t *data = buffer; - - // Check if read is valid - assert(off % cfg->read_size == 0); - assert(size % cfg->read_size == 0); - assert(block < cfg->block_count); - - // Zero out buffer for debugging - memset(data, 0, size); - - // Read data - snprintf(emu->child, LFS_NAME_MAX, "%" PRIx32, block); - - FILE *f = fopen(emu->path, "rb"); - if (!f && errno != ENOENT) { - int err = -errno; - LFS_TRACE("lfs_emubd_read -> %d", err); - return err; - } - - if (f) { - int err = fseek(f, off, SEEK_SET); - if (err) { - err = -errno; - LFS_TRACE("lfs_emubd_read -> %d", err); - fclose(f); - return err; - } - - size_t res = fread(data, 1, size, f); - if (res < size && !feof(f)) { - err = -errno; - LFS_TRACE("lfs_emubd_read -> %d", err); - fclose(f); - return err; - } - - err = fclose(f); - if (err) { - err = -errno; - LFS_TRACE("lfs_emubd_read -> %d", err); - return err; - } - } - - emu->stats.read_count += size; - LFS_TRACE("lfs_emubd_read -> %d", 0); - return 0; -} - -int lfs_emubd_prog(const struct lfs_config *cfg, lfs_block_t block, - lfs_off_t off, const void *buffer, lfs_size_t size) { - LFS_TRACE("lfs_emubd_prog(%p, 0x%"PRIx32", %"PRIu32", %p, %"PRIu32")", - (void*)cfg, block, off, buffer, size); - lfs_emubd_t *emu = cfg->context; - const uint8_t *data = buffer; - - // Check if write is valid - assert(off % cfg->prog_size == 0); - assert(size % cfg->prog_size == 0); - assert(block < cfg->block_count); - - // Program data - snprintf(emu->child, LFS_NAME_MAX, "%" PRIx32, block); - - FILE *f = fopen(emu->path, "r+b"); - if (!f) { - int err = (errno == EACCES) ? 0 : -errno; - LFS_TRACE("lfs_emubd_prog -> %d", err); - return err; - } - - // Check that file was erased - assert(f); - - int err = fseek(f, off, SEEK_SET); - if (err) { - err = -errno; - LFS_TRACE("lfs_emubd_prog -> %d", err); - fclose(f); - return err; - } - - size_t res = fwrite(data, 1, size, f); - if (res < size) { - err = -errno; - LFS_TRACE("lfs_emubd_prog -> %d", err); - fclose(f); - return err; - } - - err = fseek(f, off, SEEK_SET); - if (err) { - err = -errno; - LFS_TRACE("lfs_emubd_prog -> %d", err); - fclose(f); - return err; - } - - uint8_t dat; - res = fread(&dat, 1, 1, f); - if (res < 1) { - err = -errno; - LFS_TRACE("lfs_emubd_prog -> %d", err); - fclose(f); - return err; - } - - err = fclose(f); - if (err) { - err = -errno; - LFS_TRACE("lfs_emubd_prog -> %d", err); - return err; - } - - // update history and stats - if (block != emu->history.blocks[0]) { - memmove(&emu->history.blocks[1], &emu->history.blocks[0], - sizeof(emu->history) - sizeof(emu->history.blocks[0])); - emu->history.blocks[0] = block; - } - - emu->stats.prog_count += size; - LFS_TRACE("lfs_emubd_prog -> %d", 0); - return 0; -} - -int lfs_emubd_erase(const struct lfs_config *cfg, lfs_block_t block) { - LFS_TRACE("lfs_emubd_erase(%p, 0x%"PRIx32")", (void*)cfg, block); - lfs_emubd_t *emu = cfg->context; - - // Check if erase is valid - assert(block < cfg->block_count); - - // Erase the block - snprintf(emu->child, LFS_NAME_MAX, "%" PRIx32, block); - struct stat st; - int err = stat(emu->path, &st); - if (err && errno != ENOENT) { - err = -errno; - LFS_TRACE("lfs_emubd_erase -> %d", err); - return err; - } - - if (!err && S_ISREG(st.st_mode) && (S_IWUSR & st.st_mode)) { - err = unlink(emu->path); - if (err) { - err = -errno; - LFS_TRACE("lfs_emubd_erase -> %d", err); - return err; - } - } - - if (err || (S_ISREG(st.st_mode) && (S_IWUSR & st.st_mode))) { - FILE *f = fopen(emu->path, "w"); - if (!f) { - err = -errno; - LFS_TRACE("lfs_emubd_erase -> %d", err); - return err; - } - - err = fclose(f); - if (err) { - err = -errno; - LFS_TRACE("lfs_emubd_erase -> %d", err); - return err; - } - } - - emu->stats.erase_count += cfg->block_size; - LFS_TRACE("lfs_emubd_erase -> %d", 0); - return 0; -} - -int lfs_emubd_sync(const struct lfs_config *cfg) { - LFS_TRACE("lfs_emubd_sync(%p)", (void*)cfg); - lfs_emubd_t *emu = cfg->context; - - // Just write out info/stats for later lookup - snprintf(emu->child, LFS_NAME_MAX, ".config"); - FILE *f = fopen(emu->path, "w"); - if (!f) { - int err = -errno; - LFS_TRACE("lfs_emubd_sync -> %d", err); - return err; - } - - lfs_emubd_tole32(emu); - size_t res = fwrite(&emu->cfg, sizeof(emu->cfg), 1, f); - lfs_emubd_fromle32(emu); - if (res < 1) { - int err = -errno; - LFS_TRACE("lfs_emubd_sync -> %d", err); - fclose(f); - return err; - } - - int err = fclose(f); - if (err) { - err = -errno; - LFS_TRACE("lfs_emubd_sync -> %d", err); - return err; - } - - snprintf(emu->child, LFS_NAME_MAX, ".stats"); - f = fopen(emu->path, "w"); - if (!f) { - err = -errno; - LFS_TRACE("lfs_emubd_sync -> %d", err); - return err; - } - - lfs_emubd_tole32(emu); - res = fwrite(&emu->stats, sizeof(emu->stats), 1, f); - lfs_emubd_fromle32(emu); - if (res < 1) { - err = -errno; - LFS_TRACE("lfs_emubd_sync -> %d", err); - fclose(f); - return err; - } - - err = fclose(f); - if (err) { - err = -errno; - LFS_TRACE("lfs_emubd_sync -> %d", err); - return err; - } - - snprintf(emu->child, LFS_NAME_MAX, ".history"); - f = fopen(emu->path, "w"); - if (!f) { - err = -errno; - LFS_TRACE("lfs_emubd_sync -> %d", err); - return err; - } - - lfs_emubd_tole32(emu); - res = fwrite(&emu->history, sizeof(emu->history), 1, f); - lfs_emubd_fromle32(emu); - if (res < 1) { - err = -errno; - LFS_TRACE("lfs_emubd_sync -> %d", err); - fclose(f); - return err; - } - - err = fclose(f); - if (err) { - err = -errno; - LFS_TRACE("lfs_emubd_sync -> %d", err); - return err; - } - - LFS_TRACE("lfs_emubd_sync -> %d", 0); - return 0; -} diff --git a/emubd/lfs_emubd.h b/emubd/lfs_emubd.h deleted file mode 100644 index 0fd78c11..00000000 --- a/emubd/lfs_emubd.h +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Block device emulated on standard files - * - * Copyright (c) 2017, Arm Limited. All rights reserved. - * SPDX-License-Identifier: BSD-3-Clause - */ -#ifndef LFS_EMUBD_H -#define LFS_EMUBD_H - -#include "lfs.h" -#include "lfs_util.h" - -#ifdef __cplusplus -extern "C" -{ -#endif - - -// Config options -#ifndef LFS_EMUBD_ERASE_VALUE -#define LFS_EMUBD_ERASE_VALUE 0x00 -#endif - - -// The emu bd state -typedef struct lfs_emubd { - char *path; - char *child; - - struct { - uint64_t read_count; - uint64_t prog_count; - uint64_t erase_count; - } stats; - - struct { - lfs_block_t blocks[4]; - } history; - - struct { - uint32_t read_size; - uint32_t prog_size; - uint32_t block_size; - uint32_t block_count; - } cfg; -} lfs_emubd_t; - - -// Create a block device using path for the directory to store blocks -int lfs_emubd_create(const struct lfs_config *cfg, const char *path); - -// Clean up memory associated with emu block device -void lfs_emubd_destroy(const struct lfs_config *cfg); - -// Read a block -int lfs_emubd_read(const struct lfs_config *cfg, lfs_block_t block, - lfs_off_t off, void *buffer, lfs_size_t size); - -// Program a block -// -// The block must have previously been erased. -int lfs_emubd_prog(const struct lfs_config *cfg, lfs_block_t block, - lfs_off_t off, const void *buffer, lfs_size_t size); - -// Erase a block -// -// A block must be erased before being programmed. The -// state of an erased block is undefined. -int lfs_emubd_erase(const struct lfs_config *cfg, lfs_block_t block); - -// Sync the block device -int lfs_emubd_sync(const struct lfs_config *cfg); - - -#ifdef __cplusplus -} /* extern "C" */ -#endif - -#endif diff --git a/lfs.c b/lfs.c index efc4a7b2..97134cca 100644 --- a/lfs.c +++ b/lfs.c @@ -29,8 +29,8 @@ static int lfs_bd_read(lfs_t *lfs, lfs_block_t block, lfs_off_t off, void *buffer, lfs_size_t size) { uint8_t *data = buffer; - LFS_ASSERT(block != LFS_BLOCK_NULL); - if (off+size > lfs->cfg->block_size) { + if (block >= lfs->cfg->block_count || + off+size > lfs->cfg->block_size) { return LFS_ERR_CORRUPT; } @@ -188,7 +188,7 @@ static int lfs_bd_prog(lfs_t *lfs, lfs_block_t block, lfs_off_t off, const void *buffer, lfs_size_t size) { const uint8_t *data = buffer; - LFS_ASSERT(block != LFS_BLOCK_NULL); + LFS_ASSERT(block == LFS_BLOCK_INLINE || block < lfs->cfg->block_count); LFS_ASSERT(off + size <= lfs->cfg->block_size); while (size > 0) { @@ -280,6 +280,12 @@ typedef int32_t lfs_stag_t; #define LFS_MKTAG(type, id, size) \ (((lfs_tag_t)(type) << 20) | ((lfs_tag_t)(id) << 10) | (lfs_tag_t)(size)) +#define LFS_MKTAG_IF(cond, type, id, size) \ + ((cond) ? LFS_MKTAG(type, id, size) : LFS_MKTAG(LFS_FROM_NOOP, 0, 0)) + +#define LFS_MKTAG_IF_ELSE(cond, type1, id1, size1, type2, id2, size2) \ + ((cond) ? LFS_MKTAG(type1, id1, size1) : LFS_MKTAG(type2, id2, size2)) + static inline bool lfs_tag_isvalid(lfs_tag_t tag) { return !(tag & 0x80000000); } @@ -332,14 +338,13 @@ struct lfs_diskoff { sizeof((struct lfs_mattr[]){__VA_ARGS__}) / sizeof(struct lfs_mattr) // operations on global state -static inline void lfs_gstate_xor(struct lfs_gstate *a, - const struct lfs_gstate *b) { +static inline void lfs_gstate_xor(lfs_gstate_t *a, const lfs_gstate_t *b) { for (int i = 0; i < 3; i++) { ((uint32_t*)a)[i] ^= ((const uint32_t*)b)[i]; } } -static inline bool lfs_gstate_iszero(const struct lfs_gstate *a) { +static inline bool lfs_gstate_iszero(const lfs_gstate_t *a) { for (int i = 0; i < 3; i++) { if (((uint32_t*)a)[i] != 0) { return false; @@ -348,43 +353,30 @@ static inline bool lfs_gstate_iszero(const struct lfs_gstate *a) { return true; } -static inline bool lfs_gstate_hasorphans(const struct lfs_gstate *a) { +static inline bool lfs_gstate_hasorphans(const lfs_gstate_t *a) { return lfs_tag_size(a->tag); } -static inline uint8_t lfs_gstate_getorphans(const struct lfs_gstate *a) { +static inline uint8_t lfs_gstate_getorphans(const lfs_gstate_t *a) { return lfs_tag_size(a->tag); } -static inline bool lfs_gstate_hasmove(const struct lfs_gstate *a) { +static inline bool lfs_gstate_hasmove(const lfs_gstate_t *a) { return lfs_tag_type1(a->tag); } -static inline bool lfs_gstate_hasmovehere(const struct lfs_gstate *a, +static inline bool lfs_gstate_hasmovehere(const lfs_gstate_t *a, const lfs_block_t *pair) { return lfs_tag_type1(a->tag) && lfs_pair_cmp(a->pair, pair) == 0; } -static inline void lfs_gstate_xororphans(struct lfs_gstate *a, - const struct lfs_gstate *b, bool orphans) { - a->tag ^= LFS_MKTAG(0x800, 0, 0) & (b->tag ^ ((uint32_t)orphans << 31)); -} - -static inline void lfs_gstate_xormove(struct lfs_gstate *a, - const struct lfs_gstate *b, uint16_t id, const lfs_block_t pair[2]) { - a->tag ^= LFS_MKTAG(0x7ff, 0x3ff, 0) & (b->tag ^ ( - (id != 0x3ff) ? LFS_MKTAG(LFS_TYPE_DELETE, id, 0) : 0)); - a->pair[0] ^= b->pair[0] ^ ((id != 0x3ff) ? pair[0] : 0); - a->pair[1] ^= b->pair[1] ^ ((id != 0x3ff) ? pair[1] : 0); -} - -static inline void lfs_gstate_fromle32(struct lfs_gstate *a) { +static inline void lfs_gstate_fromle32(lfs_gstate_t *a) { a->tag = lfs_fromle32(a->tag); a->pair[0] = lfs_fromle32(a->pair[0]); a->pair[1] = lfs_fromle32(a->pair[1]); } -static inline void lfs_gstate_tole32(struct lfs_gstate *a) { +static inline void lfs_gstate_tole32(lfs_gstate_t *a) { a->tag = lfs_tole32(a->tag); a->pair[0] = lfs_tole32(a->pair[0]); a->pair[1] = lfs_tole32(a->pair[1]); @@ -437,6 +429,9 @@ static lfs_stag_t lfs_fs_parent(lfs_t *lfs, const lfs_block_t dir[2], lfs_mdir_t *parent); static int lfs_fs_relocate(lfs_t *lfs, const lfs_block_t oldpair[2], lfs_block_t newpair[2]); +int lfs_fs_traverseraw(lfs_t *lfs, + int (*cb)(void *data, lfs_block_t block), void *data, + bool includeorphans); static int lfs_fs_forceconsistency(lfs_t *lfs); static int lfs_deinit(lfs_t *lfs); #ifdef LFS_MIGRATE @@ -457,6 +452,19 @@ static int lfs_alloc_lookahead(void *p, lfs_block_t block) { return 0; } +static void lfs_alloc_ack(lfs_t *lfs) { + lfs->free.ack = lfs->cfg->block_count; +} + +// Invalidate the lookahead buffer. This is done during mounting and +// failed traversals +static void lfs_alloc_reset(lfs_t *lfs) { + lfs->free.off = lfs->seed % lfs->cfg->block_size; + lfs->free.size = 0; + lfs->free.i = 0; + lfs_alloc_ack(lfs); +} + static int lfs_alloc(lfs_t *lfs, lfs_block_t *block) { while (true) { while (lfs->free.i != lfs->free.size) { @@ -495,18 +503,14 @@ static int lfs_alloc(lfs_t *lfs, lfs_block_t *block) { // find mask of free blocks from tree memset(lfs->free.buffer, 0, lfs->cfg->lookahead_size); - int err = lfs_fs_traverse(lfs, lfs_alloc_lookahead, lfs); + int err = lfs_fs_traverseraw(lfs, lfs_alloc_lookahead, lfs, true); if (err) { + lfs_alloc_reset(lfs); return err; } } } -static void lfs_alloc_ack(lfs_t *lfs) { - lfs->free.ack = lfs->cfg->block_count; -} - - /// Metadata pair and directory operations /// static lfs_stag_t lfs_dir_getslice(lfs_t *lfs, const lfs_mdir_t *dir, lfs_tag_t gmask, lfs_tag_t gtag, @@ -515,8 +519,9 @@ static lfs_stag_t lfs_dir_getslice(lfs_t *lfs, const lfs_mdir_t *dir, lfs_tag_t ntag = dir->etag; lfs_stag_t gdiff = 0; - if (lfs_gstate_hasmovehere(&lfs->gstate, dir->pair) && - lfs_tag_id(gtag) <= lfs_tag_id(lfs->gstate.tag)) { + if (lfs_gstate_hasmovehere(&lfs->gdisk, dir->pair) && + lfs_tag_id(gmask) != 0 && + lfs_tag_id(lfs->gdisk.tag) <= lfs_tag_id(gtag)) { // synthetic moves gdiff -= LFS_MKTAG(0, 1, 0); } @@ -667,7 +672,7 @@ static int lfs_dir_traverse_filter(void *p, static int lfs_dir_traverse(lfs_t *lfs, const lfs_mdir_t *dir, lfs_off_t off, lfs_tag_t ptag, - const struct lfs_mattr *attrs, int attrcount, bool hasseenmove, + const struct lfs_mattr *attrs, int attrcount, lfs_tag_t tmask, lfs_tag_t ttag, uint16_t begin, uint16_t end, int16_t diff, int (*cb)(void *data, lfs_tag_t tag, const void *buffer), void *data) { @@ -695,13 +700,6 @@ static int lfs_dir_traverse(lfs_t *lfs, buffer = attrs[0].buffer; attrs += 1; attrcount -= 1; - } else if (!hasseenmove && - lfs_gstate_hasmovehere(&lfs->gpending, dir->pair)) { - // Wait, we have pending move? Handle this here (we need to - // or else we risk letting moves fall out of date) - tag = lfs->gpending.tag & LFS_MKTAG(0x7ff, 0x3ff, 0); - buffer = NULL; - hasseenmove = true; } else { return 0; } @@ -716,7 +714,7 @@ static int lfs_dir_traverse(lfs_t *lfs, if (lfs_tag_id(tmask) != 0) { // scan for duplicates and update tag based on creates/deletes int filter = lfs_dir_traverse(lfs, - dir, off, ptag, attrs, attrcount, hasseenmove, + dir, off, ptag, attrs, attrcount, 0, 0, 0, 0, 0, lfs_dir_traverse_filter, &tag); if (filter < 0) { @@ -740,7 +738,7 @@ static int lfs_dir_traverse(lfs_t *lfs, uint16_t fromid = lfs_tag_size(tag); uint16_t toid = lfs_tag_id(tag); int err = lfs_dir_traverse(lfs, - buffer, 0, LFS_BLOCK_NULL, NULL, 0, true, + buffer, 0, 0xffffffff, NULL, 0, LFS_MKTAG(0x600, 0x3ff, 0), LFS_MKTAG(LFS_TYPE_STRUCT, 0, 0), fromid, fromid+1, toid-fromid+diff, @@ -774,6 +772,12 @@ static lfs_stag_t lfs_dir_fetchmatch(lfs_t *lfs, // scanning the entire directory lfs_stag_t besttag = -1; + // if either block address is invalid we return LFS_ERR_CORRUPT here, + // otherwise later writes to the pair could fail + if (pair[0] >= lfs->cfg->block_count || pair[1] >= lfs->cfg->block_count) { + return LFS_ERR_CORRUPT; + } + // find the block with the most recent revision uint32_t revs[2] = {0, 0}; int r = 0; @@ -800,7 +804,7 @@ static lfs_stag_t lfs_dir_fetchmatch(lfs_t *lfs, // now scan tags to fetch the actual dir and find possible match for (int i = 0; i < 2; i++) { lfs_off_t off = 0; - lfs_tag_t ptag = LFS_BLOCK_NULL; + lfs_tag_t ptag = 0xffffffff; uint16_t tempcount = 0; lfs_block_t temptail[2] = {LFS_BLOCK_NULL, LFS_BLOCK_NULL}; @@ -808,7 +812,7 @@ static lfs_stag_t lfs_dir_fetchmatch(lfs_t *lfs, lfs_stag_t tempbesttag = besttag; dir->rev = lfs_tole32(dir->rev); - uint32_t crc = lfs_crc(LFS_BLOCK_NULL, &dir->rev, sizeof(dir->rev)); + uint32_t crc = lfs_crc(0xffffffff, &dir->rev, sizeof(dir->rev)); dir->rev = lfs_fromle32(dir->rev); while (true) { @@ -831,11 +835,13 @@ static lfs_stag_t lfs_dir_fetchmatch(lfs_t *lfs, tag = lfs_frombe32(tag) ^ ptag; // next commit not yet programmed or we're not in valid range - if (!lfs_tag_isvalid(tag) || - off + lfs_tag_dsize(tag) > lfs->cfg->block_size) { + if (!lfs_tag_isvalid(tag)) { dir->erased = (lfs_tag_type1(ptag) == LFS_TYPE_CRC && dir->off % lfs->cfg->prog_size == 0); break; + } else if (off + lfs_tag_dsize(tag) > lfs->cfg->block_size) { + dir->erased = false; + break; } ptag = tag; @@ -877,7 +883,7 @@ static lfs_stag_t lfs_dir_fetchmatch(lfs_t *lfs, dir->split = tempsplit; // reset crc - crc = LFS_BLOCK_NULL; + crc = 0xffffffff; continue; } @@ -960,11 +966,11 @@ static lfs_stag_t lfs_dir_fetchmatch(lfs_t *lfs, // consider what we have good enough if (dir->off > 0) { // synthetic move - if (lfs_gstate_hasmovehere(&lfs->gstate, dir->pair)) { - if (lfs_tag_id(lfs->gstate.tag) == lfs_tag_id(besttag)) { + if (lfs_gstate_hasmovehere(&lfs->gdisk, dir->pair)) { + if (lfs_tag_id(lfs->gdisk.tag) == lfs_tag_id(besttag)) { besttag |= 0x80000000; } else if (besttag != -1 && - lfs_tag_id(lfs->gstate.tag) < lfs_tag_id(besttag)) { + lfs_tag_id(lfs->gdisk.tag) < lfs_tag_id(besttag)) { besttag -= LFS_MKTAG(0, 1, 0); } } @@ -988,7 +994,7 @@ static lfs_stag_t lfs_dir_fetchmatch(lfs_t *lfs, dir->rev = revs[(r+1)%2]; } - LFS_ERROR("Corrupted dir pair at %"PRIx32" %"PRIx32, + LFS_ERROR("Corrupted dir pair at {0x%"PRIx32", 0x%"PRIx32"}", dir->pair[0], dir->pair[1]); return LFS_ERR_CORRUPT; } @@ -1002,8 +1008,8 @@ static int lfs_dir_fetch(lfs_t *lfs, } static int lfs_dir_getgstate(lfs_t *lfs, const lfs_mdir_t *dir, - struct lfs_gstate *gstate) { - struct lfs_gstate temp; + lfs_gstate_t *gstate) { + lfs_gstate_t temp; lfs_stag_t res = lfs_dir_get(lfs, dir, LFS_MKTAG(0x7ff, 0, 0), LFS_MKTAG(LFS_TYPE_MOVESTATE, 0, sizeof(temp)), &temp); if (res < 0 && res != LFS_ERR_NOENT) { @@ -1255,13 +1261,14 @@ static int lfs_dir_commitattr(lfs_t *lfs, struct lfs_commit *commit, } static int lfs_dir_commitcrc(lfs_t *lfs, struct lfs_commit *commit) { + const lfs_off_t off1 = commit->off; + const uint32_t crc1 = commit->crc; // align to program units - const lfs_off_t off1 = commit->off + sizeof(lfs_tag_t); - const lfs_off_t end = lfs_alignup(off1 + sizeof(uint32_t), + const lfs_off_t end = lfs_alignup(off1 + 2*sizeof(uint32_t), lfs->cfg->prog_size); // create crc tags to fill up remainder of commit, note that - // padding is not crcd, which lets fetches skip padding but + // padding is not crced, which lets fetches skip padding but // makes committing a bit more complicated while (commit->off < end) { lfs_off_t off = commit->off + sizeof(lfs_tag_t); @@ -1271,7 +1278,7 @@ static int lfs_dir_commitcrc(lfs_t *lfs, struct lfs_commit *commit) { } // read erased state from next program unit - lfs_tag_t tag = LFS_BLOCK_NULL; + lfs_tag_t tag = 0xffffffff; int err = lfs_bd_read(lfs, NULL, &lfs->rcache, sizeof(tag), commit->block, noff, &tag, sizeof(tag)); @@ -1297,7 +1304,7 @@ static int lfs_dir_commitcrc(lfs_t *lfs, struct lfs_commit *commit) { commit->off += sizeof(tag)+lfs_tag_size(tag); commit->ptag = tag ^ ((lfs_tag_t)reset << 31); - commit->crc = LFS_BLOCK_NULL; // reset crc for next "commit" + commit->crc = 0xffffffff; // reset crc for next "commit" } // flush buffers @@ -1308,10 +1315,16 @@ static int lfs_dir_commitcrc(lfs_t *lfs, struct lfs_commit *commit) { // successful commit, check checksums to make sure lfs_off_t off = commit->begin; - lfs_off_t noff = off1; + lfs_off_t noff = off1 + sizeof(uint32_t); while (off < end) { - uint32_t crc = LFS_BLOCK_NULL; + uint32_t crc = 0xffffffff; for (lfs_off_t i = off; i < noff+sizeof(uint32_t); i++) { + // check against written crc, may catch blocks that + // become readonly and match our commit size exactly + if (i == off1 && crc != crc1) { + return LFS_ERR_CORRUPT; + } + // leave it up to caching to make this efficient uint8_t dat; err = lfs_bd_read(lfs, @@ -1349,6 +1362,9 @@ static int lfs_dir_alloc(lfs_t *lfs, lfs_mdir_t *dir) { } } + // zero for reproducability in case initial block is unreadable + dir->rev = 0; + // rather than clobbering one of the blocks we just pretend // the revision may be valid int err = lfs_bd_read(lfs, @@ -1364,7 +1380,7 @@ static int lfs_dir_alloc(lfs_t *lfs, lfs_mdir_t *dir) { // set defaults dir->off = sizeof(dir->rev); - dir->etag = LFS_BLOCK_NULL; + dir->etag = 0xffffffff; dir->count = 0; dir->tail[0] = LFS_BLOCK_NULL; dir->tail[1] = LFS_BLOCK_NULL; @@ -1449,16 +1465,16 @@ static int lfs_dir_compact(lfs_t *lfs, lfs_mdir_t *dir, const struct lfs_mattr *attrs, int attrcount, lfs_mdir_t *source, uint16_t begin, uint16_t end) { // save some state in case block is bad - const lfs_block_t oldpair[2] = {dir->pair[1], dir->pair[0]}; + const lfs_block_t oldpair[2] = {dir->pair[0], dir->pair[1]}; bool relocated = false; - bool exhausted = false; + bool tired = false; // should we split? while (end - begin > 1) { // find size lfs_size_t size = 0; int err = lfs_dir_traverse(lfs, - source, 0, LFS_BLOCK_NULL, attrs, attrcount, false, + source, 0, 0xffffffff, attrs, attrcount, LFS_MKTAG(0x400, 0x3ff, 0), LFS_MKTAG(LFS_TYPE_NAME, 0, 0), begin, end, -begin, @@ -1498,8 +1514,14 @@ static int lfs_dir_compact(lfs_t *lfs, // increment revision count dir->rev += 1; + // If our revision count == n * block_cycles, we should force a relocation, + // this is how littlefs wear-levels at the metadata-pair level. Note that we + // actually use (block_cycles+1)|1, this is to avoid two corner cases: + // 1. block_cycles = 1, which would prevent relocations from terminating + // 2. block_cycles = 2n, which, due to aliasing, would only ever relocate + // one metadata block in the pair, effectively making this useless if (lfs->cfg->block_cycles > 0 && - (dir->rev % (lfs->cfg->block_cycles+1) == 0)) { + (dir->rev % ((lfs->cfg->block_cycles+1)|1) == 0)) { if (lfs_pair_cmp(dir->pair, (const lfs_block_t[2]){0, 1}) == 0) { // oh no! we're writing too much to the superblock, // should we expand? @@ -1535,7 +1557,7 @@ static int lfs_dir_compact(lfs_t *lfs, #endif } else { // we're writing too much, time to relocate - exhausted = true; + tired = true; goto relocate; } } @@ -1543,26 +1565,19 @@ static int lfs_dir_compact(lfs_t *lfs, // begin loop to commit compaction to blocks until a compact sticks while (true) { { - // There's nothing special about our global delta, so feed it into - // our local global delta - int err = lfs_dir_getgstate(lfs, dir, &lfs->gdelta); - if (err) { - return err; - } - // setup commit state struct lfs_commit commit = { .block = dir->pair[1], .off = 0, - .ptag = LFS_BLOCK_NULL, - .crc = LFS_BLOCK_NULL, + .ptag = 0xffffffff, + .crc = 0xffffffff, .begin = 0, .end = lfs->cfg->block_size - 8, }; // erase block to write to - err = lfs_bd_erase(lfs, dir->pair[1]); + int err = lfs_bd_erase(lfs, dir->pair[1]); if (err) { if (err == LFS_ERR_CORRUPT) { goto relocate; @@ -1584,7 +1599,7 @@ static int lfs_dir_compact(lfs_t *lfs, // traverse the directory, this time writing out all unique tags err = lfs_dir_traverse(lfs, - source, 0, LFS_BLOCK_NULL, attrs, attrcount, false, + source, 0, 0xffffffff, attrs, attrcount, LFS_MKTAG(0x400, 0x3ff, 0), LFS_MKTAG(LFS_TYPE_NAME, 0, 0), begin, end, -begin, @@ -1612,14 +1627,25 @@ static int lfs_dir_compact(lfs_t *lfs, } } - if (!relocated && !lfs_gstate_iszero(&lfs->gdelta)) { - // commit any globals, unless we're relocating, - // in which case our parent will steal our globals - lfs_gstate_tole32(&lfs->gdelta); + // bring over gstate? + lfs_gstate_t delta = {0}; + if (!relocated) { + lfs_gstate_xor(&delta, &lfs->gdisk); + lfs_gstate_xor(&delta, &lfs->gstate); + } + lfs_gstate_xor(&delta, &lfs->gdelta); + delta.tag &= ~LFS_MKTAG(0, 0, 0x3ff); + + err = lfs_dir_getgstate(lfs, dir, &delta); + if (err) { + return err; + } + + if (!lfs_gstate_iszero(&delta)) { + lfs_gstate_tole32(&delta); err = lfs_dir_commitattr(lfs, &commit, LFS_MKTAG(LFS_TYPE_MOVESTATE, 0x3ff, - sizeof(lfs->gdelta)), &lfs->gdelta); - lfs_gstate_fromle32(&lfs->gdelta); + sizeof(delta)), &delta); if (err) { if (err == LFS_ERR_CORRUPT) { goto relocate; @@ -1628,6 +1654,7 @@ static int lfs_dir_compact(lfs_t *lfs, } } + // complete commit with crc err = lfs_dir_commitcrc(lfs, &commit); if (err) { if (err == LFS_ERR_CORRUPT) { @@ -1642,10 +1669,10 @@ static int lfs_dir_compact(lfs_t *lfs, dir->count = end - begin; dir->off = commit.off; dir->etag = commit.ptag; - // note we able to have already handled move here - if (lfs_gstate_hasmovehere(&lfs->gpending, dir->pair)) { - lfs_gstate_xormove(&lfs->gpending, - &lfs->gpending, 0x3ff, NULL); + // update gstate + lfs->gdelta = (lfs_gstate_t){0}; + if (!relocated) { + lfs->gdisk = lfs->gstate; } } break; @@ -1654,30 +1681,31 @@ static int lfs_dir_compact(lfs_t *lfs, // commit was corrupted, drop caches and prepare to relocate block relocated = true; lfs_cache_drop(lfs, &lfs->pcache); - if (!exhausted) { - LFS_DEBUG("Bad block at %"PRIx32, dir->pair[1]); + if (!tired) { + LFS_DEBUG("Bad block at 0x%"PRIx32, dir->pair[1]); } // can't relocate superblock, filesystem is now frozen - if (lfs_pair_cmp(oldpair, (const lfs_block_t[2]){0, 1}) == 0) { - LFS_WARN("Superblock %"PRIx32" has become unwritable", oldpair[1]); + if (lfs_pair_cmp(dir->pair, (const lfs_block_t[2]){0, 1}) == 0) { + LFS_WARN("Superblock 0x%"PRIx32" has become unwritable", + dir->pair[1]); return LFS_ERR_NOSPC; } // relocate half of pair int err = lfs_alloc(lfs, &dir->pair[1]); - if (err && (err != LFS_ERR_NOSPC && !exhausted)) { + if (err && (err != LFS_ERR_NOSPC || !tired)) { return err; } + + tired = false; continue; } - if (!relocated) { - lfs->gstate = lfs->gpending; - lfs->gdelta = (struct lfs_gstate){0}; - } else { + if (relocated) { // update references if we relocated - LFS_DEBUG("Relocating %"PRIx32" %"PRIx32" -> %"PRIx32" %"PRIx32, + LFS_DEBUG("Relocating {0x%"PRIx32", 0x%"PRIx32"} " + "-> {0x%"PRIx32", 0x%"PRIx32"}", oldpair[0], oldpair[1], dir->pair[0], dir->pair[1]); int err = lfs_fs_relocate(lfs, oldpair, dir->pair); if (err) { @@ -1709,16 +1737,15 @@ static int lfs_dir_commit(lfs_t *lfs, lfs_mdir_t *dir, } // calculate changes to the directory - lfs_tag_t deletetag = LFS_BLOCK_NULL; - lfs_tag_t createtag = LFS_BLOCK_NULL; + lfs_mdir_t olddir = *dir; + bool hasdelete = false; for (int i = 0; i < attrcount; i++) { if (lfs_tag_type3(attrs[i].tag) == LFS_TYPE_CREATE) { - createtag = attrs[i].tag; dir->count += 1; } else if (lfs_tag_type3(attrs[i].tag) == LFS_TYPE_DELETE) { - deletetag = attrs[i].tag; LFS_ASSERT(dir->count > 0); dir->count -= 1; + hasdelete = true; } else if (lfs_tag_type1(attrs[i].tag) == LFS_TYPE_TAIL) { dir->tail[0] = ((lfs_block_t*)attrs[i].buffer)[0]; dir->tail[1] = ((lfs_block_t*)attrs[i].buffer)[1]; @@ -1727,26 +1754,21 @@ static int lfs_dir_commit(lfs_t *lfs, lfs_mdir_t *dir, } } - // do we have a pending move? - if (lfs_gstate_hasmovehere(&lfs->gpending, dir->pair)) { - deletetag = lfs->gpending.tag & LFS_MKTAG(0x7ff, 0x3ff, 0); - LFS_ASSERT(dir->count > 0); - dir->count -= 1; - - // mark gdelta so we reflect the move we will fix - lfs_gstate_xormove(&lfs->gdelta, &lfs->gpending, 0x3ff, NULL); - } - // should we actually drop the directory block? - if (lfs_tag_isvalid(deletetag) && dir->count == 0) { + if (hasdelete && dir->count == 0) { lfs_mdir_t pdir; int err = lfs_fs_pred(lfs, dir->pair, &pdir); if (err && err != LFS_ERR_NOENT) { + *dir = olddir; return err; } if (err != LFS_ERR_NOENT && pdir.split) { - return lfs_dir_drop(lfs, &pdir, dir); + err = lfs_dir_drop(lfs, &pdir, dir); + if (err) { + *dir = olddir; + return err; + } } } @@ -1756,7 +1778,7 @@ static int lfs_dir_commit(lfs_t *lfs, lfs_mdir_t *dir, .block = dir->pair[0], .off = dir->off, .ptag = dir->etag, - .crc = LFS_BLOCK_NULL, + .crc = 0xffffffff, .begin = dir->off, .end = lfs->cfg->block_size - 8, @@ -1765,7 +1787,7 @@ static int lfs_dir_commit(lfs_t *lfs, lfs_mdir_t *dir, // traverse attrs that need to be written out lfs_pair_tole32(dir->tail); int err = lfs_dir_traverse(lfs, - dir, dir->off, dir->etag, attrs, attrcount, false, + dir, dir->off, dir->etag, attrs, attrcount, 0, 0, 0, 0, 0, lfs_dir_commit_commit, &(struct lfs_dir_commit_commit){ lfs, &commit}); @@ -1774,25 +1796,32 @@ static int lfs_dir_commit(lfs_t *lfs, lfs_mdir_t *dir, if (err == LFS_ERR_NOSPC || err == LFS_ERR_CORRUPT) { goto compact; } + *dir = olddir; return err; } // commit any global diffs if we have any - if (!lfs_gstate_iszero(&lfs->gdelta)) { - err = lfs_dir_getgstate(lfs, dir, &lfs->gdelta); + lfs_gstate_t delta = {0}; + lfs_gstate_xor(&delta, &lfs->gstate); + lfs_gstate_xor(&delta, &lfs->gdisk); + lfs_gstate_xor(&delta, &lfs->gdelta); + delta.tag &= ~LFS_MKTAG(0, 0, 0x3ff); + if (!lfs_gstate_iszero(&delta)) { + err = lfs_dir_getgstate(lfs, dir, &delta); if (err) { + *dir = olddir; return err; } - lfs_gstate_tole32(&lfs->gdelta); + lfs_gstate_tole32(&delta); err = lfs_dir_commitattr(lfs, &commit, LFS_MKTAG(LFS_TYPE_MOVESTATE, 0x3ff, - sizeof(lfs->gdelta)), &lfs->gdelta); - lfs_gstate_fromle32(&lfs->gdelta); + sizeof(delta)), &delta); if (err) { if (err == LFS_ERR_NOSPC || err == LFS_ERR_CORRUPT) { goto compact; } + *dir = olddir; return err; } } @@ -1803,6 +1832,7 @@ static int lfs_dir_commit(lfs_t *lfs, lfs_mdir_t *dir, if (err == LFS_ERR_NOSPC || err == LFS_ERR_CORRUPT) { goto compact; } + *dir = olddir; return err; } @@ -1810,15 +1840,9 @@ static int lfs_dir_commit(lfs_t *lfs, lfs_mdir_t *dir, LFS_ASSERT(commit.off % lfs->cfg->prog_size == 0); dir->off = commit.off; dir->etag = commit.ptag; - - // note we able to have already handled move here - if (lfs_gstate_hasmovehere(&lfs->gpending, dir->pair)) { - lfs_gstate_xormove(&lfs->gpending, &lfs->gpending, 0x3ff, NULL); - } - - // update gstate - lfs->gstate = lfs->gpending; - lfs->gdelta = (struct lfs_gstate){0}; + // and update gstate + lfs->gdisk = lfs->gstate; + lfs->gdelta = (lfs_gstate_t){0}; } else { compact: // fall back to compaction @@ -1827,33 +1851,45 @@ static int lfs_dir_commit(lfs_t *lfs, lfs_mdir_t *dir, int err = lfs_dir_compact(lfs, dir, attrs, attrcount, dir, 0, dir->count); if (err) { + *dir = olddir; return err; } } - // update any directories that are affected - lfs_mdir_t copy = *dir; - - // two passes, once for things that aren't us, and one - // for things that are + // this complicated bit of logic is for fixing up any active + // metadata-pairs that we may have affected + // + // note we have to make two passes since the mdir passed to + // lfs_dir_commit could also be in this list, and even then + // we need to copy the pair so they don't get clobbered if we refetch + // our mdir. for (struct lfs_mlist *d = lfs->mlist; d; d = d->next) { - if (lfs_pair_cmp(d->m.pair, copy.pair) == 0) { + if (&d->m != dir && lfs_pair_cmp(d->m.pair, olddir.pair) == 0) { d->m = *dir; - if (d->id == lfs_tag_id(deletetag)) { - d->m.pair[0] = LFS_BLOCK_NULL; - d->m.pair[1] = LFS_BLOCK_NULL; - } else if (d->id > lfs_tag_id(deletetag)) { - d->id -= 1; - if (d->type == LFS_TYPE_DIR) { - ((lfs_dir_t*)d)->pos -= 1; - } - } else if (&d->m != dir && d->id >= lfs_tag_id(createtag)) { - d->id += 1; - if (d->type == LFS_TYPE_DIR) { - ((lfs_dir_t*)d)->pos += 1; + for (int i = 0; i < attrcount; i++) { + if (lfs_tag_type3(attrs[i].tag) == LFS_TYPE_DELETE && + d->id == lfs_tag_id(attrs[i].tag)) { + d->m.pair[0] = LFS_BLOCK_NULL; + d->m.pair[1] = LFS_BLOCK_NULL; + } else if (lfs_tag_type3(attrs[i].tag) == LFS_TYPE_DELETE && + d->id > lfs_tag_id(attrs[i].tag)) { + d->id -= 1; + if (d->type == LFS_TYPE_DIR) { + ((lfs_dir_t*)d)->pos -= 1; + } + } else if (lfs_tag_type3(attrs[i].tag) == LFS_TYPE_CREATE && + d->id >= lfs_tag_id(attrs[i].tag)) { + d->id += 1; + if (d->type == LFS_TYPE_DIR) { + ((lfs_dir_t*)d)->pos += 1; + } } } + } + } + for (struct lfs_mlist *d = lfs->mlist; d; d = d->next) { + if (lfs_pair_cmp(d->m.pair, olddir.pair) == 0) { while (d->id >= d->m.count && d->m.split) { // we split and id is on tail now d->id -= d->m.count; @@ -1879,9 +1915,10 @@ int lfs_mkdir(lfs_t *lfs, const char *path) { return err; } - lfs_mdir_t cwd; + struct lfs_mlist cwd; + cwd.next = lfs->mlist; uint16_t id; - err = lfs_dir_find(lfs, &cwd, &path, &id); + err = lfs_dir_find(lfs, &cwd.m, &path, &id); if (!(err == LFS_ERR_NOENT && id != 0x3ff)) { LFS_TRACE("lfs_mkdir -> %d", (err < 0) ? err : LFS_ERR_EXIST); return (err < 0) ? err : LFS_ERR_EXIST; @@ -1904,7 +1941,7 @@ int lfs_mkdir(lfs_t *lfs, const char *path) { } // find end of list - lfs_mdir_t pred = cwd; + lfs_mdir_t pred = cwd.m; while (pred.split) { err = lfs_dir_fetch(lfs, &pred, pred.tail); if (err) { @@ -1924,29 +1961,40 @@ int lfs_mkdir(lfs_t *lfs, const char *path) { } // current block end of list? - if (cwd.split) { + if (cwd.m.split) { // update tails, this creates a desync lfs_fs_preporphans(lfs, +1); + + // it's possible our predecessor has to be relocated, and if + // our parent is our predecessor's predecessor, this could have + // caused our parent to go out of date, fortunately we can hook + // ourselves into littlefs to catch this + cwd.type = 0; + cwd.id = 0; + lfs->mlist = &cwd; + lfs_pair_tole32(dir.pair); err = lfs_dir_commit(lfs, &pred, LFS_MKATTRS( {LFS_MKTAG(LFS_TYPE_SOFTTAIL, 0x3ff, 8), dir.pair})); lfs_pair_fromle32(dir.pair); if (err) { + lfs->mlist = cwd.next; LFS_TRACE("lfs_mkdir -> %d", err); return err; } + + lfs->mlist = cwd.next; lfs_fs_preporphans(lfs, -1); } // now insert into our parent block lfs_pair_tole32(dir.pair); - err = lfs_dir_commit(lfs, &cwd, LFS_MKATTRS( + err = lfs_dir_commit(lfs, &cwd.m, LFS_MKATTRS( {LFS_MKTAG(LFS_TYPE_CREATE, id, 0), NULL}, {LFS_MKTAG(LFS_TYPE_DIR, id, nlen), path}, {LFS_MKTAG(LFS_TYPE_DIRSTRUCT, id, 8), dir.pair}, - {!cwd.split - ? LFS_MKTAG(LFS_TYPE_SOFTTAIL, 0x3ff, 8) - : LFS_MKTAG(LFS_FROM_NOOP, 0, 0), dir.pair})); + {LFS_MKTAG_IF(!cwd.m.split, + LFS_TYPE_SOFTTAIL, 0x3ff, 8), dir.pair})); lfs_pair_fromle32(dir.pair); if (err) { LFS_TRACE("lfs_mkdir -> %d", err); @@ -2181,7 +2229,6 @@ static int lfs_ctz_find(lfs_t *lfs, return err; } - LFS_ASSERT(head >= 2 && head <= lfs->cfg->block_count); current -= 1 << skip; } @@ -2201,7 +2248,6 @@ static int lfs_ctz_extend(lfs_t *lfs, if (err) { return err; } - LFS_ASSERT(nblock >= 2 && nblock <= lfs->cfg->block_count); { err = lfs_bd_erase(lfs, nblock); @@ -2218,16 +2264,16 @@ static int lfs_ctz_extend(lfs_t *lfs, return 0; } - size -= 1; - lfs_off_t index = lfs_ctz_index(lfs, &size); - size += 1; + lfs_size_t noff = size - 1; + lfs_off_t index = lfs_ctz_index(lfs, &noff); + noff = noff + 1; // just copy out the last block if it is incomplete - if (size != lfs->cfg->block_size) { - for (lfs_off_t i = 0; i < size; i++) { + if (noff != lfs->cfg->block_size) { + for (lfs_off_t i = 0; i < noff; i++) { uint8_t data; err = lfs_bd_read(lfs, - NULL, rcache, size-i, + NULL, rcache, noff-i, head, i, &data, 1); if (err) { return err; @@ -2245,19 +2291,19 @@ static int lfs_ctz_extend(lfs_t *lfs, } *block = nblock; - *off = size; + *off = noff; return 0; } // append block index += 1; lfs_size_t skips = lfs_ctz(index) + 1; - + lfs_block_t nhead = head; for (lfs_off_t i = 0; i < skips; i++) { - head = lfs_tole32(head); + nhead = lfs_tole32(nhead); err = lfs_bd_prog(lfs, pcache, rcache, true, - nblock, 4*i, &head, 4); - head = lfs_fromle32(head); + nblock, 4*i, &nhead, 4); + nhead = lfs_fromle32(nhead); if (err) { if (err == LFS_ERR_CORRUPT) { goto relocate; @@ -2267,15 +2313,13 @@ static int lfs_ctz_extend(lfs_t *lfs, if (i != skips-1) { err = lfs_bd_read(lfs, - NULL, rcache, sizeof(head), - head, 4*i, &head, sizeof(head)); - head = lfs_fromle32(head); + NULL, rcache, sizeof(nhead), + nhead, 4*i, &nhead, sizeof(nhead)); + nhead = lfs_fromle32(nhead); if (err) { return err; } } - - LFS_ASSERT(head >= 2 && head <= lfs->cfg->block_count); } *block = nblock; @@ -2284,7 +2328,7 @@ static int lfs_ctz_extend(lfs_t *lfs, } relocate: - LFS_DEBUG("Bad block at %"PRIx32, nblock); + LFS_DEBUG("Bad block at 0x%"PRIx32, nblock); // just clear cache and try a new block lfs_cache_drop(lfs, pcache); @@ -2388,9 +2432,9 @@ int lfs_file_opencfg(lfs_t *lfs, lfs_file_t *file, // get next slot and create entry to remember name err = lfs_dir_commit(lfs, &file->m, LFS_MKATTRS( - {LFS_MKTAG(LFS_TYPE_CREATE, file->id, 0), NULL}, + {LFS_MKTAG(LFS_TYPE_CREATE, file->id, 0)}, {LFS_MKTAG(LFS_TYPE_REG, file->id, nlen), path}, - {LFS_MKTAG(LFS_TYPE_INLINESTRUCT, file->id, 0), NULL})); + {LFS_MKTAG(LFS_TYPE_INLINESTRUCT, file->id, 0)})); if (err) { err = LFS_ERR_NAMETOOLONG; goto cleanup; @@ -2588,7 +2632,7 @@ static int lfs_file_relocate(lfs_t *lfs, lfs_file_t *file) { return 0; relocate: - LFS_DEBUG("Bad block at %"PRIx32, nblock); + LFS_DEBUG("Bad block at 0x%"PRIx32, nblock); // just clear cache and try a new block lfs_cache_drop(lfs, &lfs->pcache); @@ -2665,7 +2709,7 @@ static int lfs_file_flush(lfs_t *lfs, lfs_file_t *file) { break; relocate: - LFS_DEBUG("Bad block at %"PRIx32, file->block); + LFS_DEBUG("Bad block at 0x%"PRIx32, file->block); err = lfs_file_relocate(lfs, file); if (err) { return err; @@ -2691,66 +2735,57 @@ int lfs_file_sync(lfs_t *lfs, lfs_file_t *file) { LFS_TRACE("lfs_file_sync(%p, %p)", (void*)lfs, (void*)file); LFS_ASSERT(file->flags & LFS_F_OPENED); - while (true) { - int err = lfs_file_flush(lfs, file); - if (err) { - file->flags |= LFS_F_ERRED; - LFS_TRACE("lfs_file_sync -> %d", err); - return err; - } - - if ((file->flags & LFS_F_DIRTY) && - !(file->flags & LFS_F_ERRED) && - !lfs_pair_isnull(file->m.pair)) { - // update dir entry - uint16_t type; - const void *buffer; - lfs_size_t size; - struct lfs_ctz ctz; - if (file->flags & LFS_F_INLINE) { - // inline the whole file - type = LFS_TYPE_INLINESTRUCT; - buffer = file->cache.buffer; - size = file->ctz.size; - } else { - // update the ctz reference - type = LFS_TYPE_CTZSTRUCT; - // copy ctz so alloc will work during a relocate - ctz = file->ctz; - lfs_ctz_tole32(&ctz); - buffer = &ctz; - size = sizeof(ctz); - } + if (file->flags & LFS_F_ERRED) { + // it's not safe to do anything if our file errored + LFS_TRACE("lfs_file_sync -> %d", 0); + return 0; + } - // commit file data and attributes - err = lfs_dir_commit(lfs, &file->m, LFS_MKATTRS( - {LFS_MKTAG(type, file->id, size), buffer}, - {LFS_MKTAG(LFS_FROM_USERATTRS, file->id, - file->cfg->attr_count), file->cfg->attrs})); - if (err) { - if (err == LFS_ERR_NOSPC && (file->flags & LFS_F_INLINE)) { - goto relocate; - } - file->flags |= LFS_F_ERRED; - LFS_TRACE("lfs_file_sync -> %d", err); - return err; - } + int err = lfs_file_flush(lfs, file); + if (err) { + file->flags |= LFS_F_ERRED; + LFS_TRACE("lfs_file_sync -> %d", err); + return err; + } - file->flags &= ~LFS_F_DIRTY; + if ((file->flags & LFS_F_DIRTY) && + !lfs_pair_isnull(file->m.pair)) { + // update dir entry + uint16_t type; + const void *buffer; + lfs_size_t size; + struct lfs_ctz ctz; + if (file->flags & LFS_F_INLINE) { + // inline the whole file + type = LFS_TYPE_INLINESTRUCT; + buffer = file->cache.buffer; + size = file->ctz.size; + } else { + // update the ctz reference + type = LFS_TYPE_CTZSTRUCT; + // copy ctz so alloc will work during a relocate + ctz = file->ctz; + lfs_ctz_tole32(&ctz); + buffer = &ctz; + size = sizeof(ctz); } - LFS_TRACE("lfs_file_sync -> %d", 0); - return 0; - -relocate: - // inline file doesn't fit anymore - err = lfs_file_outline(lfs, file); + // commit file data and attributes + err = lfs_dir_commit(lfs, &file->m, LFS_MKATTRS( + {LFS_MKTAG(type, file->id, size), buffer}, + {LFS_MKTAG(LFS_FROM_USERATTRS, file->id, + file->cfg->attr_count), file->cfg->attrs})); if (err) { file->flags |= LFS_F_ERRED; LFS_TRACE("lfs_file_sync -> %d", err); return err; } + + file->flags &= ~LFS_F_DIRTY; } + + LFS_TRACE("lfs_file_sync -> %d", 0); + return 0; } lfs_ssize_t lfs_file_read(lfs_t *lfs, lfs_file_t *file, @@ -3129,7 +3164,8 @@ int lfs_remove(lfs_t *lfs, const char *path) { return (tag < 0) ? (int)tag : LFS_ERR_INVAL; } - lfs_mdir_t dir; + struct lfs_mlist dir; + dir.next = lfs->mlist; if (lfs_tag_type3(tag) == LFS_TYPE_DIR) { // must be empty before removal lfs_block_t pair[2]; @@ -3141,40 +3177,48 @@ int lfs_remove(lfs_t *lfs, const char *path) { } lfs_pair_fromle32(pair); - err = lfs_dir_fetch(lfs, &dir, pair); + err = lfs_dir_fetch(lfs, &dir.m, pair); if (err) { LFS_TRACE("lfs_remove -> %d", err); return err; } - if (dir.count > 0 || dir.split) { + if (dir.m.count > 0 || dir.m.split) { LFS_TRACE("lfs_remove -> %d", LFS_ERR_NOTEMPTY); return LFS_ERR_NOTEMPTY; } // mark fs as orphaned lfs_fs_preporphans(lfs, +1); + + // I know it's crazy but yes, dir can be changed by our parent's + // commit (if predecessor is child) + dir.type = 0; + dir.id = 0; + lfs->mlist = &dir; } // delete the entry err = lfs_dir_commit(lfs, &cwd, LFS_MKATTRS( - {LFS_MKTAG(LFS_TYPE_DELETE, lfs_tag_id(tag), 0), NULL})); + {LFS_MKTAG(LFS_TYPE_DELETE, lfs_tag_id(tag), 0)})); if (err) { + lfs->mlist = dir.next; LFS_TRACE("lfs_remove -> %d", err); return err; } + lfs->mlist = dir.next; if (lfs_tag_type3(tag) == LFS_TYPE_DIR) { // fix orphan lfs_fs_preporphans(lfs, -1); - err = lfs_fs_pred(lfs, dir.pair, &cwd); + err = lfs_fs_pred(lfs, dir.m.pair, &cwd); if (err) { LFS_TRACE("lfs_remove -> %d", err); return err; } - err = lfs_dir_drop(lfs, &cwd, &dir); + err = lfs_dir_drop(lfs, &cwd, &dir.m); if (err) { LFS_TRACE("lfs_remove -> %d", err); return err; @@ -3199,7 +3243,8 @@ int lfs_rename(lfs_t *lfs, const char *oldpath, const char *newpath) { lfs_mdir_t oldcwd; lfs_stag_t oldtag = lfs_dir_find(lfs, &oldcwd, &oldpath, NULL); if (oldtag < 0 || lfs_tag_id(oldtag) == 0x3ff) { - LFS_TRACE("lfs_rename -> %"PRId32, (oldtag < 0) ? oldtag : LFS_ERR_INVAL); + LFS_TRACE("lfs_rename -> %"PRId32, + (oldtag < 0) ? oldtag : LFS_ERR_INVAL); return (oldtag < 0) ? (int)oldtag : LFS_ERR_INVAL; } @@ -3209,11 +3254,17 @@ int lfs_rename(lfs_t *lfs, const char *oldpath, const char *newpath) { lfs_stag_t prevtag = lfs_dir_find(lfs, &newcwd, &newpath, &newid); if ((prevtag < 0 || lfs_tag_id(prevtag) == 0x3ff) && !(prevtag == LFS_ERR_NOENT && newid != 0x3ff)) { - LFS_TRACE("lfs_rename -> %"PRId32, (prevtag < 0) ? prevtag : LFS_ERR_INVAL); + LFS_TRACE("lfs_rename -> %"PRId32, + (prevtag < 0) ? prevtag : LFS_ERR_INVAL); return (prevtag < 0) ? (int)prevtag : LFS_ERR_INVAL; } - lfs_mdir_t prevdir; + // if we're in the same pair there's a few special cases... + bool samepair = (lfs_pair_cmp(oldcwd.pair, newcwd.pair) == 0); + uint16_t newoldid = lfs_tag_id(oldtag); + + struct lfs_mlist prevdir; + prevdir.next = lfs->mlist; if (prevtag == LFS_ERR_NOENT) { // check that name fits lfs_size_t nlen = strlen(newpath); @@ -3221,9 +3272,20 @@ int lfs_rename(lfs_t *lfs, const char *oldpath, const char *newpath) { LFS_TRACE("lfs_rename -> %d", LFS_ERR_NAMETOOLONG); return LFS_ERR_NAMETOOLONG; } + + // there is a small chance we are being renamed in the same + // directory/ to an id less than our old id, the global update + // to handle this is a bit messy + if (samepair && newid <= newoldid) { + newoldid += 1; + } } else if (lfs_tag_type3(prevtag) != lfs_tag_type3(oldtag)) { LFS_TRACE("lfs_rename -> %d", LFS_ERR_ISDIR); return LFS_ERR_ISDIR; + } else if (samepair && newid == newoldid) { + // we're renaming to ourselves?? + LFS_TRACE("lfs_rename -> %d", 0); + return 0; } else if (lfs_tag_type3(prevtag) == LFS_TYPE_DIR) { // must be empty before removal lfs_block_t prevpair[2]; @@ -3236,68 +3298,72 @@ int lfs_rename(lfs_t *lfs, const char *oldpath, const char *newpath) { lfs_pair_fromle32(prevpair); // must be empty before removal - err = lfs_dir_fetch(lfs, &prevdir, prevpair); + err = lfs_dir_fetch(lfs, &prevdir.m, prevpair); if (err) { LFS_TRACE("lfs_rename -> %d", err); return err; } - if (prevdir.count > 0 || prevdir.split) { + if (prevdir.m.count > 0 || prevdir.m.split) { LFS_TRACE("lfs_rename -> %d", LFS_ERR_NOTEMPTY); return LFS_ERR_NOTEMPTY; } // mark fs as orphaned lfs_fs_preporphans(lfs, +1); - } - // create move to fix later - uint16_t newoldtagid = lfs_tag_id(oldtag); - if (lfs_pair_cmp(oldcwd.pair, newcwd.pair) == 0 && - prevtag == LFS_ERR_NOENT && newid <= newoldtagid) { - // there is a small chance we are being renamed in the same directory - // to an id less than our old id, the global update to handle this - // is a bit messy - newoldtagid += 1; + // I know it's crazy but yes, dir can be changed by our parent's + // commit (if predecessor is child) + prevdir.type = 0; + prevdir.id = 0; + lfs->mlist = &prevdir; } - lfs_fs_prepmove(lfs, newoldtagid, oldcwd.pair); + if (!samepair) { + lfs_fs_prepmove(lfs, newoldid, oldcwd.pair); + } // move over all attributes err = lfs_dir_commit(lfs, &newcwd, LFS_MKATTRS( - {prevtag != LFS_ERR_NOENT - ? LFS_MKTAG(LFS_TYPE_DELETE, newid, 0) - : LFS_MKTAG(LFS_FROM_NOOP, 0, 0), NULL}, - {LFS_MKTAG(LFS_TYPE_CREATE, newid, 0), NULL}, - {LFS_MKTAG(lfs_tag_type3(oldtag), newid, strlen(newpath)), - newpath}, - {LFS_MKTAG(LFS_FROM_MOVE, newid, lfs_tag_id(oldtag)), &oldcwd})); + {LFS_MKTAG_IF(prevtag != LFS_ERR_NOENT, + LFS_TYPE_DELETE, newid, 0)}, + {LFS_MKTAG(LFS_TYPE_CREATE, newid, 0)}, + {LFS_MKTAG(lfs_tag_type3(oldtag), newid, strlen(newpath)), newpath}, + {LFS_MKTAG(LFS_FROM_MOVE, newid, lfs_tag_id(oldtag)), &oldcwd}, + {LFS_MKTAG_IF(samepair, + LFS_TYPE_DELETE, newoldid, 0)})); if (err) { + lfs->mlist = prevdir.next; LFS_TRACE("lfs_rename -> %d", err); return err; } // let commit clean up after move (if we're different! otherwise move // logic already fixed it for us) - if (lfs_pair_cmp(oldcwd.pair, newcwd.pair) != 0) { - err = lfs_dir_commit(lfs, &oldcwd, NULL, 0); + if (!samepair && lfs_gstate_hasmove(&lfs->gstate)) { + // prep gstate and delete move id + lfs_fs_prepmove(lfs, 0x3ff, NULL); + err = lfs_dir_commit(lfs, &oldcwd, LFS_MKATTRS( + {LFS_MKTAG(LFS_TYPE_DELETE, lfs_tag_id(oldtag), 0)})); if (err) { + lfs->mlist = prevdir.next; LFS_TRACE("lfs_rename -> %d", err); return err; } } + lfs->mlist = prevdir.next; if (prevtag != LFS_ERR_NOENT && lfs_tag_type3(prevtag) == LFS_TYPE_DIR) { // fix orphan lfs_fs_preporphans(lfs, -1); - err = lfs_fs_pred(lfs, prevdir.pair, &newcwd); + err = lfs_fs_pred(lfs, prevdir.m.pair, &newcwd); if (err) { LFS_TRACE("lfs_rename -> %d", err); return err; } - err = lfs_dir_drop(lfs, &newcwd, &prevdir); + err = lfs_dir_drop(lfs, &newcwd, &prevdir.m); if (err) { LFS_TRACE("lfs_rename -> %d", err); return err; @@ -3411,7 +3477,7 @@ static int lfs_init(lfs_t *lfs, const struct lfs_config *cfg) { LFS_ASSERT(lfs->cfg->block_size % lfs->cfg->cache_size == 0); // check that the block size is large enough to fit ctz pointers - LFS_ASSERT(4*lfs_npw2(LFS_BLOCK_NULL / (lfs->cfg->block_size-2*4)) + LFS_ASSERT(4*lfs_npw2(0xffffffff / (lfs->cfg->block_size-2*4)) <= lfs->cfg->block_size); // block_cycles = 0 is no longer supported. @@ -3487,9 +3553,9 @@ static int lfs_init(lfs_t *lfs, const struct lfs_config *cfg) { lfs->root[1] = LFS_BLOCK_NULL; lfs->mlist = NULL; lfs->seed = 0; - lfs->gstate = (struct lfs_gstate){0}; - lfs->gpending = (struct lfs_gstate){0}; - lfs->gdelta = (struct lfs_gstate){0}; + lfs->gdisk = (lfs_gstate_t){0}; + lfs->gstate = (lfs_gstate_t){0}; + lfs->gdelta = (lfs_gstate_t){0}; #ifdef LFS_MIGRATE lfs->lfs1 = NULL; #endif @@ -3570,7 +3636,7 @@ int lfs_format(lfs_t *lfs, const struct lfs_config *cfg) { lfs_superblock_tole32(&superblock); err = lfs_dir_commit(lfs, &root, LFS_MKATTRS( - {LFS_MKTAG(LFS_TYPE_CREATE, 0, 0), NULL}, + {LFS_MKTAG(LFS_TYPE_CREATE, 0, 0)}, {LFS_MKTAG(LFS_TYPE_SUPERBLOCK, 0, 8), "littlefs"}, {LFS_MKTAG(LFS_TYPE_INLINESTRUCT, 0, sizeof(superblock)), &superblock})); @@ -3624,7 +3690,15 @@ int lfs_mount(lfs_t *lfs, const struct lfs_config *cfg) { // scan directory blocks for superblock and any global updates lfs_mdir_t dir = {.tail = {0, 1}}; + lfs_block_t cycle = 0; while (!lfs_pair_isnull(dir.tail)) { + if (cycle >= lfs->cfg->block_count/2) { + // loop detected + err = LFS_ERR_CORRUPT; + goto cleanup; + } + cycle += 1; + // fetch next block in tail list lfs_stag_t tag = lfs_dir_fetchmatch(lfs, &dir, dir.tail, LFS_MKTAG(0x7ff, 0x3ff, 0), @@ -3659,7 +3733,7 @@ int lfs_mount(lfs_t *lfs, const struct lfs_config *cfg) { uint16_t minor_version = (0xffff & (superblock.version >> 0)); if ((major_version != LFS_DISK_VERSION_MAJOR || minor_version > LFS_DISK_VERSION_MINOR)) { - LFS_ERROR("Invalid version %"PRIu16".%"PRIu16, + LFS_ERROR("Invalid version v%"PRIu16".%"PRIu16, major_version, minor_version); err = LFS_ERR_INVAL; goto cleanup; @@ -3701,7 +3775,7 @@ int lfs_mount(lfs_t *lfs, const struct lfs_config *cfg) { } // has gstate? - err = lfs_dir_getgstate(lfs, &dir, &lfs->gpending); + err = lfs_dir_getgstate(lfs, &dir, &lfs->gstate); if (err) { goto cleanup; } @@ -3714,20 +3788,17 @@ int lfs_mount(lfs_t *lfs, const struct lfs_config *cfg) { } // update littlefs with gstate - lfs->gpending.tag += !lfs_tag_isvalid(lfs->gpending.tag); - lfs->gstate = lfs->gpending; - if (lfs_gstate_hasmove(&lfs->gstate)) { - LFS_DEBUG("Found move %"PRIx32" %"PRIx32" %"PRIx16, + if (!lfs_gstate_iszero(&lfs->gstate)) { + LFS_DEBUG("Found pending gstate 0x%08"PRIx32"%08"PRIx32"%08"PRIx32, + lfs->gstate.tag, lfs->gstate.pair[0], - lfs->gstate.pair[1], - lfs_tag_id(lfs->gstate.tag)); + lfs->gstate.pair[1]); } + lfs->gstate.tag += !lfs_tag_isvalid(lfs->gstate.tag); + lfs->gdisk = lfs->gstate; // setup free lookahead - lfs->free.off = lfs->seed % lfs->cfg->block_size; - lfs->free.size = 0; - lfs->free.i = 0; - lfs_alloc_ack(lfs); + lfs_alloc_reset(lfs); LFS_TRACE("lfs_mount -> %d", 0); return 0; @@ -3747,10 +3818,9 @@ int lfs_unmount(lfs_t *lfs) { /// Filesystem filesystem operations /// -int lfs_fs_traverse(lfs_t *lfs, - int (*cb)(void *data, lfs_block_t block), void *data) { - LFS_TRACE("lfs_fs_traverse(%p, %p, %p)", - (void*)lfs, (void*)(uintptr_t)cb, data); +int lfs_fs_traverseraw(lfs_t *lfs, + int (*cb)(void *data, lfs_block_t block), void *data, + bool includeorphans) { // iterate over metadata pairs lfs_mdir_t dir = {.tail = {0, 1}}; @@ -3759,7 +3829,6 @@ int lfs_fs_traverse(lfs_t *lfs, if (lfs->lfs1) { int err = lfs1_traverse(lfs, cb, data); if (err) { - LFS_TRACE("lfs_fs_traverse -> %d", err); return err; } @@ -3768,11 +3837,17 @@ int lfs_fs_traverse(lfs_t *lfs, } #endif + lfs_block_t cycle = 0; while (!lfs_pair_isnull(dir.tail)) { + if (cycle >= lfs->cfg->block_count/2) { + // loop detected + return LFS_ERR_CORRUPT; + } + cycle += 1; + for (int i = 0; i < 2; i++) { int err = cb(data, dir.tail[i]); if (err) { - LFS_TRACE("lfs_fs_traverse -> %d", err); return err; } } @@ -3780,7 +3855,6 @@ int lfs_fs_traverse(lfs_t *lfs, // iterate through ids in directory int err = lfs_dir_fetch(lfs, &dir, dir.tail); if (err) { - LFS_TRACE("lfs_fs_traverse -> %d", err); return err; } @@ -3792,7 +3866,6 @@ int lfs_fs_traverse(lfs_t *lfs, if (tag == LFS_ERR_NOENT) { continue; } - LFS_TRACE("lfs_fs_traverse -> %"PRId32, tag); return tag; } lfs_ctz_fromle32(&ctz); @@ -3801,9 +3874,16 @@ int lfs_fs_traverse(lfs_t *lfs, err = lfs_ctz_traverse(lfs, NULL, &lfs->rcache, ctz.head, ctz.size, cb, data); if (err) { - LFS_TRACE("lfs_fs_traverse -> %d", err); return err; } + } else if (includeorphans && + lfs_tag_type3(tag) == LFS_TYPE_DIRSTRUCT) { + for (int i = 0; i < 2; i++) { + err = cb(data, (&ctz.head)[i]); + if (err) { + return err; + } + } } } } @@ -3818,7 +3898,6 @@ int lfs_fs_traverse(lfs_t *lfs, int err = lfs_ctz_traverse(lfs, &f->cache, &lfs->rcache, f->ctz.head, f->ctz.size, cb, data); if (err) { - LFS_TRACE("lfs_fs_traverse -> %d", err); return err; } } @@ -3827,22 +3906,36 @@ int lfs_fs_traverse(lfs_t *lfs, int err = lfs_ctz_traverse(lfs, &f->cache, &lfs->rcache, f->block, f->pos, cb, data); if (err) { - LFS_TRACE("lfs_fs_traverse -> %d", err); return err; } } } - LFS_TRACE("lfs_fs_traverse -> %d", 0); return 0; } +int lfs_fs_traverse(lfs_t *lfs, + int (*cb)(void *data, lfs_block_t block), void *data) { + LFS_TRACE("lfs_fs_traverse(%p, %p, %p)", + (void*)lfs, (void*)(uintptr_t)cb, data); + int err = lfs_fs_traverseraw(lfs, cb, data, true); + LFS_TRACE("lfs_fs_traverse -> %d", 0); + return err; +} + static int lfs_fs_pred(lfs_t *lfs, const lfs_block_t pair[2], lfs_mdir_t *pdir) { // iterate over all directory directory entries pdir->tail[0] = 0; pdir->tail[1] = 1; + lfs_block_t cycle = 0; while (!lfs_pair_isnull(pdir->tail)) { + if (cycle >= lfs->cfg->block_count/2) { + // loop detected + return LFS_ERR_CORRUPT; + } + cycle += 1; + if (lfs_pair_cmp(pdir->tail, pair) == 0) { return 0; } @@ -3885,7 +3978,14 @@ static lfs_stag_t lfs_fs_parent(lfs_t *lfs, const lfs_block_t pair[2], // use fetchmatch with callback to find pairs parent->tail[0] = 0; parent->tail[1] = 1; + lfs_block_t cycle = 0; while (!lfs_pair_isnull(parent->tail)) { + if (cycle >= lfs->cfg->block_count/2) { + // loop detected + return LFS_ERR_CORRUPT; + } + cycle += 1; + lfs_stag_t tag = lfs_dir_fetchmatch(lfs, parent, parent->tail, LFS_MKTAG(0x7ff, 0, 0x3ff), LFS_MKTAG(LFS_TYPE_DIRSTRUCT, 0, 8), @@ -3904,8 +4004,6 @@ static int lfs_fs_relocate(lfs_t *lfs, const lfs_block_t oldpair[2], lfs_block_t newpair[2]) { // update internal root if (lfs_pair_cmp(oldpair, lfs->root) == 0) { - LFS_DEBUG("Relocating root %"PRIx32" %"PRIx32, - newpair[0], newpair[1]); lfs->root[0] = newpair[0]; lfs->root[1] = newpair[1]; } @@ -3935,8 +4033,25 @@ static int lfs_fs_relocate(lfs_t *lfs, // update disk, this creates a desync lfs_fs_preporphans(lfs, +1); + // fix pending move in this pair? this looks like an optimization but + // is in fact _required_ since relocating may outdate the move. + uint16_t moveid = 0x3ff; + if (lfs_gstate_hasmovehere(&lfs->gstate, parent.pair)) { + moveid = lfs_tag_id(lfs->gstate.tag); + LFS_DEBUG("Fixing move while relocating " + "{0x%"PRIx32", 0x%"PRIx32"} 0x%"PRIx16"\n", + parent.pair[0], parent.pair[1], moveid); + lfs_fs_prepmove(lfs, 0x3ff, NULL); + if (moveid < lfs_tag_id(tag)) { + tag -= LFS_MKTAG(0, 1, 0); + } + } + lfs_pair_tole32(newpair); - int err = lfs_dir_commit(lfs, &parent, LFS_MKATTRS({tag, newpair})); + int err = lfs_dir_commit(lfs, &parent, LFS_MKATTRS( + {LFS_MKTAG_IF(moveid != 0x3ff, + LFS_TYPE_DELETE, moveid, 0)}, + {tag, newpair})); lfs_pair_fromle32(newpair); if (err) { return err; @@ -3954,9 +4069,22 @@ static int lfs_fs_relocate(lfs_t *lfs, // if we can't find dir, it must be new if (err != LFS_ERR_NOENT) { + // fix pending move in this pair? this looks like an optimization but + // is in fact _required_ since relocating may outdate the move. + uint16_t moveid = 0x3ff; + if (lfs_gstate_hasmovehere(&lfs->gstate, parent.pair)) { + moveid = lfs_tag_id(lfs->gstate.tag); + LFS_DEBUG("Fixing move while relocating " + "{0x%"PRIx32", 0x%"PRIx32"} 0x%"PRIx16"\n", + parent.pair[0], parent.pair[1], moveid); + lfs_fs_prepmove(lfs, 0x3ff, NULL); + } + // replace bad pair, either we clean up desync, or no desync occured lfs_pair_tole32(newpair); err = lfs_dir_commit(lfs, &parent, LFS_MKATTRS( + {LFS_MKTAG_IF(moveid != 0x3ff, + LFS_TYPE_DELETE, moveid, 0)}, {LFS_MKTAG(LFS_TYPE_TAIL + parent.split, 0x3ff, 8), newpair})); lfs_pair_fromle32(newpair); if (err) { @@ -3968,40 +4096,43 @@ static int lfs_fs_relocate(lfs_t *lfs, } static void lfs_fs_preporphans(lfs_t *lfs, int8_t orphans) { - lfs->gpending.tag += orphans; - lfs_gstate_xororphans(&lfs->gdelta, &lfs->gpending, - lfs_gstate_hasorphans(&lfs->gpending)); - lfs_gstate_xororphans(&lfs->gpending, &lfs->gpending, - lfs_gstate_hasorphans(&lfs->gpending)); + LFS_ASSERT(lfs_tag_size(lfs->gstate.tag) > 0 || orphans >= 0); + lfs->gstate.tag += orphans; + lfs->gstate.tag = ((lfs->gstate.tag & ~LFS_MKTAG(0x800, 0, 0)) | + ((uint32_t)lfs_gstate_hasorphans(&lfs->gstate) << 31)); } static void lfs_fs_prepmove(lfs_t *lfs, uint16_t id, const lfs_block_t pair[2]) { - lfs_gstate_xormove(&lfs->gdelta, &lfs->gpending, id, pair); - lfs_gstate_xormove(&lfs->gpending, &lfs->gpending, id, pair); + lfs->gstate.tag = ((lfs->gstate.tag & ~LFS_MKTAG(0x7ff, 0x3ff, 0)) | + ((id != 0x3ff) ? LFS_MKTAG(LFS_TYPE_DELETE, id, 0) : 0)); + lfs->gstate.pair[0] = (id != 0x3ff) ? pair[0] : 0; + lfs->gstate.pair[1] = (id != 0x3ff) ? pair[1] : 0; } - static int lfs_fs_demove(lfs_t *lfs) { - if (!lfs_gstate_hasmove(&lfs->gstate)) { + if (!lfs_gstate_hasmove(&lfs->gdisk)) { return 0; } // Fix bad moves - LFS_DEBUG("Fixing move %"PRIx32" %"PRIx32" %"PRIx16, - lfs->gstate.pair[0], - lfs->gstate.pair[1], - lfs_tag_id(lfs->gstate.tag)); + LFS_DEBUG("Fixing move {0x%"PRIx32", 0x%"PRIx32"} 0x%"PRIx16, + lfs->gdisk.pair[0], + lfs->gdisk.pair[1], + lfs_tag_id(lfs->gdisk.tag)); // fetch and delete the moved entry lfs_mdir_t movedir; - int err = lfs_dir_fetch(lfs, &movedir, lfs->gstate.pair); + int err = lfs_dir_fetch(lfs, &movedir, lfs->gdisk.pair); if (err) { return err; } - // rely on cancel logic inside commit - err = lfs_dir_commit(lfs, &movedir, NULL, 0); + // prep gstate and delete move id + uint16_t moveid = lfs_tag_id(lfs->gdisk.tag); + lfs_fs_prepmove(lfs, 0x3ff, NULL); + err = lfs_dir_commit(lfs, &movedir, LFS_MKATTRS( + {LFS_MKTAG(LFS_TYPE_DELETE, moveid, 0)})); if (err) { return err; } @@ -4015,12 +4146,12 @@ static int lfs_fs_deorphan(lfs_t *lfs) { } // Fix any orphans - lfs_mdir_t pdir = {.split = true}; - lfs_mdir_t dir = {.tail = {0, 1}}; + lfs_mdir_t pdir = {.split = true, .tail = {0, 1}}; + lfs_mdir_t dir; // iterate over all directory directory entries - while (!lfs_pair_isnull(dir.tail)) { - int err = lfs_dir_fetch(lfs, &dir, dir.tail); + while (!lfs_pair_isnull(pdir.tail)) { + int err = lfs_dir_fetch(lfs, &dir, pdir.tail); if (err) { return err; } @@ -4036,7 +4167,7 @@ static int lfs_fs_deorphan(lfs_t *lfs) { if (tag == LFS_ERR_NOENT) { // we are an orphan - LFS_DEBUG("Fixing orphan %"PRIx32" %"PRIx32, + LFS_DEBUG("Fixing orphan {0x%"PRIx32", 0x%"PRIx32"}", pdir.tail[0], pdir.tail[1]); err = lfs_dir_drop(lfs, &pdir, &dir); @@ -4044,7 +4175,8 @@ static int lfs_fs_deorphan(lfs_t *lfs) { return err; } - break; + // refetch tail + continue; } lfs_block_t pair[2]; @@ -4057,8 +4189,9 @@ static int lfs_fs_deorphan(lfs_t *lfs) { if (!lfs_pair_sync(pair, pdir.tail)) { // we have desynced - LFS_DEBUG("Fixing half-orphan %"PRIx32" %"PRIx32, - pair[0], pair[1]); + LFS_DEBUG("Fixing half-orphan {0x%"PRIx32", 0x%"PRIx32"} " + "-> {0x%"PRIx32", 0x%"PRIx32"}", + pdir.tail[0], pdir.tail[1], pair[0], pair[1]); lfs_pair_tole32(pair); err = lfs_dir_commit(lfs, &pdir, LFS_MKATTRS( @@ -4068,16 +4201,16 @@ static int lfs_fs_deorphan(lfs_t *lfs) { return err; } - break; + // refetch tail + continue; } } - memcpy(&pdir, &dir, sizeof(pdir)); + pdir = dir; } // mark orphans as fixed lfs_fs_preporphans(lfs, -lfs_gstate_getorphans(&lfs->gstate)); - lfs->gstate = lfs->gpending; return 0; } @@ -4105,7 +4238,7 @@ static int lfs_fs_size_count(void *p, lfs_block_t block) { lfs_ssize_t lfs_fs_size(lfs_t *lfs) { LFS_TRACE("lfs_fs_size(%p)", (void*)lfs); lfs_size_t size = 0; - int err = lfs_fs_traverse(lfs, lfs_fs_size_count, &size); + int err = lfs_fs_traverseraw(lfs, lfs_fs_size_count, &size, false); if (err) { LFS_TRACE("lfs_fs_size -> %d", err); return err; @@ -4293,7 +4426,7 @@ static int lfs1_dir_fetch(lfs_t *lfs, continue; } - uint32_t crc = LFS_BLOCK_NULL; + uint32_t crc = 0xffffffff; lfs1_dir_tole32(&test); lfs1_crc(&crc, &test, sizeof(test)); lfs1_dir_fromle32(&test); @@ -4320,7 +4453,7 @@ static int lfs1_dir_fetch(lfs_t *lfs, } if (!valid) { - LFS_ERROR("Corrupted dir pair at %" PRIx32 " %" PRIx32 , + LFS_ERROR("Corrupted dir pair at {0x%"PRIx32", 0x%"PRIx32"}", tpair[0], tpair[1]); return LFS_ERR_CORRUPT; } @@ -4508,7 +4641,8 @@ static int lfs1_mount(lfs_t *lfs, struct lfs1 *lfs1, } if (err || memcmp(superblock.d.magic, "littlefs", 8) != 0) { - LFS_ERROR("Invalid superblock at %d %d", 0, 1); + LFS_ERROR("Invalid superblock at {0x%"PRIx32", 0x%"PRIx32"}", + 0, 1); err = LFS_ERR_CORRUPT; goto cleanup; } @@ -4517,7 +4651,7 @@ static int lfs1_mount(lfs_t *lfs, struct lfs1 *lfs1, uint16_t minor_version = (0xffff & (superblock.d.version >> 0)); if ((major_version != LFS1_DISK_VERSION_MAJOR || minor_version > LFS1_DISK_VERSION_MINOR)) { - LFS_ERROR("Invalid version %d.%d", major_version, minor_version); + LFS_ERROR("Invalid version v%d.%d", major_version, minor_version); err = LFS_ERR_INVAL; goto cleanup; } @@ -4643,13 +4777,15 @@ int lfs_migrate(lfs_t *lfs, const struct lfs_config *cfg) { lfs1_entry_tole32(&entry1.d); err = lfs_dir_commit(lfs, &dir2, LFS_MKATTRS( - {LFS_MKTAG(LFS_TYPE_CREATE, id, 0), NULL}, - {LFS_MKTAG( - isdir ? LFS_TYPE_DIR : LFS_TYPE_REG, - id, entry1.d.nlen), name}, - {LFS_MKTAG( - isdir ? LFS_TYPE_DIRSTRUCT : LFS_TYPE_CTZSTRUCT, - id, sizeof(entry1.d.u)), &entry1.d.u})); + {LFS_MKTAG(LFS_TYPE_CREATE, id, 0)}, + {LFS_MKTAG_IF_ELSE(isdir, + LFS_TYPE_DIR, id, entry1.d.nlen, + LFS_TYPE_REG, id, entry1.d.nlen), + name}, + {LFS_MKTAG_IF_ELSE(isdir, + LFS_TYPE_DIRSTRUCT, id, sizeof(entry1.d.u), + LFS_TYPE_CTZSTRUCT, id, sizeof(entry1.d.u)), + &entry1.d.u})); lfs1_entry_fromle32(&entry1.d); if (err) { goto cleanup; @@ -4672,8 +4808,7 @@ int lfs_migrate(lfs_t *lfs, const struct lfs_config *cfg) { lfs_pair_tole32(dir2.pair); err = lfs_dir_commit(lfs, &dir2, LFS_MKATTRS( - {LFS_MKTAG(LFS_TYPE_SOFTTAIL, 0x3ff, 8), - dir1.d.tail})); + {LFS_MKTAG(LFS_TYPE_SOFTTAIL, 0x3ff, 8), dir1.d.tail})); lfs_pair_fromle32(dir2.pair); if (err) { goto cleanup; @@ -4682,7 +4817,8 @@ int lfs_migrate(lfs_t *lfs, const struct lfs_config *cfg) { // Copy over first block to thread into fs. Unfortunately // if this fails there is not much we can do. - LFS_DEBUG("Migrating %"PRIx32" %"PRIx32" -> %"PRIx32" %"PRIx32, + LFS_DEBUG("Migrating {0x%"PRIx32", 0x%"PRIx32"} " + "-> {0x%"PRIx32", 0x%"PRIx32"}", lfs->root[0], lfs->root[1], dir1.head[0], dir1.head[1]); err = lfs_bd_erase(lfs, dir1.head[1]); @@ -4728,7 +4864,7 @@ int lfs_migrate(lfs_t *lfs, const struct lfs_config *cfg) { dir2.pair[1] = dir1.pair[1]; dir2.rev = dir1.d.rev; dir2.off = sizeof(dir2.rev); - dir2.etag = LFS_BLOCK_NULL; + dir2.etag = 0xffffffff; dir2.count = 0; dir2.tail[0] = lfs->lfs1->root[0]; dir2.tail[1] = lfs->lfs1->root[1]; @@ -4746,7 +4882,7 @@ int lfs_migrate(lfs_t *lfs, const struct lfs_config *cfg) { lfs_superblock_tole32(&superblock); err = lfs_dir_commit(lfs, &dir2, LFS_MKATTRS( - {LFS_MKTAG(LFS_TYPE_CREATE, 0, 0), NULL}, + {LFS_MKTAG(LFS_TYPE_CREATE, 0, 0)}, {LFS_MKTAG(LFS_TYPE_SUPERBLOCK, 0, 8), "littlefs"}, {LFS_MKTAG(LFS_TYPE_INLINESTRUCT, 0, sizeof(superblock)), &superblock})); diff --git a/lfs.h b/lfs.h index 04054e91..35bbbabf 100644 --- a/lfs.h +++ b/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 0x00020001 +#define LFS_VERSION 0x00020002 #define LFS_VERSION_MAJOR (0xffff & (LFS_VERSION >> 16)) #define LFS_VERSION_MINOR (0xffff & (LFS_VERSION >> 0)) @@ -355,6 +355,11 @@ typedef struct lfs_superblock { lfs_size_t attr_max; } lfs_superblock_t; +typedef struct lfs_gstate { + uint32_t tag; + lfs_block_t pair[2]; +} lfs_gstate_t; + // The littlefs filesystem type typedef struct lfs { lfs_cache_t rcache; @@ -369,10 +374,9 @@ typedef struct lfs { } *mlist; uint32_t seed; - struct lfs_gstate { - uint32_t tag; - lfs_block_t pair[2]; - } gstate, gpending, gdelta; + lfs_gstate_t gstate; + lfs_gstate_t gdisk; + lfs_gstate_t gdelta; struct lfs_free { lfs_block_t off; diff --git a/lfs_util.h b/lfs_util.h index 2e049747..dbb4c5ba 100644 --- a/lfs_util.h +++ b/lfs_util.h @@ -51,7 +51,7 @@ extern "C" // Logging functions #ifdef LFS_YES_TRACE #define LFS_TRACE_(fmt, ...) \ - printf("lfs_trace:%d: " fmt "%s\n", __LINE__, __VA_ARGS__) + printf("%s:%d:trace: " fmt "%s\n", __FILE__, __LINE__, __VA_ARGS__) #define LFS_TRACE(...) LFS_TRACE_(__VA_ARGS__, "") #else #define LFS_TRACE(...) @@ -59,7 +59,7 @@ extern "C" #ifndef LFS_NO_DEBUG #define LFS_DEBUG_(fmt, ...) \ - printf("lfs_debug:%d: " fmt "%s\n", __LINE__, __VA_ARGS__) + printf("%s:%d:debug: " fmt "%s\n", __FILE__, __LINE__, __VA_ARGS__) #define LFS_DEBUG(...) LFS_DEBUG_(__VA_ARGS__, "") #else #define LFS_DEBUG(...) @@ -67,7 +67,7 @@ extern "C" #ifndef LFS_NO_WARN #define LFS_WARN_(fmt, ...) \ - printf("lfs_warn:%d: " fmt "%s\n", __LINE__, __VA_ARGS__) + printf("%s:%d:warn: " fmt "%s\n", __FILE__, __LINE__, __VA_ARGS__) #define LFS_WARN(...) LFS_WARN_(__VA_ARGS__, "") #else #define LFS_WARN(...) @@ -75,7 +75,7 @@ extern "C" #ifndef LFS_NO_ERROR #define LFS_ERROR_(fmt, ...) \ - printf("lfs_error:%d: " fmt "%s\n", __LINE__, __VA_ARGS__) + printf("%s:%d:error: " fmt "%s\n", __FILE__, __LINE__, __VA_ARGS__) #define LFS_ERROR(...) LFS_ERROR_(__VA_ARGS__, "") #else #define LFS_ERROR(...) diff --git a/scripts/corrupt.py b/scripts/corrupt.py deleted file mode 100755 index c452c423..00000000 --- a/scripts/corrupt.py +++ /dev/null @@ -1,44 +0,0 @@ -#!/usr/bin/env python2 - -import struct -import sys -import os -import argparse - -def corrupt(block): - with open(block, 'r+b') as file: - # skip rev - file.read(4) - - # go to last commit - tag = 0xffffffff - while True: - try: - ntag, = struct.unpack('>I', file.read(4)) - except struct.error: - break - - tag ^= ntag - size = (tag & 0x3ff) if (tag & 0x3ff) != 0x3ff else 0 - file.seek(size, os.SEEK_CUR) - - # lob off last 3 bytes - file.seek(-(size + 3), os.SEEK_CUR) - file.truncate() - -def main(args): - if args.n or not args.blocks: - with open('blocks/.history', 'rb') as file: - for i in range(int(args.n or 1)): - last, = struct.unpack('I', data) - except struct.error: - break - - tag ^= ntag - off += 4 - - type = (tag & 0x7ff00000) >> 20 - id = (tag & 0x000ffc00) >> 10 - size = (tag & 0x000003ff) >> 0 - iscrc = (type & 0x700) == 0x500 - - data = file.read(size if size != 0x3ff else 0) - if iscrc: - crc = binascii.crc32(data[:4], crc) - else: - crc = binascii.crc32(data, crc) - - print '%04x: %08x %-15s %3s %4s %-23s %-8s' % ( - off, tag, - typeof(type) + (' bad!' if iscrc and ~crc else ''), - hex(id)[2:] if id != 0x3ff else '.', - size if size != 0x3ff else 'x', - ' '.join('%02x' % ord(c) for c in data[:8]), - ''.join(c if c >= ' ' and c <= '~' else '.' for c in data[:8])) - - off += size if size != 0x3ff else 0 - if iscrc: - crc = 0 - tag ^= (type & 1) << 31 - - return 0 - -if __name__ == "__main__": - import sys - sys.exit(main(*sys.argv[1:])) diff --git a/scripts/explode_asserts.py b/scripts/explode_asserts.py new file mode 100755 index 00000000..8a8e5b1c --- /dev/null +++ b/scripts/explode_asserts.py @@ -0,0 +1,383 @@ +#!/usr/bin/env python3 + +import re +import sys + +PATTERN = ['LFS_ASSERT', 'assert'] +PREFIX = 'LFS' +MAXWIDTH = 16 + +ASSERT = "__{PREFIX}_ASSERT_{TYPE}_{COMP}" +FAIL = """ +__attribute__((unused)) +static void __{prefix}_assert_fail_{type}( + const char *file, int line, const char *comp, + {ctype} lh, size_t lsize, + {ctype} rh, size_t rsize) {{ + printf("%s:%d:assert: assert failed with ", file, line); + __{prefix}_assert_print_{type}(lh, lsize); + printf(", expected %s ", comp); + __{prefix}_assert_print_{type}(rh, rsize); + printf("\\n"); + fflush(NULL); + raise(SIGABRT); +}} +""" + +COMP = { + '==': 'eq', + '!=': 'ne', + '<=': 'le', + '>=': 'ge', + '<': 'lt', + '>': 'gt', +} + +TYPE = { + 'int': { + 'ctype': 'intmax_t', + 'fail': FAIL, + 'print': """ + __attribute__((unused)) + static void __{prefix}_assert_print_{type}({ctype} v, size_t size) {{ + (void)size; + printf("%"PRIiMAX, v); + }} + """, + 'assert': """ + #define __{PREFIX}_ASSERT_{TYPE}_{COMP}(file, line, lh, rh) + do {{ + __typeof__(lh) _lh = lh; + __typeof__(lh) _rh = (__typeof__(lh))rh; + if (!(_lh {op} _rh)) {{ + __{prefix}_assert_fail_{type}(file, line, "{comp}", + (intmax_t)_lh, 0, (intmax_t)_rh, 0); + }} + }} while (0) + """ + }, + 'bool': { + 'ctype': 'bool', + 'fail': FAIL, + 'print': """ + __attribute__((unused)) + static void __{prefix}_assert_print_{type}({ctype} v, size_t size) {{ + (void)size; + printf("%s", v ? "true" : "false"); + }} + """, + 'assert': """ + #define __{PREFIX}_ASSERT_{TYPE}_{COMP}(file, line, lh, rh) + do {{ + bool _lh = !!(lh); + bool _rh = !!(rh); + if (!(_lh {op} _rh)) {{ + __{prefix}_assert_fail_{type}(file, line, "{comp}", + _lh, 0, _rh, 0); + }} + }} while (0) + """ + }, + 'mem': { + 'ctype': 'const void *', + 'fail': FAIL, + 'print': """ + __attribute__((unused)) + static void __{prefix}_assert_print_{type}({ctype} v, size_t size) {{ + const uint8_t *s = v; + printf("\\\""); + for (size_t i = 0; i < size && i < {maxwidth}; i++) {{ + if (s[i] >= ' ' && s[i] <= '~') {{ + printf("%c", s[i]); + }} else {{ + printf("\\\\x%02x", s[i]); + }} + }} + if (size > {maxwidth}) {{ + printf("..."); + }} + printf("\\\""); + }} + """, + 'assert': """ + #define __{PREFIX}_ASSERT_{TYPE}_{COMP}(file, line, lh, rh, size) + do {{ + const void *_lh = lh; + const void *_rh = rh; + if (!(memcmp(_lh, _rh, size) {op} 0)) {{ + __{prefix}_assert_fail_{type}(file, line, "{comp}", + _lh, size, _rh, size); + }} + }} while (0) + """ + }, + 'str': { + 'ctype': 'const char *', + 'fail': FAIL, + 'print': """ + __attribute__((unused)) + static void __{prefix}_assert_print_{type}({ctype} v, size_t size) {{ + __{prefix}_assert_print_mem(v, size); + }} + """, + 'assert': """ + #define __{PREFIX}_ASSERT_{TYPE}_{COMP}(file, line, lh, rh) + do {{ + const char *_lh = lh; + const char *_rh = rh; + if (!(strcmp(_lh, _rh) {op} 0)) {{ + __{prefix}_assert_fail_{type}(file, line, "{comp}", + _lh, strlen(_lh), _rh, strlen(_rh)); + }} + }} while (0) + """ + } +} + +def mkdecls(outf, maxwidth=16): + outf.write("#include \n") + outf.write("#include \n") + outf.write("#include \n") + outf.write("#include \n") + outf.write("#include \n") + + for type, desc in sorted(TYPE.items()): + format = { + 'type': type.lower(), 'TYPE': type.upper(), + 'ctype': desc['ctype'], + 'prefix': PREFIX.lower(), 'PREFIX': PREFIX.upper(), + 'maxwidth': maxwidth, + } + outf.write(re.sub('\s+', ' ', + desc['print'].strip().format(**format))+'\n') + outf.write(re.sub('\s+', ' ', + desc['fail'].strip().format(**format))+'\n') + + for op, comp in sorted(COMP.items()): + format.update({ + 'comp': comp.lower(), 'COMP': comp.upper(), + 'op': op, + }) + outf.write(re.sub('\s+', ' ', + desc['assert'].strip().format(**format))+'\n') + +def mkassert(type, comp, lh, rh, size=None): + format = { + 'type': type.lower(), 'TYPE': type.upper(), + 'comp': comp.lower(), 'COMP': comp.upper(), + 'prefix': PREFIX.lower(), 'PREFIX': PREFIX.upper(), + 'lh': lh.strip(' '), + 'rh': rh.strip(' '), + 'size': size, + } + if size: + return ((ASSERT + '(__FILE__, __LINE__, {lh}, {rh}, {size})') + .format(**format)) + else: + return ((ASSERT + '(__FILE__, __LINE__, {lh}, {rh})') + .format(**format)) + + +# simple recursive descent parser +LEX = { + 'ws': [r'(?:\s|\n|#.*?\n|//.*?\n|/\*.*?\*/)+'], + 'assert': PATTERN, + 'string': [r'"(?:\\.|[^"])*"', r"'(?:\\.|[^'])\'"], + 'arrow': ['=>'], + 'paren': ['\(', '\)'], + 'op': ['strcmp', 'memcmp', '->'], + 'comp': ['==', '!=', '<=', '>=', '<', '>'], + 'logic': ['\&\&', '\|\|'], + 'sep': [':', ';', '\{', '\}', ','], +} + +class ParseFailure(Exception): + def __init__(self, expected, found): + self.expected = expected + self.found = found + + def __str__(self): + return "expected %r, found %s..." % ( + self.expected, repr(self.found)[:70]) + +class Parse: + def __init__(self, inf, lexemes): + p = '|'.join('(?P<%s>%s)' % (n, '|'.join(l)) + for n, l in lexemes.items()) + p = re.compile(p, re.DOTALL) + data = inf.read() + tokens = [] + while True: + m = p.search(data) + if m: + if m.start() > 0: + tokens.append((None, data[:m.start()])) + tokens.append((m.lastgroup, m.group())) + data = data[m.end():] + else: + tokens.append((None, data)) + break + self.tokens = tokens + self.off = 0 + + def lookahead(self, *pattern): + if self.off < len(self.tokens): + token = self.tokens[self.off] + if token[0] in pattern or token[1] in pattern: + self.m = token[1] + return self.m + self.m = None + return self.m + + def accept(self, *patterns): + m = self.lookahead(*patterns) + if m is not None: + self.off += 1 + return m + + def expect(self, *patterns): + m = self.accept(*patterns) + if not m: + raise ParseFailure(patterns, self.tokens[self.off:]) + return m + + def push(self): + return self.off + + def pop(self, state): + self.off = state + +def passert(p): + def pastr(p): + p.expect('assert') ; p.accept('ws') ; p.expect('(') ; p.accept('ws') + p.expect('strcmp') ; p.accept('ws') ; p.expect('(') ; p.accept('ws') + lh = pexpr(p) ; p.accept('ws') + p.expect(',') ; p.accept('ws') + rh = pexpr(p) ; p.accept('ws') + p.expect(')') ; p.accept('ws') + comp = p.expect('comp') ; p.accept('ws') + p.expect('0') ; p.accept('ws') + p.expect(')') + return mkassert('str', COMP[comp], lh, rh) + + def pamem(p): + p.expect('assert') ; p.accept('ws') ; p.expect('(') ; p.accept('ws') + p.expect('memcmp') ; p.accept('ws') ; p.expect('(') ; p.accept('ws') + lh = pexpr(p) ; p.accept('ws') + p.expect(',') ; p.accept('ws') + rh = pexpr(p) ; p.accept('ws') + p.expect(',') ; p.accept('ws') + size = pexpr(p) ; p.accept('ws') + p.expect(')') ; p.accept('ws') + comp = p.expect('comp') ; p.accept('ws') + p.expect('0') ; p.accept('ws') + p.expect(')') + return mkassert('mem', COMP[comp], lh, rh, size) + + def paint(p): + p.expect('assert') ; p.accept('ws') ; p.expect('(') ; p.accept('ws') + lh = pexpr(p) ; p.accept('ws') + comp = p.expect('comp') ; p.accept('ws') + rh = pexpr(p) ; p.accept('ws') + p.expect(')') + return mkassert('int', COMP[comp], lh, rh) + + def pabool(p): + p.expect('assert') ; p.accept('ws') ; p.expect('(') ; p.accept('ws') + lh = pexprs(p) ; p.accept('ws') + p.expect(')') + return mkassert('bool', 'eq', lh, 'true') + + def pa(p): + return p.expect('assert') + + state = p.push() + lastf = None + for pa in [pastr, pamem, paint, pabool, pa]: + try: + return pa(p) + except ParseFailure as f: + p.pop(state) + lastf = f + else: + raise lastf + +def pexpr(p): + res = [] + while True: + if p.accept('('): + res.append(p.m) + while True: + res.append(pexprs(p)) + if p.accept('sep'): + res.append(p.m) + else: + break + res.append(p.expect(')')) + elif p.lookahead('assert'): + res.append(passert(p)) + elif p.accept('assert', 'ws', 'string', 'op', None): + res.append(p.m) + else: + return ''.join(res) + +def pexprs(p): + res = [] + while True: + res.append(pexpr(p)) + if p.accept('comp', 'logic', ','): + res.append(p.m) + else: + return ''.join(res) + +def pstmt(p): + ws = p.accept('ws') or '' + lh = pexprs(p) + if p.accept('=>'): + rh = pexprs(p) + return ws + mkassert('int', 'eq', lh, rh) + else: + return ws + lh + + +def main(args): + inf = open(args.input, 'r') if args.input else sys.stdin + outf = open(args.output, 'w') if args.output else sys.stdout + + lexemes = LEX.copy() + if args.pattern: + lexemes['assert'] = args.pattern + p = Parse(inf, lexemes) + + # write extra verbose asserts + mkdecls(outf, maxwidth=args.maxwidth) + if args.input: + outf.write("#line %d \"%s\"\n" % (1, args.input)) + + # parse and write out stmt at a time + try: + while True: + outf.write(pstmt(p)) + if p.accept('sep'): + outf.write(p.m) + else: + break + except ParseFailure as f: + pass + + for i in range(p.off, len(p.tokens)): + outf.write(p.tokens[i][1]) + +if __name__ == "__main__": + import argparse + parser = argparse.ArgumentParser( + description="Cpp step that increases assert verbosity") + parser.add_argument('input', nargs='?', + help="Input C file after cpp.") + parser.add_argument('-o', '--output', required=True, + help="Output C file.") + parser.add_argument('-p', '--pattern', action='append', + help="Patterns to search for starting an assert statement.") + parser.add_argument('--maxwidth', default=MAXWIDTH, type=int, + help="Maximum number of characters to display for strcmp and memcmp.") + main(parser.parse_args()) diff --git a/scripts/readblock.py b/scripts/readblock.py new file mode 100755 index 00000000..817517bc --- /dev/null +++ b/scripts/readblock.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python3 + +import subprocess as sp + +def main(args): + with open(args.disk, 'rb') as f: + f.seek(args.block * args.block_size) + block = (f.read(args.block_size) + .ljust(args.block_size, b'\xff')) + + # what did you expect? + print("%-8s %-s" % ('off', 'data')) + return sp.run(['xxd', '-g1', '-'], input=block).returncode + +if __name__ == "__main__": + import argparse + import sys + parser = argparse.ArgumentParser( + description="Hex dump a specific block in a disk.") + parser.add_argument('disk', + help="File representing the block device.") + parser.add_argument('block_size', type=lambda x: int(x, 0), + help="Size of a block in bytes.") + parser.add_argument('block', type=lambda x: int(x, 0), + help="Address of block to dump.") + sys.exit(main(parser.parse_args())) diff --git a/scripts/readmdir.py b/scripts/readmdir.py new file mode 100755 index 00000000..b6c3dcca --- /dev/null +++ b/scripts/readmdir.py @@ -0,0 +1,367 @@ +#!/usr/bin/env python3 + +import struct +import binascii +import sys +import itertools as it + +TAG_TYPES = { + 'splice': (0x700, 0x400), + 'create': (0x7ff, 0x401), + 'delete': (0x7ff, 0x4ff), + 'name': (0x700, 0x000), + 'reg': (0x7ff, 0x001), + 'dir': (0x7ff, 0x002), + 'superblock': (0x7ff, 0x0ff), + 'struct': (0x700, 0x200), + 'dirstruct': (0x7ff, 0x200), + 'ctzstruct': (0x7ff, 0x202), + 'inlinestruct': (0x7ff, 0x201), + 'userattr': (0x700, 0x300), + 'tail': (0x700, 0x600), + 'softtail': (0x7ff, 0x600), + 'hardtail': (0x7ff, 0x601), + 'gstate': (0x700, 0x700), + 'movestate': (0x7ff, 0x7ff), + 'crc': (0x700, 0x500), +} + +class Tag: + def __init__(self, *args): + if len(args) == 1: + self.tag = args[0] + elif len(args) == 3: + if isinstance(args[0], str): + type = TAG_TYPES[args[0]][1] + else: + type = args[0] + + if isinstance(args[1], str): + id = int(args[1], 0) if args[1] not in 'x.' else 0x3ff + else: + id = args[1] + + if isinstance(args[2], str): + size = int(args[2], str) if args[2] not in 'x.' else 0x3ff + else: + size = args[2] + + self.tag = (type << 20) | (id << 10) | size + else: + assert False + + @property + def isvalid(self): + return not bool(self.tag & 0x80000000) + + @property + def isattr(self): + return not bool(self.tag & 0x40000000) + + @property + def iscompactable(self): + return bool(self.tag & 0x20000000) + + @property + def isunique(self): + return not bool(self.tag & 0x10000000) + + @property + def type(self): + return (self.tag & 0x7ff00000) >> 20 + + @property + def type1(self): + return (self.tag & 0x70000000) >> 20 + + @property + def type3(self): + return (self.tag & 0x7ff00000) >> 20 + + @property + def id(self): + return (self.tag & 0x000ffc00) >> 10 + + @property + def size(self): + return (self.tag & 0x000003ff) >> 0 + + @property + def dsize(self): + return 4 + (self.size if self.size != 0x3ff else 0) + + @property + def chunk(self): + return self.type & 0xff + + @property + def schunk(self): + return struct.unpack('b', struct.pack('B', self.chunk))[0] + + def is_(self, type): + return (self.type & TAG_TYPES[type][0]) == TAG_TYPES[type][1] + + def mkmask(self): + return Tag( + 0x700 if self.isunique else 0x7ff, + 0x3ff if self.isattr else 0, + 0) + + def chid(self, nid): + ntag = Tag(self.type, nid, self.size) + if hasattr(self, 'off'): ntag.off = self.off + if hasattr(self, 'data'): ntag.data = self.data + if hasattr(self, 'crc'): ntag.crc = self.crc + return ntag + + def typerepr(self): + if self.is_('crc') and getattr(self, 'crc', 0xffffffff) != 0xffffffff: + return 'crc (bad)' + + reverse_types = {v: k for k, v in TAG_TYPES.items()} + for prefix in range(12): + mask = 0x7ff & ~((1 << prefix)-1) + if (mask, self.type & mask) in reverse_types: + type = reverse_types[mask, self.type & mask] + if prefix > 0: + return '%s %#0*x' % ( + type, prefix//4, self.type & ((1 << prefix)-1)) + else: + return type + else: + return '%02x' % self.type + + def idrepr(self): + return repr(self.id) if self.id != 0x3ff else '.' + + def sizerepr(self): + return repr(self.size) if self.size != 0x3ff else 'x' + + def __repr__(self): + return 'Tag(%r, %d, %d)' % (self.typerepr(), self.id, self.size) + + def __lt__(self, other): + return (self.id, self.type) < (other.id, other.type) + + def __bool__(self): + return self.isvalid + + def __int__(self): + return self.tag + + def __index__(self): + return self.tag + +class MetadataPair: + def __init__(self, blocks): + if len(blocks) > 1: + self.pair = [MetadataPair([block]) for block in blocks] + self.pair = sorted(self.pair, reverse=True) + + self.data = self.pair[0].data + self.rev = self.pair[0].rev + self.tags = self.pair[0].tags + self.ids = self.pair[0].ids + self.log = self.pair[0].log + self.all_ = self.pair[0].all_ + return + + self.pair = [self] + self.data = blocks[0] + block = self.data + + self.rev, = struct.unpack('= 4: + ntag, = struct.unpack('>I', block[off:off+4]) + + tag = Tag(int(tag) ^ ntag) + tag.off = off + 4 + tag.data = block[off+4:off+tag.dsize] + if tag.is_('crc'): + crc = binascii.crc32(block[off:off+4+4], crc) + else: + crc = binascii.crc32(block[off:off+tag.dsize], crc) + tag.crc = crc + off += tag.dsize + + self.all_.append(tag) + + if tag.is_('crc'): + # is valid commit? + if crc != 0xffffffff: + corrupt = True + if not corrupt: + self.log = self.all_.copy() + + # reset tag parsing + crc = 0 + tag = Tag(int(tag) ^ ((tag.type & 1) << 31)) + + # find active ids + self.ids = list(it.takewhile( + lambda id: Tag('name', id, 0) in self, + it.count())) + + # find most recent tags + self.tags = [] + for tag in self.log: + if tag.is_('crc') or tag.is_('splice'): + continue + elif tag.id == 0x3ff: + if tag in self and self[tag] is tag: + self.tags.append(tag) + else: + # id could have change, I know this is messy and slow + # but it works + for id in self.ids: + ntag = tag.chid(id) + if ntag in self and self[ntag] is tag: + self.tags.append(ntag) + + self.tags = sorted(self.tags) + + def __bool__(self): + return bool(self.log) + + def __lt__(self, other): + # corrupt blocks don't count + if not self or not other: + return bool(other) + + # use sequence arithmetic to avoid overflow + return not ((other.rev - self.rev) & 0x80000000) + + def __contains__(self, args): + try: + self[args] + return True + except KeyError: + return False + + def __getitem__(self, args): + if isinstance(args, tuple): + gmask, gtag = args + else: + gmask, gtag = args.mkmask(), args + + gdiff = 0 + for tag in reversed(self.log): + if (gmask.id != 0 and tag.is_('splice') and + tag.id <= gtag.id - gdiff): + if tag.is_('create') and tag.id == gtag.id - gdiff: + # creation point + break + + gdiff += tag.schunk + + if ((int(gmask) & int(tag)) == + (int(gmask) & int(gtag.chid(gtag.id - gdiff)))): + if tag.size == 0x3ff: + # deleted + break + + return tag + + raise KeyError(gmask, gtag) + + def _dump_tags(self, tags, f=sys.stdout, truncate=True): + f.write("%-8s %-8s %-13s %4s %4s" % ( + 'off', 'tag', 'type', 'id', 'len')) + if truncate: + f.write(' data (truncated)') + f.write('\n') + + for tag in tags: + f.write("%08x: %08x %-13s %4s %4s" % ( + tag.off, tag, + tag.typerepr(), tag.idrepr(), tag.sizerepr())) + if truncate: + f.write(" %-23s %-8s\n" % ( + ' '.join('%02x' % c for c in tag.data[:8]), + ''.join(c if c >= ' ' and c <= '~' else '.' + for c in map(chr, tag.data[:8])))) + else: + f.write("\n") + for i in range(0, len(tag.data), 16): + f.write(" %08x: %-47s %-16s\n" % ( + tag.off+i, + ' '.join('%02x' % c for c in tag.data[i:i+16]), + ''.join(c if c >= ' ' and c <= '~' else '.' + for c in map(chr, tag.data[i:i+16])))) + + def dump_tags(self, f=sys.stdout, truncate=True): + self._dump_tags(self.tags, f=f, truncate=truncate) + + def dump_log(self, f=sys.stdout, truncate=True): + self._dump_tags(self.log, f=f, truncate=truncate) + + def dump_all(self, f=sys.stdout, truncate=True): + self._dump_tags(self.all_, f=f, truncate=truncate) + +def main(args): + blocks = [] + with open(args.disk, 'rb') as f: + for block in [args.block1, args.block2]: + if block is None: + continue + f.seek(block * args.block_size) + blocks.append(f.read(args.block_size) + .ljust(args.block_size, b'\xff')) + + # find most recent pair + mdir = MetadataPair(blocks) + + try: + mdir.tail = mdir[Tag('tail', 0, 0)] + if mdir.tail.size != 8 or mdir.tail.data == 8*b'\xff': + mdir.tail = None + except KeyError: + mdir.tail = None + + print("mdir {%s} rev %d%s%s%s" % ( + ', '.join('%#x' % b + for b in [args.block1, args.block2] + if b is not None), + mdir.rev, + ' (was %s)' % ', '.join('%d' % m.rev for m in mdir.pair[1:]) + if len(mdir.pair) > 1 else '', + ' (corrupted!)' if not mdir else '', + ' -> {%#x, %#x}' % struct.unpack('=%d" % max(tag.size, 1)) + if tag.type: + print(" move dir {%#x, %#x} id %d" % ( + blocks[0], blocks[1], tag.id)) + + # print mdir info + for i, dir in enumerate(dirs): + print("dir %s" % (json.dumps(dir[0].path) + if hasattr(dir[0], 'path') else '(orphan)')) + + for j, mdir in enumerate(dir): + print("mdir {%#x, %#x} rev %d (was %d)%s%s" % ( + mdir.blocks[0], mdir.blocks[1], mdir.rev, mdir.pair[1].rev, + ' (corrupted!)' if not mdir else '', + ' -> {%#x, %#x}' % struct.unpack(' -#include -#include - - -// test stuff -static void test_assert(const char *file, unsigned line, - const char *s, uintmax_t v, uintmax_t e) {{ - if (v != e) {{ - fprintf(stderr, "\033[97m%s:%u: \033[91m" - "assert failed with %jd, expected %jd\033[0m\n" - " %s\n\n", file, line, v, e, s); - exit(-2); - }} -}} - -#define test_assert(v, e) \ - test_assert(__FILE__, __LINE__, #v " => " #e, v, e) - -// implicit variable for asserts -uintmax_t test; - -// utility functions for traversals -static int __attribute__((used)) test_count(void *p, lfs_block_t b) {{ - (void)b; - unsigned *u = (unsigned*)p; - *u += 1; - return 0; -}} - -// lfs declarations -lfs_t lfs; -lfs_emubd_t bd; -// other declarations for convenience -lfs_file_t file; -lfs_dir_t dir; -struct lfs_info info; -uint8_t buffer[1024]; -char path[1024]; - -// test configuration options -#ifndef LFS_READ_SIZE -#define LFS_READ_SIZE 16 -#endif - -#ifndef LFS_PROG_SIZE -#define LFS_PROG_SIZE LFS_READ_SIZE -#endif - -#ifndef LFS_BLOCK_SIZE -#define LFS_BLOCK_SIZE 512 -#endif - -#ifndef LFS_BLOCK_COUNT -#define LFS_BLOCK_COUNT 1024 -#endif - -#ifndef LFS_BLOCK_CYCLES -#define LFS_BLOCK_CYCLES 1024 -#endif - -#ifndef LFS_CACHE_SIZE -#define LFS_CACHE_SIZE (64 % LFS_PROG_SIZE == 0 ? 64 : LFS_PROG_SIZE) -#endif - -#ifndef LFS_LOOKAHEAD_SIZE -#define LFS_LOOKAHEAD_SIZE 16 -#endif - -const struct lfs_config cfg = {{ - .context = &bd, - .read = &lfs_emubd_read, - .prog = &lfs_emubd_prog, - .erase = &lfs_emubd_erase, - .sync = &lfs_emubd_sync, - - .read_size = LFS_READ_SIZE, - .prog_size = LFS_PROG_SIZE, - .block_size = LFS_BLOCK_SIZE, - .block_count = LFS_BLOCK_COUNT, - .block_cycles = LFS_BLOCK_CYCLES, - .cache_size = LFS_CACHE_SIZE, - .lookahead_size = LFS_LOOKAHEAD_SIZE, -}}; - - -// Entry point -int main(void) {{ - lfs_emubd_create(&cfg, "blocks"); - -{tests} - lfs_emubd_destroy(&cfg); -}} diff --git a/scripts/test.py b/scripts/test.py index 3135c654..e5869c20 100755 --- a/scripts/test.py +++ b/scripts/test.py @@ -1,81 +1,778 @@ -#!/usr/bin/env python2 +#!/usr/bin/env python3 +# This script manages littlefs tests, which are configured with +# .toml files stored in the tests directory. +# + +import toml +import glob import re -import sys -import subprocess import os +import io +import itertools as it +import collections.abc as abc +import subprocess as sp +import base64 +import sys +import copy +import shlex +import pty +import errno +import signal + +TESTDIR = 'tests' +RULES = """ +define FLATTEN +tests/%$(subst /,.,$(target)): $(target) + ./scripts/explode_asserts.py $$< -o $$@ +endef +$(foreach target,$(SRC),$(eval $(FLATTEN))) + +-include tests/*.d +.SECONDARY: +%.test: %.test.o $(foreach f,$(subst /,.,$(SRC:.c=.o)),%.$f) + $(CC) $(CFLAGS) $^ $(LFLAGS) -o $@ +""" +GLOBALS = """ +//////////////// AUTOGENERATED TEST //////////////// +#include "lfs.h" +#include "bd/lfs_testbd.h" +#include +extern const char *lfs_testbd_path; +extern uint32_t lfs_testbd_cycles; +""" +DEFINES = { + 'LFS_READ_SIZE': 16, + 'LFS_PROG_SIZE': 'LFS_READ_SIZE', + 'LFS_BLOCK_SIZE': 512, + 'LFS_BLOCK_COUNT': 1024, + 'LFS_BLOCK_CYCLES': -1, + 'LFS_CACHE_SIZE': '(64 % LFS_PROG_SIZE == 0 ? 64 : LFS_PROG_SIZE)', + 'LFS_LOOKAHEAD_SIZE': 16, + 'LFS_ERASE_VALUE': 0xff, + 'LFS_ERASE_CYCLES': 0, + 'LFS_BADBLOCK_BEHAVIOR': 'LFS_TESTBD_BADBLOCK_PROGERROR', +} +PROLOGUE = """ + // prologue + __attribute__((unused)) lfs_t lfs; + __attribute__((unused)) lfs_testbd_t bd; + __attribute__((unused)) lfs_file_t file; + __attribute__((unused)) lfs_dir_t dir; + __attribute__((unused)) struct lfs_info info; + __attribute__((unused)) char path[1024]; + __attribute__((unused)) uint8_t buffer[1024]; + __attribute__((unused)) lfs_size_t size; + __attribute__((unused)) int err; + + __attribute__((unused)) const struct lfs_config cfg = { + .context = &bd, + .read = lfs_testbd_read, + .prog = lfs_testbd_prog, + .erase = lfs_testbd_erase, + .sync = lfs_testbd_sync, + .read_size = LFS_READ_SIZE, + .prog_size = LFS_PROG_SIZE, + .block_size = LFS_BLOCK_SIZE, + .block_count = LFS_BLOCK_COUNT, + .block_cycles = LFS_BLOCK_CYCLES, + .cache_size = LFS_CACHE_SIZE, + .lookahead_size = LFS_LOOKAHEAD_SIZE, + }; -def generate(test): - with open("scripts/template.fmt") as file: - template = file.read() + __attribute__((unused)) const struct lfs_testbd_config bdcfg = { + .erase_value = LFS_ERASE_VALUE, + .erase_cycles = LFS_ERASE_CYCLES, + .badblock_behavior = LFS_BADBLOCK_BEHAVIOR, + .power_cycles = lfs_testbd_cycles, + }; - haslines = 'TEST_LINE' in os.environ and 'TEST_FILE' in os.environ + lfs_testbd_createcfg(&cfg, lfs_testbd_path, &bdcfg) => 0; +""" +EPILOGUE = """ + // epilogue + lfs_testbd_destroy(&cfg) => 0; +""" +PASS = '\033[32m✓\033[0m' +FAIL = '\033[31m✗\033[0m' - lines = [] - for offset, line in enumerate( - re.split('(?<=(?:.;| [{}]))\n', test.read())): - match = re.match('((?: *\n)*)( *)(.*)=>(.*);', - line, re.DOTALL | re.MULTILINE) - if match: - preface, tab, test, expect = match.groups() - lines.extend(['']*preface.count('\n')) - lines.append(tab+'test_assert({test}, {expect});'.format( - test=test.strip(), expect=expect.strip())) +class TestFailure(Exception): + def __init__(self, case, returncode=None, stdout=None, assert_=None): + self.case = case + self.returncode = returncode + self.stdout = stdout + self.assert_ = assert_ + +class TestCase: + def __init__(self, config, filter=filter, + suite=None, caseno=None, lineno=None, **_): + self.config = config + self.filter = filter + self.suite = suite + self.caseno = caseno + self.lineno = lineno + + self.code = config['code'] + self.code_lineno = config['code_lineno'] + self.defines = config.get('define', {}) + self.if_ = config.get('if', None) + self.in_ = config.get('in', None) + + def __str__(self): + if hasattr(self, 'permno'): + if any(k not in self.case.defines for k in self.defines): + return '%s#%d#%d (%s)' % ( + self.suite.name, self.caseno, self.permno, ', '.join( + '%s=%s' % (k, v) for k, v in self.defines.items() + if k not in self.case.defines)) + else: + return '%s#%d#%d' % ( + self.suite.name, self.caseno, self.permno) else: - lines.append(line) + return '%s#%d' % ( + self.suite.name, self.caseno) - # Create test file - with open('test.c', 'w') as file: - if 'TEST_LINE' in os.environ and 'TEST_FILE' in os.environ: - lines.insert(0, '#line %d "%s"' % ( - int(os.environ['TEST_LINE']) + 1, - os.environ['TEST_FILE'])) - lines.append('#line %d "test.c"' % ( - template[:template.find('{tests}')].count('\n') - + len(lines) + 2)) + def permute(self, class_=None, defines={}, permno=None, **_): + ncase = (class_ or type(self))(self.config) + for k, v in self.__dict__.items(): + setattr(ncase, k, v) + ncase.case = self + ncase.perms = [ncase] + ncase.permno = permno + ncase.defines = defines + return ncase - file.write(template.format(tests='\n'.join(lines))) + def build(self, f, **_): + # prologue + for k, v in sorted(self.defines.items()): + if k not in self.suite.defines: + f.write('#define %s %s\n' % (k, v)) - # Remove build artifacts to force rebuild - try: - os.remove('test.o') - os.remove('lfs') - except OSError: - pass + f.write('void test_case%d(%s) {' % (self.caseno, ','.join( + '\n'+8*' '+'__attribute__((unused)) intmax_t %s' % k + for k in sorted(self.perms[0].defines) + if k not in self.defines))) -def compile(): - subprocess.check_call([ - os.environ.get('MAKE', 'make'), - '--no-print-directory', '-s']) + f.write(PROLOGUE) + f.write('\n') + f.write(4*' '+'// test case %d\n' % self.caseno) + f.write(4*' '+'#line %d "%s"\n' % (self.code_lineno, self.suite.path)) -def execute(): - if 'EXEC' in os.environ: - subprocess.check_call([os.environ['EXEC'], "./lfs"]) - else: - subprocess.check_call(["./lfs"]) + # test case goes here + f.write(self.code) -def main(test=None): - try: - if test and not test.startswith('-'): - with open(test) as file: - generate(file) + # epilogue + f.write(EPILOGUE) + f.write('}\n') + + for k, v in sorted(self.defines.items()): + if k not in self.suite.defines: + f.write('#undef %s\n' % k) + + def shouldtest(self, **args): + if (self.filter is not None and + len(self.filter) >= 1 and + self.filter[0] != self.caseno): + return False + elif (self.filter is not None and + len(self.filter) >= 2 and + self.filter[1] != self.permno): + return False + elif args.get('no_internal', False) and self.in_ is not None: + return False + elif self.if_ is not None: + if_ = self.if_ + while True: + for k, v in sorted(self.defines.items(), + key=lambda x: len(x[0]), reverse=True): + if k in if_: + if_ = if_.replace(k, '(%s)' % v) + break + else: + break + if_ = ( + re.sub('(\&\&|\?)', ' and ', + re.sub('(\|\||:)', ' or ', + re.sub('!(?!=)', ' not ', if_)))) + return eval(if_) + else: + return True + + def test(self, exec=[], persist=False, cycles=None, + gdb=False, failure=None, disk=None, **args): + # build command + cmd = exec + ['./%s.test' % self.suite.path, + repr(self.caseno), repr(self.permno)] + + # persist disk or keep in RAM for speed? + if persist: + if not disk: + disk = self.suite.path + '.disk' + if persist != 'noerase': + try: + with open(disk, 'w') as f: + f.truncate(0) + if args.get('verbose', False): + print('truncate --size=0', disk) + except FileNotFoundError: + pass + + cmd.append(disk) + + # simulate power-loss after n cycles? + if cycles: + cmd.append(str(cycles)) + + # failed? drop into debugger? + if gdb and failure: + ncmd = ['gdb'] + if gdb == 'assert': + ncmd.extend(['-ex', 'r']) + if failure.assert_: + ncmd.extend(['-ex', 'up 2']) + elif gdb == 'main': + ncmd.extend([ + '-ex', 'b %s:%d' % (self.suite.path, self.code_lineno), + '-ex', 'r']) + ncmd.extend(['--args'] + cmd) + + if args.get('verbose', False): + print(' '.join(shlex.quote(c) for c in ncmd)) + signal.signal(signal.SIGINT, signal.SIG_IGN) + sys.exit(sp.call(ncmd)) + + # run test case! + mpty, spty = pty.openpty() + if args.get('verbose', False): + print(' '.join(shlex.quote(c) for c in cmd)) + proc = sp.Popen(cmd, stdout=spty, stderr=spty) + os.close(spty) + mpty = os.fdopen(mpty, 'r', 1) + stdout = [] + assert_ = None + try: + while True: + try: + line = mpty.readline() + except OSError as e: + if e.errno == errno.EIO: + break + raise + stdout.append(line) + if args.get('verbose', False): + sys.stdout.write(line) + # intercept asserts + m = re.match( + '^{0}([^:]+):(\d+):(?:\d+:)?{0}{1}:{0}(.*)$' + .format('(?:\033\[[\d;]*.| )*', 'assert'), + line) + if m and assert_ is None: + try: + with open(m.group(1)) as f: + lineno = int(m.group(2)) + line = (next(it.islice(f, lineno-1, None)) + .strip('\n')) + assert_ = { + 'path': m.group(1), + 'line': line, + 'lineno': lineno, + 'message': m.group(3)} + except: + pass + except KeyboardInterrupt: + raise TestFailure(self, 1, stdout, None) + proc.wait() + + # did we pass? + if proc.returncode != 0: + raise TestFailure(self, proc.returncode, stdout, assert_) + else: + return PASS + +class ValgrindTestCase(TestCase): + def __init__(self, config, **args): + self.leaky = config.get('leaky', False) + super().__init__(config, **args) + + def shouldtest(self, **args): + return not self.leaky and super().shouldtest(**args) + + def test(self, exec=[], **args): + verbose = args.get('verbose', False) + uninit = (self.defines.get('LFS_ERASE_VALUE', None) == -1) + exec = [ + 'valgrind', + '--leak-check=full', + ] + (['--undef-value-errors=no'] if uninit else []) + [ + ] + (['--track-origins=yes'] if not uninit else []) + [ + '--error-exitcode=4', + '--error-limit=no', + ] + (['--num-callers=1'] if not verbose else []) + [ + '-q'] + exec + return super().test(exec=exec, **args) + +class ReentrantTestCase(TestCase): + def __init__(self, config, **args): + self.reentrant = config.get('reentrant', False) + super().__init__(config, **args) + + def shouldtest(self, **args): + return self.reentrant and super().shouldtest(**args) + + def test(self, persist=False, gdb=False, failure=None, **args): + for cycles in it.count(1): + # clear disk first? + if cycles == 1 and persist != 'noerase': + persist = 'erase' + else: + persist = 'noerase' + + # exact cycle we should drop into debugger? + if gdb and failure and failure.cycleno == cycles: + return super().test(gdb=gdb, persist=persist, cycles=cycles, + failure=failure, **args) + + # run tests, but kill the program after prog/erase has + # been hit n cycles. We exit with a special return code if the + # program has not finished, since this isn't a test failure. + try: + return super().test(persist=persist, cycles=cycles, **args) + except TestFailure as nfailure: + if nfailure.returncode == 33: + continue + else: + nfailure.cycleno = cycles + raise + +class TestSuite: + def __init__(self, path, classes=[TestCase], defines={}, + filter=None, **args): + self.name = os.path.basename(path) + if self.name.endswith('.toml'): + self.name = self.name[:-len('.toml')] + self.path = path + self.classes = classes + self.defines = defines.copy() + self.filter = filter + + with open(path) as f: + # load tests + config = toml.load(f) + + # find line numbers + f.seek(0) + linenos = [] + code_linenos = [] + for i, line in enumerate(f): + if re.match(r'\[\[\s*case\s*\]\]', line): + linenos.append(i+1) + if re.match(r'code\s*=\s*(\'\'\'|""")', line): + code_linenos.append(i+2) + + code_linenos.reverse() + + # grab global config + for k, v in config.get('define', {}).items(): + if k not in self.defines: + self.defines[k] = v + self.code = config.get('code', None) + if self.code is not None: + self.code_lineno = code_linenos.pop() + + # create initial test cases + self.cases = [] + for i, (case, lineno) in enumerate(zip(config['case'], linenos)): + # code lineno? + if 'code' in case: + case['code_lineno'] = code_linenos.pop() + # merge conditions if necessary + if 'if' in config and 'if' in case: + case['if'] = '(%s) && (%s)' % (config['if'], case['if']) + elif 'if' in config: + case['if'] = config['if'] + # initialize test case + self.cases.append(TestCase(case, filter=filter, + suite=self, caseno=i+1, lineno=lineno, **args)) + + def __str__(self): + return self.name + + def __lt__(self, other): + return self.name < other.name + + def permute(self, **args): + for case in self.cases: + # lets find all parameterized definitions, in one of [args.D, + # suite.defines, case.defines, DEFINES]. Note that each of these + # can be either a dict of defines, or a list of dicts, expressing + # an initial set of permutations. + pending = [{}] + for inits in [self.defines, case.defines, DEFINES]: + if not isinstance(inits, list): + inits = [inits] + + npending = [] + for init, pinit in it.product(inits, pending): + ninit = pinit.copy() + for k, v in init.items(): + if k not in ninit: + try: + ninit[k] = eval(v) + except: + ninit[k] = v + npending.append(ninit) + + pending = npending + + # expand permutations + pending = list(reversed(pending)) + expanded = [] + while pending: + perm = pending.pop() + for k, v in sorted(perm.items()): + if not isinstance(v, str) and isinstance(v, abc.Iterable): + for nv in reversed(v): + nperm = perm.copy() + nperm[k] = nv + pending.append(nperm) + break + else: + expanded.append(perm) + + # generate permutations + case.perms = [] + for i, (class_, defines) in enumerate( + it.product(self.classes, expanded)): + case.perms.append(case.permute( + class_, defines, permno=i+1, **args)) + + # also track non-unique defines + case.defines = {} + for k, v in case.perms[0].defines.items(): + if all(perm.defines[k] == v for perm in case.perms): + case.defines[k] = v + + # track all perms and non-unique defines + self.perms = [] + for case in self.cases: + self.perms.extend(case.perms) + + self.defines = {} + for k, v in self.perms[0].defines.items(): + if all(perm.defines.get(k, None) == v for perm in self.perms): + self.defines[k] = v + + return self.perms + + def build(self, **args): + # build test files + tf = open(self.path + '.test.c.t', 'w') + tf.write(GLOBALS) + if self.code is not None: + tf.write('#line %d "%s"\n' % (self.code_lineno, self.path)) + tf.write(self.code) + + tfs = {None: tf} + for case in self.cases: + if case.in_ not in tfs: + tfs[case.in_] = open(self.path+'.'+ + case.in_.replace('/', '.')+'.t', 'w') + tfs[case.in_].write('#line 1 "%s"\n' % case.in_) + with open(case.in_) as f: + for line in f: + tfs[case.in_].write(line) + tfs[case.in_].write('\n') + tfs[case.in_].write(GLOBALS) + + tfs[case.in_].write('\n') + case.build(tfs[case.in_], **args) + + tf.write('\n') + tf.write('const char *lfs_testbd_path;\n') + tf.write('uint32_t lfs_testbd_cycles;\n') + tf.write('int main(int argc, char **argv) {\n') + tf.write(4*' '+'int case_ = (argc > 1) ? atoi(argv[1]) : 0;\n') + tf.write(4*' '+'int perm = (argc > 2) ? atoi(argv[2]) : 0;\n') + tf.write(4*' '+'lfs_testbd_path = (argc > 3) ? argv[3] : NULL;\n') + tf.write(4*' '+'lfs_testbd_cycles = (argc > 4) ? atoi(argv[4]) : 0;\n') + for perm in self.perms: + # test declaration + tf.write(4*' '+'extern void test_case%d(%s);\n' % ( + perm.caseno, ', '.join( + 'intmax_t %s' % k for k in sorted(perm.defines) + if k not in perm.case.defines))) + # test call + tf.write(4*' '+ + 'if (argc < 3 || (case_ == %d && perm == %d)) {' + ' test_case%d(%s); ' + '}\n' % (perm.caseno, perm.permno, perm.caseno, ', '.join( + str(v) for k, v in sorted(perm.defines.items()) + if k not in perm.case.defines))) + tf.write('}\n') + + for tf in tfs.values(): + tf.close() + + # write makefiles + with open(self.path + '.mk', 'w') as mk: + mk.write(RULES.replace(4*' ', '\t')) + mk.write('\n') + + # add truely global defines globally + for k, v in sorted(self.defines.items()): + mk.write('%s: override CFLAGS += -D%s=%r\n' % ( + self.path+'.test', k, v)) + + for path in tfs: + if path is None: + mk.write('%s: %s | %s\n' % ( + self.path+'.test.c', + self.path, + self.path+'.test.c.t')) + else: + mk.write('%s: %s %s | %s\n' % ( + self.path+'.'+path.replace('/', '.'), + self.path, path, + self.path+'.'+path.replace('/', '.')+'.t')) + mk.write('\t./scripts/explode_asserts.py $| -o $@\n') + + self.makefile = self.path + '.mk' + self.target = self.path + '.test' + return self.makefile, self.target + + def test(self, **args): + # run test suite! + if not args.get('verbose', True): + sys.stdout.write(self.name + ' ') + sys.stdout.flush() + for perm in self.perms: + if not perm.shouldtest(**args): + continue + + try: + result = perm.test(**args) + except TestFailure as failure: + perm.result = failure + if not args.get('verbose', True): + sys.stdout.write(FAIL) + sys.stdout.flush() + if not args.get('keep_going', False): + if not args.get('verbose', True): + sys.stdout.write('\n') + raise + else: + perm.result = PASS + if not args.get('verbose', True): + sys.stdout.write(PASS) + sys.stdout.flush() + + if not args.get('verbose', True): + sys.stdout.write('\n') + +def main(**args): + # figure out explicit defines + defines = {} + for define in args['D']: + k, v, *_ = define.split('=', 2) + [''] + defines[k] = v + + # and what class of TestCase to run + classes = [] + if args.get('normal', False): + classes.append(TestCase) + if args.get('reentrant', False): + classes.append(ReentrantTestCase) + if args.get('valgrind', False): + classes.append(ValgrindTestCase) + if not classes: + classes = [TestCase] + + suites = [] + for testpath in args['testpaths']: + # optionally specified test case/perm + testpath, *filter = testpath.split('#') + filter = [int(f) for f in filter] + + # figure out the suite's toml file + if os.path.isdir(testpath): + testpath = testpath + '/test_*.toml' + elif os.path.isfile(testpath): + testpath = testpath + elif testpath.endswith('.toml'): + testpath = TESTDIR + '/' + testpath else: - generate(sys.stdin) + testpath = TESTDIR + '/' + testpath + '.toml' + + # find tests + for path in glob.glob(testpath): + suites.append(TestSuite(path, classes, defines, filter, **args)) + + # sort for reproducability + suites = sorted(suites) + + # generate permutations + for suite in suites: + suite.permute(**args) + + # build tests in parallel + print('====== building ======') + makefiles = [] + targets = [] + for suite in suites: + makefile, target = suite.build(**args) + makefiles.append(makefile) + targets.append(target) + + cmd = (['make', '-f', 'Makefile'] + + list(it.chain.from_iterable(['-f', m] for m in makefiles)) + + [target for target in targets]) + mpty, spty = pty.openpty() + if args.get('verbose', False): + print(' '.join(shlex.quote(c) for c in cmd)) + proc = sp.Popen(cmd, stdout=spty, stderr=spty) + os.close(spty) + mpty = os.fdopen(mpty, 'r', 1) + stdout = [] + while True: + try: + line = mpty.readline() + except OSError as e: + if e.errno == errno.EIO: + break + raise + stdout.append(line) + if args.get('verbose', False): + sys.stdout.write(line) + # intercept warnings + m = re.match( + '^{0}([^:]+):(\d+):(?:\d+:)?{0}{1}:{0}(.*)$' + .format('(?:\033\[[\d;]*.| )*', 'warning'), + line) + if m and not args.get('verbose', False): + try: + with open(m.group(1)) as f: + lineno = int(m.group(2)) + line = next(it.islice(f, lineno-1, None)).strip('\n') + sys.stdout.write( + "\033[01m{path}:{lineno}:\033[01;35mwarning:\033[m " + "{message}\n{line}\n\n".format( + path=m.group(1), line=line, lineno=lineno, + message=m.group(3))) + except: + pass + proc.wait() + + if proc.returncode != 0: + if not args.get('verbose', False): + for line in stdout: + sys.stdout.write(line) + sys.exit(-3) + + print('built %d test suites, %d test cases, %d permutations' % ( + len(suites), + sum(len(suite.cases) for suite in suites), + sum(len(suite.perms) for suite in suites))) + + filtered = 0 + for suite in suites: + for perm in suite.perms: + filtered += perm.shouldtest(**args) + if filtered != sum(len(suite.perms) for suite in suites): + print('filtered down to %d permutations' % filtered) + + # only requested to build? + if args.get('build', False): + return 0 + + print('====== testing ======') + try: + for suite in suites: + suite.test(**args) + except TestFailure: + pass - compile() + print('====== results ======') + passed = 0 + failed = 0 + for suite in suites: + for perm in suite.perms: + if not hasattr(perm, 'result'): + continue - if test == '-s': - sys.exit(1) + if perm.result == PASS: + passed += 1 + else: + sys.stdout.write( + "\033[01m{path}:{lineno}:\033[01;31mfailure:\033[m " + "{perm} failed with {returncode}\n".format( + perm=perm, path=perm.suite.path, lineno=perm.lineno, + returncode=perm.result.returncode or 0)) + if perm.result.stdout: + if perm.result.assert_: + stdout = perm.result.stdout[:-1] + else: + stdout = perm.result.stdout + for line in stdout[-5:]: + sys.stdout.write(line) + if perm.result.assert_: + sys.stdout.write( + "\033[01m{path}:{lineno}:\033[01;31massert:\033[m " + "{message}\n{line}\n".format( + **perm.result.assert_)) + sys.stdout.write('\n') + failed += 1 - execute() + if args.get('gdb', False): + failure = None + for suite in suites: + for perm in suite.perms: + if getattr(perm, 'result', PASS) != PASS: + failure = perm.result + if failure is not None: + print('======= gdb ======') + # drop into gdb + failure.case.test(failure=failure, **args) + sys.exit(0) - except subprocess.CalledProcessError: - # Python stack trace is counterproductive, just exit - sys.exit(2) - except KeyboardInterrupt: - # Python stack trace is counterproductive, just exit - sys.exit(3) + print('tests passed: %d' % passed) + print('tests failed: %d' % failed) + return 1 if failed > 0 else 0 if __name__ == "__main__": - main(*sys.argv[1:]) + import argparse + parser = argparse.ArgumentParser( + description="Run parameterized tests in various configurations.") + parser.add_argument('testpaths', nargs='*', default=[TESTDIR], + help="Description of test(s) to run. By default, this is all tests \ + found in the \"{0}\" directory. Here, you can specify a different \ + directory of tests, a specific file, a suite by name, and even a \ + specific test case by adding brackets. For example \ + \"test_dirs[0]\" or \"{0}/test_dirs.toml[0]\".".format(TESTDIR)) + parser.add_argument('-D', action='append', default=[], + help="Overriding parameter definitions.") + parser.add_argument('-v', '--verbose', action='store_true', + help="Output everything that is happening.") + parser.add_argument('-k', '--keep-going', action='store_true', + help="Run all tests instead of stopping on first error. Useful for CI.") + parser.add_argument('-p', '--persist', choices=['erase', 'noerase'], + nargs='?', const='erase', + help="Store disk image in a file.") + parser.add_argument('-b', '--build', action='store_true', + help="Only build the tests, do not execute.") + parser.add_argument('-g', '--gdb', choices=['init', 'main', 'assert'], + nargs='?', const='assert', + help="Drop into gdb on test failure.") + parser.add_argument('--no-internal', action='store_true', + help="Don't run tests that require internal knowledge.") + parser.add_argument('-n', '--normal', action='store_true', + help="Run tests normally.") + parser.add_argument('-r', '--reentrant', action='store_true', + help="Run reentrant tests with simulated power-loss.") + parser.add_argument('-V', '--valgrind', action='store_true', + help="Run non-leaky tests under valgrind to check for memory leaks.") + parser.add_argument('-e', '--exec', default=[], type=lambda e: e.split(' '), + help="Run tests with another executable prefixed on the command line.") + parser.add_argument('-d', '--disk', + help="Specify a file to use for persistent/reentrant tests.") + sys.exit(main(**vars(parser.parse_args()))) diff --git a/tests/test_alloc.sh b/tests/test_alloc.toml old mode 100755 new mode 100644 similarity index 51% rename from tests/test_alloc.sh rename to tests/test_alloc.toml index 06698508..fa92da51 --- a/tests/test_alloc.sh +++ b/tests/test_alloc.toml @@ -1,127 +1,201 @@ -#!/bin/bash -set -euE -export TEST_FILE=$0 -trap 'export TEST_LINE=$LINENO' DEBUG - -echo "=== Allocator tests ===" -rm -rf blocks -scripts/test.py << TEST - lfs_format(&lfs, &cfg) => 0; -TEST - -SIZE=15000 +# allocator tests +# note for these to work there are a number constraints on the device geometry +if = 'LFS_BLOCK_CYCLES == -1' -lfs_mkdir() { -scripts/test.py << TEST - lfs_mount(&lfs, &cfg) => 0; - lfs_mkdir(&lfs, "$1") => 0; - lfs_unmount(&lfs) => 0; -TEST -} +[[case]] # parallel allocation test +define.FILES = 3 +define.SIZE = '(((LFS_BLOCK_SIZE-8)*(LFS_BLOCK_COUNT-6)) / FILES)' +code = ''' + const char *names[FILES] = {"bacon", "eggs", "pancakes"}; + lfs_file_t files[FILES]; -lfs_remove() { -scripts/test.py << TEST + lfs_format(&lfs, &cfg) => 0; lfs_mount(&lfs, &cfg) => 0; - lfs_remove(&lfs, "$1/eggs") => 0; - lfs_remove(&lfs, "$1/bacon") => 0; - lfs_remove(&lfs, "$1/pancakes") => 0; - lfs_remove(&lfs, "$1") => 0; + lfs_mkdir(&lfs, "breakfast") => 0; lfs_unmount(&lfs) => 0; -TEST -} -lfs_alloc_singleproc() { -scripts/test.py << TEST - const char *names[] = {"bacon", "eggs", "pancakes"}; - lfs_file_t files[sizeof(names)/sizeof(names[0])]; lfs_mount(&lfs, &cfg) => 0; - for (unsigned n = 0; n < sizeof(names)/sizeof(names[0]); n++) { - sprintf(path, "$1/%s", names[n]); + for (int n = 0; n < FILES; n++) { + sprintf(path, "breakfast/%s", names[n]); lfs_file_open(&lfs, &files[n], path, LFS_O_WRONLY | LFS_O_CREAT | LFS_O_APPEND) => 0; } - for (unsigned n = 0; n < sizeof(names)/sizeof(names[0]); n++) { - lfs_size_t size = strlen(names[n]); - for (int i = 0; i < $SIZE; i++) { + for (int n = 0; n < FILES; n++) { + size = strlen(names[n]); + for (lfs_size_t i = 0; i < SIZE; i += size) { lfs_file_write(&lfs, &files[n], names[n], size) => size; } } - for (unsigned n = 0; n < sizeof(names)/sizeof(names[0]); n++) { + for (int n = 0; n < FILES; n++) { lfs_file_close(&lfs, &files[n]) => 0; } lfs_unmount(&lfs) => 0; -TEST -} -lfs_alloc_multiproc() { -for name in bacon eggs pancakes -do -scripts/test.py << TEST lfs_mount(&lfs, &cfg) => 0; - lfs_file_open(&lfs, &file, "$1/$name", - LFS_O_WRONLY | LFS_O_CREAT | LFS_O_APPEND) => 0; - lfs_size_t size = strlen("$name"); - memcpy(buffer, "$name", size); - for (int i = 0; i < $SIZE; i++) { - lfs_file_write(&lfs, &file, buffer, size) => size; + for (int n = 0; n < FILES; n++) { + sprintf(path, "breakfast/%s", names[n]); + lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0; + size = strlen(names[n]); + for (lfs_size_t i = 0; i < SIZE; i += size) { + lfs_file_read(&lfs, &file, buffer, size) => size; + assert(memcmp(buffer, names[n], size) == 0); + } + lfs_file_close(&lfs, &file) => 0; } - lfs_file_close(&lfs, &file) => 0; lfs_unmount(&lfs) => 0; -TEST -done -} - -lfs_verify() { -for name in bacon eggs pancakes -do -scripts/test.py << TEST +''' + +[[case]] # serial allocation test +define.FILES = 3 +define.SIZE = '(((LFS_BLOCK_SIZE-8)*(LFS_BLOCK_COUNT-6)) / FILES)' +code = ''' + const char *names[FILES] = {"bacon", "eggs", "pancakes"}; + + lfs_format(&lfs, &cfg) => 0; lfs_mount(&lfs, &cfg) => 0; - lfs_file_open(&lfs, &file, "$1/$name", LFS_O_RDONLY) => 0; - lfs_size_t size = strlen("$name"); - for (int i = 0; i < $SIZE; i++) { - lfs_file_read(&lfs, &file, buffer, size) => size; - memcmp(buffer, "$name", size) => 0; + lfs_mkdir(&lfs, "breakfast") => 0; + lfs_unmount(&lfs) => 0; + + for (int n = 0; n < FILES; n++) { + lfs_mount(&lfs, &cfg) => 0; + sprintf(path, "breakfast/%s", names[n]); + lfs_file_open(&lfs, &file, path, + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_APPEND) => 0; + size = strlen(names[n]); + memcpy(buffer, names[n], size); + for (int i = 0; i < SIZE; i += size) { + lfs_file_write(&lfs, &file, buffer, size) => size; + } + lfs_file_close(&lfs, &file) => 0; + lfs_unmount(&lfs) => 0; + } + + lfs_mount(&lfs, &cfg) => 0; + for (int n = 0; n < FILES; n++) { + sprintf(path, "breakfast/%s", names[n]); + lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0; + size = strlen(names[n]); + for (int i = 0; i < SIZE; i += size) { + lfs_file_read(&lfs, &file, buffer, size) => size; + assert(memcmp(buffer, names[n], size) == 0); + } + lfs_file_close(&lfs, &file) => 0; } - lfs_file_close(&lfs, &file) => 0; lfs_unmount(&lfs) => 0; -TEST -done -} - -echo "--- Single-process allocation test ---" -lfs_mkdir singleproc -lfs_alloc_singleproc singleproc -lfs_verify singleproc - -echo "--- Multi-process allocation test ---" -lfs_mkdir multiproc -lfs_alloc_multiproc multiproc -lfs_verify multiproc -lfs_verify singleproc - -echo "--- Single-process reuse test ---" -lfs_remove singleproc -lfs_mkdir singleprocreuse -lfs_alloc_singleproc singleprocreuse -lfs_verify singleprocreuse -lfs_verify multiproc - -echo "--- Multi-process reuse test ---" -lfs_remove multiproc -lfs_mkdir multiprocreuse -lfs_alloc_singleproc multiprocreuse -lfs_verify multiprocreuse -lfs_verify singleprocreuse - -echo "--- Cleanup ---" -lfs_remove multiprocreuse -lfs_remove singleprocreuse - -echo "--- Exhaustion test ---" -scripts/test.py << TEST +''' + +[[case]] # parallel allocation reuse test +define.FILES = 3 +define.SIZE = '(((LFS_BLOCK_SIZE-8)*(LFS_BLOCK_COUNT-6)) / FILES)' +define.CYCLES = [1, 10] +code = ''' + const char *names[FILES] = {"bacon", "eggs", "pancakes"}; + lfs_file_t files[FILES]; + + lfs_format(&lfs, &cfg) => 0; + + for (int c = 0; c < CYCLES; c++) { + lfs_mount(&lfs, &cfg) => 0; + lfs_mkdir(&lfs, "breakfast") => 0; + lfs_unmount(&lfs) => 0; + + lfs_mount(&lfs, &cfg) => 0; + for (int n = 0; n < FILES; n++) { + sprintf(path, "breakfast/%s", names[n]); + lfs_file_open(&lfs, &files[n], path, + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_APPEND) => 0; + } + for (int n = 0; n < FILES; n++) { + size = strlen(names[n]); + for (int i = 0; i < SIZE; i += size) { + lfs_file_write(&lfs, &files[n], names[n], size) => size; + } + } + for (int n = 0; n < FILES; n++) { + lfs_file_close(&lfs, &files[n]) => 0; + } + lfs_unmount(&lfs) => 0; + + lfs_mount(&lfs, &cfg) => 0; + for (int n = 0; n < FILES; n++) { + sprintf(path, "breakfast/%s", names[n]); + lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0; + size = strlen(names[n]); + for (int i = 0; i < SIZE; i += size) { + lfs_file_read(&lfs, &file, buffer, size) => size; + assert(memcmp(buffer, names[n], size) == 0); + } + lfs_file_close(&lfs, &file) => 0; + } + lfs_unmount(&lfs) => 0; + + lfs_mount(&lfs, &cfg) => 0; + for (int n = 0; n < FILES; n++) { + sprintf(path, "breakfast/%s", names[n]); + lfs_remove(&lfs, path) => 0; + } + lfs_remove(&lfs, "breakfast") => 0; + lfs_unmount(&lfs) => 0; + } +''' + +[[case]] # serial allocation reuse test +define.FILES = 3 +define.SIZE = '(((LFS_BLOCK_SIZE-8)*(LFS_BLOCK_COUNT-6)) / FILES)' +define.CYCLES = [1, 10] +code = ''' + const char *names[FILES] = {"bacon", "eggs", "pancakes"}; + + lfs_format(&lfs, &cfg) => 0; + + for (int c = 0; c < CYCLES; c++) { + lfs_mount(&lfs, &cfg) => 0; + lfs_mkdir(&lfs, "breakfast") => 0; + lfs_unmount(&lfs) => 0; + + for (int n = 0; n < FILES; n++) { + lfs_mount(&lfs, &cfg) => 0; + sprintf(path, "breakfast/%s", names[n]); + lfs_file_open(&lfs, &file, path, + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_APPEND) => 0; + size = strlen(names[n]); + memcpy(buffer, names[n], size); + for (int i = 0; i < SIZE; i += size) { + lfs_file_write(&lfs, &file, buffer, size) => size; + } + lfs_file_close(&lfs, &file) => 0; + lfs_unmount(&lfs) => 0; + } + + lfs_mount(&lfs, &cfg) => 0; + for (int n = 0; n < FILES; n++) { + sprintf(path, "breakfast/%s", names[n]); + lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0; + size = strlen(names[n]); + for (int i = 0; i < SIZE; i += size) { + lfs_file_read(&lfs, &file, buffer, size) => size; + assert(memcmp(buffer, names[n], size) == 0); + } + lfs_file_close(&lfs, &file) => 0; + } + lfs_unmount(&lfs) => 0; + + lfs_mount(&lfs, &cfg) => 0; + for (int n = 0; n < FILES; n++) { + sprintf(path, "breakfast/%s", names[n]); + lfs_remove(&lfs, path) => 0; + } + lfs_remove(&lfs, "breakfast") => 0; + lfs_unmount(&lfs) => 0; + } +''' + +[[case]] # exhaustion test +code = ''' + lfs_format(&lfs, &cfg) => 0; lfs_mount(&lfs, &cfg) => 0; lfs_file_open(&lfs, &file, "exhaustion", LFS_O_WRONLY | LFS_O_CREAT); - lfs_size_t size = strlen("exhaustion"); + size = strlen("exhaustion"); memcpy(buffer, "exhaustion", size); lfs_file_write(&lfs, &file, buffer, size) => size; lfs_file_sync(&lfs, &file) => 0; @@ -141,27 +215,27 @@ scripts/test.py << TEST lfs_file_close(&lfs, &file) => 0; lfs_unmount(&lfs) => 0; -TEST -scripts/test.py << TEST + lfs_mount(&lfs, &cfg) => 0; lfs_file_open(&lfs, &file, "exhaustion", LFS_O_RDONLY); - lfs_size_t size = strlen("exhaustion"); + size = strlen("exhaustion"); lfs_file_size(&lfs, &file) => size; lfs_file_read(&lfs, &file, buffer, size) => size; memcmp(buffer, "exhaustion", size) => 0; lfs_file_close(&lfs, &file) => 0; lfs_unmount(&lfs) => 0; -TEST +''' -echo "--- Exhaustion wraparound test ---" -scripts/test.py << TEST +[[case]] # exhaustion wraparound test +define.SIZE = '(((LFS_BLOCK_SIZE-8)*(LFS_BLOCK_COUNT-4)) / 3)' +code = ''' + lfs_format(&lfs, &cfg) => 0; lfs_mount(&lfs, &cfg) => 0; - lfs_remove(&lfs, "exhaustion") => 0; lfs_file_open(&lfs, &file, "padding", LFS_O_WRONLY | LFS_O_CREAT); - lfs_size_t size = strlen("buffering"); + size = strlen("buffering"); memcpy(buffer, "buffering", size); - for (int i = 0; i < $SIZE; i++) { + for (int i = 0; i < SIZE; i += size) { lfs_file_write(&lfs, &file, buffer, size) => size; } lfs_file_close(&lfs, &file) => 0; @@ -188,30 +262,29 @@ scripts/test.py << TEST lfs_file_close(&lfs, &file) => 0; lfs_unmount(&lfs) => 0; -TEST -scripts/test.py << TEST + lfs_mount(&lfs, &cfg) => 0; lfs_file_open(&lfs, &file, "exhaustion", LFS_O_RDONLY); - lfs_size_t size = strlen("exhaustion"); + size = strlen("exhaustion"); lfs_file_size(&lfs, &file) => size; lfs_file_read(&lfs, &file, buffer, size) => size; memcmp(buffer, "exhaustion", size) => 0; lfs_file_close(&lfs, &file) => 0; lfs_remove(&lfs, "exhaustion") => 0; lfs_unmount(&lfs) => 0; -TEST +''' -echo "--- Dir exhaustion test ---" -scripts/test.py << TEST +[[case]] # dir exhaustion test +code = ''' + lfs_format(&lfs, &cfg) => 0; lfs_mount(&lfs, &cfg) => 0; // find out max file size lfs_mkdir(&lfs, "exhaustiondir") => 0; - lfs_size_t size = strlen("blahblahblahblah"); + size = strlen("blahblahblahblah"); memcpy(buffer, "blahblahblahblah", size); lfs_file_open(&lfs, &file, "exhaustion", LFS_O_WRONLY | LFS_O_CREAT); int count = 0; - int err; while (true) { err = lfs_file_write(&lfs, &file, buffer, size); if (err < 0) { @@ -248,18 +321,102 @@ scripts/test.py << TEST lfs_remove(&lfs, "exhaustion") => 0; lfs_unmount(&lfs) => 0; -TEST +''' + +[[case]] # what if we have a bad block during an allocation scan? +in = "lfs.c" +define.LFS_ERASE_CYCLES = 0xffffffff +define.LFS_BADBLOCK_BEHAVIOR = 'LFS_TESTBD_BADBLOCK_READERROR' +code = ''' + lfs_format(&lfs, &cfg) => 0; + lfs_mount(&lfs, &cfg) => 0; + // first fill to exhaustion to find available space + lfs_file_open(&lfs, &file, "pacman", LFS_O_WRONLY | LFS_O_CREAT) => 0; + strcpy((char*)buffer, "waka"); + size = strlen("waka"); + lfs_size_t filesize = 0; + while (true) { + lfs_ssize_t res = lfs_file_write(&lfs, &file, buffer, size); + assert(res == (lfs_ssize_t)size || res == LFS_ERR_NOSPC); + if (res == LFS_ERR_NOSPC) { + break; + } + filesize += size; + } + lfs_file_close(&lfs, &file) => 0; + // now fill all but a couple of blocks of the filesystem with data + filesize -= 3*LFS_BLOCK_SIZE; + lfs_file_open(&lfs, &file, "pacman", LFS_O_WRONLY | LFS_O_CREAT) => 0; + strcpy((char*)buffer, "waka"); + size = strlen("waka"); + for (lfs_size_t i = 0; i < filesize/size; i++) { + lfs_file_write(&lfs, &file, buffer, size) => size; + } + lfs_file_close(&lfs, &file) => 0; + // also save head of file so we can error during lookahead scan + lfs_block_t fileblock = file.ctz.head; + lfs_unmount(&lfs) => 0; + + // remount to force an alloc scan + lfs_mount(&lfs, &cfg) => 0; + + // but mark the head of our file as a "bad block", this is force our + // scan to bail early + lfs_testbd_setwear(&cfg, fileblock, 0xffffffff) => 0; + lfs_file_open(&lfs, &file, "ghost", LFS_O_WRONLY | LFS_O_CREAT) => 0; + strcpy((char*)buffer, "chomp"); + size = strlen("chomp"); + while (true) { + lfs_ssize_t res = lfs_file_write(&lfs, &file, buffer, size); + assert(res == (lfs_ssize_t)size || res == LFS_ERR_CORRUPT); + if (res == LFS_ERR_CORRUPT) { + break; + } + } + lfs_file_close(&lfs, &file) => 0; + + // now reverse the "bad block" and try to write the file again until we + // run out of space + lfs_testbd_setwear(&cfg, fileblock, 0) => 0; + lfs_file_open(&lfs, &file, "ghost", LFS_O_WRONLY | LFS_O_CREAT) => 0; + strcpy((char*)buffer, "chomp"); + size = strlen("chomp"); + while (true) { + lfs_ssize_t res = lfs_file_write(&lfs, &file, buffer, size); + assert(res == (lfs_ssize_t)size || res == LFS_ERR_NOSPC); + if (res == LFS_ERR_NOSPC) { + break; + } + } + lfs_file_close(&lfs, &file) => 0; + + lfs_unmount(&lfs) => 0; + + // check that the disk isn't hurt + lfs_mount(&lfs, &cfg) => 0; + lfs_file_open(&lfs, &file, "pacman", LFS_O_RDONLY) => 0; + strcpy((char*)buffer, "waka"); + size = strlen("waka"); + for (lfs_size_t i = 0; i < filesize/size; i++) { + uint8_t rbuffer[4]; + lfs_file_read(&lfs, &file, rbuffer, size) => size; + assert(memcmp(rbuffer, buffer, size) == 0); + } + lfs_file_close(&lfs, &file) => 0; + lfs_unmount(&lfs) => 0; +''' -## Below, these tests depend _very_ heavily on the geometry of the -## block device being tested, they should be removed and replaced -## by generalized tests. For now we'll just skip if the geometry -## is customized. -if [[ ! $MAKEFLAGS =~ "LFS_BLOCK_CYCLES" ]] -then +# Below, I don't like these tests. They're fragile and depend _heavily_ +# on the geometry of the block device. But they are valuable. Eventually they +# should be removed and replaced with generalized tests. -echo "--- Chained dir exhaustion test ---" -scripts/test.py << TEST +[[case]] # chained dir exhaustion test +define.LFS_BLOCK_SIZE = 512 +define.LFS_BLOCK_COUNT = 1024 +if = 'LFS_BLOCK_SIZE == 512 && LFS_BLOCK_COUNT == 1024' +code = ''' + lfs_format(&lfs, &cfg) => 0; lfs_mount(&lfs, &cfg) => 0; // find out max file size @@ -268,11 +425,10 @@ scripts/test.py << TEST sprintf(path, "dirwithanexhaustivelylongnameforpadding%d", i); lfs_mkdir(&lfs, path) => 0; } - lfs_size_t size = strlen("blahblahblahblah"); + size = strlen("blahblahblahblah"); memcpy(buffer, "blahblahblahblah", size); lfs_file_open(&lfs, &file, "exhaustion", LFS_O_WRONLY | LFS_O_CREAT); int count = 0; - int err; while (true) { err = lfs_file_write(&lfs, &file, buffer, size); if (err < 0) { @@ -324,13 +480,14 @@ scripts/test.py << TEST lfs_file_close(&lfs, &file) => 0; lfs_unmount(&lfs) => 0; -TEST +''' -echo "--- Split dir test ---" -scripts/test.py << TEST +[[case]] # split dir test +define.LFS_BLOCK_SIZE = 512 +define.LFS_BLOCK_COUNT = 1024 +if = 'LFS_BLOCK_SIZE == 512 && LFS_BLOCK_COUNT == 1024' +code = ''' lfs_format(&lfs, &cfg) => 0; -TEST -scripts/test.py << TEST lfs_mount(&lfs, &cfg) => 0; // create one block hole for half a directory @@ -342,7 +499,7 @@ scripts/test.py << TEST lfs_file_close(&lfs, &file) => 0; lfs_file_open(&lfs, &file, "exhaustion", LFS_O_WRONLY | LFS_O_CREAT); - lfs_size_t size = strlen("blahblahblahblah"); + size = strlen("blahblahblahblah"); memcpy(buffer, "blahblahblahblah", size); for (lfs_size_t i = 0; i < (cfg.block_count-4)*(cfg.block_size-8); @@ -368,18 +525,20 @@ scripts/test.py << TEST lfs_file_close(&lfs, &file) => 0; lfs_unmount(&lfs) => 0; -TEST +''' -echo "--- Outdated lookahead test ---" -scripts/test.py << TEST +[[case]] # outdated lookahead test +define.LFS_BLOCK_SIZE = 512 +define.LFS_BLOCK_COUNT = 1024 +if = 'LFS_BLOCK_SIZE == 512 && LFS_BLOCK_COUNT == 1024' +code = ''' lfs_format(&lfs, &cfg) => 0; - lfs_mount(&lfs, &cfg) => 0; // fill completely with two files lfs_file_open(&lfs, &file, "exhaustion1", LFS_O_WRONLY | LFS_O_CREAT) => 0; - lfs_size_t size = strlen("blahblahblahblah"); + size = strlen("blahblahblahblah"); memcpy(buffer, "blahblahblahblah", size); for (lfs_size_t i = 0; i < ((cfg.block_count-2)/2)*(cfg.block_size-8); @@ -429,18 +588,22 @@ scripts/test.py << TEST lfs_file_write(&lfs, &file, buffer, size) => size; } lfs_file_close(&lfs, &file) => 0; -TEST -echo "--- Outdated lookahead and split dir test ---" -scripts/test.py << TEST - lfs_format(&lfs, &cfg) => 0; + lfs_unmount(&lfs) => 0; +''' +[[case]] # outdated lookahead and split dir test +define.LFS_BLOCK_SIZE = 512 +define.LFS_BLOCK_COUNT = 1024 +if = 'LFS_BLOCK_SIZE == 512 && LFS_BLOCK_COUNT == 1024' +code = ''' + lfs_format(&lfs, &cfg) => 0; lfs_mount(&lfs, &cfg) => 0; // fill completely with two files lfs_file_open(&lfs, &file, "exhaustion1", LFS_O_WRONLY | LFS_O_CREAT) => 0; - lfs_size_t size = strlen("blahblahblahblah"); + size = strlen("blahblahblahblah"); memcpy(buffer, "blahblahblahblah", size); for (lfs_size_t i = 0; i < ((cfg.block_count-2)/2)*(cfg.block_size-8); @@ -487,8 +650,4 @@ scripts/test.py << TEST lfs_file_close(&lfs, &file) => 0; lfs_unmount(&lfs) => 0; -TEST - -fi - -scripts/results.py +''' diff --git a/tests/test_attrs.sh b/tests/test_attrs.toml old mode 100755 new mode 100644 similarity index 87% rename from tests/test_attrs.sh rename to tests/test_attrs.toml index 612cae1e..db8d0c7e --- a/tests/test_attrs.sh +++ b/tests/test_attrs.toml @@ -1,25 +1,13 @@ -#!/bin/bash -set -eu -export TEST_FILE=$0 -trap 'export TEST_LINE=$LINENO' DEBUG - -echo "=== Attr tests ===" -rm -rf blocks -scripts/test.py << TEST +[[case]] # set/get attribute +code = ''' lfs_format(&lfs, &cfg) => 0; - lfs_mount(&lfs, &cfg) => 0; lfs_mkdir(&lfs, "hello") => 0; - lfs_file_open(&lfs, &file, "hello/hello", - LFS_O_WRONLY | LFS_O_CREAT) => 0; - lfs_file_write(&lfs, &file, "hello", strlen("hello")) - => strlen("hello"); + lfs_file_open(&lfs, &file, "hello/hello", LFS_O_WRONLY | LFS_O_CREAT) => 0; + lfs_file_write(&lfs, &file, "hello", strlen("hello")) => strlen("hello"); lfs_file_close(&lfs, &file); lfs_unmount(&lfs) => 0; -TEST -echo "--- Set/get attribute ---" -scripts/test.py << TEST lfs_mount(&lfs, &cfg) => 0; memset(buffer, 0, sizeof(buffer)); lfs_setattr(&lfs, "hello", 'A', "aaaa", 4) => 0; @@ -71,8 +59,7 @@ scripts/test.py << TEST lfs_getattr(&lfs, "hello", 'C', buffer+10, 5) => 5; lfs_unmount(&lfs) => 0; -TEST -scripts/test.py << TEST + lfs_mount(&lfs, &cfg) => 0; memset(buffer, 0, sizeof(buffer)); lfs_getattr(&lfs, "hello", 'A', buffer, 4) => 4; @@ -87,10 +74,18 @@ scripts/test.py << TEST memcmp(buffer, "hello", strlen("hello")) => 0; lfs_file_close(&lfs, &file); lfs_unmount(&lfs) => 0; -TEST +''' + +[[case]] # set/get root attribute +code = ''' + lfs_format(&lfs, &cfg) => 0; + lfs_mount(&lfs, &cfg) => 0; + lfs_mkdir(&lfs, "hello") => 0; + lfs_file_open(&lfs, &file, "hello/hello", LFS_O_WRONLY | LFS_O_CREAT) => 0; + lfs_file_write(&lfs, &file, "hello", strlen("hello")) => strlen("hello"); + lfs_file_close(&lfs, &file); + lfs_unmount(&lfs) => 0; -echo "--- Set/get root attribute ---" -scripts/test.py << TEST lfs_mount(&lfs, &cfg) => 0; memset(buffer, 0, sizeof(buffer)); lfs_setattr(&lfs, "/", 'A', "aaaa", 4) => 0; @@ -141,8 +136,7 @@ scripts/test.py << TEST lfs_getattr(&lfs, "/", 'B', buffer+4, 6) => 9; lfs_getattr(&lfs, "/", 'C', buffer+10, 5) => 5; lfs_unmount(&lfs) => 0; -TEST -scripts/test.py << TEST + lfs_mount(&lfs, &cfg) => 0; memset(buffer, 0, sizeof(buffer)); lfs_getattr(&lfs, "/", 'A', buffer, 4) => 4; @@ -157,10 +151,18 @@ scripts/test.py << TEST memcmp(buffer, "hello", strlen("hello")) => 0; lfs_file_close(&lfs, &file); lfs_unmount(&lfs) => 0; -TEST +''' + +[[case]] # set/get file attribute +code = ''' + lfs_format(&lfs, &cfg) => 0; + lfs_mount(&lfs, &cfg) => 0; + lfs_mkdir(&lfs, "hello") => 0; + lfs_file_open(&lfs, &file, "hello/hello", LFS_O_WRONLY | LFS_O_CREAT) => 0; + lfs_file_write(&lfs, &file, "hello", strlen("hello")) => strlen("hello"); + lfs_file_close(&lfs, &file); + lfs_unmount(&lfs) => 0; -echo "--- Set/get file attribute ---" -scripts/test.py << TEST lfs_mount(&lfs, &cfg) => 0; memset(buffer, 0, sizeof(buffer)); struct lfs_attr attrs1[] = { @@ -235,18 +237,17 @@ scripts/test.py << TEST lfs_file_close(&lfs, &file) => 0; lfs_unmount(&lfs) => 0; -TEST -scripts/test.py << TEST + lfs_mount(&lfs, &cfg) => 0; memset(buffer, 0, sizeof(buffer)); - struct lfs_attr attrs2[] = { + struct lfs_attr attrs3[] = { {'A', buffer, 4}, {'B', buffer+4, 9}, {'C', buffer+13, 5}, }; - struct lfs_file_config cfg2 = {.attrs=attrs2, .attr_count=3}; + struct lfs_file_config cfg3 = {.attrs=attrs3, .attr_count=3}; - lfs_file_opencfg(&lfs, &file, "hello/hello", LFS_O_RDONLY, &cfg2) => 0; + lfs_file_opencfg(&lfs, &file, "hello/hello", LFS_O_RDONLY, &cfg3) => 0; lfs_file_close(&lfs, &file) => 0; memcmp(buffer, "aaaa", 4) => 0; memcmp(buffer+4, "fffffffff", 9) => 0; @@ -257,11 +258,22 @@ scripts/test.py << TEST memcmp(buffer, "hello", strlen("hello")) => 0; lfs_file_close(&lfs, &file); lfs_unmount(&lfs) => 0; -TEST +''' -echo "--- Deferred file attributes ---" -scripts/test.py << TEST +[[case]] # deferred file attributes +code = ''' + lfs_format(&lfs, &cfg) => 0; lfs_mount(&lfs, &cfg) => 0; + lfs_mkdir(&lfs, "hello") => 0; + lfs_file_open(&lfs, &file, "hello/hello", LFS_O_WRONLY | LFS_O_CREAT) => 0; + lfs_file_write(&lfs, &file, "hello", strlen("hello")) => strlen("hello"); + lfs_file_close(&lfs, &file); + lfs_unmount(&lfs) => 0; + + lfs_mount(&lfs, &cfg) => 0; + lfs_setattr(&lfs, "hello/hello", 'B', "fffffffff", 9) => 0; + lfs_setattr(&lfs, "hello/hello", 'C', "ccccc", 5) => 0; + memset(buffer, 0, sizeof(buffer)); struct lfs_attr attrs1[] = { {'B', "gggg", 4}, @@ -289,6 +301,4 @@ scripts/test.py << TEST lfs_file_close(&lfs, &file) => 0; lfs_unmount(&lfs) => 0; -TEST - -scripts/results.py +''' diff --git a/tests/test_badblocks.toml b/tests/test_badblocks.toml new file mode 100644 index 00000000..06967a67 --- /dev/null +++ b/tests/test_badblocks.toml @@ -0,0 +1,241 @@ +# bad blocks with block cycles should be tested in test_relocations +if = 'LFS_BLOCK_CYCLES == -1' + +[[case]] # single bad blocks +define.LFS_BLOCK_COUNT = 256 # small bd so test runs faster +define.LFS_ERASE_CYCLES = 0xffffffff +define.LFS_ERASE_VALUE = [0x00, 0xff, -1] +define.LFS_BADBLOCK_BEHAVIOR = [ + 'LFS_TESTBD_BADBLOCK_PROGERROR', + 'LFS_TESTBD_BADBLOCK_ERASEERROR', + 'LFS_TESTBD_BADBLOCK_READERROR', + 'LFS_TESTBD_BADBLOCK_PROGNOOP', + 'LFS_TESTBD_BADBLOCK_ERASENOOP', +] +define.NAMEMULT = 64 +define.FILEMULT = 1 +code = ''' + for (lfs_block_t badblock = 2; badblock < LFS_BLOCK_COUNT; badblock++) { + lfs_testbd_setwear(&cfg, badblock-1, 0) => 0; + lfs_testbd_setwear(&cfg, badblock, 0xffffffff) => 0; + + lfs_format(&lfs, &cfg) => 0; + + lfs_mount(&lfs, &cfg) => 0; + for (int i = 1; i < 10; i++) { + for (int j = 0; j < NAMEMULT; j++) { + buffer[j] = '0'+i; + } + buffer[NAMEMULT] = '\0'; + lfs_mkdir(&lfs, (char*)buffer) => 0; + + buffer[NAMEMULT] = '/'; + for (int j = 0; j < NAMEMULT; j++) { + buffer[j+NAMEMULT+1] = '0'+i; + } + buffer[2*NAMEMULT+1] = '\0'; + lfs_file_open(&lfs, &file, (char*)buffer, + LFS_O_WRONLY | LFS_O_CREAT) => 0; + + size = NAMEMULT; + for (int j = 0; j < i*FILEMULT; j++) { + lfs_file_write(&lfs, &file, buffer, size) => size; + } + + lfs_file_close(&lfs, &file) => 0; + } + lfs_unmount(&lfs) => 0; + + lfs_mount(&lfs, &cfg) => 0; + for (int i = 1; i < 10; i++) { + for (int j = 0; j < NAMEMULT; j++) { + buffer[j] = '0'+i; + } + buffer[NAMEMULT] = '\0'; + lfs_stat(&lfs, (char*)buffer, &info) => 0; + info.type => LFS_TYPE_DIR; + + buffer[NAMEMULT] = '/'; + for (int j = 0; j < NAMEMULT; j++) { + buffer[j+NAMEMULT+1] = '0'+i; + } + buffer[2*NAMEMULT+1] = '\0'; + lfs_file_open(&lfs, &file, (char*)buffer, LFS_O_RDONLY) => 0; + + size = NAMEMULT; + for (int j = 0; j < i*FILEMULT; j++) { + uint8_t rbuffer[1024]; + lfs_file_read(&lfs, &file, rbuffer, size) => size; + memcmp(buffer, rbuffer, size) => 0; + } + + lfs_file_close(&lfs, &file) => 0; + } + lfs_unmount(&lfs) => 0; + } +''' + +[[case]] # region corruption (causes cascading failures) +define.LFS_BLOCK_COUNT = 256 # small bd so test runs faster +define.LFS_ERASE_CYCLES = 0xffffffff +define.LFS_ERASE_VALUE = [0x00, 0xff, -1] +define.LFS_BADBLOCK_BEHAVIOR = [ + 'LFS_TESTBD_BADBLOCK_PROGERROR', + 'LFS_TESTBD_BADBLOCK_ERASEERROR', + 'LFS_TESTBD_BADBLOCK_READERROR', + 'LFS_TESTBD_BADBLOCK_PROGNOOP', + 'LFS_TESTBD_BADBLOCK_ERASENOOP', +] +define.NAMEMULT = 64 +define.FILEMULT = 1 +code = ''' + for (lfs_block_t i = 0; i < (LFS_BLOCK_COUNT-2)/2; i++) { + lfs_testbd_setwear(&cfg, i+2, 0xffffffff) => 0; + } + + lfs_format(&lfs, &cfg) => 0; + + lfs_mount(&lfs, &cfg) => 0; + for (int i = 1; i < 10; i++) { + for (int j = 0; j < NAMEMULT; j++) { + buffer[j] = '0'+i; + } + buffer[NAMEMULT] = '\0'; + lfs_mkdir(&lfs, (char*)buffer) => 0; + + buffer[NAMEMULT] = '/'; + for (int j = 0; j < NAMEMULT; j++) { + buffer[j+NAMEMULT+1] = '0'+i; + } + buffer[2*NAMEMULT+1] = '\0'; + lfs_file_open(&lfs, &file, (char*)buffer, + LFS_O_WRONLY | LFS_O_CREAT) => 0; + + size = NAMEMULT; + for (int j = 0; j < i*FILEMULT; j++) { + lfs_file_write(&lfs, &file, buffer, size) => size; + } + + lfs_file_close(&lfs, &file) => 0; + } + lfs_unmount(&lfs) => 0; + + lfs_mount(&lfs, &cfg) => 0; + for (int i = 1; i < 10; i++) { + for (int j = 0; j < NAMEMULT; j++) { + buffer[j] = '0'+i; + } + buffer[NAMEMULT] = '\0'; + lfs_stat(&lfs, (char*)buffer, &info) => 0; + info.type => LFS_TYPE_DIR; + + buffer[NAMEMULT] = '/'; + for (int j = 0; j < NAMEMULT; j++) { + buffer[j+NAMEMULT+1] = '0'+i; + } + buffer[2*NAMEMULT+1] = '\0'; + lfs_file_open(&lfs, &file, (char*)buffer, LFS_O_RDONLY) => 0; + + size = NAMEMULT; + for (int j = 0; j < i*FILEMULT; j++) { + uint8_t rbuffer[1024]; + lfs_file_read(&lfs, &file, rbuffer, size) => size; + memcmp(buffer, rbuffer, size) => 0; + } + + lfs_file_close(&lfs, &file) => 0; + } + lfs_unmount(&lfs) => 0; +''' + +[[case]] # alternating corruption (causes cascading failures) +define.LFS_BLOCK_COUNT = 256 # small bd so test runs faster +define.LFS_ERASE_CYCLES = 0xffffffff +define.LFS_ERASE_VALUE = [0x00, 0xff, -1] +define.LFS_BADBLOCK_BEHAVIOR = [ + 'LFS_TESTBD_BADBLOCK_PROGERROR', + 'LFS_TESTBD_BADBLOCK_ERASEERROR', + 'LFS_TESTBD_BADBLOCK_READERROR', + 'LFS_TESTBD_BADBLOCK_PROGNOOP', + 'LFS_TESTBD_BADBLOCK_ERASENOOP', +] +define.NAMEMULT = 64 +define.FILEMULT = 1 +code = ''' + for (lfs_block_t i = 0; i < (LFS_BLOCK_COUNT-2)/2; i++) { + lfs_testbd_setwear(&cfg, (2*i) + 2, 0xffffffff) => 0; + } + + lfs_format(&lfs, &cfg) => 0; + + lfs_mount(&lfs, &cfg) => 0; + for (int i = 1; i < 10; i++) { + for (int j = 0; j < NAMEMULT; j++) { + buffer[j] = '0'+i; + } + buffer[NAMEMULT] = '\0'; + lfs_mkdir(&lfs, (char*)buffer) => 0; + + buffer[NAMEMULT] = '/'; + for (int j = 0; j < NAMEMULT; j++) { + buffer[j+NAMEMULT+1] = '0'+i; + } + buffer[2*NAMEMULT+1] = '\0'; + lfs_file_open(&lfs, &file, (char*)buffer, + LFS_O_WRONLY | LFS_O_CREAT) => 0; + + size = NAMEMULT; + for (int j = 0; j < i*FILEMULT; j++) { + lfs_file_write(&lfs, &file, buffer, size) => size; + } + + lfs_file_close(&lfs, &file) => 0; + } + lfs_unmount(&lfs) => 0; + + lfs_mount(&lfs, &cfg) => 0; + for (int i = 1; i < 10; i++) { + for (int j = 0; j < NAMEMULT; j++) { + buffer[j] = '0'+i; + } + buffer[NAMEMULT] = '\0'; + lfs_stat(&lfs, (char*)buffer, &info) => 0; + info.type => LFS_TYPE_DIR; + + buffer[NAMEMULT] = '/'; + for (int j = 0; j < NAMEMULT; j++) { + buffer[j+NAMEMULT+1] = '0'+i; + } + buffer[2*NAMEMULT+1] = '\0'; + lfs_file_open(&lfs, &file, (char*)buffer, LFS_O_RDONLY) => 0; + + size = NAMEMULT; + for (int j = 0; j < i*FILEMULT; j++) { + uint8_t rbuffer[1024]; + lfs_file_read(&lfs, &file, rbuffer, size) => size; + memcmp(buffer, rbuffer, size) => 0; + } + + lfs_file_close(&lfs, &file) => 0; + } + lfs_unmount(&lfs) => 0; +''' + +# other corner cases +[[case]] # bad superblocks (corrupt 1 or 0) +define.LFS_ERASE_CYCLES = 0xffffffff +define.LFS_ERASE_VALUE = [0x00, 0xff, -1] +define.LFS_BADBLOCK_BEHAVIOR = [ + 'LFS_TESTBD_BADBLOCK_PROGERROR', + 'LFS_TESTBD_BADBLOCK_ERASEERROR', + 'LFS_TESTBD_BADBLOCK_READERROR', + 'LFS_TESTBD_BADBLOCK_PROGNOOP', + 'LFS_TESTBD_BADBLOCK_ERASENOOP', +] +code = ''' + lfs_testbd_setwear(&cfg, 0, 0xffffffff) => 0; + lfs_testbd_setwear(&cfg, 1, 0xffffffff) => 0; + + lfs_format(&lfs, &cfg) => LFS_ERR_NOSPC; + lfs_mount(&lfs, &cfg) => LFS_ERR_CORRUPT; +''' diff --git a/tests/test_corrupt.sh b/tests/test_corrupt.sh deleted file mode 100755 index 30015224..00000000 --- a/tests/test_corrupt.sh +++ /dev/null @@ -1,120 +0,0 @@ -#!/bin/bash -set -eu -export TEST_FILE=$0 -trap 'export TEST_LINE=$LINENO' DEBUG - -echo "=== Corrupt tests ===" - -NAMEMULT=64 -FILEMULT=1 - -lfs_mktree() { -scripts/test.py ${1:-} << TEST - lfs_format(&lfs, &cfg) => 0; - - lfs_mount(&lfs, &cfg) => 0; - for (int i = 1; i < 10; i++) { - for (int j = 0; j < $NAMEMULT; j++) { - buffer[j] = '0'+i; - } - buffer[$NAMEMULT] = '\0'; - lfs_mkdir(&lfs, (char*)buffer) => 0; - - buffer[$NAMEMULT] = '/'; - for (int j = 0; j < $NAMEMULT; j++) { - buffer[j+$NAMEMULT+1] = '0'+i; - } - buffer[2*$NAMEMULT+1] = '\0'; - lfs_file_open(&lfs, &file, (char*)buffer, - LFS_O_WRONLY | LFS_O_CREAT) => 0; - - lfs_size_t size = $NAMEMULT; - for (int j = 0; j < i*$FILEMULT; j++) { - lfs_file_write(&lfs, &file, buffer, size) => size; - } - - lfs_file_close(&lfs, &file) => 0; - } - lfs_unmount(&lfs) => 0; -TEST -} - -lfs_chktree() { -scripts/test.py ${1:-} << TEST - lfs_mount(&lfs, &cfg) => 0; - for (int i = 1; i < 10; i++) { - for (int j = 0; j < $NAMEMULT; j++) { - buffer[j] = '0'+i; - } - buffer[$NAMEMULT] = '\0'; - lfs_stat(&lfs, (char*)buffer, &info) => 0; - info.type => LFS_TYPE_DIR; - - buffer[$NAMEMULT] = '/'; - for (int j = 0; j < $NAMEMULT; j++) { - buffer[j+$NAMEMULT+1] = '0'+i; - } - buffer[2*$NAMEMULT+1] = '\0'; - lfs_file_open(&lfs, &file, (char*)buffer, LFS_O_RDONLY) => 0; - - lfs_size_t size = $NAMEMULT; - for (int j = 0; j < i*$FILEMULT; j++) { - uint8_t rbuffer[1024]; - lfs_file_read(&lfs, &file, rbuffer, size) => size; - memcmp(buffer, rbuffer, size) => 0; - } - - lfs_file_close(&lfs, &file) => 0; - } - lfs_unmount(&lfs) => 0; -TEST -} - -echo "--- Sanity check ---" -rm -rf blocks -lfs_mktree -lfs_chktree -BLOCKS="$(ls blocks | grep -vw '[01]')" - -echo "--- Block corruption ---" -for b in $BLOCKS -do - rm -rf blocks - mkdir blocks - ln -s /dev/zero blocks/$b - lfs_mktree - lfs_chktree -done - -echo "--- Block persistance ---" -for b in $BLOCKS -do - rm -rf blocks - mkdir blocks - lfs_mktree - chmod a-w blocks/$b || true - lfs_mktree - lfs_chktree -done - -echo "--- Big region corruption ---" -rm -rf blocks -mkdir blocks -for i in {2..512} -do - ln -s /dev/zero blocks/$(printf '%x' $i) -done -lfs_mktree -lfs_chktree - -echo "--- Alternating corruption ---" -rm -rf blocks -mkdir blocks -for i in {2..1024..2} -do - ln -s /dev/zero blocks/$(printf '%x' $i) -done -lfs_mktree -lfs_chktree - -scripts/results.py diff --git a/tests/test_dirs.sh b/tests/test_dirs.sh deleted file mode 100755 index 0125bfd4..00000000 --- a/tests/test_dirs.sh +++ /dev/null @@ -1,489 +0,0 @@ -#!/bin/bash -set -eu -export TEST_FILE=$0 -trap 'export TEST_LINE=$LINENO' DEBUG - -echo "=== Directory tests ===" - -LARGESIZE=128 - -rm -rf blocks -scripts/test.py << TEST - lfs_format(&lfs, &cfg) => 0; -TEST - -echo "--- Root directory ---" -scripts/test.py << TEST - lfs_mount(&lfs, &cfg) => 0; - lfs_dir_open(&lfs, &dir, "/") => 0; - lfs_dir_close(&lfs, &dir) => 0; - lfs_unmount(&lfs) => 0; -TEST - -echo "--- Directory creation ---" -scripts/test.py << TEST - lfs_mount(&lfs, &cfg) => 0; - lfs_mkdir(&lfs, "potato") => 0; - lfs_unmount(&lfs) => 0; -TEST - -echo "--- File creation ---" -scripts/test.py << TEST - lfs_mount(&lfs, &cfg) => 0; - lfs_file_open(&lfs, &file, "burito", LFS_O_CREAT | LFS_O_WRONLY) => 0; - lfs_file_close(&lfs, &file) => 0; - lfs_unmount(&lfs) => 0; -TEST - -echo "--- Directory iteration ---" -scripts/test.py << TEST - lfs_mount(&lfs, &cfg) => 0; - lfs_dir_open(&lfs, &dir, "/") => 0; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, ".") => 0; - info.type => LFS_TYPE_DIR; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, "..") => 0; - info.type => LFS_TYPE_DIR; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, "burito") => 0; - info.type => LFS_TYPE_REG; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, "potato") => 0; - info.type => LFS_TYPE_DIR; - lfs_dir_read(&lfs, &dir, &info) => 0; - lfs_dir_close(&lfs, &dir) => 0; - lfs_unmount(&lfs) => 0; -TEST - -echo "--- Directory failures ---" -scripts/test.py << TEST - lfs_mount(&lfs, &cfg) => 0; - lfs_mkdir(&lfs, "potato") => LFS_ERR_EXIST; - lfs_dir_open(&lfs, &dir, "tomato") => LFS_ERR_NOENT; - lfs_dir_open(&lfs, &dir, "burito") => LFS_ERR_NOTDIR; - lfs_file_open(&lfs, &file, "tomato", LFS_O_RDONLY) => LFS_ERR_NOENT; - lfs_file_open(&lfs, &file, "potato", LFS_O_RDONLY) => LFS_ERR_ISDIR; - lfs_unmount(&lfs) => 0; -TEST - -echo "--- Nested directories ---" -scripts/test.py << TEST - lfs_mount(&lfs, &cfg) => 0; - lfs_mkdir(&lfs, "potato/baked") => 0; - lfs_mkdir(&lfs, "potato/sweet") => 0; - lfs_mkdir(&lfs, "potato/fried") => 0; - lfs_unmount(&lfs) => 0; -TEST -scripts/test.py << TEST - lfs_mount(&lfs, &cfg) => 0; - lfs_dir_open(&lfs, &dir, "potato") => 0; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, ".") => 0; - info.type => LFS_TYPE_DIR; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, "..") => 0; - info.type => LFS_TYPE_DIR; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, "baked") => 0; - info.type => LFS_TYPE_DIR; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, "fried") => 0; - info.type => LFS_TYPE_DIR; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, "sweet") => 0; - info.type => LFS_TYPE_DIR; - lfs_dir_read(&lfs, &dir, &info) => 0; - lfs_dir_close(&lfs, &dir) => 0; - lfs_unmount(&lfs) => 0; -TEST - -echo "--- Multi-block directory ---" -scripts/test.py << TEST - lfs_mount(&lfs, &cfg) => 0; - lfs_mkdir(&lfs, "cactus") => 0; - for (int i = 0; i < $LARGESIZE; i++) { - sprintf(path, "cactus/test%03d", i); - lfs_mkdir(&lfs, path) => 0; - } - lfs_unmount(&lfs) => 0; -TEST -scripts/test.py << TEST - lfs_mount(&lfs, &cfg) => 0; - lfs_dir_open(&lfs, &dir, "cactus") => 0; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, ".") => 0; - info.type => LFS_TYPE_DIR; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, "..") => 0; - info.type => LFS_TYPE_DIR; - for (int i = 0; i < $LARGESIZE; i++) { - sprintf(path, "test%03d", i); - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, path) => 0; - info.type => LFS_TYPE_DIR; - } - lfs_dir_read(&lfs, &dir, &info) => 0; - lfs_unmount(&lfs) => 0; -TEST - -echo "--- Directory remove ---" -scripts/test.py << TEST - lfs_mount(&lfs, &cfg) => 0; - lfs_remove(&lfs, "potato") => LFS_ERR_NOTEMPTY; - lfs_remove(&lfs, "potato/sweet") => 0; - lfs_remove(&lfs, "potato/baked") => 0; - lfs_remove(&lfs, "potato/fried") => 0; - - lfs_dir_open(&lfs, &dir, "potato") => 0; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, ".") => 0; - info.type => LFS_TYPE_DIR; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, "..") => 0; - info.type => LFS_TYPE_DIR; - lfs_dir_read(&lfs, &dir, &info) => 0; - lfs_dir_close(&lfs, &dir) => 0; - - lfs_remove(&lfs, "potato") => 0; - - lfs_dir_open(&lfs, &dir, "/") => 0; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, ".") => 0; - info.type => LFS_TYPE_DIR; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, "..") => 0; - info.type => LFS_TYPE_DIR; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, "burito") => 0; - info.type => LFS_TYPE_REG; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, "cactus") => 0; - info.type => LFS_TYPE_DIR; - lfs_dir_read(&lfs, &dir, &info) => 0; - lfs_dir_close(&lfs, &dir) => 0; - lfs_unmount(&lfs) => 0; -TEST -scripts/test.py << TEST - lfs_mount(&lfs, &cfg) => 0; - lfs_dir_open(&lfs, &dir, "/") => 0; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, ".") => 0; - info.type => LFS_TYPE_DIR; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, "..") => 0; - info.type => LFS_TYPE_DIR; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, "burito") => 0; - info.type => LFS_TYPE_REG; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, "cactus") => 0; - info.type => LFS_TYPE_DIR; - lfs_dir_read(&lfs, &dir, &info) => 0; - lfs_dir_close(&lfs, &dir) => 0; - lfs_unmount(&lfs) => 0; -TEST - -echo "--- Directory rename ---" -scripts/test.py << TEST - lfs_mount(&lfs, &cfg) => 0; - lfs_mkdir(&lfs, "coldpotato") => 0; - lfs_mkdir(&lfs, "coldpotato/baked") => 0; - lfs_mkdir(&lfs, "coldpotato/sweet") => 0; - lfs_mkdir(&lfs, "coldpotato/fried") => 0; - lfs_unmount(&lfs) => 0; -TEST -scripts/test.py << TEST - lfs_mount(&lfs, &cfg) => 0; - lfs_rename(&lfs, "coldpotato", "hotpotato") => 0; - lfs_unmount(&lfs) => 0; -TEST -scripts/test.py << TEST - lfs_mount(&lfs, &cfg) => 0; - lfs_dir_open(&lfs, &dir, "hotpotato") => 0; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, ".") => 0; - info.type => LFS_TYPE_DIR; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, "..") => 0; - info.type => LFS_TYPE_DIR; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, "baked") => 0; - info.type => LFS_TYPE_DIR; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, "fried") => 0; - info.type => LFS_TYPE_DIR; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, "sweet") => 0; - info.type => LFS_TYPE_DIR; - lfs_dir_read(&lfs, &dir, &info) => 0; - lfs_dir_close(&lfs, &dir) => 0; - lfs_unmount(&lfs) => 0; -TEST -scripts/test.py << TEST - lfs_mount(&lfs, &cfg) => 0; - lfs_mkdir(&lfs, "warmpotato") => 0; - lfs_mkdir(&lfs, "warmpotato/mushy") => 0; - lfs_rename(&lfs, "hotpotato", "warmpotato") => LFS_ERR_NOTEMPTY; - - lfs_remove(&lfs, "warmpotato/mushy") => 0; - lfs_rename(&lfs, "hotpotato", "warmpotato") => 0; - - lfs_unmount(&lfs) => 0; -TEST -scripts/test.py << TEST - lfs_mount(&lfs, &cfg) => 0; - lfs_dir_open(&lfs, &dir, "warmpotato") => 0; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, ".") => 0; - info.type => LFS_TYPE_DIR; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, "..") => 0; - info.type => LFS_TYPE_DIR; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, "baked") => 0; - info.type => LFS_TYPE_DIR; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, "fried") => 0; - info.type => LFS_TYPE_DIR; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, "sweet") => 0; - info.type => LFS_TYPE_DIR; - lfs_dir_read(&lfs, &dir, &info) => 0; - lfs_dir_close(&lfs, &dir) => 0; - lfs_unmount(&lfs) => 0; -TEST -scripts/test.py << TEST - lfs_mount(&lfs, &cfg) => 0; - lfs_mkdir(&lfs, "coldpotato") => 0; - lfs_rename(&lfs, "warmpotato/baked", "coldpotato/baked") => 0; - lfs_rename(&lfs, "warmpotato/sweet", "coldpotato/sweet") => 0; - lfs_rename(&lfs, "warmpotato/fried", "coldpotato/fried") => 0; - lfs_remove(&lfs, "coldpotato") => LFS_ERR_NOTEMPTY; - lfs_remove(&lfs, "warmpotato") => 0; - lfs_unmount(&lfs) => 0; -TEST -scripts/test.py << TEST - lfs_mount(&lfs, &cfg) => 0; - lfs_dir_open(&lfs, &dir, "coldpotato") => 0; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, ".") => 0; - info.type => LFS_TYPE_DIR; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, "..") => 0; - info.type => LFS_TYPE_DIR; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, "baked") => 0; - info.type => LFS_TYPE_DIR; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, "fried") => 0; - info.type => LFS_TYPE_DIR; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, "sweet") => 0; - info.type => LFS_TYPE_DIR; - lfs_dir_read(&lfs, &dir, &info) => 0; - lfs_dir_close(&lfs, &dir) => 0; - lfs_unmount(&lfs) => 0; -TEST - -echo "--- Recursive remove ---" -scripts/test.py << TEST - lfs_mount(&lfs, &cfg) => 0; - lfs_remove(&lfs, "coldpotato") => LFS_ERR_NOTEMPTY; - - lfs_dir_open(&lfs, &dir, "coldpotato") => 0; - lfs_dir_read(&lfs, &dir, &info) => 1; - lfs_dir_read(&lfs, &dir, &info) => 1; - - while (true) { - int err = lfs_dir_read(&lfs, &dir, &info); - err >= 0 => 1; - if (err == 0) { - break; - } - - strcpy(path, "coldpotato/"); - strcat(path, info.name); - lfs_remove(&lfs, path) => 0; - } - - lfs_remove(&lfs, "coldpotato") => 0; -TEST -scripts/test.py << TEST - lfs_mount(&lfs, &cfg) => 0; - lfs_dir_open(&lfs, &dir, "/") => 0; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, ".") => 0; - info.type => LFS_TYPE_DIR; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, "..") => 0; - info.type => LFS_TYPE_DIR; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, "burito") => 0; - info.type => LFS_TYPE_REG; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, "cactus") => 0; - info.type => LFS_TYPE_DIR; - lfs_dir_read(&lfs, &dir, &info) => 0; - lfs_dir_close(&lfs, &dir) => 0; - lfs_unmount(&lfs) => 0; -TEST - -echo "--- Multi-block rename ---" -scripts/test.py << TEST - lfs_mount(&lfs, &cfg) => 0; - for (int i = 0; i < $LARGESIZE; i++) { - char oldpath[1024]; - char newpath[1024]; - sprintf(oldpath, "cactus/test%03d", i); - sprintf(newpath, "cactus/tedd%03d", i); - lfs_rename(&lfs, oldpath, newpath) => 0; - } - lfs_unmount(&lfs) => 0; -TEST -scripts/test.py << TEST - lfs_mount(&lfs, &cfg) => 0; - lfs_dir_open(&lfs, &dir, "cactus") => 0; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, ".") => 0; - info.type => LFS_TYPE_DIR; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, "..") => 0; - info.type => LFS_TYPE_DIR; - for (int i = 0; i < $LARGESIZE; i++) { - sprintf(path, "tedd%03d", i); - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, path) => 0; - info.type => LFS_TYPE_DIR; - } - lfs_dir_read(&lfs, &dir, &info) => 0; - lfs_unmount(&lfs) => 0; -TEST - -echo "--- Multi-block remove ---" -scripts/test.py << TEST - lfs_mount(&lfs, &cfg) => 0; - lfs_remove(&lfs, "cactus") => LFS_ERR_NOTEMPTY; - - for (int i = 0; i < $LARGESIZE; i++) { - sprintf(path, "cactus/tedd%03d", i); - lfs_remove(&lfs, path) => 0; - } - - lfs_remove(&lfs, "cactus") => 0; - lfs_unmount(&lfs) => 0; -TEST -scripts/test.py << TEST - lfs_mount(&lfs, &cfg) => 0; - lfs_dir_open(&lfs, &dir, "/") => 0; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, ".") => 0; - info.type => LFS_TYPE_DIR; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, "..") => 0; - info.type => LFS_TYPE_DIR; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, "burito") => 0; - info.type => LFS_TYPE_REG; - lfs_dir_read(&lfs, &dir, &info) => 0; - lfs_dir_close(&lfs, &dir) => 0; - lfs_unmount(&lfs) => 0; -TEST - -echo "--- Multi-block directory with files ---" -scripts/test.py << TEST - lfs_mount(&lfs, &cfg) => 0; - lfs_mkdir(&lfs, "prickly-pear") => 0; - for (int i = 0; i < $LARGESIZE; i++) { - sprintf(path, "prickly-pear/test%03d", i); - lfs_file_open(&lfs, &file, path, LFS_O_WRONLY | LFS_O_CREAT) => 0; - lfs_size_t size = 6; - memcpy(buffer, "Hello", size); - lfs_file_write(&lfs, &file, buffer, size) => size; - lfs_file_close(&lfs, &file) => 0; - } - lfs_unmount(&lfs) => 0; -TEST -scripts/test.py << TEST - lfs_mount(&lfs, &cfg) => 0; - lfs_dir_open(&lfs, &dir, "prickly-pear") => 0; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, ".") => 0; - info.type => LFS_TYPE_DIR; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, "..") => 0; - info.type => LFS_TYPE_DIR; - for (int i = 0; i < $LARGESIZE; i++) { - sprintf(path, "test%03d", i); - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, path) => 0; - info.type => LFS_TYPE_REG; - info.size => 6; - } - lfs_dir_read(&lfs, &dir, &info) => 0; - lfs_unmount(&lfs) => 0; -TEST - -echo "--- Multi-block rename with files ---" -scripts/test.py << TEST - lfs_mount(&lfs, &cfg) => 0; - for (int i = 0; i < $LARGESIZE; i++) { - char oldpath[1024]; - char newpath[1024]; - sprintf(oldpath, "prickly-pear/test%03d", i); - sprintf(newpath, "prickly-pear/tedd%03d", i); - lfs_rename(&lfs, oldpath, newpath) => 0; - } - lfs_unmount(&lfs) => 0; -TEST -scripts/test.py << TEST - lfs_mount(&lfs, &cfg) => 0; - lfs_dir_open(&lfs, &dir, "prickly-pear") => 0; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, ".") => 0; - info.type => LFS_TYPE_DIR; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, "..") => 0; - info.type => LFS_TYPE_DIR; - for (int i = 0; i < $LARGESIZE; i++) { - sprintf(path, "tedd%03d", i); - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, path) => 0; - info.type => LFS_TYPE_REG; - info.size => 6; - } - lfs_dir_read(&lfs, &dir, &info) => 0; - lfs_unmount(&lfs) => 0; -TEST - -echo "--- Multi-block remove with files ---" -scripts/test.py << TEST - lfs_mount(&lfs, &cfg) => 0; - lfs_remove(&lfs, "prickly-pear") => LFS_ERR_NOTEMPTY; - - for (int i = 0; i < $LARGESIZE; i++) { - sprintf(path, "prickly-pear/tedd%03d", i); - lfs_remove(&lfs, path) => 0; - } - - lfs_remove(&lfs, "prickly-pear") => 0; - lfs_unmount(&lfs) => 0; -TEST -scripts/test.py << TEST - lfs_mount(&lfs, &cfg) => 0; - lfs_dir_open(&lfs, &dir, "/") => 0; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, ".") => 0; - info.type => LFS_TYPE_DIR; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, "..") => 0; - info.type => LFS_TYPE_DIR; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, "burito") => 0; - info.type => LFS_TYPE_REG; - lfs_dir_read(&lfs, &dir, &info) => 0; - lfs_dir_close(&lfs, &dir) => 0; - lfs_unmount(&lfs) => 0; -TEST - -scripts/results.py diff --git a/tests/test_dirs.toml b/tests/test_dirs.toml new file mode 100644 index 00000000..270f4f8e --- /dev/null +++ b/tests/test_dirs.toml @@ -0,0 +1,838 @@ +[[case]] # root +code = ''' + lfs_format(&lfs, &cfg) => 0; + lfs_mount(&lfs, &cfg) => 0; + lfs_dir_open(&lfs, &dir, "/") => 0; + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(info.type == LFS_TYPE_DIR); + assert(strcmp(info.name, ".") == 0); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(info.type == LFS_TYPE_DIR); + assert(strcmp(info.name, "..") == 0); + lfs_dir_read(&lfs, &dir, &info) => 0; + lfs_dir_close(&lfs, &dir) => 0; + lfs_unmount(&lfs) => 0; +''' + +[[case]] # many directory creation +define.N = 'range(0, 100, 3)' +code = ''' + lfs_format(&lfs, &cfg) => 0; + + lfs_mount(&lfs, &cfg) => 0; + for (int i = 0; i < N; i++) { + sprintf(path, "dir%03d", i); + lfs_mkdir(&lfs, path) => 0; + } + lfs_unmount(&lfs) => 0; + + lfs_mount(&lfs, &cfg) => 0; + lfs_dir_open(&lfs, &dir, "/") => 0; + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(info.type == LFS_TYPE_DIR); + assert(strcmp(info.name, ".") == 0); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(info.type == LFS_TYPE_DIR); + assert(strcmp(info.name, "..") == 0); + for (int i = 0; i < N; i++) { + sprintf(path, "dir%03d", i); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(info.type == LFS_TYPE_DIR); + assert(strcmp(info.name, path) == 0); + } + lfs_dir_read(&lfs, &dir, &info) => 0; + lfs_dir_close(&lfs, &dir) => 0; + lfs_unmount(&lfs) => 0; +''' + +[[case]] # many directory removal +define.N = 'range(3, 100, 11)' +code = ''' + lfs_format(&lfs, &cfg) => 0; + + lfs_mount(&lfs, &cfg) => 0; + for (int i = 0; i < N; i++) { + sprintf(path, "removeme%03d", i); + lfs_mkdir(&lfs, path) => 0; + } + lfs_unmount(&lfs) => 0; + + lfs_mount(&lfs, &cfg) => 0; + lfs_dir_open(&lfs, &dir, "/") => 0; + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(info.type == LFS_TYPE_DIR); + assert(strcmp(info.name, ".") == 0); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(info.type == LFS_TYPE_DIR); + assert(strcmp(info.name, "..") == 0); + for (int i = 0; i < N; i++) { + sprintf(path, "removeme%03d", i); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(info.type == LFS_TYPE_DIR); + assert(strcmp(info.name, path) == 0); + } + lfs_dir_read(&lfs, &dir, &info) => 0; + lfs_dir_close(&lfs, &dir) => 0; + lfs_unmount(&lfs); + + lfs_mount(&lfs, &cfg) => 0; + for (int i = 0; i < N; i++) { + sprintf(path, "removeme%03d", i); + lfs_remove(&lfs, path) => 0; + } + lfs_unmount(&lfs); + + lfs_mount(&lfs, &cfg) => 0; + lfs_dir_open(&lfs, &dir, "/") => 0; + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(info.type == LFS_TYPE_DIR); + assert(strcmp(info.name, ".") == 0); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(info.type == LFS_TYPE_DIR); + assert(strcmp(info.name, "..") == 0); + lfs_dir_read(&lfs, &dir, &info) => 0; + lfs_dir_close(&lfs, &dir) => 0; + lfs_unmount(&lfs) => 0; +''' + +[[case]] # many directory rename +define.N = 'range(3, 100, 11)' +code = ''' + lfs_format(&lfs, &cfg) => 0; + + lfs_mount(&lfs, &cfg) => 0; + for (int i = 0; i < N; i++) { + sprintf(path, "test%03d", i); + lfs_mkdir(&lfs, path) => 0; + } + lfs_unmount(&lfs) => 0; + + lfs_mount(&lfs, &cfg) => 0; + lfs_dir_open(&lfs, &dir, "/") => 0; + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(info.type == LFS_TYPE_DIR); + assert(strcmp(info.name, ".") == 0); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(info.type == LFS_TYPE_DIR); + assert(strcmp(info.name, "..") == 0); + for (int i = 0; i < N; i++) { + sprintf(path, "test%03d", i); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(info.type == LFS_TYPE_DIR); + assert(strcmp(info.name, path) == 0); + } + lfs_dir_read(&lfs, &dir, &info) => 0; + lfs_dir_close(&lfs, &dir) => 0; + lfs_unmount(&lfs); + + lfs_mount(&lfs, &cfg) => 0; + for (int i = 0; i < N; i++) { + char oldpath[128]; + char newpath[128]; + sprintf(oldpath, "test%03d", i); + sprintf(newpath, "tedd%03d", i); + lfs_rename(&lfs, oldpath, newpath) => 0; + } + lfs_unmount(&lfs); + + lfs_mount(&lfs, &cfg) => 0; + lfs_dir_open(&lfs, &dir, "/") => 0; + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(info.type == LFS_TYPE_DIR); + assert(strcmp(info.name, ".") == 0); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(info.type == LFS_TYPE_DIR); + assert(strcmp(info.name, "..") == 0); + for (int i = 0; i < N; i++) { + sprintf(path, "tedd%03d", i); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(info.type == LFS_TYPE_DIR); + assert(strcmp(info.name, path) == 0); + } + lfs_dir_read(&lfs, &dir, &info) => 0; + lfs_dir_close(&lfs, &dir) => 0; + lfs_unmount(&lfs); +''' + +[[case]] # reentrant many directory creation/rename/removal +define.N = [5, 11] +reentrant = true +code = ''' + err = lfs_mount(&lfs, &cfg); + if (err) { + lfs_format(&lfs, &cfg) => 0; + lfs_mount(&lfs, &cfg) => 0; + } + + for (int i = 0; i < N; i++) { + sprintf(path, "hi%03d", i); + err = lfs_mkdir(&lfs, path); + assert(err == 0 || err == LFS_ERR_EXIST); + } + + for (int i = 0; i < N; i++) { + sprintf(path, "hello%03d", i); + err = lfs_remove(&lfs, path); + assert(err == 0 || err == LFS_ERR_NOENT); + } + + lfs_dir_open(&lfs, &dir, "/") => 0; + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(info.type == LFS_TYPE_DIR); + assert(strcmp(info.name, ".") == 0); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(info.type == LFS_TYPE_DIR); + assert(strcmp(info.name, "..") == 0); + for (int i = 0; i < N; i++) { + sprintf(path, "hi%03d", i); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(info.type == LFS_TYPE_DIR); + assert(strcmp(info.name, path) == 0); + } + lfs_dir_read(&lfs, &dir, &info) => 0; + lfs_dir_close(&lfs, &dir) => 0; + + for (int i = 0; i < N; i++) { + char oldpath[128]; + char newpath[128]; + sprintf(oldpath, "hi%03d", i); + sprintf(newpath, "hello%03d", i); + // YES this can overwrite an existing newpath + lfs_rename(&lfs, oldpath, newpath) => 0; + } + + lfs_dir_open(&lfs, &dir, "/") => 0; + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(info.type == LFS_TYPE_DIR); + assert(strcmp(info.name, ".") == 0); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(info.type == LFS_TYPE_DIR); + assert(strcmp(info.name, "..") == 0); + for (int i = 0; i < N; i++) { + sprintf(path, "hello%03d", i); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(info.type == LFS_TYPE_DIR); + assert(strcmp(info.name, path) == 0); + } + lfs_dir_read(&lfs, &dir, &info) => 0; + lfs_dir_close(&lfs, &dir) => 0; + + for (int i = 0; i < N; i++) { + sprintf(path, "hello%03d", i); + lfs_remove(&lfs, path) => 0; + } + + lfs_dir_open(&lfs, &dir, "/") => 0; + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(info.type == LFS_TYPE_DIR); + assert(strcmp(info.name, ".") == 0); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(info.type == LFS_TYPE_DIR); + assert(strcmp(info.name, "..") == 0); + lfs_dir_read(&lfs, &dir, &info) => 0; + lfs_dir_close(&lfs, &dir) => 0; + lfs_unmount(&lfs) => 0; +''' + +[[case]] # file creation +define.N = 'range(3, 100, 11)' +code = ''' + lfs_format(&lfs, &cfg) => 0; + + lfs_mount(&lfs, &cfg) => 0; + for (int i = 0; i < N; i++) { + sprintf(path, "file%03d", i); + lfs_file_open(&lfs, &file, path, + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + lfs_file_close(&lfs, &file) => 0; + } + lfs_unmount(&lfs) => 0; + + lfs_mount(&lfs, &cfg) => 0; + lfs_dir_open(&lfs, &dir, "/") => 0; + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(info.type == LFS_TYPE_DIR); + assert(strcmp(info.name, ".") == 0); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(info.type == LFS_TYPE_DIR); + assert(strcmp(info.name, "..") == 0); + for (int i = 0; i < N; i++) { + sprintf(path, "file%03d", i); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(info.type == LFS_TYPE_REG); + assert(strcmp(info.name, path) == 0); + } + lfs_dir_read(&lfs, &dir, &info) => 0; + lfs_dir_close(&lfs, &dir) => 0; + lfs_unmount(&lfs); +''' + +[[case]] # file removal +define.N = 'range(0, 100, 3)' +code = ''' + lfs_format(&lfs, &cfg) => 0; + + lfs_mount(&lfs, &cfg) => 0; + for (int i = 0; i < N; i++) { + sprintf(path, "removeme%03d", i); + lfs_file_open(&lfs, &file, path, + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + lfs_file_close(&lfs, &file) => 0; + } + lfs_unmount(&lfs) => 0; + + lfs_mount(&lfs, &cfg) => 0; + lfs_dir_open(&lfs, &dir, "/") => 0; + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(info.type == LFS_TYPE_DIR); + assert(strcmp(info.name, ".") == 0); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(info.type == LFS_TYPE_DIR); + assert(strcmp(info.name, "..") == 0); + for (int i = 0; i < N; i++) { + sprintf(path, "removeme%03d", i); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(info.type == LFS_TYPE_REG); + assert(strcmp(info.name, path) == 0); + } + lfs_dir_read(&lfs, &dir, &info) => 0; + lfs_dir_close(&lfs, &dir) => 0; + lfs_unmount(&lfs); + + lfs_mount(&lfs, &cfg) => 0; + for (int i = 0; i < N; i++) { + sprintf(path, "removeme%03d", i); + lfs_remove(&lfs, path) => 0; + } + lfs_unmount(&lfs); + + lfs_mount(&lfs, &cfg) => 0; + lfs_dir_open(&lfs, &dir, "/") => 0; + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(info.type == LFS_TYPE_DIR); + assert(strcmp(info.name, ".") == 0); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(info.type == LFS_TYPE_DIR); + assert(strcmp(info.name, "..") == 0); + lfs_dir_read(&lfs, &dir, &info) => 0; + lfs_dir_close(&lfs, &dir) => 0; + lfs_unmount(&lfs) => 0; +''' + +[[case]] # file rename +define.N = 'range(0, 100, 3)' +code = ''' + lfs_format(&lfs, &cfg) => 0; + + lfs_mount(&lfs, &cfg) => 0; + for (int i = 0; i < N; i++) { + sprintf(path, "test%03d", i); + lfs_file_open(&lfs, &file, path, + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + lfs_file_close(&lfs, &file) => 0; + } + lfs_unmount(&lfs) => 0; + + lfs_mount(&lfs, &cfg) => 0; + lfs_dir_open(&lfs, &dir, "/") => 0; + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(info.type == LFS_TYPE_DIR); + assert(strcmp(info.name, ".") == 0); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(info.type == LFS_TYPE_DIR); + assert(strcmp(info.name, "..") == 0); + for (int i = 0; i < N; i++) { + sprintf(path, "test%03d", i); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(info.type == LFS_TYPE_REG); + assert(strcmp(info.name, path) == 0); + } + lfs_dir_read(&lfs, &dir, &info) => 0; + lfs_dir_close(&lfs, &dir) => 0; + lfs_unmount(&lfs); + + lfs_mount(&lfs, &cfg) => 0; + for (int i = 0; i < N; i++) { + char oldpath[128]; + char newpath[128]; + sprintf(oldpath, "test%03d", i); + sprintf(newpath, "tedd%03d", i); + lfs_rename(&lfs, oldpath, newpath) => 0; + } + lfs_unmount(&lfs); + + lfs_mount(&lfs, &cfg) => 0; + lfs_dir_open(&lfs, &dir, "/") => 0; + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(info.type == LFS_TYPE_DIR); + assert(strcmp(info.name, ".") == 0); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(info.type == LFS_TYPE_DIR); + assert(strcmp(info.name, "..") == 0); + for (int i = 0; i < N; i++) { + sprintf(path, "tedd%03d", i); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(info.type == LFS_TYPE_REG); + assert(strcmp(info.name, path) == 0); + } + lfs_dir_read(&lfs, &dir, &info) => 0; + lfs_dir_close(&lfs, &dir) => 0; + lfs_unmount(&lfs); +''' + +[[case]] # reentrant file creation/rename/removal +define.N = [5, 25] +reentrant = true +code = ''' + err = lfs_mount(&lfs, &cfg); + if (err) { + lfs_format(&lfs, &cfg) => 0; + lfs_mount(&lfs, &cfg) => 0; + } + + for (int i = 0; i < N; i++) { + sprintf(path, "hi%03d", i); + lfs_file_open(&lfs, &file, path, LFS_O_CREAT | LFS_O_WRONLY) => 0; + lfs_file_close(&lfs, &file) => 0; + } + + for (int i = 0; i < N; i++) { + sprintf(path, "hello%03d", i); + err = lfs_remove(&lfs, path); + assert(err == 0 || err == LFS_ERR_NOENT); + } + + lfs_dir_open(&lfs, &dir, "/") => 0; + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(info.type == LFS_TYPE_DIR); + assert(strcmp(info.name, ".") == 0); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(info.type == LFS_TYPE_DIR); + assert(strcmp(info.name, "..") == 0); + for (int i = 0; i < N; i++) { + sprintf(path, "hi%03d", i); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(info.type == LFS_TYPE_REG); + assert(strcmp(info.name, path) == 0); + } + lfs_dir_read(&lfs, &dir, &info) => 0; + lfs_dir_close(&lfs, &dir) => 0; + + for (int i = 0; i < N; i++) { + char oldpath[128]; + char newpath[128]; + sprintf(oldpath, "hi%03d", i); + sprintf(newpath, "hello%03d", i); + // YES this can overwrite an existing newpath + lfs_rename(&lfs, oldpath, newpath) => 0; + } + + lfs_dir_open(&lfs, &dir, "/") => 0; + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(info.type == LFS_TYPE_DIR); + assert(strcmp(info.name, ".") == 0); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(info.type == LFS_TYPE_DIR); + assert(strcmp(info.name, "..") == 0); + for (int i = 0; i < N; i++) { + sprintf(path, "hello%03d", i); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(info.type == LFS_TYPE_REG); + assert(strcmp(info.name, path) == 0); + } + lfs_dir_read(&lfs, &dir, &info) => 0; + lfs_dir_close(&lfs, &dir) => 0; + + for (int i = 0; i < N; i++) { + sprintf(path, "hello%03d", i); + lfs_remove(&lfs, path) => 0; + } + + lfs_dir_open(&lfs, &dir, "/") => 0; + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(info.type == LFS_TYPE_DIR); + assert(strcmp(info.name, ".") == 0); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(info.type == LFS_TYPE_DIR); + assert(strcmp(info.name, "..") == 0); + lfs_dir_read(&lfs, &dir, &info) => 0; + lfs_dir_close(&lfs, &dir) => 0; + lfs_unmount(&lfs) => 0; +''' + +[[case]] # nested directories +code = ''' + lfs_format(&lfs, &cfg) => 0; + lfs_mount(&lfs, &cfg) => 0; + lfs_mkdir(&lfs, "potato") => 0; + lfs_file_open(&lfs, &file, "burito", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + lfs_file_close(&lfs, &file) => 0; + lfs_unmount(&lfs) => 0; + + lfs_mount(&lfs, &cfg) => 0; + lfs_mkdir(&lfs, "potato/baked") => 0; + lfs_mkdir(&lfs, "potato/sweet") => 0; + lfs_mkdir(&lfs, "potato/fried") => 0; + lfs_unmount(&lfs) => 0; + + lfs_mount(&lfs, &cfg) => 0; + lfs_dir_open(&lfs, &dir, "potato") => 0; + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, ".") == 0); + info.type => LFS_TYPE_DIR; + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "..") == 0); + info.type => LFS_TYPE_DIR; + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "baked") == 0); + info.type => LFS_TYPE_DIR; + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "fried") == 0); + info.type => LFS_TYPE_DIR; + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "sweet") == 0); + info.type => LFS_TYPE_DIR; + lfs_dir_read(&lfs, &dir, &info) => 0; + lfs_dir_close(&lfs, &dir) => 0; + lfs_unmount(&lfs) => 0; + + // try removing? + lfs_mount(&lfs, &cfg) => 0; + lfs_remove(&lfs, "potato") => LFS_ERR_NOTEMPTY; + lfs_unmount(&lfs) => 0; + + // try renaming? + lfs_mount(&lfs, &cfg) => 0; + lfs_rename(&lfs, "potato", "coldpotato") => 0; + lfs_unmount(&lfs) => 0; + + lfs_mount(&lfs, &cfg) => 0; + lfs_rename(&lfs, "coldpotato", "warmpotato") => 0; + lfs_rename(&lfs, "warmpotato", "hotpotato") => 0; + lfs_unmount(&lfs) => 0; + + lfs_mount(&lfs, &cfg) => 0; + lfs_remove(&lfs, "potato") => LFS_ERR_NOENT; + lfs_remove(&lfs, "coldpotato") => LFS_ERR_NOENT; + lfs_remove(&lfs, "warmpotato") => LFS_ERR_NOENT; + lfs_remove(&lfs, "hotpotato") => LFS_ERR_NOTEMPTY; + lfs_unmount(&lfs) => 0; + + // try cross-directory renaming + lfs_mount(&lfs, &cfg) => 0; + lfs_mkdir(&lfs, "coldpotato") => 0; + lfs_rename(&lfs, "hotpotato/baked", "coldpotato/baked") => 0; + lfs_rename(&lfs, "coldpotato", "hotpotato") => LFS_ERR_NOTEMPTY; + lfs_remove(&lfs, "coldpotato") => LFS_ERR_NOTEMPTY; + lfs_remove(&lfs, "hotpotato") => LFS_ERR_NOTEMPTY; + lfs_rename(&lfs, "hotpotato/fried", "coldpotato/fried") => 0; + lfs_rename(&lfs, "coldpotato", "hotpotato") => LFS_ERR_NOTEMPTY; + lfs_remove(&lfs, "coldpotato") => LFS_ERR_NOTEMPTY; + lfs_remove(&lfs, "hotpotato") => LFS_ERR_NOTEMPTY; + lfs_rename(&lfs, "hotpotato/sweet", "coldpotato/sweet") => 0; + lfs_rename(&lfs, "coldpotato", "hotpotato") => 0; + lfs_remove(&lfs, "coldpotato") => LFS_ERR_NOENT; + lfs_remove(&lfs, "hotpotato") => LFS_ERR_NOTEMPTY; + lfs_unmount(&lfs) => 0; + + lfs_mount(&lfs, &cfg) => 0; + lfs_dir_open(&lfs, &dir, "hotpotato") => 0; + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, ".") == 0); + info.type => LFS_TYPE_DIR; + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "..") == 0); + info.type => LFS_TYPE_DIR; + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "baked") == 0); + info.type => LFS_TYPE_DIR; + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "fried") == 0); + info.type => LFS_TYPE_DIR; + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "sweet") == 0); + info.type => LFS_TYPE_DIR; + lfs_dir_read(&lfs, &dir, &info) => 0; + lfs_dir_close(&lfs, &dir) => 0; + lfs_unmount(&lfs) => 0; + + // final remove + lfs_mount(&lfs, &cfg) => 0; + lfs_remove(&lfs, "hotpotato") => LFS_ERR_NOTEMPTY; + lfs_remove(&lfs, "hotpotato/baked") => 0; + lfs_remove(&lfs, "hotpotato") => LFS_ERR_NOTEMPTY; + lfs_remove(&lfs, "hotpotato/fried") => 0; + lfs_remove(&lfs, "hotpotato") => LFS_ERR_NOTEMPTY; + lfs_remove(&lfs, "hotpotato/sweet") => 0; + lfs_remove(&lfs, "hotpotato") => 0; + lfs_unmount(&lfs) => 0; + + lfs_mount(&lfs, &cfg) => 0; + lfs_dir_open(&lfs, &dir, "/") => 0; + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, ".") == 0); + info.type => LFS_TYPE_DIR; + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "..") == 0); + info.type => LFS_TYPE_DIR; + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "burito") == 0); + info.type => LFS_TYPE_REG; + lfs_dir_read(&lfs, &dir, &info) => 0; + lfs_dir_close(&lfs, &dir) => 0; + lfs_unmount(&lfs) => 0; +''' + +[[case]] # recursive remove +define.N = [10, 100] +code = ''' + lfs_format(&lfs, &cfg) => 0; + lfs_mount(&lfs, &cfg) => 0; + lfs_mkdir(&lfs, "prickly-pear") => 0; + for (int i = 0; i < N; i++) { + sprintf(path, "prickly-pear/cactus%03d", i); + lfs_mkdir(&lfs, path) => 0; + } + lfs_dir_open(&lfs, &dir, "prickly-pear") => 0; + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(info.type == LFS_TYPE_DIR); + assert(strcmp(info.name, ".") == 0); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(info.type == LFS_TYPE_DIR); + assert(strcmp(info.name, "..") == 0); + for (int i = 0; i < N; i++) { + sprintf(path, "cactus%03d", i); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(info.type == LFS_TYPE_DIR); + assert(strcmp(info.name, path) == 0); + } + lfs_dir_read(&lfs, &dir, &info) => 0; + lfs_dir_close(&lfs, &dir) => 0; + lfs_unmount(&lfs); + + lfs_mount(&lfs, &cfg) => 0; + lfs_remove(&lfs, "prickly-pear") => LFS_ERR_NOTEMPTY; + + lfs_dir_open(&lfs, &dir, "prickly-pear") => 0; + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(info.type == LFS_TYPE_DIR); + assert(strcmp(info.name, ".") == 0); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(info.type == LFS_TYPE_DIR); + assert(strcmp(info.name, "..") == 0); + for (int i = 0; i < N; i++) { + sprintf(path, "cactus%03d", i); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(info.type == LFS_TYPE_DIR); + assert(strcmp(info.name, path) == 0); + sprintf(path, "prickly-pear/%s", info.name); + lfs_remove(&lfs, path) => 0; + } + lfs_dir_read(&lfs, &dir, &info) => 0; + lfs_dir_close(&lfs, &dir) => 0; + + lfs_remove(&lfs, "prickly-pear") => 0; + lfs_remove(&lfs, "prickly-pear") => LFS_ERR_NOENT; + lfs_unmount(&lfs) => 0; + + lfs_mount(&lfs, &cfg) => 0; + lfs_remove(&lfs, "prickly-pear") => LFS_ERR_NOENT; + lfs_unmount(&lfs) => 0; +''' + +[[case]] # other error cases +code = ''' + lfs_format(&lfs, &cfg) => 0; + lfs_mount(&lfs, &cfg) => 0; + lfs_mkdir(&lfs, "potato") => 0; + lfs_file_open(&lfs, &file, "burito", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + lfs_file_close(&lfs, &file) => 0; + lfs_unmount(&lfs) => 0; + + lfs_mount(&lfs, &cfg) => 0; + + lfs_mkdir(&lfs, "potato") => LFS_ERR_EXIST; + lfs_mkdir(&lfs, "burito") => LFS_ERR_EXIST; + lfs_file_open(&lfs, &file, "burito", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST; + lfs_file_open(&lfs, &file, "potato", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST; + lfs_dir_open(&lfs, &dir, "tomato") => LFS_ERR_NOENT; + lfs_dir_open(&lfs, &dir, "burito") => LFS_ERR_NOTDIR; + lfs_file_open(&lfs, &file, "tomato", LFS_O_RDONLY) => LFS_ERR_NOENT; + lfs_file_open(&lfs, &file, "potato", LFS_O_RDONLY) => LFS_ERR_ISDIR; + lfs_file_open(&lfs, &file, "tomato", LFS_O_WRONLY) => LFS_ERR_NOENT; + lfs_file_open(&lfs, &file, "potato", LFS_O_WRONLY) => LFS_ERR_ISDIR; + lfs_file_open(&lfs, &file, "potato", + LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR; + + lfs_mkdir(&lfs, "/") => LFS_ERR_EXIST; + lfs_file_open(&lfs, &file, "/", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST; + lfs_file_open(&lfs, &file, "/", LFS_O_RDONLY) => LFS_ERR_ISDIR; + lfs_file_open(&lfs, &file, "/", LFS_O_WRONLY) => LFS_ERR_ISDIR; + lfs_file_open(&lfs, &file, "/", + LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR; + + // check that errors did not corrupt directory + lfs_dir_open(&lfs, &dir, "/") => 0; + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(info.type == LFS_TYPE_DIR); + assert(strcmp(info.name, ".") == 0); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(info.type == LFS_TYPE_DIR); + assert(strcmp(info.name, "..") == 0); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(info.type == LFS_TYPE_REG); + assert(strcmp(info.name, "burito") == 0); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(info.type == LFS_TYPE_DIR); + assert(strcmp(info.name, "potato") == 0); + lfs_dir_read(&lfs, &dir, &info) => 0; + lfs_dir_close(&lfs, &dir) => 0; + + lfs_unmount(&lfs) => 0; + + // or on disk + lfs_mount(&lfs, &cfg) => 0; + lfs_dir_open(&lfs, &dir, "/") => 0; + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(info.type == LFS_TYPE_DIR); + assert(strcmp(info.name, ".") == 0); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(info.type == LFS_TYPE_DIR); + assert(strcmp(info.name, "..") == 0); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(info.type == LFS_TYPE_REG); + assert(strcmp(info.name, "burito") == 0); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(info.type == LFS_TYPE_DIR); + assert(strcmp(info.name, "potato") == 0); + lfs_dir_read(&lfs, &dir, &info) => 0; + lfs_dir_close(&lfs, &dir) => 0; + lfs_unmount(&lfs) => 0; +''' + +[[case]] # directory seek +define.COUNT = [4, 128, 132] +code = ''' + lfs_format(&lfs, &cfg) => 0; + lfs_mount(&lfs, &cfg) => 0; + lfs_mkdir(&lfs, "hello") => 0; + for (int i = 0; i < COUNT; i++) { + sprintf(path, "hello/kitty%03d", i); + lfs_mkdir(&lfs, path) => 0; + } + lfs_unmount(&lfs) => 0; + + for (int j = 2; j < COUNT; j++) { + lfs_mount(&lfs, &cfg) => 0; + lfs_dir_open(&lfs, &dir, "hello") => 0; + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, ".") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "..") == 0); + assert(info.type == LFS_TYPE_DIR); + + lfs_soff_t pos; + for (int i = 0; i < j; i++) { + sprintf(path, "kitty%03d", i); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, path) == 0); + assert(info.type == LFS_TYPE_DIR); + pos = lfs_dir_tell(&lfs, &dir); + assert(pos >= 0); + } + + lfs_dir_seek(&lfs, &dir, pos) => 0; + sprintf(path, "kitty%03d", j); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, path) == 0); + assert(info.type == LFS_TYPE_DIR); + + lfs_dir_rewind(&lfs, &dir) => 0; + sprintf(path, "kitty%03d", 0); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, ".") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "..") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, path) == 0); + assert(info.type == LFS_TYPE_DIR); + + lfs_dir_seek(&lfs, &dir, pos) => 0; + sprintf(path, "kitty%03d", j); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, path) == 0); + assert(info.type == LFS_TYPE_DIR); + + lfs_dir_close(&lfs, &dir) => 0; + lfs_unmount(&lfs) => 0; + } +''' + +[[case]] # root seek +define.COUNT = [4, 128, 132] +code = ''' + lfs_format(&lfs, &cfg) => 0; + lfs_mount(&lfs, &cfg) => 0; + for (int i = 0; i < COUNT; i++) { + sprintf(path, "hi%03d", i); + lfs_mkdir(&lfs, path) => 0; + } + lfs_unmount(&lfs) => 0; + + for (int j = 2; j < COUNT; j++) { + lfs_mount(&lfs, &cfg) => 0; + lfs_dir_open(&lfs, &dir, "/") => 0; + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, ".") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "..") == 0); + assert(info.type == LFS_TYPE_DIR); + + lfs_soff_t pos; + for (int i = 0; i < j; i++) { + sprintf(path, "hi%03d", i); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, path) == 0); + assert(info.type == LFS_TYPE_DIR); + pos = lfs_dir_tell(&lfs, &dir); + assert(pos >= 0); + } + + lfs_dir_seek(&lfs, &dir, pos) => 0; + sprintf(path, "hi%03d", j); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, path) == 0); + assert(info.type == LFS_TYPE_DIR); + + lfs_dir_rewind(&lfs, &dir) => 0; + sprintf(path, "hi%03d", 0); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, ".") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "..") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, path) == 0); + assert(info.type == LFS_TYPE_DIR); + + lfs_dir_seek(&lfs, &dir, pos) => 0; + sprintf(path, "hi%03d", j); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, path) == 0); + assert(info.type == LFS_TYPE_DIR); + + lfs_dir_close(&lfs, &dir) => 0; + lfs_unmount(&lfs) => 0; + } +''' + diff --git a/tests/test_entries.sh b/tests/test_entries.sh deleted file mode 100755 index 5075faf5..00000000 --- a/tests/test_entries.sh +++ /dev/null @@ -1,251 +0,0 @@ -#!/bin/bash -set -eu -export TEST_FILE=$0 -trap 'export TEST_LINE=$LINENO' DEBUG - -echo "=== Entry tests ===" - -# Note: These tests are intended for 512 byte inline size at different -# inline sizes they should still pass, but won't be testing anything - -rm -rf blocks -function read_file { -cat << TEST - - size = $2; - lfs_file_open(&lfs, &file, "$1", LFS_O_RDONLY) => 0; - lfs_file_read(&lfs, &file, rbuffer, size) => size; - memcmp(rbuffer, wbuffer, size) => 0; - lfs_file_close(&lfs, &file) => 0; -TEST -} - -function write_file { -cat << TEST - - size = $2; - lfs_file_open(&lfs, &file, "$1", - LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0; - memset(wbuffer, 'c', size); - lfs_file_write(&lfs, &file, wbuffer, size) => size; - lfs_file_close(&lfs, &file) => 0; -TEST -} - -echo "--- Entry grow test ---" -scripts/test.py << TEST - lfs_format(&lfs, &cfg) => 0; - - uint8_t wbuffer[1024]; - uint8_t rbuffer[1024]; - lfs_size_t size; - - lfs_mount(&lfs, &cfg) => 0; - $(write_file "hi0" 20) - $(write_file "hi1" 20) - $(write_file "hi2" 20) - $(write_file "hi3" 20) - - $(read_file "hi1" 20) - $(write_file "hi1" 200) - - $(read_file "hi0" 20) - $(read_file "hi1" 200) - $(read_file "hi2" 20) - $(read_file "hi3" 20) - lfs_unmount(&lfs) => 0; -TEST - -echo "--- Entry shrink test ---" -scripts/test.py << TEST - lfs_format(&lfs, &cfg) => 0; - - uint8_t wbuffer[1024]; - uint8_t rbuffer[1024]; - lfs_size_t size; - - lfs_mount(&lfs, &cfg) => 0; - $(write_file "hi0" 20) - $(write_file "hi1" 200) - $(write_file "hi2" 20) - $(write_file "hi3" 20) - - $(read_file "hi1" 200) - $(write_file "hi1" 20) - - $(read_file "hi0" 20) - $(read_file "hi1" 20) - $(read_file "hi2" 20) - $(read_file "hi3" 20) - lfs_unmount(&lfs) => 0; -TEST - -echo "--- Entry spill test ---" -scripts/test.py << TEST - lfs_format(&lfs, &cfg) => 0; - - uint8_t wbuffer[1024]; - uint8_t rbuffer[1024]; - lfs_size_t size; - - lfs_mount(&lfs, &cfg) => 0; - $(write_file "hi0" 200) - $(write_file "hi1" 200) - $(write_file "hi2" 200) - $(write_file "hi3" 200) - - $(read_file "hi0" 200) - $(read_file "hi1" 200) - $(read_file "hi2" 200) - $(read_file "hi3" 200) - lfs_unmount(&lfs) => 0; -TEST - -echo "--- Entry push spill test ---" -scripts/test.py << TEST - lfs_format(&lfs, &cfg) => 0; - - uint8_t wbuffer[1024]; - uint8_t rbuffer[1024]; - lfs_size_t size; - - lfs_mount(&lfs, &cfg) => 0; - $(write_file "hi0" 200) - $(write_file "hi1" 20) - $(write_file "hi2" 200) - $(write_file "hi3" 200) - - $(read_file "hi1" 20) - $(write_file "hi1" 200) - - $(read_file "hi0" 200) - $(read_file "hi1" 200) - $(read_file "hi2" 200) - $(read_file "hi3" 200) - lfs_unmount(&lfs) => 0; -TEST - -echo "--- Entry push spill two test ---" -scripts/test.py << TEST - lfs_format(&lfs, &cfg) => 0; - - uint8_t wbuffer[1024]; - uint8_t rbuffer[1024]; - lfs_size_t size; - - lfs_mount(&lfs, &cfg) => 0; - $(write_file "hi0" 200) - $(write_file "hi1" 20) - $(write_file "hi2" 200) - $(write_file "hi3" 200) - $(write_file "hi4" 200) - - $(read_file "hi1" 20) - $(write_file "hi1" 200) - - $(read_file "hi0" 200) - $(read_file "hi1" 200) - $(read_file "hi2" 200) - $(read_file "hi3" 200) - $(read_file "hi4" 200) - lfs_unmount(&lfs) => 0; -TEST - -echo "--- Entry drop test ---" -scripts/test.py << TEST - lfs_format(&lfs, &cfg) => 0; - - uint8_t wbuffer[1024]; - uint8_t rbuffer[1024]; - lfs_size_t size; - - lfs_mount(&lfs, &cfg) => 0; - $(write_file "hi0" 200) - $(write_file "hi1" 200) - $(write_file "hi2" 200) - $(write_file "hi3" 200) - - lfs_remove(&lfs, "hi1") => 0; - lfs_stat(&lfs, "hi1", &info) => LFS_ERR_NOENT; - $(read_file "hi0" 200) - $(read_file "hi2" 200) - $(read_file "hi3" 200) - - lfs_remove(&lfs, "hi2") => 0; - lfs_stat(&lfs, "hi2", &info) => LFS_ERR_NOENT; - $(read_file "hi0" 200) - $(read_file "hi3" 200) - - lfs_remove(&lfs, "hi3") => 0; - lfs_stat(&lfs, "hi3", &info) => LFS_ERR_NOENT; - $(read_file "hi0" 200) - - lfs_remove(&lfs, "hi0") => 0; - lfs_stat(&lfs, "hi0", &info) => LFS_ERR_NOENT; - lfs_unmount(&lfs) => 0; -TEST - -echo "--- Create too big ---" -scripts/test.py << TEST - lfs_format(&lfs, &cfg) => 0; - - lfs_mount(&lfs, &cfg) => 0; - memset(path, 'm', 200); - path[200] = '\0'; - - lfs_size_t size = 400; - lfs_file_open(&lfs, &file, path, - LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0; - uint8_t wbuffer[1024]; - memset(wbuffer, 'c', size); - lfs_file_write(&lfs, &file, wbuffer, size) => size; - lfs_file_close(&lfs, &file) => 0; - - size = 400; - lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0; - uint8_t rbuffer[1024]; - lfs_file_read(&lfs, &file, rbuffer, size) => size; - memcmp(rbuffer, wbuffer, size) => 0; - lfs_file_close(&lfs, &file) => 0; - lfs_unmount(&lfs) => 0; -TEST - -echo "--- Resize too big ---" -scripts/test.py << TEST - lfs_format(&lfs, &cfg) => 0; - - lfs_mount(&lfs, &cfg) => 0; - memset(path, 'm', 200); - path[200] = '\0'; - - lfs_size_t size = 40; - lfs_file_open(&lfs, &file, path, - LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0; - uint8_t wbuffer[1024]; - memset(wbuffer, 'c', size); - lfs_file_write(&lfs, &file, wbuffer, size) => size; - lfs_file_close(&lfs, &file) => 0; - - size = 40; - lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0; - uint8_t rbuffer[1024]; - lfs_file_read(&lfs, &file, rbuffer, size) => size; - memcmp(rbuffer, wbuffer, size) => 0; - lfs_file_close(&lfs, &file) => 0; - - size = 400; - lfs_file_open(&lfs, &file, path, - LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0; - memset(wbuffer, 'c', size); - lfs_file_write(&lfs, &file, wbuffer, size) => size; - lfs_file_close(&lfs, &file) => 0; - - size = 400; - lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0; - lfs_file_read(&lfs, &file, rbuffer, size) => size; - memcmp(rbuffer, wbuffer, size) => 0; - lfs_file_close(&lfs, &file) => 0; - lfs_unmount(&lfs) => 0; -TEST - -scripts/results.py diff --git a/tests/test_entries.toml b/tests/test_entries.toml new file mode 100644 index 00000000..81e175f5 --- /dev/null +++ b/tests/test_entries.toml @@ -0,0 +1,611 @@ +# These tests are for some specific corner cases with neighboring inline files. +# Note that these tests are intended for 512 byte inline sizes. They should +# still pass with other inline sizes but wouldn't be testing anything. + +define.LFS_CACHE_SIZE = 512 +if = 'LFS_CACHE_SIZE % LFS_PROG_SIZE == 0 && LFS_CACHE_SIZE == 512' + +[[case]] # entry grow test +code = ''' + uint8_t wbuffer[1024]; + uint8_t rbuffer[1024]; + + lfs_format(&lfs, &cfg) => 0; + lfs_mount(&lfs, &cfg) => 0; + + // write hi0 20 + sprintf(path, "hi0"); size = 20; + lfs_file_open(&lfs, &file, path, + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0; + memset(wbuffer, 'c', size); + lfs_file_write(&lfs, &file, wbuffer, size) => size; + lfs_file_size(&lfs, &file) => size; + lfs_file_close(&lfs, &file) => 0; + // write hi1 20 + sprintf(path, "hi1"); size = 20; + lfs_file_open(&lfs, &file, path, + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0; + memset(wbuffer, 'c', size); + lfs_file_write(&lfs, &file, wbuffer, size) => size; + lfs_file_size(&lfs, &file) => size; + lfs_file_close(&lfs, &file) => 0; + // write hi2 20 + sprintf(path, "hi2"); size = 20; + lfs_file_open(&lfs, &file, path, + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0; + memset(wbuffer, 'c', size); + lfs_file_write(&lfs, &file, wbuffer, size) => size; + lfs_file_size(&lfs, &file) => size; + lfs_file_close(&lfs, &file) => 0; + // write hi3 20 + sprintf(path, "hi3"); size = 20; + lfs_file_open(&lfs, &file, path, + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0; + memset(wbuffer, 'c', size); + lfs_file_write(&lfs, &file, wbuffer, size) => size; + lfs_file_size(&lfs, &file) => size; + lfs_file_close(&lfs, &file) => 0; + + // read hi1 20 + sprintf(path, "hi1"); size = 20; + lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0; + lfs_file_size(&lfs, &file) => size; + lfs_file_read(&lfs, &file, rbuffer, size) => size; + memcmp(rbuffer, wbuffer, size) => 0; + lfs_file_close(&lfs, &file) => 0; + // write hi1 200 + sprintf(path, "hi1"); size = 200; + lfs_file_open(&lfs, &file, path, + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0; + memset(wbuffer, 'c', size); + lfs_file_write(&lfs, &file, wbuffer, size) => size; + lfs_file_size(&lfs, &file) => size; + lfs_file_close(&lfs, &file) => 0; + + // read hi0 20 + sprintf(path, "hi0"); size = 20; + lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0; + lfs_file_size(&lfs, &file) => size; + lfs_file_read(&lfs, &file, rbuffer, size) => size; + memcmp(rbuffer, wbuffer, size) => 0; + lfs_file_close(&lfs, &file) => 0; + // read hi1 200 + sprintf(path, "hi1"); size = 200; + lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0; + lfs_file_size(&lfs, &file) => size; + lfs_file_read(&lfs, &file, rbuffer, size) => size; + memcmp(rbuffer, wbuffer, size) => 0; + lfs_file_close(&lfs, &file) => 0; + // read hi2 20 + sprintf(path, "hi2"); size = 20; + lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0; + lfs_file_size(&lfs, &file) => size; + lfs_file_read(&lfs, &file, rbuffer, size) => size; + memcmp(rbuffer, wbuffer, size) => 0; + lfs_file_close(&lfs, &file) => 0; + // read hi3 20 + sprintf(path, "hi3"); size = 20; + lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0; + lfs_file_size(&lfs, &file) => size; + lfs_file_read(&lfs, &file, rbuffer, size) => size; + memcmp(rbuffer, wbuffer, size) => 0; + lfs_file_close(&lfs, &file) => 0; + + lfs_unmount(&lfs) => 0; +''' + +[[case]] # entry shrink test +code = ''' + uint8_t wbuffer[1024]; + uint8_t rbuffer[1024]; + + lfs_format(&lfs, &cfg) => 0; + lfs_mount(&lfs, &cfg) => 0; + + // write hi0 20 + sprintf(path, "hi0"); size = 20; + lfs_file_open(&lfs, &file, path, + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0; + memset(wbuffer, 'c', size); + lfs_file_write(&lfs, &file, wbuffer, size) => size; + lfs_file_size(&lfs, &file) => size; + lfs_file_close(&lfs, &file) => 0; + // write hi1 200 + sprintf(path, "hi1"); size = 200; + lfs_file_open(&lfs, &file, path, + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0; + memset(wbuffer, 'c', size); + lfs_file_write(&lfs, &file, wbuffer, size) => size; + lfs_file_size(&lfs, &file) => size; + lfs_file_close(&lfs, &file) => 0; + // write hi2 20 + sprintf(path, "hi2"); size = 20; + lfs_file_open(&lfs, &file, path, + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0; + memset(wbuffer, 'c', size); + lfs_file_write(&lfs, &file, wbuffer, size) => size; + lfs_file_size(&lfs, &file) => size; + lfs_file_close(&lfs, &file) => 0; + // write hi3 20 + sprintf(path, "hi3"); size = 20; + lfs_file_open(&lfs, &file, path, + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0; + memset(wbuffer, 'c', size); + lfs_file_write(&lfs, &file, wbuffer, size) => size; + lfs_file_size(&lfs, &file) => size; + lfs_file_close(&lfs, &file) => 0; + + // read hi1 200 + sprintf(path, "hi1"); size = 200; + lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0; + lfs_file_size(&lfs, &file) => size; + lfs_file_read(&lfs, &file, rbuffer, size) => size; + memcmp(rbuffer, wbuffer, size) => 0; + lfs_file_close(&lfs, &file) => 0; + // write hi1 20 + sprintf(path, "hi1"); size = 20; + lfs_file_open(&lfs, &file, path, + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0; + memset(wbuffer, 'c', size); + lfs_file_write(&lfs, &file, wbuffer, size) => size; + lfs_file_size(&lfs, &file) => size; + lfs_file_close(&lfs, &file) => 0; + + // read hi0 20 + sprintf(path, "hi0"); size = 20; + lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0; + lfs_file_size(&lfs, &file) => size; + lfs_file_read(&lfs, &file, rbuffer, size) => size; + memcmp(rbuffer, wbuffer, size) => 0; + lfs_file_close(&lfs, &file) => 0; + // read hi1 20 + sprintf(path, "hi1"); size = 20; + lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0; + lfs_file_size(&lfs, &file) => size; + lfs_file_read(&lfs, &file, rbuffer, size) => size; + memcmp(rbuffer, wbuffer, size) => 0; + lfs_file_close(&lfs, &file) => 0; + // read hi2 20 + sprintf(path, "hi2"); size = 20; + lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0; + lfs_file_size(&lfs, &file) => size; + lfs_file_read(&lfs, &file, rbuffer, size) => size; + memcmp(rbuffer, wbuffer, size) => 0; + lfs_file_close(&lfs, &file) => 0; + // read hi3 20 + sprintf(path, "hi3"); size = 20; + lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0; + lfs_file_size(&lfs, &file) => size; + lfs_file_read(&lfs, &file, rbuffer, size) => size; + memcmp(rbuffer, wbuffer, size) => 0; + lfs_file_close(&lfs, &file) => 0; + + lfs_unmount(&lfs) => 0; +''' + +[[case]] # entry spill test +code = ''' + uint8_t wbuffer[1024]; + uint8_t rbuffer[1024]; + + lfs_format(&lfs, &cfg) => 0; + lfs_mount(&lfs, &cfg) => 0; + + // write hi0 200 + sprintf(path, "hi0"); size = 200; + lfs_file_open(&lfs, &file, path, + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0; + memset(wbuffer, 'c', size); + lfs_file_write(&lfs, &file, wbuffer, size) => size; + lfs_file_size(&lfs, &file) => size; + lfs_file_close(&lfs, &file) => 0; + // write hi1 200 + sprintf(path, "hi1"); size = 200; + lfs_file_open(&lfs, &file, path, + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0; + memset(wbuffer, 'c', size); + lfs_file_write(&lfs, &file, wbuffer, size) => size; + lfs_file_size(&lfs, &file) => size; + lfs_file_close(&lfs, &file) => 0; + // write hi2 200 + sprintf(path, "hi2"); size = 200; + lfs_file_open(&lfs, &file, path, + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0; + memset(wbuffer, 'c', size); + lfs_file_write(&lfs, &file, wbuffer, size) => size; + lfs_file_size(&lfs, &file) => size; + lfs_file_close(&lfs, &file) => 0; + // write hi3 200 + sprintf(path, "hi3"); size = 200; + lfs_file_open(&lfs, &file, path, + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0; + memset(wbuffer, 'c', size); + lfs_file_write(&lfs, &file, wbuffer, size) => size; + lfs_file_size(&lfs, &file) => size; + lfs_file_close(&lfs, &file) => 0; + + // read hi0 200 + sprintf(path, "hi0"); size = 200; + lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0; + lfs_file_size(&lfs, &file) => size; + lfs_file_read(&lfs, &file, rbuffer, size) => size; + memcmp(rbuffer, wbuffer, size) => 0; + lfs_file_close(&lfs, &file) => 0; + // read hi1 200 + sprintf(path, "hi1"); size = 200; + lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0; + lfs_file_size(&lfs, &file) => size; + lfs_file_read(&lfs, &file, rbuffer, size) => size; + memcmp(rbuffer, wbuffer, size) => 0; + lfs_file_close(&lfs, &file) => 0; + // read hi2 200 + sprintf(path, "hi2"); size = 200; + lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0; + lfs_file_size(&lfs, &file) => size; + lfs_file_read(&lfs, &file, rbuffer, size) => size; + memcmp(rbuffer, wbuffer, size) => 0; + lfs_file_close(&lfs, &file) => 0; + // read hi3 200 + sprintf(path, "hi3"); size = 200; + lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0; + lfs_file_size(&lfs, &file) => size; + lfs_file_read(&lfs, &file, rbuffer, size) => size; + memcmp(rbuffer, wbuffer, size) => 0; + lfs_file_close(&lfs, &file) => 0; + + lfs_unmount(&lfs) => 0; +''' + +[[case]] # entry push spill test +code = ''' + uint8_t wbuffer[1024]; + uint8_t rbuffer[1024]; + + lfs_format(&lfs, &cfg) => 0; + lfs_mount(&lfs, &cfg) => 0; + + // write hi0 200 + sprintf(path, "hi0"); size = 200; + lfs_file_open(&lfs, &file, path, + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0; + memset(wbuffer, 'c', size); + lfs_file_write(&lfs, &file, wbuffer, size) => size; + lfs_file_size(&lfs, &file) => size; + lfs_file_close(&lfs, &file) => 0; + // write hi1 20 + sprintf(path, "hi1"); size = 20; + lfs_file_open(&lfs, &file, path, + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0; + memset(wbuffer, 'c', size); + lfs_file_write(&lfs, &file, wbuffer, size) => size; + lfs_file_size(&lfs, &file) => size; + lfs_file_close(&lfs, &file) => 0; + // write hi2 200 + sprintf(path, "hi2"); size = 200; + lfs_file_open(&lfs, &file, path, + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0; + memset(wbuffer, 'c', size); + lfs_file_write(&lfs, &file, wbuffer, size) => size; + lfs_file_size(&lfs, &file) => size; + lfs_file_close(&lfs, &file) => 0; + // write hi3 200 + sprintf(path, "hi3"); size = 200; + lfs_file_open(&lfs, &file, path, + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0; + memset(wbuffer, 'c', size); + lfs_file_write(&lfs, &file, wbuffer, size) => size; + lfs_file_size(&lfs, &file) => size; + lfs_file_close(&lfs, &file) => 0; + + // read hi1 20 + sprintf(path, "hi1"); size = 20; + lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0; + lfs_file_size(&lfs, &file) => size; + lfs_file_read(&lfs, &file, rbuffer, size) => size; + memcmp(rbuffer, wbuffer, size) => 0; + lfs_file_close(&lfs, &file) => 0; + // write hi1 200 + sprintf(path, "hi1"); size = 200; + lfs_file_open(&lfs, &file, path, + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0; + memset(wbuffer, 'c', size); + lfs_file_write(&lfs, &file, wbuffer, size) => size; + lfs_file_size(&lfs, &file) => size; + lfs_file_close(&lfs, &file) => 0; + + // read hi0 200 + sprintf(path, "hi0"); size = 200; + lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0; + lfs_file_size(&lfs, &file) => size; + lfs_file_read(&lfs, &file, rbuffer, size) => size; + memcmp(rbuffer, wbuffer, size) => 0; + lfs_file_close(&lfs, &file) => 0; + // read hi1 200 + sprintf(path, "hi1"); size = 200; + lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0; + lfs_file_size(&lfs, &file) => size; + lfs_file_read(&lfs, &file, rbuffer, size) => size; + memcmp(rbuffer, wbuffer, size) => 0; + lfs_file_close(&lfs, &file) => 0; + // read hi2 200 + sprintf(path, "hi2"); size = 200; + lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0; + lfs_file_size(&lfs, &file) => size; + lfs_file_read(&lfs, &file, rbuffer, size) => size; + memcmp(rbuffer, wbuffer, size) => 0; + lfs_file_close(&lfs, &file) => 0; + // read hi3 200 + sprintf(path, "hi3"); size = 200; + lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0; + lfs_file_size(&lfs, &file) => size; + lfs_file_read(&lfs, &file, rbuffer, size) => size; + memcmp(rbuffer, wbuffer, size) => 0; + lfs_file_close(&lfs, &file) => 0; + + lfs_unmount(&lfs) => 0; +''' + +[[case]] # entry push spill two test +code = ''' + uint8_t wbuffer[1024]; + uint8_t rbuffer[1024]; + + lfs_format(&lfs, &cfg) => 0; + lfs_mount(&lfs, &cfg) => 0; + + // write hi0 200 + sprintf(path, "hi0"); size = 200; + lfs_file_open(&lfs, &file, path, + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0; + memset(wbuffer, 'c', size); + lfs_file_write(&lfs, &file, wbuffer, size) => size; + lfs_file_size(&lfs, &file) => size; + lfs_file_close(&lfs, &file) => 0; + // write hi1 20 + sprintf(path, "hi1"); size = 20; + lfs_file_open(&lfs, &file, path, + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0; + memset(wbuffer, 'c', size); + lfs_file_write(&lfs, &file, wbuffer, size) => size; + lfs_file_size(&lfs, &file) => size; + lfs_file_close(&lfs, &file) => 0; + // write hi2 200 + sprintf(path, "hi2"); size = 200; + lfs_file_open(&lfs, &file, path, + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0; + memset(wbuffer, 'c', size); + lfs_file_write(&lfs, &file, wbuffer, size) => size; + lfs_file_size(&lfs, &file) => size; + lfs_file_close(&lfs, &file) => 0; + // write hi3 200 + sprintf(path, "hi3"); size = 200; + lfs_file_open(&lfs, &file, path, + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0; + memset(wbuffer, 'c', size); + lfs_file_write(&lfs, &file, wbuffer, size) => size; + lfs_file_size(&lfs, &file) => size; + lfs_file_close(&lfs, &file) => 0; + // write hi4 200 + sprintf(path, "hi4"); size = 200; + lfs_file_open(&lfs, &file, path, + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0; + memset(wbuffer, 'c', size); + lfs_file_write(&lfs, &file, wbuffer, size) => size; + lfs_file_size(&lfs, &file) => size; + lfs_file_close(&lfs, &file) => 0; + + // read hi1 20 + sprintf(path, "hi1"); size = 20; + lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0; + lfs_file_size(&lfs, &file) => size; + lfs_file_read(&lfs, &file, rbuffer, size) => size; + memcmp(rbuffer, wbuffer, size) => 0; + lfs_file_close(&lfs, &file) => 0; + // write hi1 200 + sprintf(path, "hi1"); size = 200; + lfs_file_open(&lfs, &file, path, + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0; + memset(wbuffer, 'c', size); + lfs_file_write(&lfs, &file, wbuffer, size) => size; + lfs_file_size(&lfs, &file) => size; + lfs_file_close(&lfs, &file) => 0; + + // read hi0 200 + sprintf(path, "hi0"); size = 200; + lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0; + lfs_file_size(&lfs, &file) => size; + lfs_file_read(&lfs, &file, rbuffer, size) => size; + memcmp(rbuffer, wbuffer, size) => 0; + lfs_file_close(&lfs, &file) => 0; + // read hi1 200 + sprintf(path, "hi1"); size = 200; + lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0; + lfs_file_size(&lfs, &file) => size; + lfs_file_read(&lfs, &file, rbuffer, size) => size; + memcmp(rbuffer, wbuffer, size) => 0; + lfs_file_close(&lfs, &file) => 0; + // read hi2 200 + sprintf(path, "hi2"); size = 200; + lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0; + lfs_file_size(&lfs, &file) => size; + lfs_file_read(&lfs, &file, rbuffer, size) => size; + memcmp(rbuffer, wbuffer, size) => 0; + lfs_file_close(&lfs, &file) => 0; + // read hi3 200 + sprintf(path, "hi3"); size = 200; + lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0; + lfs_file_size(&lfs, &file) => size; + lfs_file_read(&lfs, &file, rbuffer, size) => size; + memcmp(rbuffer, wbuffer, size) => 0; + lfs_file_close(&lfs, &file) => 0; + // read hi4 200 + sprintf(path, "hi4"); size = 200; + lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0; + lfs_file_size(&lfs, &file) => size; + lfs_file_read(&lfs, &file, rbuffer, size) => size; + memcmp(rbuffer, wbuffer, size) => 0; + lfs_file_close(&lfs, &file) => 0; + + lfs_unmount(&lfs) => 0; +''' + +[[case]] # entry drop test +code = ''' + uint8_t wbuffer[1024]; + uint8_t rbuffer[1024]; + + lfs_format(&lfs, &cfg) => 0; + lfs_mount(&lfs, &cfg) => 0; + + // write hi0 200 + sprintf(path, "hi0"); size = 200; + lfs_file_open(&lfs, &file, path, + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0; + memset(wbuffer, 'c', size); + lfs_file_write(&lfs, &file, wbuffer, size) => size; + lfs_file_size(&lfs, &file) => size; + lfs_file_close(&lfs, &file) => 0; + // write hi1 200 + sprintf(path, "hi1"); size = 200; + lfs_file_open(&lfs, &file, path, + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0; + memset(wbuffer, 'c', size); + lfs_file_write(&lfs, &file, wbuffer, size) => size; + lfs_file_size(&lfs, &file) => size; + lfs_file_close(&lfs, &file) => 0; + // write hi2 200 + sprintf(path, "hi2"); size = 200; + lfs_file_open(&lfs, &file, path, + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0; + memset(wbuffer, 'c', size); + lfs_file_write(&lfs, &file, wbuffer, size) => size; + lfs_file_size(&lfs, &file) => size; + lfs_file_close(&lfs, &file) => 0; + // write hi3 200 + sprintf(path, "hi3"); size = 200; + lfs_file_open(&lfs, &file, path, + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0; + memset(wbuffer, 'c', size); + lfs_file_write(&lfs, &file, wbuffer, size) => size; + lfs_file_size(&lfs, &file) => size; + lfs_file_close(&lfs, &file) => 0; + + lfs_remove(&lfs, "hi1") => 0; + lfs_stat(&lfs, "hi1", &info) => LFS_ERR_NOENT; + // read hi0 200 + sprintf(path, "hi0"); size = 200; + lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0; + lfs_file_size(&lfs, &file) => size; + lfs_file_read(&lfs, &file, rbuffer, size) => size; + memcmp(rbuffer, wbuffer, size) => 0; + lfs_file_close(&lfs, &file) => 0; + // read hi2 200 + sprintf(path, "hi2"); size = 200; + lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0; + lfs_file_size(&lfs, &file) => size; + lfs_file_read(&lfs, &file, rbuffer, size) => size; + memcmp(rbuffer, wbuffer, size) => 0; + lfs_file_close(&lfs, &file) => 0; + // read hi3 200 + sprintf(path, "hi3"); size = 200; + lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0; + lfs_file_size(&lfs, &file) => size; + lfs_file_read(&lfs, &file, rbuffer, size) => size; + memcmp(rbuffer, wbuffer, size) => 0; + lfs_file_close(&lfs, &file) => 0; + + lfs_remove(&lfs, "hi2") => 0; + lfs_stat(&lfs, "hi2", &info) => LFS_ERR_NOENT; + // read hi0 200 + sprintf(path, "hi0"); size = 200; + lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0; + lfs_file_size(&lfs, &file) => size; + lfs_file_read(&lfs, &file, rbuffer, size) => size; + memcmp(rbuffer, wbuffer, size) => 0; + lfs_file_close(&lfs, &file) => 0; + // read hi3 200 + sprintf(path, "hi3"); size = 200; + lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0; + lfs_file_size(&lfs, &file) => size; + lfs_file_read(&lfs, &file, rbuffer, size) => size; + memcmp(rbuffer, wbuffer, size) => 0; + lfs_file_close(&lfs, &file) => 0; + + lfs_remove(&lfs, "hi3") => 0; + lfs_stat(&lfs, "hi3", &info) => LFS_ERR_NOENT; + // read hi0 200 + sprintf(path, "hi0"); size = 200; + lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0; + lfs_file_size(&lfs, &file) => size; + lfs_file_read(&lfs, &file, rbuffer, size) => size; + memcmp(rbuffer, wbuffer, size) => 0; + lfs_file_close(&lfs, &file) => 0; + + lfs_remove(&lfs, "hi0") => 0; + lfs_stat(&lfs, "hi0", &info) => LFS_ERR_NOENT; + + lfs_unmount(&lfs) => 0; +''' + +[[case]] # create too big +code = ''' + lfs_format(&lfs, &cfg) => 0; + + lfs_mount(&lfs, &cfg) => 0; + memset(path, 'm', 200); + path[200] = '\0'; + + size = 400; + lfs_file_open(&lfs, &file, path, + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0; + uint8_t wbuffer[1024]; + memset(wbuffer, 'c', size); + lfs_file_write(&lfs, &file, wbuffer, size) => size; + lfs_file_close(&lfs, &file) => 0; + + size = 400; + lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0; + uint8_t rbuffer[1024]; + lfs_file_read(&lfs, &file, rbuffer, size) => size; + memcmp(rbuffer, wbuffer, size) => 0; + lfs_file_close(&lfs, &file) => 0; + lfs_unmount(&lfs) => 0; +''' + +[[case]] # resize too big +code = ''' + lfs_format(&lfs, &cfg) => 0; + + lfs_mount(&lfs, &cfg) => 0; + memset(path, 'm', 200); + path[200] = '\0'; + + size = 40; + lfs_file_open(&lfs, &file, path, + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0; + uint8_t wbuffer[1024]; + memset(wbuffer, 'c', size); + lfs_file_write(&lfs, &file, wbuffer, size) => size; + lfs_file_close(&lfs, &file) => 0; + + size = 40; + lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0; + uint8_t rbuffer[1024]; + lfs_file_read(&lfs, &file, rbuffer, size) => size; + memcmp(rbuffer, wbuffer, size) => 0; + lfs_file_close(&lfs, &file) => 0; + + size = 400; + lfs_file_open(&lfs, &file, path, + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0; + memset(wbuffer, 'c', size); + lfs_file_write(&lfs, &file, wbuffer, size) => size; + lfs_file_close(&lfs, &file) => 0; + + size = 400; + lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0; + lfs_file_read(&lfs, &file, rbuffer, size) => size; + memcmp(rbuffer, wbuffer, size) => 0; + lfs_file_close(&lfs, &file) => 0; + lfs_unmount(&lfs) => 0; +''' diff --git a/tests/test_evil.toml b/tests/test_evil.toml new file mode 100644 index 00000000..920d3a0e --- /dev/null +++ b/tests/test_evil.toml @@ -0,0 +1,288 @@ +# Tests for recovering from conditions which shouldn't normally +# happen during normal operation of littlefs + +# invalid pointer tests (outside of block_count) + +[[case]] # invalid tail-pointer test +define.TAIL_TYPE = ['LFS_TYPE_HARDTAIL', 'LFS_TYPE_SOFTTAIL'] +define.INVALSET = [0x3, 0x1, 0x2] +in = "lfs.c" +code = ''' + // create littlefs + lfs_format(&lfs, &cfg) => 0; + + // change tail-pointer to invalid pointers + lfs_init(&lfs, &cfg) => 0; + lfs_mdir_t mdir; + lfs_dir_fetch(&lfs, &mdir, (lfs_block_t[2]){0, 1}) => 0; + lfs_dir_commit(&lfs, &mdir, LFS_MKATTRS( + {LFS_MKTAG(LFS_TYPE_HARDTAIL, 0x3ff, 8), + (lfs_block_t[2]){ + (INVALSET & 0x1) ? 0xcccccccc : 0, + (INVALSET & 0x2) ? 0xcccccccc : 0}})) => 0; + lfs_deinit(&lfs) => 0; + + // test that mount fails gracefully + lfs_mount(&lfs, &cfg) => LFS_ERR_CORRUPT; +''' + +[[case]] # invalid dir pointer test +define.INVALSET = [0x3, 0x1, 0x2] +in = "lfs.c" +code = ''' + // create littlefs + lfs_format(&lfs, &cfg) => 0; + // make a dir + lfs_mount(&lfs, &cfg) => 0; + lfs_mkdir(&lfs, "dir_here") => 0; + lfs_unmount(&lfs) => 0; + + // change the dir pointer to be invalid + lfs_init(&lfs, &cfg) => 0; + lfs_mdir_t mdir; + lfs_dir_fetch(&lfs, &mdir, (lfs_block_t[2]){0, 1}) => 0; + // make sure id 1 == our directory + lfs_dir_get(&lfs, &mdir, + LFS_MKTAG(0x700, 0x3ff, 0), + LFS_MKTAG(LFS_TYPE_NAME, 1, strlen("dir_here")), buffer) + => LFS_MKTAG(LFS_TYPE_DIR, 1, strlen("dir_here")); + assert(memcmp((char*)buffer, "dir_here", strlen("dir_here")) == 0); + // change dir pointer + lfs_dir_commit(&lfs, &mdir, LFS_MKATTRS( + {LFS_MKTAG(LFS_TYPE_DIRSTRUCT, 1, 8), + (lfs_block_t[2]){ + (INVALSET & 0x1) ? 0xcccccccc : 0, + (INVALSET & 0x2) ? 0xcccccccc : 0}})) => 0; + lfs_deinit(&lfs) => 0; + + // test that accessing our bad dir fails, note there's a number + // of ways to access the dir, some can fail, but some don't + lfs_mount(&lfs, &cfg) => 0; + lfs_stat(&lfs, "dir_here", &info) => 0; + assert(strcmp(info.name, "dir_here") == 0); + assert(info.type == LFS_TYPE_DIR); + + lfs_dir_open(&lfs, &dir, "dir_here") => LFS_ERR_CORRUPT; + lfs_stat(&lfs, "dir_here/file_here", &info) => LFS_ERR_CORRUPT; + lfs_dir_open(&lfs, &dir, "dir_here/dir_here") => LFS_ERR_CORRUPT; + lfs_file_open(&lfs, &file, "dir_here/file_here", + LFS_O_RDONLY) => LFS_ERR_CORRUPT; + lfs_file_open(&lfs, &file, "dir_here/file_here", + LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_CORRUPT; + lfs_unmount(&lfs) => 0; +''' + +[[case]] # invalid file pointer test +in = "lfs.c" +define.SIZE = [10, 1000, 100000] # faked file size +code = ''' + // create littlefs + lfs_format(&lfs, &cfg) => 0; + // make a file + lfs_mount(&lfs, &cfg) => 0; + lfs_file_open(&lfs, &file, "file_here", + LFS_O_WRONLY | LFS_O_CREAT) => 0; + lfs_file_close(&lfs, &file) => 0; + lfs_unmount(&lfs) => 0; + + // change the file pointer to be invalid + lfs_init(&lfs, &cfg) => 0; + lfs_mdir_t mdir; + lfs_dir_fetch(&lfs, &mdir, (lfs_block_t[2]){0, 1}) => 0; + // make sure id 1 == our file + lfs_dir_get(&lfs, &mdir, + LFS_MKTAG(0x700, 0x3ff, 0), + LFS_MKTAG(LFS_TYPE_NAME, 1, strlen("file_here")), buffer) + => LFS_MKTAG(LFS_TYPE_REG, 1, strlen("file_here")); + assert(memcmp((char*)buffer, "file_here", strlen("file_here")) == 0); + // change file pointer + lfs_dir_commit(&lfs, &mdir, LFS_MKATTRS( + {LFS_MKTAG(LFS_TYPE_CTZSTRUCT, 1, sizeof(struct lfs_ctz)), + &(struct lfs_ctz){0xcccccccc, lfs_tole32(SIZE)}})) => 0; + lfs_deinit(&lfs) => 0; + + // test that accessing our bad file fails, note there's a number + // of ways to access the dir, some can fail, but some don't + lfs_mount(&lfs, &cfg) => 0; + lfs_stat(&lfs, "file_here", &info) => 0; + assert(strcmp(info.name, "file_here") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == SIZE); + + lfs_file_open(&lfs, &file, "file_here", LFS_O_RDONLY) => 0; + lfs_file_read(&lfs, &file, buffer, SIZE) => LFS_ERR_CORRUPT; + lfs_file_close(&lfs, &file) => 0; + + // any allocs that traverse CTZ must unfortunately must fail + if (SIZE > 2*LFS_BLOCK_SIZE) { + lfs_mkdir(&lfs, "dir_here") => LFS_ERR_CORRUPT; + } + lfs_unmount(&lfs) => 0; +''' + +[[case]] # invalid pointer in CTZ skip-list test +define.SIZE = ['2*LFS_BLOCK_SIZE', '3*LFS_BLOCK_SIZE', '4*LFS_BLOCK_SIZE'] +in = "lfs.c" +code = ''' + // create littlefs + lfs_format(&lfs, &cfg) => 0; + // make a file + lfs_mount(&lfs, &cfg) => 0; + lfs_file_open(&lfs, &file, "file_here", + LFS_O_WRONLY | LFS_O_CREAT) => 0; + for (int i = 0; i < SIZE; i++) { + char c = 'c'; + lfs_file_write(&lfs, &file, &c, 1) => 1; + } + lfs_file_close(&lfs, &file) => 0; + lfs_unmount(&lfs) => 0; + // change pointer in CTZ skip-list to be invalid + lfs_init(&lfs, &cfg) => 0; + lfs_mdir_t mdir; + lfs_dir_fetch(&lfs, &mdir, (lfs_block_t[2]){0, 1}) => 0; + // make sure id 1 == our file and get our CTZ structure + lfs_dir_get(&lfs, &mdir, + LFS_MKTAG(0x700, 0x3ff, 0), + LFS_MKTAG(LFS_TYPE_NAME, 1, strlen("file_here")), buffer) + => LFS_MKTAG(LFS_TYPE_REG, 1, strlen("file_here")); + assert(memcmp((char*)buffer, "file_here", strlen("file_here")) == 0); + struct lfs_ctz ctz; + lfs_dir_get(&lfs, &mdir, + LFS_MKTAG(0x700, 0x3ff, 0), + LFS_MKTAG(LFS_TYPE_STRUCT, 1, sizeof(struct lfs_ctz)), &ctz) + => LFS_MKTAG(LFS_TYPE_CTZSTRUCT, 1, sizeof(struct lfs_ctz)); + lfs_ctz_fromle32(&ctz); + // rewrite block to contain bad pointer + uint8_t bbuffer[LFS_BLOCK_SIZE]; + cfg.read(&cfg, ctz.head, 0, bbuffer, LFS_BLOCK_SIZE) => 0; + uint32_t bad = lfs_tole32(0xcccccccc); + memcpy(&bbuffer[0], &bad, sizeof(bad)); + memcpy(&bbuffer[4], &bad, sizeof(bad)); + cfg.erase(&cfg, ctz.head) => 0; + cfg.prog(&cfg, ctz.head, 0, bbuffer, LFS_BLOCK_SIZE) => 0; + lfs_deinit(&lfs) => 0; + + // test that accessing our bad file fails, note there's a number + // of ways to access the dir, some can fail, but some don't + lfs_mount(&lfs, &cfg) => 0; + lfs_stat(&lfs, "file_here", &info) => 0; + assert(strcmp(info.name, "file_here") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == SIZE); + + lfs_file_open(&lfs, &file, "file_here", LFS_O_RDONLY) => 0; + lfs_file_read(&lfs, &file, buffer, SIZE) => LFS_ERR_CORRUPT; + lfs_file_close(&lfs, &file) => 0; + + // any allocs that traverse CTZ must unfortunately must fail + if (SIZE > 2*LFS_BLOCK_SIZE) { + lfs_mkdir(&lfs, "dir_here") => LFS_ERR_CORRUPT; + } + lfs_unmount(&lfs) => 0; +''' + + +[[case]] # invalid gstate pointer +define.INVALSET = [0x3, 0x1, 0x2] +in = "lfs.c" +code = ''' + // create littlefs + lfs_format(&lfs, &cfg) => 0; + + // create an invalid gstate + lfs_init(&lfs, &cfg) => 0; + lfs_mdir_t mdir; + lfs_dir_fetch(&lfs, &mdir, (lfs_block_t[2]){0, 1}) => 0; + lfs_fs_prepmove(&lfs, 1, (lfs_block_t [2]){ + (INVALSET & 0x1) ? 0xcccccccc : 0, + (INVALSET & 0x2) ? 0xcccccccc : 0}); + lfs_dir_commit(&lfs, &mdir, NULL, 0) => 0; + lfs_deinit(&lfs) => 0; + + // test that mount fails gracefully + // mount may not fail, but our first alloc should fail when + // we try to fix the gstate + lfs_mount(&lfs, &cfg) => 0; + lfs_mkdir(&lfs, "should_fail") => LFS_ERR_CORRUPT; + lfs_unmount(&lfs) => 0; +''' + +# cycle detection/recovery tests + +[[case]] # metadata-pair threaded-list loop test +in = "lfs.c" +code = ''' + // create littlefs + lfs_format(&lfs, &cfg) => 0; + + // change tail-pointer to point to ourself + lfs_init(&lfs, &cfg) => 0; + lfs_mdir_t mdir; + lfs_dir_fetch(&lfs, &mdir, (lfs_block_t[2]){0, 1}) => 0; + lfs_dir_commit(&lfs, &mdir, LFS_MKATTRS( + {LFS_MKTAG(LFS_TYPE_HARDTAIL, 0x3ff, 8), + (lfs_block_t[2]){0, 1}})) => 0; + lfs_deinit(&lfs) => 0; + + // test that mount fails gracefully + lfs_mount(&lfs, &cfg) => LFS_ERR_CORRUPT; +''' + +[[case]] # metadata-pair threaded-list 2-length loop test +in = "lfs.c" +code = ''' + // create littlefs with child dir + lfs_format(&lfs, &cfg) => 0; + lfs_mount(&lfs, &cfg) => 0; + lfs_mkdir(&lfs, "child") => 0; + lfs_unmount(&lfs) => 0; + + // find child + lfs_init(&lfs, &cfg) => 0; + lfs_mdir_t mdir; + lfs_block_t pair[2]; + 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_DIRSTRUCT, 1, sizeof(pair)), pair) + => LFS_MKTAG(LFS_TYPE_DIRSTRUCT, 1, sizeof(pair)); + lfs_pair_fromle32(pair); + // change tail-pointer to point to root + lfs_dir_fetch(&lfs, &mdir, pair) => 0; + lfs_dir_commit(&lfs, &mdir, LFS_MKATTRS( + {LFS_MKTAG(LFS_TYPE_HARDTAIL, 0x3ff, 8), + (lfs_block_t[2]){0, 1}})) => 0; + lfs_deinit(&lfs) => 0; + + // test that mount fails gracefully + lfs_mount(&lfs, &cfg) => LFS_ERR_CORRUPT; +''' + +[[case]] # metadata-pair threaded-list 1-length child loop test +in = "lfs.c" +code = ''' + // create littlefs with child dir + lfs_format(&lfs, &cfg) => 0; + lfs_mount(&lfs, &cfg) => 0; + lfs_mkdir(&lfs, "child") => 0; + lfs_unmount(&lfs) => 0; + + // find child + lfs_init(&lfs, &cfg) => 0; + lfs_mdir_t mdir; + lfs_block_t pair[2]; + 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_DIRSTRUCT, 1, sizeof(pair)), pair) + => LFS_MKTAG(LFS_TYPE_DIRSTRUCT, 1, sizeof(pair)); + lfs_pair_fromle32(pair); + // change tail-pointer to point to ourself + lfs_dir_fetch(&lfs, &mdir, pair) => 0; + lfs_dir_commit(&lfs, &mdir, LFS_MKATTRS( + {LFS_MKTAG(LFS_TYPE_HARDTAIL, 0x3ff, 8), pair})) => 0; + lfs_deinit(&lfs) => 0; + + // test that mount fails gracefully + lfs_mount(&lfs, &cfg) => LFS_ERR_CORRUPT; +''' diff --git a/tests/test_exhaustion.toml b/tests/test_exhaustion.toml new file mode 100644 index 00000000..569611c5 --- /dev/null +++ b/tests/test_exhaustion.toml @@ -0,0 +1,465 @@ +[[case]] # test running a filesystem to exhaustion +define.LFS_ERASE_CYCLES = 10 +define.LFS_BLOCK_COUNT = 256 # small bd so test runs faster +define.LFS_BLOCK_CYCLES = 'LFS_ERASE_CYCLES / 2' +define.LFS_BADBLOCK_BEHAVIOR = [ + 'LFS_TESTBD_BADBLOCK_PROGERROR', + 'LFS_TESTBD_BADBLOCK_ERASEERROR', + 'LFS_TESTBD_BADBLOCK_READERROR', + 'LFS_TESTBD_BADBLOCK_PROGNOOP', + 'LFS_TESTBD_BADBLOCK_ERASENOOP', +] +define.FILES = 10 +code = ''' + lfs_format(&lfs, &cfg) => 0; + lfs_mount(&lfs, &cfg) => 0; + lfs_mkdir(&lfs, "roadrunner") => 0; + lfs_unmount(&lfs) => 0; + + uint32_t cycle = 0; + while (true) { + lfs_mount(&lfs, &cfg) => 0; + for (uint32_t i = 0; i < FILES; i++) { + // chose name, roughly random seed, and random 2^n size + sprintf(path, "roadrunner/test%d", i); + srand(cycle * i); + size = 1 << ((rand() % 10)+2); + + lfs_file_open(&lfs, &file, path, + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0; + + for (lfs_size_t j = 0; j < size; j++) { + char c = 'a' + (rand() % 26); + lfs_ssize_t res = lfs_file_write(&lfs, &file, &c, 1); + assert(res == 1 || res == LFS_ERR_NOSPC); + if (res == LFS_ERR_NOSPC) { + err = lfs_file_close(&lfs, &file); + assert(err == 0 || err == LFS_ERR_NOSPC); + lfs_unmount(&lfs) => 0; + goto exhausted; + } + } + + err = lfs_file_close(&lfs, &file); + assert(err == 0 || err == LFS_ERR_NOSPC); + if (err == LFS_ERR_NOSPC) { + lfs_unmount(&lfs) => 0; + goto exhausted; + } + } + + for (uint32_t i = 0; i < FILES; i++) { + // check for errors + sprintf(path, "roadrunner/test%d", i); + srand(cycle * i); + size = 1 << ((rand() % 10)+2); + + lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0; + for (lfs_size_t j = 0; j < size; j++) { + char c = 'a' + (rand() % 26); + char r; + lfs_file_read(&lfs, &file, &r, 1) => 1; + assert(r == c); + } + + lfs_file_close(&lfs, &file) => 0; + } + lfs_unmount(&lfs) => 0; + + cycle += 1; + } + +exhausted: + // should still be readable + lfs_mount(&lfs, &cfg) => 0; + for (uint32_t i = 0; i < FILES; i++) { + // check for errors + sprintf(path, "roadrunner/test%d", i); + lfs_stat(&lfs, path, &info) => 0; + } + lfs_unmount(&lfs) => 0; + + LFS_WARN("completed %d cycles", cycle); +''' + +[[case]] # test running a filesystem to exhaustion + # which also requires expanding superblocks +define.LFS_ERASE_CYCLES = 10 +define.LFS_BLOCK_COUNT = 256 # small bd so test runs faster +define.LFS_BLOCK_CYCLES = 'LFS_ERASE_CYCLES / 2' +define.LFS_BADBLOCK_BEHAVIOR = [ + 'LFS_TESTBD_BADBLOCK_PROGERROR', + 'LFS_TESTBD_BADBLOCK_ERASEERROR', + 'LFS_TESTBD_BADBLOCK_READERROR', + 'LFS_TESTBD_BADBLOCK_PROGNOOP', + 'LFS_TESTBD_BADBLOCK_ERASENOOP', +] +define.FILES = 10 +code = ''' + lfs_format(&lfs, &cfg) => 0; + + uint32_t cycle = 0; + while (true) { + lfs_mount(&lfs, &cfg) => 0; + for (uint32_t i = 0; i < FILES; i++) { + // chose name, roughly random seed, and random 2^n size + sprintf(path, "test%d", i); + srand(cycle * i); + size = 1 << ((rand() % 10)+2); + + lfs_file_open(&lfs, &file, path, + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0; + + for (lfs_size_t j = 0; j < size; j++) { + char c = 'a' + (rand() % 26); + lfs_ssize_t res = lfs_file_write(&lfs, &file, &c, 1); + assert(res == 1 || res == LFS_ERR_NOSPC); + if (res == LFS_ERR_NOSPC) { + err = lfs_file_close(&lfs, &file); + assert(err == 0 || err == LFS_ERR_NOSPC); + lfs_unmount(&lfs) => 0; + goto exhausted; + } + } + + err = lfs_file_close(&lfs, &file); + assert(err == 0 || err == LFS_ERR_NOSPC); + if (err == LFS_ERR_NOSPC) { + lfs_unmount(&lfs) => 0; + goto exhausted; + } + } + + for (uint32_t i = 0; i < FILES; i++) { + // check for errors + sprintf(path, "test%d", i); + srand(cycle * i); + size = 1 << ((rand() % 10)+2); + + lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0; + for (lfs_size_t j = 0; j < size; j++) { + char c = 'a' + (rand() % 26); + char r; + lfs_file_read(&lfs, &file, &r, 1) => 1; + assert(r == c); + } + + lfs_file_close(&lfs, &file) => 0; + } + lfs_unmount(&lfs) => 0; + + cycle += 1; + } + +exhausted: + // should still be readable + lfs_mount(&lfs, &cfg) => 0; + for (uint32_t i = 0; i < FILES; i++) { + // check for errors + sprintf(path, "test%d", i); + lfs_stat(&lfs, path, &info) => 0; + } + lfs_unmount(&lfs) => 0; + + LFS_WARN("completed %d cycles", cycle); +''' + +# These are a sort of high-level litmus test for wear-leveling. One definition +# of wear-leveling is that increasing a block device's space translates directly +# into increasing the block devices lifetime. This is something we can actually +# check for. + +[[case]] # wear-level test running a filesystem to exhaustion +define.LFS_ERASE_CYCLES = 20 +define.LFS_BLOCK_COUNT = 256 # small bd so test runs faster +define.LFS_BLOCK_CYCLES = 'LFS_ERASE_CYCLES / 2' +define.FILES = 10 +code = ''' + uint32_t run_cycles[2]; + const uint32_t run_block_count[2] = {LFS_BLOCK_COUNT/2, LFS_BLOCK_COUNT}; + + for (int run = 0; run < 2; run++) { + for (lfs_block_t b = 0; b < LFS_BLOCK_COUNT; b++) { + lfs_testbd_setwear(&cfg, b, + (b < run_block_count[run]) ? 0 : LFS_ERASE_CYCLES) => 0; + } + + lfs_format(&lfs, &cfg) => 0; + lfs_mount(&lfs, &cfg) => 0; + lfs_mkdir(&lfs, "roadrunner") => 0; + lfs_unmount(&lfs) => 0; + + uint32_t cycle = 0; + while (true) { + lfs_mount(&lfs, &cfg) => 0; + for (uint32_t i = 0; i < FILES; i++) { + // chose name, roughly random seed, and random 2^n size + sprintf(path, "roadrunner/test%d", i); + srand(cycle * i); + size = 1 << ((rand() % 10)+2); + + lfs_file_open(&lfs, &file, path, + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0; + + for (lfs_size_t j = 0; j < size; j++) { + char c = 'a' + (rand() % 26); + lfs_ssize_t res = lfs_file_write(&lfs, &file, &c, 1); + assert(res == 1 || res == LFS_ERR_NOSPC); + if (res == LFS_ERR_NOSPC) { + err = lfs_file_close(&lfs, &file); + assert(err == 0 || err == LFS_ERR_NOSPC); + lfs_unmount(&lfs) => 0; + goto exhausted; + } + } + + err = lfs_file_close(&lfs, &file); + assert(err == 0 || err == LFS_ERR_NOSPC); + if (err == LFS_ERR_NOSPC) { + lfs_unmount(&lfs) => 0; + goto exhausted; + } + } + + for (uint32_t i = 0; i < FILES; i++) { + // check for errors + sprintf(path, "roadrunner/test%d", i); + srand(cycle * i); + size = 1 << ((rand() % 10)+2); + + lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0; + for (lfs_size_t j = 0; j < size; j++) { + char c = 'a' + (rand() % 26); + char r; + lfs_file_read(&lfs, &file, &r, 1) => 1; + assert(r == c); + } + + lfs_file_close(&lfs, &file) => 0; + } + lfs_unmount(&lfs) => 0; + + cycle += 1; + } + +exhausted: + // should still be readable + lfs_mount(&lfs, &cfg) => 0; + for (uint32_t i = 0; i < FILES; i++) { + // check for errors + sprintf(path, "roadrunner/test%d", i); + lfs_stat(&lfs, path, &info) => 0; + } + lfs_unmount(&lfs) => 0; + + run_cycles[run] = cycle; + LFS_WARN("completed %d blocks %d cycles", + run_block_count[run], run_cycles[run]); + } + + // check we increased the lifetime by 2x with ~10% error + LFS_ASSERT(run_cycles[1]*110/100 > 2*run_cycles[0]); +''' + +[[case]] # wear-level test + expanding superblock +define.LFS_ERASE_CYCLES = 20 +define.LFS_BLOCK_COUNT = 256 # small bd so test runs faster +define.LFS_BLOCK_CYCLES = 'LFS_ERASE_CYCLES / 2' +define.FILES = 10 +code = ''' + uint32_t run_cycles[2]; + const uint32_t run_block_count[2] = {LFS_BLOCK_COUNT/2, LFS_BLOCK_COUNT}; + + for (int run = 0; run < 2; run++) { + for (lfs_block_t b = 0; b < LFS_BLOCK_COUNT; b++) { + lfs_testbd_setwear(&cfg, b, + (b < run_block_count[run]) ? 0 : LFS_ERASE_CYCLES) => 0; + } + + lfs_format(&lfs, &cfg) => 0; + + uint32_t cycle = 0; + while (true) { + lfs_mount(&lfs, &cfg) => 0; + for (uint32_t i = 0; i < FILES; i++) { + // chose name, roughly random seed, and random 2^n size + sprintf(path, "test%d", i); + srand(cycle * i); + size = 1 << ((rand() % 10)+2); + + lfs_file_open(&lfs, &file, path, + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0; + + for (lfs_size_t j = 0; j < size; j++) { + char c = 'a' + (rand() % 26); + lfs_ssize_t res = lfs_file_write(&lfs, &file, &c, 1); + assert(res == 1 || res == LFS_ERR_NOSPC); + if (res == LFS_ERR_NOSPC) { + err = lfs_file_close(&lfs, &file); + assert(err == 0 || err == LFS_ERR_NOSPC); + lfs_unmount(&lfs) => 0; + goto exhausted; + } + } + + err = lfs_file_close(&lfs, &file); + assert(err == 0 || err == LFS_ERR_NOSPC); + if (err == LFS_ERR_NOSPC) { + lfs_unmount(&lfs) => 0; + goto exhausted; + } + } + + for (uint32_t i = 0; i < FILES; i++) { + // check for errors + sprintf(path, "test%d", i); + srand(cycle * i); + size = 1 << ((rand() % 10)+2); + + lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0; + for (lfs_size_t j = 0; j < size; j++) { + char c = 'a' + (rand() % 26); + char r; + lfs_file_read(&lfs, &file, &r, 1) => 1; + assert(r == c); + } + + lfs_file_close(&lfs, &file) => 0; + } + lfs_unmount(&lfs) => 0; + + cycle += 1; + } + +exhausted: + // should still be readable + lfs_mount(&lfs, &cfg) => 0; + for (uint32_t i = 0; i < FILES; i++) { + // check for errors + sprintf(path, "test%d", i); + lfs_stat(&lfs, path, &info) => 0; + } + lfs_unmount(&lfs) => 0; + + run_cycles[run] = cycle; + LFS_WARN("completed %d blocks %d cycles", + run_block_count[run], run_cycles[run]); + } + + // check we increased the lifetime by 2x with ~10% error + LFS_ASSERT(run_cycles[1]*110/100 > 2*run_cycles[0]); +''' + +[[case]] # test that we wear blocks roughly evenly +define.LFS_ERASE_CYCLES = 0xffffffff +define.LFS_BLOCK_COUNT = 256 # small bd so test runs faster +define.LFS_BLOCK_CYCLES = [5, 4, 3, 2, 1] +define.CYCLES = 100 +define.FILES = 10 +if = 'LFS_BLOCK_CYCLES < CYCLES/10' +code = ''' + lfs_format(&lfs, &cfg) => 0; + lfs_mount(&lfs, &cfg) => 0; + lfs_mkdir(&lfs, "roadrunner") => 0; + lfs_unmount(&lfs) => 0; + + uint32_t cycle = 0; + while (cycle < CYCLES) { + lfs_mount(&lfs, &cfg) => 0; + for (uint32_t i = 0; i < FILES; i++) { + // chose name, roughly random seed, and random 2^n size + sprintf(path, "roadrunner/test%d", i); + srand(cycle * i); + size = 1 << 4; //((rand() % 10)+2); + + lfs_file_open(&lfs, &file, path, + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0; + + for (lfs_size_t j = 0; j < size; j++) { + char c = 'a' + (rand() % 26); + lfs_ssize_t res = lfs_file_write(&lfs, &file, &c, 1); + assert(res == 1 || res == LFS_ERR_NOSPC); + if (res == LFS_ERR_NOSPC) { + err = lfs_file_close(&lfs, &file); + assert(err == 0 || err == LFS_ERR_NOSPC); + lfs_unmount(&lfs) => 0; + goto exhausted; + } + } + + err = lfs_file_close(&lfs, &file); + assert(err == 0 || err == LFS_ERR_NOSPC); + if (err == LFS_ERR_NOSPC) { + lfs_unmount(&lfs) => 0; + goto exhausted; + } + } + + for (uint32_t i = 0; i < FILES; i++) { + // check for errors + sprintf(path, "roadrunner/test%d", i); + srand(cycle * i); + size = 1 << 4; //((rand() % 10)+2); + + lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0; + for (lfs_size_t j = 0; j < size; j++) { + char c = 'a' + (rand() % 26); + char r; + lfs_file_read(&lfs, &file, &r, 1) => 1; + assert(r == c); + } + + lfs_file_close(&lfs, &file) => 0; + } + lfs_unmount(&lfs) => 0; + + cycle += 1; + } + +exhausted: + // should still be readable + lfs_mount(&lfs, &cfg) => 0; + for (uint32_t i = 0; i < FILES; i++) { + // check for errors + sprintf(path, "roadrunner/test%d", i); + lfs_stat(&lfs, path, &info) => 0; + } + lfs_unmount(&lfs) => 0; + + LFS_WARN("completed %d cycles", cycle); + + // check the wear on our block device + lfs_testbd_wear_t minwear = -1; + lfs_testbd_wear_t totalwear = 0; + lfs_testbd_wear_t maxwear = 0; + // skip 0 and 1 as superblock movement is intentionally avoided + for (lfs_block_t b = 2; b < LFS_BLOCK_COUNT; b++) { + lfs_testbd_wear_t wear = lfs_testbd_getwear(&cfg, b); + printf("%08x: wear %d\n", b, wear); + assert(wear >= 0); + if (wear < minwear) { + minwear = wear; + } + if (wear > maxwear) { + maxwear = wear; + } + totalwear += wear; + } + lfs_testbd_wear_t avgwear = totalwear / LFS_BLOCK_COUNT; + LFS_WARN("max wear: %d cycles", maxwear); + LFS_WARN("avg wear: %d cycles", totalwear / LFS_BLOCK_COUNT); + LFS_WARN("min wear: %d cycles", minwear); + + // find standard deviation^2 + lfs_testbd_wear_t dev2 = 0; + for (lfs_block_t b = 2; b < LFS_BLOCK_COUNT; b++) { + lfs_testbd_wear_t wear = lfs_testbd_getwear(&cfg, b); + assert(wear >= 0); + lfs_testbd_swear_t diff = wear - avgwear; + dev2 += diff*diff; + } + dev2 /= totalwear; + LFS_WARN("std dev^2: %d", dev2); + assert(dev2 < 8); +''' + diff --git a/tests/test_files.sh b/tests/test_files.sh deleted file mode 100755 index f6535f6a..00000000 --- a/tests/test_files.sh +++ /dev/null @@ -1,221 +0,0 @@ -#!/bin/bash -set -eu -export TEST_FILE=$0 -trap 'export TEST_LINE=$LINENO' DEBUG - -echo "=== File tests ===" - -SMALLSIZE=32 -MEDIUMSIZE=8192 -LARGESIZE=262144 - -rm -rf blocks -scripts/test.py << TEST - lfs_format(&lfs, &cfg) => 0; -TEST - -echo "--- Simple file test ---" -scripts/test.py << TEST - lfs_mount(&lfs, &cfg) => 0; - lfs_file_open(&lfs, &file, "hello", LFS_O_WRONLY | LFS_O_CREAT) => 0; - lfs_size_t size = strlen("Hello World!\n"); - uint8_t wbuffer[1024]; - memcpy(wbuffer, "Hello World!\n", size); - lfs_file_write(&lfs, &file, wbuffer, size) => size; - lfs_file_close(&lfs, &file) => 0; - - lfs_file_open(&lfs, &file, "hello", LFS_O_RDONLY) => 0; - size = strlen("Hello World!\n"); - uint8_t rbuffer[1024]; - lfs_file_read(&lfs, &file, rbuffer, size) => size; - memcmp(rbuffer, wbuffer, size) => 0; - lfs_file_close(&lfs, &file) => 0; - lfs_unmount(&lfs) => 0; -TEST - -w_test() { -scripts/test.py ${4:-} << TEST - lfs_size_t size = $1; - lfs_size_t chunk = 31; - srand(0); - lfs_mount(&lfs, &cfg) => 0; - lfs_file_open(&lfs, &file, "$2", - ${3:-LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC}) => 0; - for (lfs_size_t i = 0; i < size; i += chunk) { - chunk = (chunk < size - i) ? chunk : size - i; - for (lfs_size_t b = 0; b < chunk; b++) { - buffer[b] = rand() & 0xff; - } - lfs_file_write(&lfs, &file, buffer, chunk) => chunk; - } - lfs_file_close(&lfs, &file) => 0; - lfs_unmount(&lfs) => 0; -TEST -} - -r_test() { -scripts/test.py << TEST - lfs_size_t size = $1; - lfs_size_t chunk = 29; - srand(0); - lfs_mount(&lfs, &cfg) => 0; - lfs_stat(&lfs, "$2", &info) => 0; - info.type => LFS_TYPE_REG; - info.size => size; - lfs_file_open(&lfs, &file, "$2", ${3:-LFS_O_RDONLY}) => 0; - for (lfs_size_t i = 0; i < size; i += chunk) { - chunk = (chunk < size - i) ? chunk : size - i; - lfs_file_read(&lfs, &file, buffer, chunk) => chunk; - for (lfs_size_t b = 0; b < chunk && i+b < size; b++) { - buffer[b] => rand() & 0xff; - } - } - lfs_file_close(&lfs, &file) => 0; - lfs_unmount(&lfs) => 0; -TEST -} - -echo "--- Small file test ---" -w_test $SMALLSIZE smallavacado -r_test $SMALLSIZE smallavacado - -echo "--- Medium file test ---" -w_test $MEDIUMSIZE mediumavacado -r_test $MEDIUMSIZE mediumavacado - -echo "--- Large file test ---" -w_test $LARGESIZE largeavacado -r_test $LARGESIZE largeavacado - -echo "--- Zero file test ---" -w_test 0 noavacado -r_test 0 noavacado - -echo "--- Truncate small test ---" -w_test $SMALLSIZE mediumavacado -r_test $SMALLSIZE mediumavacado -w_test $MEDIUMSIZE mediumavacado -r_test $MEDIUMSIZE mediumavacado - -echo "--- Truncate zero test ---" -w_test $SMALLSIZE noavacado -r_test $SMALLSIZE noavacado -w_test 0 noavacado -r_test 0 noavacado - -echo "--- Non-overlap check ---" -r_test $SMALLSIZE smallavacado -r_test $MEDIUMSIZE mediumavacado -r_test $LARGESIZE largeavacado -r_test 0 noavacado - -echo "--- Dir check ---" -scripts/test.py << TEST - lfs_mount(&lfs, &cfg) => 0; - lfs_dir_open(&lfs, &dir, "/") => 0; - lfs_dir_read(&lfs, &dir, &info) => 1; - lfs_dir_read(&lfs, &dir, &info) => 1; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, "hello") => 0; - info.type => LFS_TYPE_REG; - info.size => strlen("Hello World!\n"); - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, "largeavacado") => 0; - info.type => LFS_TYPE_REG; - info.size => $LARGESIZE; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, "mediumavacado") => 0; - info.type => LFS_TYPE_REG; - info.size => $MEDIUMSIZE; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, "noavacado") => 0; - info.type => LFS_TYPE_REG; - info.size => 0; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, "smallavacado") => 0; - info.type => LFS_TYPE_REG; - info.size => $SMALLSIZE; - lfs_dir_read(&lfs, &dir, &info) => 0; - lfs_dir_close(&lfs, &dir) => 0; - lfs_unmount(&lfs) => 0; -TEST - -echo "--- Many files test ---" -scripts/test.py << TEST - lfs_format(&lfs, &cfg) => 0; -TEST -scripts/test.py << TEST - // Create 300 files of 7 bytes - lfs_mount(&lfs, &cfg) => 0; - for (unsigned i = 0; i < 300; i++) { - sprintf(path, "file_%03d", i); - lfs_file_open(&lfs, &file, path, - LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0; - lfs_size_t size = 7; - uint8_t wbuffer[1024]; - uint8_t rbuffer[1024]; - snprintf((char*)wbuffer, size, "Hi %03d", i); - lfs_file_write(&lfs, &file, wbuffer, size) => size; - lfs_file_rewind(&lfs, &file) => 0; - lfs_file_read(&lfs, &file, rbuffer, size) => size; - memcmp(wbuffer, rbuffer, size) => 0; - lfs_file_close(&lfs, &file) => 0; - } - lfs_unmount(&lfs) => 0; -TEST - -echo "--- Many files with flush test ---" -scripts/test.py << TEST - lfs_format(&lfs, &cfg) => 0; -TEST -scripts/test.py << TEST - // Create 300 files of 7 bytes - lfs_mount(&lfs, &cfg) => 0; - for (unsigned i = 0; i < 300; i++) { - sprintf(path, "file_%03d", i); - lfs_file_open(&lfs, &file, path, - LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; - lfs_size_t size = 7; - uint8_t wbuffer[1024]; - uint8_t rbuffer[1024]; - snprintf((char*)wbuffer, size, "Hi %03d", i); - lfs_file_write(&lfs, &file, wbuffer, size) => size; - lfs_file_close(&lfs, &file) => 0; - - lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0; - lfs_file_read(&lfs, &file, rbuffer, size) => size; - memcmp(wbuffer, rbuffer, size) => 0; - lfs_file_close(&lfs, &file) => 0; - } - lfs_unmount(&lfs) => 0; -TEST - -echo "--- Many files with power cycle test ---" -scripts/test.py << TEST - lfs_format(&lfs, &cfg) => 0; -TEST -scripts/test.py << TEST - // Create 300 files of 7 bytes - lfs_mount(&lfs, &cfg) => 0; - for (unsigned i = 0; i < 300; i++) { - sprintf(path, "file_%03d", i); - lfs_file_open(&lfs, &file, path, - LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; - lfs_size_t size = 7; - uint8_t wbuffer[1024]; - uint8_t rbuffer[1024]; - snprintf((char*)wbuffer, size, "Hi %03d", i); - lfs_file_write(&lfs, &file, wbuffer, size) => size; - lfs_file_close(&lfs, &file) => 0; - lfs_unmount(&lfs) => 0; - - lfs_mount(&lfs, &cfg) => 0; - lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0; - lfs_file_read(&lfs, &file, rbuffer, size) => size; - memcmp(wbuffer, rbuffer, size) => 0; - lfs_file_close(&lfs, &file) => 0; - } - lfs_unmount(&lfs) => 0; -TEST - -scripts/results.py diff --git a/tests/test_files.toml b/tests/test_files.toml new file mode 100644 index 00000000..565e665b --- /dev/null +++ b/tests/test_files.toml @@ -0,0 +1,486 @@ + +[[case]] # simple file test +code = ''' + lfs_format(&lfs, &cfg) => 0; + lfs_mount(&lfs, &cfg) => 0; + lfs_file_open(&lfs, &file, "hello", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + size = strlen("Hello World!")+1; + strcpy((char*)buffer, "Hello World!"); + lfs_file_write(&lfs, &file, buffer, size) => size; + lfs_file_close(&lfs, &file) => 0; + lfs_unmount(&lfs) => 0; + + lfs_mount(&lfs, &cfg) => 0; + lfs_file_open(&lfs, &file, "hello", LFS_O_RDONLY) => 0; + lfs_file_read(&lfs, &file, buffer, size) => size; + assert(strcmp((char*)buffer, "Hello World!") == 0); + lfs_file_close(&lfs, &file) => 0; + lfs_unmount(&lfs) => 0; +''' + +[[case]] # larger files +define.SIZE = [32, 8192, 262144, 0, 7, 8193] +define.CHUNKSIZE = [31, 16, 33, 1, 1023] +code = ''' + lfs_format(&lfs, &cfg) => 0; + + // write + lfs_mount(&lfs, &cfg) => 0; + lfs_file_open(&lfs, &file, "avacado", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + srand(1); + for (lfs_size_t i = 0; i < SIZE; i += CHUNKSIZE) { + lfs_size_t chunk = lfs_min(CHUNKSIZE, SIZE-i); + for (lfs_size_t b = 0; b < chunk; b++) { + buffer[b] = rand() & 0xff; + } + lfs_file_write(&lfs, &file, buffer, chunk) => chunk; + } + lfs_file_close(&lfs, &file) => 0; + lfs_unmount(&lfs) => 0; + + // read + lfs_mount(&lfs, &cfg) => 0; + lfs_file_open(&lfs, &file, "avacado", LFS_O_RDONLY) => 0; + lfs_file_size(&lfs, &file) => SIZE; + srand(1); + for (lfs_size_t i = 0; i < SIZE; i += CHUNKSIZE) { + lfs_size_t chunk = lfs_min(CHUNKSIZE, SIZE-i); + lfs_file_read(&lfs, &file, buffer, chunk) => chunk; + for (lfs_size_t b = 0; b < chunk; b++) { + assert(buffer[b] == (rand() & 0xff)); + } + } + lfs_file_read(&lfs, &file, buffer, CHUNKSIZE) => 0; + lfs_file_close(&lfs, &file) => 0; + lfs_unmount(&lfs) => 0; +''' + +[[case]] # rewriting files +define.SIZE1 = [32, 8192, 131072, 0, 7, 8193] +define.SIZE2 = [32, 8192, 131072, 0, 7, 8193] +define.CHUNKSIZE = [31, 16, 1] +code = ''' + lfs_format(&lfs, &cfg) => 0; + + // write + lfs_mount(&lfs, &cfg) => 0; + lfs_file_open(&lfs, &file, "avacado", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + srand(1); + for (lfs_size_t i = 0; i < SIZE1; i += CHUNKSIZE) { + lfs_size_t chunk = lfs_min(CHUNKSIZE, SIZE1-i); + for (lfs_size_t b = 0; b < chunk; b++) { + buffer[b] = rand() & 0xff; + } + lfs_file_write(&lfs, &file, buffer, chunk) => chunk; + } + lfs_file_close(&lfs, &file) => 0; + lfs_unmount(&lfs) => 0; + + // read + lfs_mount(&lfs, &cfg) => 0; + lfs_file_open(&lfs, &file, "avacado", LFS_O_RDONLY) => 0; + lfs_file_size(&lfs, &file) => SIZE1; + srand(1); + for (lfs_size_t i = 0; i < SIZE1; i += CHUNKSIZE) { + lfs_size_t chunk = lfs_min(CHUNKSIZE, SIZE1-i); + lfs_file_read(&lfs, &file, buffer, chunk) => chunk; + for (lfs_size_t b = 0; b < chunk; b++) { + assert(buffer[b] == (rand() & 0xff)); + } + } + lfs_file_read(&lfs, &file, buffer, CHUNKSIZE) => 0; + lfs_file_close(&lfs, &file) => 0; + lfs_unmount(&lfs) => 0; + + // rewrite + lfs_mount(&lfs, &cfg) => 0; + lfs_file_open(&lfs, &file, "avacado", LFS_O_WRONLY) => 0; + srand(2); + for (lfs_size_t i = 0; i < SIZE2; i += CHUNKSIZE) { + lfs_size_t chunk = lfs_min(CHUNKSIZE, SIZE2-i); + for (lfs_size_t b = 0; b < chunk; b++) { + buffer[b] = rand() & 0xff; + } + lfs_file_write(&lfs, &file, buffer, chunk) => chunk; + } + lfs_file_close(&lfs, &file) => 0; + lfs_unmount(&lfs) => 0; + + // read + lfs_mount(&lfs, &cfg) => 0; + lfs_file_open(&lfs, &file, "avacado", LFS_O_RDONLY) => 0; + lfs_file_size(&lfs, &file) => lfs_max(SIZE1, SIZE2); + srand(2); + for (lfs_size_t i = 0; i < SIZE2; i += CHUNKSIZE) { + lfs_size_t chunk = lfs_min(CHUNKSIZE, SIZE2-i); + lfs_file_read(&lfs, &file, buffer, chunk) => chunk; + for (lfs_size_t b = 0; b < chunk; b++) { + assert(buffer[b] == (rand() & 0xff)); + } + } + if (SIZE1 > SIZE2) { + srand(1); + for (lfs_size_t b = 0; b < SIZE2; b++) { + rand(); + } + for (lfs_size_t i = SIZE2; i < SIZE1; i += CHUNKSIZE) { + lfs_size_t chunk = lfs_min(CHUNKSIZE, SIZE1-i); + lfs_file_read(&lfs, &file, buffer, chunk) => chunk; + for (lfs_size_t b = 0; b < chunk; b++) { + assert(buffer[b] == (rand() & 0xff)); + } + } + } + lfs_file_read(&lfs, &file, buffer, CHUNKSIZE) => 0; + lfs_file_close(&lfs, &file) => 0; + lfs_unmount(&lfs) => 0; +''' + +[[case]] # appending files +define.SIZE1 = [32, 8192, 131072, 0, 7, 8193] +define.SIZE2 = [32, 8192, 131072, 0, 7, 8193] +define.CHUNKSIZE = [31, 16, 1] +code = ''' + lfs_format(&lfs, &cfg) => 0; + + // write + lfs_mount(&lfs, &cfg) => 0; + lfs_file_open(&lfs, &file, "avacado", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + srand(1); + for (lfs_size_t i = 0; i < SIZE1; i += CHUNKSIZE) { + lfs_size_t chunk = lfs_min(CHUNKSIZE, SIZE1-i); + for (lfs_size_t b = 0; b < chunk; b++) { + buffer[b] = rand() & 0xff; + } + lfs_file_write(&lfs, &file, buffer, chunk) => chunk; + } + lfs_file_close(&lfs, &file) => 0; + lfs_unmount(&lfs) => 0; + + // read + lfs_mount(&lfs, &cfg) => 0; + lfs_file_open(&lfs, &file, "avacado", LFS_O_RDONLY) => 0; + lfs_file_size(&lfs, &file) => SIZE1; + srand(1); + for (lfs_size_t i = 0; i < SIZE1; i += CHUNKSIZE) { + lfs_size_t chunk = lfs_min(CHUNKSIZE, SIZE1-i); + lfs_file_read(&lfs, &file, buffer, chunk) => chunk; + for (lfs_size_t b = 0; b < chunk; b++) { + assert(buffer[b] == (rand() & 0xff)); + } + } + lfs_file_read(&lfs, &file, buffer, CHUNKSIZE) => 0; + lfs_file_close(&lfs, &file) => 0; + lfs_unmount(&lfs) => 0; + + // append + lfs_mount(&lfs, &cfg) => 0; + lfs_file_open(&lfs, &file, "avacado", LFS_O_WRONLY | LFS_O_APPEND) => 0; + srand(2); + for (lfs_size_t i = 0; i < SIZE2; i += CHUNKSIZE) { + lfs_size_t chunk = lfs_min(CHUNKSIZE, SIZE2-i); + for (lfs_size_t b = 0; b < chunk; b++) { + buffer[b] = rand() & 0xff; + } + lfs_file_write(&lfs, &file, buffer, chunk) => chunk; + } + lfs_file_close(&lfs, &file) => 0; + lfs_unmount(&lfs) => 0; + + // read + lfs_mount(&lfs, &cfg) => 0; + lfs_file_open(&lfs, &file, "avacado", LFS_O_RDONLY) => 0; + lfs_file_size(&lfs, &file) => SIZE1 + SIZE2; + srand(1); + for (lfs_size_t i = 0; i < SIZE1; i += CHUNKSIZE) { + lfs_size_t chunk = lfs_min(CHUNKSIZE, SIZE1-i); + lfs_file_read(&lfs, &file, buffer, chunk) => chunk; + for (lfs_size_t b = 0; b < chunk; b++) { + assert(buffer[b] == (rand() & 0xff)); + } + } + srand(2); + for (lfs_size_t i = 0; i < SIZE2; i += CHUNKSIZE) { + lfs_size_t chunk = lfs_min(CHUNKSIZE, SIZE2-i); + lfs_file_read(&lfs, &file, buffer, chunk) => chunk; + for (lfs_size_t b = 0; b < chunk; b++) { + assert(buffer[b] == (rand() & 0xff)); + } + } + lfs_file_read(&lfs, &file, buffer, CHUNKSIZE) => 0; + lfs_file_close(&lfs, &file) => 0; + lfs_unmount(&lfs) => 0; +''' + +[[case]] # truncating files +define.SIZE1 = [32, 8192, 131072, 0, 7, 8193] +define.SIZE2 = [32, 8192, 131072, 0, 7, 8193] +define.CHUNKSIZE = [31, 16, 1] +code = ''' + lfs_format(&lfs, &cfg) => 0; + + // write + lfs_mount(&lfs, &cfg) => 0; + lfs_file_open(&lfs, &file, "avacado", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + srand(1); + for (lfs_size_t i = 0; i < SIZE1; i += CHUNKSIZE) { + lfs_size_t chunk = lfs_min(CHUNKSIZE, SIZE1-i); + for (lfs_size_t b = 0; b < chunk; b++) { + buffer[b] = rand() & 0xff; + } + lfs_file_write(&lfs, &file, buffer, chunk) => chunk; + } + lfs_file_close(&lfs, &file) => 0; + lfs_unmount(&lfs) => 0; + + // read + lfs_mount(&lfs, &cfg) => 0; + lfs_file_open(&lfs, &file, "avacado", LFS_O_RDONLY) => 0; + lfs_file_size(&lfs, &file) => SIZE1; + srand(1); + for (lfs_size_t i = 0; i < SIZE1; i += CHUNKSIZE) { + lfs_size_t chunk = lfs_min(CHUNKSIZE, SIZE1-i); + lfs_file_read(&lfs, &file, buffer, chunk) => chunk; + for (lfs_size_t b = 0; b < chunk; b++) { + assert(buffer[b] == (rand() & 0xff)); + } + } + lfs_file_read(&lfs, &file, buffer, CHUNKSIZE) => 0; + lfs_file_close(&lfs, &file) => 0; + lfs_unmount(&lfs) => 0; + + // truncate + lfs_mount(&lfs, &cfg) => 0; + lfs_file_open(&lfs, &file, "avacado", LFS_O_WRONLY | LFS_O_TRUNC) => 0; + srand(2); + for (lfs_size_t i = 0; i < SIZE2; i += CHUNKSIZE) { + lfs_size_t chunk = lfs_min(CHUNKSIZE, SIZE2-i); + for (lfs_size_t b = 0; b < chunk; b++) { + buffer[b] = rand() & 0xff; + } + lfs_file_write(&lfs, &file, buffer, chunk) => chunk; + } + lfs_file_close(&lfs, &file) => 0; + lfs_unmount(&lfs) => 0; + + // read + lfs_mount(&lfs, &cfg) => 0; + lfs_file_open(&lfs, &file, "avacado", LFS_O_RDONLY) => 0; + lfs_file_size(&lfs, &file) => SIZE2; + srand(2); + for (lfs_size_t i = 0; i < SIZE2; i += CHUNKSIZE) { + lfs_size_t chunk = lfs_min(CHUNKSIZE, SIZE2-i); + lfs_file_read(&lfs, &file, buffer, chunk) => chunk; + for (lfs_size_t b = 0; b < chunk; b++) { + assert(buffer[b] == (rand() & 0xff)); + } + } + lfs_file_read(&lfs, &file, buffer, CHUNKSIZE) => 0; + lfs_file_close(&lfs, &file) => 0; + lfs_unmount(&lfs) => 0; +''' + +[[case]] # reentrant file writing +define.SIZE = [32, 0, 7, 2049] +define.CHUNKSIZE = [31, 16, 65] +reentrant = true +code = ''' + err = lfs_mount(&lfs, &cfg); + if (err) { + lfs_format(&lfs, &cfg) => 0; + lfs_mount(&lfs, &cfg) => 0; + } + + err = lfs_file_open(&lfs, &file, "avacado", LFS_O_RDONLY); + assert(err == LFS_ERR_NOENT || err == 0); + if (err == 0) { + // can only be 0 (new file) or full size + size = lfs_file_size(&lfs, &file); + assert(size == 0 || size == SIZE); + lfs_file_close(&lfs, &file) => 0; + } + + // write + lfs_file_open(&lfs, &file, "avacado", LFS_O_WRONLY | LFS_O_CREAT) => 0; + srand(1); + for (lfs_size_t i = 0; i < SIZE; i += CHUNKSIZE) { + lfs_size_t chunk = lfs_min(CHUNKSIZE, SIZE-i); + for (lfs_size_t b = 0; b < chunk; b++) { + buffer[b] = rand() & 0xff; + } + lfs_file_write(&lfs, &file, buffer, chunk) => chunk; + } + lfs_file_close(&lfs, &file) => 0; + + // read + lfs_file_open(&lfs, &file, "avacado", LFS_O_RDONLY) => 0; + lfs_file_size(&lfs, &file) => SIZE; + srand(1); + for (lfs_size_t i = 0; i < SIZE; i += CHUNKSIZE) { + lfs_size_t chunk = lfs_min(CHUNKSIZE, SIZE-i); + lfs_file_read(&lfs, &file, buffer, chunk) => chunk; + for (lfs_size_t b = 0; b < chunk; b++) { + assert(buffer[b] == (rand() & 0xff)); + } + } + lfs_file_read(&lfs, &file, buffer, CHUNKSIZE) => 0; + lfs_file_close(&lfs, &file) => 0; + lfs_unmount(&lfs) => 0; +''' + +[[case]] # reentrant file writing with syncs +define = [ + # append (O(n)) + {MODE='LFS_O_APPEND', SIZE=[32, 0, 7, 2049], CHUNKSIZE=[31, 16, 65]}, + # truncate (O(n^2)) + {MODE='LFS_O_TRUNC', SIZE=[32, 0, 7, 200], CHUNKSIZE=[31, 16, 65]}, + # rewrite (O(n^2)) + {MODE=0, SIZE=[32, 0, 7, 200], CHUNKSIZE=[31, 16, 65]}, +] +reentrant = true +code = ''' + err = lfs_mount(&lfs, &cfg); + if (err) { + lfs_format(&lfs, &cfg) => 0; + lfs_mount(&lfs, &cfg) => 0; + } + + err = lfs_file_open(&lfs, &file, "avacado", LFS_O_RDONLY); + assert(err == LFS_ERR_NOENT || err == 0); + if (err == 0) { + // with syncs we could be any size, but it at least must be valid data + size = lfs_file_size(&lfs, &file); + assert(size <= SIZE); + srand(1); + for (lfs_size_t i = 0; i < size; i += CHUNKSIZE) { + lfs_size_t chunk = lfs_min(CHUNKSIZE, size-i); + lfs_file_read(&lfs, &file, buffer, chunk) => chunk; + for (lfs_size_t b = 0; b < chunk; b++) { + assert(buffer[b] == (rand() & 0xff)); + } + } + lfs_file_close(&lfs, &file) => 0; + } + + // write + lfs_file_open(&lfs, &file, "avacado", + LFS_O_WRONLY | LFS_O_CREAT | MODE) => 0; + size = lfs_file_size(&lfs, &file); + assert(size <= SIZE); + srand(1); + lfs_size_t skip = (MODE == LFS_O_APPEND) ? size : 0; + for (lfs_size_t b = 0; b < skip; b++) { + rand(); + } + for (lfs_size_t i = skip; i < SIZE; i += CHUNKSIZE) { + lfs_size_t chunk = lfs_min(CHUNKSIZE, SIZE-i); + for (lfs_size_t b = 0; b < chunk; b++) { + buffer[b] = rand() & 0xff; + } + lfs_file_write(&lfs, &file, buffer, chunk) => chunk; + lfs_file_sync(&lfs, &file) => 0; + } + lfs_file_close(&lfs, &file) => 0; + + // read + lfs_file_open(&lfs, &file, "avacado", LFS_O_RDONLY) => 0; + lfs_file_size(&lfs, &file) => SIZE; + srand(1); + for (lfs_size_t i = 0; i < SIZE; i += CHUNKSIZE) { + lfs_size_t chunk = lfs_min(CHUNKSIZE, SIZE-i); + lfs_file_read(&lfs, &file, buffer, chunk) => chunk; + for (lfs_size_t b = 0; b < chunk; b++) { + assert(buffer[b] == (rand() & 0xff)); + } + } + lfs_file_read(&lfs, &file, buffer, CHUNKSIZE) => 0; + lfs_file_close(&lfs, &file) => 0; + lfs_unmount(&lfs) => 0; +''' + +[[case]] # many files +define.N = 300 +code = ''' + lfs_format(&lfs, &cfg) => 0; + // create N files of 7 bytes + lfs_mount(&lfs, &cfg) => 0; + for (int i = 0; i < N; i++) { + sprintf(path, "file_%03d", i); + lfs_file_open(&lfs, &file, path, + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + char wbuffer[1024]; + size = 7; + snprintf(wbuffer, size, "Hi %03d", i); + lfs_file_write(&lfs, &file, wbuffer, size) => size; + lfs_file_close(&lfs, &file) => 0; + + char rbuffer[1024]; + lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0; + lfs_file_read(&lfs, &file, rbuffer, size) => size; + assert(strcmp(rbuffer, wbuffer) == 0); + lfs_file_close(&lfs, &file) => 0; + } + lfs_unmount(&lfs) => 0; +''' + +[[case]] # many files with power cycle +define.N = 300 +code = ''' + lfs_format(&lfs, &cfg) => 0; + // create N files of 7 bytes + lfs_mount(&lfs, &cfg) => 0; + for (int i = 0; i < N; i++) { + sprintf(path, "file_%03d", i); + lfs_file_open(&lfs, &file, path, + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + char wbuffer[1024]; + size = 7; + snprintf(wbuffer, size, "Hi %03d", i); + lfs_file_write(&lfs, &file, wbuffer, size) => size; + lfs_file_close(&lfs, &file) => 0; + lfs_unmount(&lfs) => 0; + + char rbuffer[1024]; + lfs_mount(&lfs, &cfg) => 0; + lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0; + lfs_file_read(&lfs, &file, rbuffer, size) => size; + assert(strcmp(rbuffer, wbuffer) == 0); + lfs_file_close(&lfs, &file) => 0; + } + lfs_unmount(&lfs) => 0; +''' + +[[case]] # many files with power loss +define.N = 300 +reentrant = true +code = ''' + err = lfs_mount(&lfs, &cfg); + if (err) { + lfs_format(&lfs, &cfg) => 0; + lfs_mount(&lfs, &cfg) => 0; + } + // create N files of 7 bytes + for (int i = 0; i < N; i++) { + sprintf(path, "file_%03d", i); + err = lfs_file_open(&lfs, &file, path, LFS_O_WRONLY | LFS_O_CREAT); + char wbuffer[1024]; + size = 7; + snprintf(wbuffer, size, "Hi %03d", i); + if ((lfs_size_t)lfs_file_size(&lfs, &file) != size) { + lfs_file_write(&lfs, &file, wbuffer, size) => size; + } + lfs_file_close(&lfs, &file) => 0; + + char rbuffer[1024]; + lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0; + lfs_file_read(&lfs, &file, rbuffer, size) => size; + assert(strcmp(rbuffer, wbuffer) == 0); + lfs_file_close(&lfs, &file) => 0; + } + lfs_unmount(&lfs) => 0; +''' diff --git a/tests/test_format.sh b/tests/test_format.sh deleted file mode 100755 index f0972bda..00000000 --- a/tests/test_format.sh +++ /dev/null @@ -1,51 +0,0 @@ -#!/bin/bash -set -eu -export TEST_FILE=$0 -trap 'export TEST_LINE=$LINENO' DEBUG - -echo "=== Formatting tests ===" -rm -rf blocks - -echo "--- Basic formatting ---" -scripts/test.py << TEST - lfs_format(&lfs, &cfg) => 0; -TEST - -echo "--- Basic mounting ---" -scripts/test.py << TEST - lfs_format(&lfs, &cfg) => 0; - - lfs_mount(&lfs, &cfg) => 0; - lfs_unmount(&lfs) => 0; -TEST - -echo "--- Invalid superblocks ---" -ln -f -s /dev/zero blocks/0 -ln -f -s /dev/zero blocks/1 -scripts/test.py << TEST - lfs_format(&lfs, &cfg) => LFS_ERR_NOSPC; -TEST -rm blocks/0 blocks/1 - -echo "--- Invalid mount ---" -scripts/test.py << TEST - lfs_mount(&lfs, &cfg) => LFS_ERR_CORRUPT; -TEST - -echo "--- Expanding superblock ---" -scripts/test.py << TEST - lfs_format(&lfs, &cfg) => 0; - lfs_mount(&lfs, &cfg) => 0; - for (int i = 0; i < 100; i++) { - lfs_mkdir(&lfs, "dummy") => 0; - lfs_remove(&lfs, "dummy") => 0; - } - lfs_unmount(&lfs) => 0; -TEST -scripts/test.py << TEST - lfs_mount(&lfs, &cfg) => 0; - lfs_mkdir(&lfs, "dummy") => 0; - lfs_unmount(&lfs) => 0; -TEST - -scripts/results.py diff --git a/tests/test_interspersed.sh b/tests/test_interspersed.sh deleted file mode 100755 index 84c5dd87..00000000 --- a/tests/test_interspersed.sh +++ /dev/null @@ -1,190 +0,0 @@ -#!/bin/bash -set -eu -export TEST_FILE=$0 -trap 'export TEST_LINE=$LINENO' DEBUG - -echo "=== Interspersed tests ===" -rm -rf blocks -scripts/test.py << TEST - lfs_format(&lfs, &cfg) => 0; -TEST - -echo "--- Interspersed file test ---" -scripts/test.py << TEST - lfs_mount(&lfs, &cfg) => 0; - lfs_file_t files[4]; - lfs_file_open(&lfs, &files[0], "a", LFS_O_WRONLY | LFS_O_CREAT) => 0; - lfs_file_open(&lfs, &files[1], "b", LFS_O_WRONLY | LFS_O_CREAT) => 0; - lfs_file_open(&lfs, &files[2], "c", LFS_O_WRONLY | LFS_O_CREAT) => 0; - lfs_file_open(&lfs, &files[3], "d", LFS_O_WRONLY | LFS_O_CREAT) => 0; - - for (int i = 0; i < 10; i++) { - lfs_file_write(&lfs, &files[0], (const void*)"a", 1) => 1; - lfs_file_write(&lfs, &files[1], (const void*)"b", 1) => 1; - lfs_file_write(&lfs, &files[2], (const void*)"c", 1) => 1; - lfs_file_write(&lfs, &files[3], (const void*)"d", 1) => 1; - } - - lfs_file_close(&lfs, &files[0]); - lfs_file_close(&lfs, &files[1]); - lfs_file_close(&lfs, &files[2]); - lfs_file_close(&lfs, &files[3]); - - lfs_dir_open(&lfs, &dir, "/") => 0; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, ".") => 0; - info.type => LFS_TYPE_DIR; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, "..") => 0; - info.type => LFS_TYPE_DIR; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, "a") => 0; - info.type => LFS_TYPE_REG; - info.size => 10; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, "b") => 0; - info.type => LFS_TYPE_REG; - info.size => 10; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, "c") => 0; - info.type => LFS_TYPE_REG; - info.size => 10; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, "d") => 0; - info.type => LFS_TYPE_REG; - info.size => 10; - lfs_dir_read(&lfs, &dir, &info) => 0; - lfs_dir_close(&lfs, &dir) => 0; - - lfs_file_open(&lfs, &files[0], "a", LFS_O_RDONLY) => 0; - lfs_file_open(&lfs, &files[1], "b", LFS_O_RDONLY) => 0; - lfs_file_open(&lfs, &files[2], "c", LFS_O_RDONLY) => 0; - lfs_file_open(&lfs, &files[3], "d", LFS_O_RDONLY) => 0; - - for (int i = 0; i < 10; i++) { - lfs_file_read(&lfs, &files[0], buffer, 1) => 1; - buffer[0] => 'a'; - lfs_file_read(&lfs, &files[1], buffer, 1) => 1; - buffer[0] => 'b'; - lfs_file_read(&lfs, &files[2], buffer, 1) => 1; - buffer[0] => 'c'; - lfs_file_read(&lfs, &files[3], buffer, 1) => 1; - buffer[0] => 'd'; - } - - lfs_file_close(&lfs, &files[0]); - lfs_file_close(&lfs, &files[1]); - lfs_file_close(&lfs, &files[2]); - lfs_file_close(&lfs, &files[3]); - - lfs_unmount(&lfs) => 0; -TEST - -echo "--- Interspersed remove file test ---" -scripts/test.py << TEST - lfs_mount(&lfs, &cfg) => 0; - lfs_file_t files[4]; - lfs_file_open(&lfs, &files[0], "e", LFS_O_WRONLY | LFS_O_CREAT) => 0; - - for (int i = 0; i < 5; i++) { - lfs_file_write(&lfs, &files[0], (const void*)"e", 1) => 1; - } - - lfs_remove(&lfs, "a") => 0; - lfs_remove(&lfs, "b") => 0; - lfs_remove(&lfs, "c") => 0; - lfs_remove(&lfs, "d") => 0; - - for (int i = 0; i < 5; i++) { - lfs_file_write(&lfs, &files[0], (const void*)"e", 1) => 1; - } - - lfs_file_close(&lfs, &files[0]); - - lfs_dir_open(&lfs, &dir, "/") => 0; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, ".") => 0; - info.type => LFS_TYPE_DIR; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, "..") => 0; - info.type => LFS_TYPE_DIR; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, "e") => 0; - info.type => LFS_TYPE_REG; - info.size => 10; - lfs_dir_read(&lfs, &dir, &info) => 0; - lfs_dir_close(&lfs, &dir) => 0; - - lfs_file_open(&lfs, &files[0], "e", LFS_O_RDONLY) => 0; - - for (int i = 0; i < 10; i++) { - lfs_file_read(&lfs, &files[0], buffer, 1) => 1; - buffer[0] => 'e'; - } - - lfs_file_close(&lfs, &files[0]); - - lfs_unmount(&lfs) => 0; -TEST - -echo "--- Remove inconveniently test ---" -scripts/test.py << TEST - lfs_mount(&lfs, &cfg) => 0; - lfs_file_t files[4]; - lfs_file_open(&lfs, &files[0], "e", LFS_O_WRONLY | LFS_O_TRUNC) => 0; - lfs_file_open(&lfs, &files[1], "f", LFS_O_WRONLY | LFS_O_CREAT) => 0; - lfs_file_open(&lfs, &files[2], "g", LFS_O_WRONLY | LFS_O_CREAT) => 0; - - for (int i = 0; i < 5; i++) { - lfs_file_write(&lfs, &files[0], (const void*)"e", 1) => 1; - lfs_file_write(&lfs, &files[1], (const void*)"f", 1) => 1; - lfs_file_write(&lfs, &files[2], (const void*)"g", 1) => 1; - } - - lfs_remove(&lfs, "f") => 0; - - for (int i = 0; i < 5; i++) { - lfs_file_write(&lfs, &files[0], (const void*)"e", 1) => 1; - lfs_file_write(&lfs, &files[1], (const void*)"f", 1) => 1; - lfs_file_write(&lfs, &files[2], (const void*)"g", 1) => 1; - } - - lfs_file_close(&lfs, &files[0]); - lfs_file_close(&lfs, &files[1]); - lfs_file_close(&lfs, &files[2]); - - lfs_dir_open(&lfs, &dir, "/") => 0; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, ".") => 0; - info.type => LFS_TYPE_DIR; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, "..") => 0; - info.type => LFS_TYPE_DIR; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, "e") => 0; - info.type => LFS_TYPE_REG; - info.size => 10; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, "g") => 0; - info.type => LFS_TYPE_REG; - info.size => 10; - lfs_dir_read(&lfs, &dir, &info) => 0; - lfs_dir_close(&lfs, &dir) => 0; - - lfs_file_open(&lfs, &files[0], "e", LFS_O_RDONLY) => 0; - lfs_file_open(&lfs, &files[1], "g", LFS_O_RDONLY) => 0; - - for (int i = 0; i < 10; i++) { - lfs_file_read(&lfs, &files[0], buffer, 1) => 1; - buffer[0] => 'e'; - lfs_file_read(&lfs, &files[1], buffer, 1) => 1; - buffer[0] => 'g'; - } - - lfs_file_close(&lfs, &files[0]); - lfs_file_close(&lfs, &files[1]); - - lfs_unmount(&lfs) => 0; -TEST - -scripts/results.py diff --git a/tests/test_interspersed.toml b/tests/test_interspersed.toml new file mode 100644 index 00000000..87a05780 --- /dev/null +++ b/tests/test_interspersed.toml @@ -0,0 +1,244 @@ + +[[case]] # interspersed file test +define.SIZE = [10, 100] +define.FILES = [4, 10, 26] +code = ''' + lfs_file_t files[FILES]; + const char alphas[] = "abcdefghijklmnopqrstuvwxyz"; + lfs_format(&lfs, &cfg) => 0; + lfs_mount(&lfs, &cfg) => 0; + for (int j = 0; j < FILES; j++) { + sprintf(path, "%c", alphas[j]); + lfs_file_open(&lfs, &files[j], path, + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + } + + for (int i = 0; i < SIZE; i++) { + for (int j = 0; j < FILES; j++) { + lfs_file_write(&lfs, &files[j], &alphas[j], 1) => 1; + } + } + + for (int j = 0; j < FILES; j++) { + lfs_file_close(&lfs, &files[j]); + } + + lfs_dir_open(&lfs, &dir, "/") => 0; + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, ".") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "..") == 0); + assert(info.type == LFS_TYPE_DIR); + for (int j = 0; j < FILES; j++) { + sprintf(path, "%c", alphas[j]); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, path) == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == SIZE); + } + lfs_dir_read(&lfs, &dir, &info) => 0; + lfs_dir_close(&lfs, &dir) => 0; + + for (int j = 0; j < FILES; j++) { + sprintf(path, "%c", alphas[j]); + lfs_file_open(&lfs, &files[j], path, LFS_O_RDONLY) => 0; + } + + for (int i = 0; i < 10; i++) { + for (int j = 0; j < FILES; j++) { + lfs_file_read(&lfs, &files[j], buffer, 1) => 1; + assert(buffer[0] == alphas[j]); + } + } + + for (int j = 0; j < FILES; j++) { + lfs_file_close(&lfs, &files[j]); + } + + lfs_unmount(&lfs) => 0; +''' + +[[case]] # interspersed remove file test +define.SIZE = [10, 100] +define.FILES = [4, 10, 26] +code = ''' + const char alphas[] = "abcdefghijklmnopqrstuvwxyz"; + lfs_format(&lfs, &cfg) => 0; + lfs_mount(&lfs, &cfg) => 0; + for (int j = 0; j < FILES; j++) { + sprintf(path, "%c", alphas[j]); + lfs_file_open(&lfs, &file, path, + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + for (int i = 0; i < SIZE; i++) { + lfs_file_write(&lfs, &file, &alphas[j], 1) => 1; + } + lfs_file_close(&lfs, &file); + } + lfs_unmount(&lfs) => 0; + + lfs_mount(&lfs, &cfg) => 0; + lfs_file_open(&lfs, &file, "zzz", LFS_O_WRONLY | LFS_O_CREAT) => 0; + for (int j = 0; j < FILES; j++) { + lfs_file_write(&lfs, &file, (const void*)"~", 1) => 1; + lfs_file_sync(&lfs, &file) => 0; + + sprintf(path, "%c", alphas[j]); + lfs_remove(&lfs, path) => 0; + } + lfs_file_close(&lfs, &file); + + lfs_dir_open(&lfs, &dir, "/") => 0; + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, ".") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "..") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "zzz") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == FILES); + lfs_dir_read(&lfs, &dir, &info) => 0; + lfs_dir_close(&lfs, &dir) => 0; + + lfs_file_open(&lfs, &file, "zzz", LFS_O_RDONLY) => 0; + for (int i = 0; i < FILES; i++) { + lfs_file_read(&lfs, &file, buffer, 1) => 1; + assert(buffer[0] == '~'); + } + lfs_file_close(&lfs, &file); + + lfs_unmount(&lfs) => 0; +''' + +[[case]] # remove inconveniently test +define.SIZE = [10, 100] +code = ''' + lfs_format(&lfs, &cfg) => 0; + lfs_mount(&lfs, &cfg) => 0; + lfs_file_t files[3]; + lfs_file_open(&lfs, &files[0], "e", LFS_O_WRONLY | LFS_O_CREAT) => 0; + lfs_file_open(&lfs, &files[1], "f", LFS_O_WRONLY | LFS_O_CREAT) => 0; + lfs_file_open(&lfs, &files[2], "g", LFS_O_WRONLY | LFS_O_CREAT) => 0; + + for (int i = 0; i < SIZE/2; i++) { + lfs_file_write(&lfs, &files[0], (const void*)"e", 1) => 1; + lfs_file_write(&lfs, &files[1], (const void*)"f", 1) => 1; + lfs_file_write(&lfs, &files[2], (const void*)"g", 1) => 1; + } + + lfs_remove(&lfs, "f") => 0; + + for (int i = 0; i < SIZE/2; i++) { + lfs_file_write(&lfs, &files[0], (const void*)"e", 1) => 1; + lfs_file_write(&lfs, &files[1], (const void*)"f", 1) => 1; + lfs_file_write(&lfs, &files[2], (const void*)"g", 1) => 1; + } + + lfs_file_close(&lfs, &files[0]); + lfs_file_close(&lfs, &files[1]); + lfs_file_close(&lfs, &files[2]); + + lfs_dir_open(&lfs, &dir, "/") => 0; + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, ".") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "..") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "e") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == SIZE); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "g") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == SIZE); + lfs_dir_read(&lfs, &dir, &info) => 0; + lfs_dir_close(&lfs, &dir) => 0; + + lfs_file_open(&lfs, &files[0], "e", LFS_O_RDONLY) => 0; + lfs_file_open(&lfs, &files[1], "g", LFS_O_RDONLY) => 0; + for (int i = 0; i < SIZE; i++) { + lfs_file_read(&lfs, &files[0], buffer, 1) => 1; + assert(buffer[0] == 'e'); + lfs_file_read(&lfs, &files[1], buffer, 1) => 1; + assert(buffer[0] == 'g'); + } + lfs_file_close(&lfs, &files[0]); + lfs_file_close(&lfs, &files[1]); + + lfs_unmount(&lfs) => 0; +''' + +[[case]] # reentrant interspersed file test +define.SIZE = [10, 100] +define.FILES = [4, 10, 26] +reentrant = true +code = ''' + lfs_file_t files[FILES]; + const char alphas[] = "abcdefghijklmnopqrstuvwxyz"; + + err = lfs_mount(&lfs, &cfg); + if (err) { + lfs_format(&lfs, &cfg) => 0; + lfs_mount(&lfs, &cfg) => 0; + } + + for (int j = 0; j < FILES; j++) { + sprintf(path, "%c", alphas[j]); + lfs_file_open(&lfs, &files[j], path, + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_APPEND) => 0; + } + + for (int i = 0; i < SIZE; i++) { + for (int j = 0; j < FILES; j++) { + size = lfs_file_size(&lfs, &files[j]); + assert((int)size >= 0); + if ((int)size <= i) { + lfs_file_write(&lfs, &files[j], &alphas[j], 1) => 1; + lfs_file_sync(&lfs, &files[j]) => 0; + } + } + } + + for (int j = 0; j < FILES; j++) { + lfs_file_close(&lfs, &files[j]); + } + + lfs_dir_open(&lfs, &dir, "/") => 0; + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, ".") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "..") == 0); + assert(info.type == LFS_TYPE_DIR); + for (int j = 0; j < FILES; j++) { + sprintf(path, "%c", alphas[j]); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, path) == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == SIZE); + } + lfs_dir_read(&lfs, &dir, &info) => 0; + lfs_dir_close(&lfs, &dir) => 0; + + for (int j = 0; j < FILES; j++) { + sprintf(path, "%c", alphas[j]); + lfs_file_open(&lfs, &files[j], path, LFS_O_RDONLY) => 0; + } + + for (int i = 0; i < 10; i++) { + for (int j = 0; j < FILES; j++) { + lfs_file_read(&lfs, &files[j], buffer, 1) => 1; + assert(buffer[0] == alphas[j]); + } + } + + for (int j = 0; j < FILES; j++) { + lfs_file_close(&lfs, &files[j]); + } + + lfs_unmount(&lfs) => 0; +''' diff --git a/tests/test_move.sh b/tests/test_move.sh deleted file mode 100755 index f52ef22a..00000000 --- a/tests/test_move.sh +++ /dev/null @@ -1,333 +0,0 @@ -#!/bin/bash -set -eu -export TEST_FILE=$0 -trap 'export TEST_LINE=$LINENO' DEBUG - -echo "=== Move tests ===" -rm -rf blocks -scripts/test.py << TEST - lfs_format(&lfs, &cfg) => 0; - - lfs_mount(&lfs, &cfg) => 0; - lfs_mkdir(&lfs, "a") => 0; - lfs_mkdir(&lfs, "b") => 0; - lfs_mkdir(&lfs, "c") => 0; - lfs_mkdir(&lfs, "d") => 0; - - lfs_mkdir(&lfs, "a/hi") => 0; - lfs_mkdir(&lfs, "a/hi/hola") => 0; - lfs_mkdir(&lfs, "a/hi/bonjour") => 0; - lfs_mkdir(&lfs, "a/hi/ohayo") => 0; - - lfs_file_open(&lfs, &file, "a/hello", LFS_O_CREAT | LFS_O_WRONLY) => 0; - lfs_file_write(&lfs, &file, "hola\n", 5) => 5; - lfs_file_write(&lfs, &file, "bonjour\n", 8) => 8; - lfs_file_write(&lfs, &file, "ohayo\n", 6) => 6; - lfs_file_close(&lfs, &file) => 0; - lfs_unmount(&lfs) => 0; -TEST - -echo "--- Move file ---" -scripts/test.py << TEST - lfs_mount(&lfs, &cfg) => 0; - lfs_rename(&lfs, "a/hello", "b/hello") => 0; - lfs_unmount(&lfs) => 0; -TEST -scripts/test.py << TEST - lfs_mount(&lfs, &cfg) => 0; - lfs_dir_open(&lfs, &dir, "a") => 0; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, ".") => 0; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, "..") => 0; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, "hi") => 0; - lfs_dir_read(&lfs, &dir, &info) => 0; - lfs_dir_close(&lfs, &dir) => 0; - lfs_dir_open(&lfs, &dir, "b") => 0; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, ".") => 0; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, "..") => 0; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, "hello") => 0; - lfs_dir_read(&lfs, &dir, &info) => 0; - lfs_unmount(&lfs) => 0; -TEST - -echo "--- Move file corrupt source ---" -scripts/test.py << TEST - lfs_mount(&lfs, &cfg) => 0; - lfs_rename(&lfs, "b/hello", "c/hello") => 0; - lfs_unmount(&lfs) => 0; -TEST -scripts/corrupt.py -n 1 -scripts/test.py << TEST - lfs_mount(&lfs, &cfg) => 0; - lfs_dir_open(&lfs, &dir, "b") => 0; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, ".") => 0; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, "..") => 0; - lfs_dir_read(&lfs, &dir, &info) => 0; - lfs_dir_close(&lfs, &dir) => 0; - lfs_dir_open(&lfs, &dir, "c") => 0; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, ".") => 0; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, "..") => 0; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, "hello") => 0; - lfs_dir_read(&lfs, &dir, &info) => 0; - lfs_unmount(&lfs) => 0; -TEST - -echo "--- Move file corrupt source and dest ---" -scripts/test.py << TEST - lfs_mount(&lfs, &cfg) => 0; - lfs_rename(&lfs, "c/hello", "d/hello") => 0; - lfs_unmount(&lfs) => 0; -TEST -scripts/corrupt.py -n 2 -scripts/test.py << TEST - lfs_mount(&lfs, &cfg) => 0; - lfs_dir_open(&lfs, &dir, "c") => 0; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, ".") => 0; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, "..") => 0; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, "hello") => 0; - lfs_dir_read(&lfs, &dir, &info) => 0; - lfs_dir_close(&lfs, &dir) => 0; - lfs_dir_open(&lfs, &dir, "d") => 0; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, ".") => 0; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, "..") => 0; - lfs_dir_read(&lfs, &dir, &info) => 0; - lfs_unmount(&lfs) => 0; -TEST - -echo "--- Move file after corrupt ---" -scripts/test.py << TEST - lfs_mount(&lfs, &cfg) => 0; - lfs_rename(&lfs, "c/hello", "d/hello") => 0; - lfs_unmount(&lfs) => 0; -TEST -scripts/test.py << TEST - lfs_mount(&lfs, &cfg) => 0; - lfs_dir_open(&lfs, &dir, "c") => 0; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, ".") => 0; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, "..") => 0; - lfs_dir_read(&lfs, &dir, &info) => 0; - lfs_dir_close(&lfs, &dir) => 0; - lfs_dir_open(&lfs, &dir, "d") => 0; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, ".") => 0; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, "..") => 0; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, "hello") => 0; - lfs_dir_read(&lfs, &dir, &info) => 0; - lfs_unmount(&lfs) => 0; -TEST - -echo "--- Move dir ---" -scripts/test.py << TEST - lfs_mount(&lfs, &cfg) => 0; - lfs_rename(&lfs, "a/hi", "b/hi") => 0; - lfs_unmount(&lfs) => 0; -TEST -scripts/test.py << TEST - lfs_mount(&lfs, &cfg) => 0; - lfs_dir_open(&lfs, &dir, "a") => 0; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, ".") => 0; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, "..") => 0; - lfs_dir_read(&lfs, &dir, &info) => 0; - lfs_dir_close(&lfs, &dir) => 0; - lfs_dir_open(&lfs, &dir, "b") => 0; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, ".") => 0; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, "..") => 0; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, "hi") => 0; - lfs_dir_read(&lfs, &dir, &info) => 0; - lfs_unmount(&lfs) => 0; -TEST - -echo "--- Move dir corrupt source ---" -scripts/test.py << TEST - lfs_mount(&lfs, &cfg) => 0; - lfs_rename(&lfs, "b/hi", "c/hi") => 0; - lfs_unmount(&lfs) => 0; -TEST -scripts/corrupt.py -n 1 -scripts/test.py << TEST - lfs_mount(&lfs, &cfg) => 0; - lfs_dir_open(&lfs, &dir, "b") => 0; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, ".") => 0; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, "..") => 0; - lfs_dir_read(&lfs, &dir, &info) => 0; - lfs_dir_close(&lfs, &dir) => 0; - lfs_dir_open(&lfs, &dir, "c") => 0; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, ".") => 0; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, "..") => 0; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, "hi") => 0; - lfs_dir_read(&lfs, &dir, &info) => 0; - lfs_unmount(&lfs) => 0; -TEST - -echo "--- Move dir corrupt source and dest ---" -scripts/test.py << TEST - lfs_mount(&lfs, &cfg) => 0; - lfs_rename(&lfs, "c/hi", "d/hi") => 0; - lfs_unmount(&lfs) => 0; -TEST -scripts/corrupt.py -n 2 -scripts/test.py << TEST - lfs_mount(&lfs, &cfg) => 0; - lfs_dir_open(&lfs, &dir, "c") => 0; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, ".") => 0; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, "..") => 0; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, "hi") => 0; - lfs_dir_read(&lfs, &dir, &info) => 0; - lfs_dir_close(&lfs, &dir) => 0; - lfs_dir_open(&lfs, &dir, "d") => 0; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, ".") => 0; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, "..") => 0; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, "hello") => 0; - lfs_dir_read(&lfs, &dir, &info) => 0; - lfs_unmount(&lfs) => 0; -TEST - -echo "--- Move dir after corrupt ---" -scripts/test.py << TEST - lfs_mount(&lfs, &cfg) => 0; - lfs_rename(&lfs, "c/hi", "d/hi") => 0; - lfs_unmount(&lfs) => 0; -TEST -scripts/test.py << TEST - lfs_mount(&lfs, &cfg) => 0; - lfs_dir_open(&lfs, &dir, "c") => 0; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, ".") => 0; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, "..") => 0; - lfs_dir_read(&lfs, &dir, &info) => 0; - lfs_dir_close(&lfs, &dir) => 0; - lfs_dir_open(&lfs, &dir, "d") => 0; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, ".") => 0; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, "..") => 0; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, "hello") => 0; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, "hi") => 0; - lfs_dir_read(&lfs, &dir, &info) => 0; - lfs_unmount(&lfs) => 0; -TEST - -echo "--- Move check ---" -scripts/test.py << TEST - lfs_mount(&lfs, &cfg) => 0; - - lfs_dir_open(&lfs, &dir, "a/hi") => LFS_ERR_NOENT; - lfs_dir_open(&lfs, &dir, "b/hi") => LFS_ERR_NOENT; - lfs_dir_open(&lfs, &dir, "c/hi") => LFS_ERR_NOENT; - - lfs_dir_open(&lfs, &dir, "d/hi") => 0; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, ".") => 0; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, "..") => 0; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, "bonjour") => 0; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, "hola") => 0; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, "ohayo") => 0; - lfs_dir_read(&lfs, &dir, &info) => 0; - lfs_dir_close(&lfs, &dir) => 0; - - lfs_dir_open(&lfs, &dir, "a/hello") => LFS_ERR_NOENT; - lfs_dir_open(&lfs, &dir, "b/hello") => LFS_ERR_NOENT; - lfs_dir_open(&lfs, &dir, "c/hello") => LFS_ERR_NOENT; - - lfs_file_open(&lfs, &file, "d/hello", LFS_O_RDONLY) => 0; - lfs_file_read(&lfs, &file, buffer, 5) => 5; - memcmp(buffer, "hola\n", 5) => 0; - lfs_file_read(&lfs, &file, buffer, 8) => 8; - memcmp(buffer, "bonjour\n", 8) => 0; - lfs_file_read(&lfs, &file, buffer, 6) => 6; - memcmp(buffer, "ohayo\n", 6) => 0; - lfs_file_close(&lfs, &file) => 0; - - lfs_unmount(&lfs) => 0; -TEST - -echo "--- Move state stealing ---" -scripts/test.py << TEST - lfs_mount(&lfs, &cfg) => 0; - - lfs_remove(&lfs, "b") => 0; - lfs_remove(&lfs, "c") => 0; - - lfs_unmount(&lfs) => 0; -TEST -scripts/test.py << TEST - lfs_mount(&lfs, &cfg) => 0; - - lfs_dir_open(&lfs, &dir, "a/hi") => LFS_ERR_NOENT; - lfs_dir_open(&lfs, &dir, "b") => LFS_ERR_NOENT; - lfs_dir_open(&lfs, &dir, "c") => LFS_ERR_NOENT; - - lfs_dir_open(&lfs, &dir, "d/hi") => 0; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, ".") => 0; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, "..") => 0; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, "bonjour") => 0; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, "hola") => 0; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, "ohayo") => 0; - lfs_dir_read(&lfs, &dir, &info) => 0; - lfs_dir_close(&lfs, &dir) => 0; - - lfs_dir_open(&lfs, &dir, "a/hello") => LFS_ERR_NOENT; - lfs_dir_open(&lfs, &dir, "b") => LFS_ERR_NOENT; - lfs_dir_open(&lfs, &dir, "c") => LFS_ERR_NOENT; - - lfs_file_open(&lfs, &file, "d/hello", LFS_O_RDONLY) => 0; - lfs_file_read(&lfs, &file, buffer, 5) => 5; - memcmp(buffer, "hola\n", 5) => 0; - lfs_file_read(&lfs, &file, buffer, 8) => 8; - memcmp(buffer, "bonjour\n", 8) => 0; - lfs_file_read(&lfs, &file, buffer, 6) => 6; - memcmp(buffer, "ohayo\n", 6) => 0; - lfs_file_close(&lfs, &file) => 0; - - lfs_unmount(&lfs) => 0; -TEST - - -scripts/results.py diff --git a/tests/test_move.toml b/tests/test_move.toml new file mode 100644 index 00000000..bb3b713f --- /dev/null +++ b/tests/test_move.toml @@ -0,0 +1,1815 @@ +[[case]] # move file +code = ''' + lfs_format(&lfs, &cfg) => 0; + lfs_mount(&lfs, &cfg) => 0; + lfs_mkdir(&lfs, "a") => 0; + lfs_mkdir(&lfs, "b") => 0; + lfs_mkdir(&lfs, "c") => 0; + lfs_mkdir(&lfs, "d") => 0; + lfs_file_open(&lfs, &file, "a/hello", LFS_O_CREAT | LFS_O_WRONLY) => 0; + lfs_file_write(&lfs, &file, "hola\n", 5) => 5; + lfs_file_write(&lfs, &file, "bonjour\n", 8) => 8; + lfs_file_write(&lfs, &file, "ohayo\n", 6) => 6; + lfs_file_close(&lfs, &file) => 0; + lfs_unmount(&lfs) => 0; + + lfs_mount(&lfs, &cfg) => 0; + lfs_rename(&lfs, "a/hello", "c/hello") => 0; + lfs_unmount(&lfs) => 0; + + lfs_mount(&lfs, &cfg) => 0; + lfs_dir_open(&lfs, &dir, "a") => 0; + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, ".") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "..") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 0; + lfs_dir_close(&lfs, &dir) => 0; + lfs_dir_open(&lfs, &dir, "c") => 0; + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, ".") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "..") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "hello") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == 5+8+6); + lfs_dir_read(&lfs, &dir, &info) => 0; + lfs_dir_close(&lfs, &dir) => 0; + + lfs_file_open(&lfs, &file, "a/hello", LFS_O_RDONLY) => LFS_ERR_NOENT; + lfs_file_open(&lfs, &file, "b/hello", LFS_O_RDONLY) => LFS_ERR_NOENT; + lfs_file_open(&lfs, &file, "c/hello", LFS_O_RDONLY) => 0; + lfs_file_read(&lfs, &file, buffer, 5) => 5; + memcmp(buffer, "hola\n", 5) => 0; + lfs_file_read(&lfs, &file, buffer, 8) => 8; + memcmp(buffer, "bonjour\n", 8) => 0; + lfs_file_read(&lfs, &file, buffer, 6) => 6; + memcmp(buffer, "ohayo\n", 6) => 0; + lfs_file_close(&lfs, &file) => 0; + lfs_file_open(&lfs, &file, "d/hello", LFS_O_RDONLY) => LFS_ERR_NOENT; + lfs_unmount(&lfs) => 0; +''' + +[[case]] # noop move, yes this is legal +code = ''' + lfs_format(&lfs, &cfg) => 0; + lfs_mount(&lfs, &cfg) => 0; + lfs_mkdir(&lfs, "hi") => 0; + lfs_rename(&lfs, "hi", "hi") => 0; + lfs_mkdir(&lfs, "hi/hi") => 0; + lfs_rename(&lfs, "hi/hi", "hi/hi") => 0; + lfs_mkdir(&lfs, "hi/hi/hi") => 0; + lfs_rename(&lfs, "hi/hi/hi", "hi/hi/hi") => 0; + lfs_stat(&lfs, "hi/hi/hi", &info) => 0; + assert(strcmp(info.name, "hi") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_unmount(&lfs) => 0; +''' + +[[case]] # move file corrupt source +in = "lfs.c" +code = ''' + lfs_format(&lfs, &cfg) => 0; + lfs_mount(&lfs, &cfg) => 0; + lfs_mkdir(&lfs, "a") => 0; + lfs_mkdir(&lfs, "b") => 0; + lfs_mkdir(&lfs, "c") => 0; + lfs_mkdir(&lfs, "d") => 0; + lfs_file_open(&lfs, &file, "a/hello", LFS_O_CREAT | LFS_O_WRONLY) => 0; + lfs_file_write(&lfs, &file, "hola\n", 5) => 5; + lfs_file_write(&lfs, &file, "bonjour\n", 8) => 8; + lfs_file_write(&lfs, &file, "ohayo\n", 6) => 6; + lfs_file_close(&lfs, &file) => 0; + lfs_unmount(&lfs) => 0; + + lfs_mount(&lfs, &cfg) => 0; + lfs_rename(&lfs, "a/hello", "c/hello") => 0; + lfs_unmount(&lfs) => 0; + + // corrupt the source + lfs_mount(&lfs, &cfg) => 0; + lfs_dir_open(&lfs, &dir, "a") => 0; + lfs_block_t block = dir.m.pair[0]; + lfs_dir_close(&lfs, &dir) => 0; + lfs_unmount(&lfs) => 0; + uint8_t bbuffer[LFS_BLOCK_SIZE]; + cfg.read(&cfg, block, 0, bbuffer, LFS_BLOCK_SIZE) => 0; + int off = LFS_BLOCK_SIZE-1; + while (off >= 0 && bbuffer[off] == LFS_ERASE_VALUE) { + off -= 1; + } + memset(&bbuffer[off-3], LFS_BLOCK_SIZE, 3); + cfg.erase(&cfg, block) => 0; + cfg.prog(&cfg, block, 0, bbuffer, LFS_BLOCK_SIZE) => 0; + cfg.sync(&cfg) => 0; + + lfs_mount(&lfs, &cfg) => 0; + lfs_dir_open(&lfs, &dir, "a") => 0; + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, ".") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "..") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 0; + lfs_dir_close(&lfs, &dir) => 0; + lfs_dir_open(&lfs, &dir, "c") => 0; + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, ".") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "..") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "hello") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == 5+8+6); + lfs_dir_read(&lfs, &dir, &info) => 0; + lfs_dir_close(&lfs, &dir) => 0; + + lfs_file_open(&lfs, &file, "a/hello", LFS_O_RDONLY) => LFS_ERR_NOENT; + lfs_file_open(&lfs, &file, "b/hello", LFS_O_RDONLY) => LFS_ERR_NOENT; + lfs_file_open(&lfs, &file, "c/hello", LFS_O_RDONLY) => 0; + lfs_file_read(&lfs, &file, buffer, 5) => 5; + memcmp(buffer, "hola\n", 5) => 0; + lfs_file_read(&lfs, &file, buffer, 8) => 8; + memcmp(buffer, "bonjour\n", 8) => 0; + lfs_file_read(&lfs, &file, buffer, 6) => 6; + memcmp(buffer, "ohayo\n", 6) => 0; + lfs_file_close(&lfs, &file) => 0; + lfs_file_open(&lfs, &file, "d/hello", LFS_O_RDONLY) => LFS_ERR_NOENT; + lfs_unmount(&lfs) => 0; +''' + +[[case]] # move file corrupt source and dest +in = "lfs.c" +if = 'LFS_PROG_SIZE <= 0x3fe' # only works with one crc per commit +code = ''' + lfs_format(&lfs, &cfg) => 0; + lfs_mount(&lfs, &cfg) => 0; + lfs_mkdir(&lfs, "a") => 0; + lfs_mkdir(&lfs, "b") => 0; + lfs_mkdir(&lfs, "c") => 0; + lfs_mkdir(&lfs, "d") => 0; + lfs_file_open(&lfs, &file, "a/hello", LFS_O_CREAT | LFS_O_WRONLY) => 0; + lfs_file_write(&lfs, &file, "hola\n", 5) => 5; + lfs_file_write(&lfs, &file, "bonjour\n", 8) => 8; + lfs_file_write(&lfs, &file, "ohayo\n", 6) => 6; + lfs_file_close(&lfs, &file) => 0; + lfs_unmount(&lfs) => 0; + + lfs_mount(&lfs, &cfg) => 0; + lfs_rename(&lfs, "a/hello", "c/hello") => 0; + lfs_unmount(&lfs) => 0; + + // corrupt the source + lfs_mount(&lfs, &cfg) => 0; + lfs_dir_open(&lfs, &dir, "a") => 0; + lfs_block_t block = dir.m.pair[0]; + lfs_dir_close(&lfs, &dir) => 0; + lfs_unmount(&lfs) => 0; + uint8_t bbuffer[LFS_BLOCK_SIZE]; + cfg.read(&cfg, block, 0, bbuffer, LFS_BLOCK_SIZE) => 0; + int off = LFS_BLOCK_SIZE-1; + while (off >= 0 && bbuffer[off] == LFS_ERASE_VALUE) { + off -= 1; + } + memset(&bbuffer[off-3], LFS_BLOCK_SIZE, 3); + cfg.erase(&cfg, block) => 0; + cfg.prog(&cfg, block, 0, bbuffer, LFS_BLOCK_SIZE) => 0; + cfg.sync(&cfg) => 0; + + // corrupt the destination + lfs_mount(&lfs, &cfg) => 0; + lfs_dir_open(&lfs, &dir, "c") => 0; + block = dir.m.pair[0]; + lfs_dir_close(&lfs, &dir) => 0; + lfs_unmount(&lfs) => 0; + cfg.read(&cfg, block, 0, bbuffer, LFS_BLOCK_SIZE) => 0; + off = LFS_BLOCK_SIZE-1; + while (off >= 0 && bbuffer[off] == LFS_ERASE_VALUE) { + off -= 1; + } + memset(&bbuffer[off-3], LFS_BLOCK_SIZE, 3); + cfg.erase(&cfg, block) => 0; + cfg.prog(&cfg, block, 0, bbuffer, LFS_BLOCK_SIZE) => 0; + cfg.sync(&cfg) => 0; + + lfs_mount(&lfs, &cfg) => 0; + lfs_dir_open(&lfs, &dir, "a") => 0; + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, ".") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "..") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "hello") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == 5+8+6); + lfs_dir_read(&lfs, &dir, &info) => 0; + lfs_dir_close(&lfs, &dir) => 0; + lfs_dir_open(&lfs, &dir, "c") => 0; + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, ".") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "..") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 0; + lfs_dir_close(&lfs, &dir) => 0; + + lfs_file_open(&lfs, &file, "a/hello", LFS_O_RDONLY) => 0; + lfs_file_read(&lfs, &file, buffer, 5) => 5; + memcmp(buffer, "hola\n", 5) => 0; + lfs_file_read(&lfs, &file, buffer, 8) => 8; + memcmp(buffer, "bonjour\n", 8) => 0; + lfs_file_read(&lfs, &file, buffer, 6) => 6; + memcmp(buffer, "ohayo\n", 6) => 0; + lfs_file_close(&lfs, &file) => 0; + lfs_file_open(&lfs, &file, "b/hello", LFS_O_RDONLY) => LFS_ERR_NOENT; + lfs_file_open(&lfs, &file, "c/hello", LFS_O_RDONLY) => LFS_ERR_NOENT; + lfs_file_open(&lfs, &file, "d/hello", LFS_O_RDONLY) => LFS_ERR_NOENT; + lfs_unmount(&lfs) => 0; +''' + +[[case]] # move file after corrupt +in = "lfs.c" +if = 'LFS_PROG_SIZE <= 0x3fe' # only works with one crc per commit +code = ''' + lfs_format(&lfs, &cfg) => 0; + lfs_mount(&lfs, &cfg) => 0; + lfs_mkdir(&lfs, "a") => 0; + lfs_mkdir(&lfs, "b") => 0; + lfs_mkdir(&lfs, "c") => 0; + lfs_mkdir(&lfs, "d") => 0; + lfs_file_open(&lfs, &file, "a/hello", LFS_O_CREAT | LFS_O_WRONLY) => 0; + lfs_file_write(&lfs, &file, "hola\n", 5) => 5; + lfs_file_write(&lfs, &file, "bonjour\n", 8) => 8; + lfs_file_write(&lfs, &file, "ohayo\n", 6) => 6; + lfs_file_close(&lfs, &file) => 0; + lfs_unmount(&lfs) => 0; + + lfs_mount(&lfs, &cfg) => 0; + lfs_rename(&lfs, "a/hello", "c/hello") => 0; + lfs_unmount(&lfs) => 0; + + // corrupt the source + lfs_mount(&lfs, &cfg) => 0; + lfs_dir_open(&lfs, &dir, "a") => 0; + lfs_block_t block = dir.m.pair[0]; + lfs_dir_close(&lfs, &dir) => 0; + lfs_unmount(&lfs) => 0; + uint8_t bbuffer[LFS_BLOCK_SIZE]; + cfg.read(&cfg, block, 0, bbuffer, LFS_BLOCK_SIZE) => 0; + int off = LFS_BLOCK_SIZE-1; + while (off >= 0 && bbuffer[off] == LFS_ERASE_VALUE) { + off -= 1; + } + memset(&bbuffer[off-3], LFS_BLOCK_SIZE, 3); + cfg.erase(&cfg, block) => 0; + cfg.prog(&cfg, block, 0, bbuffer, LFS_BLOCK_SIZE) => 0; + cfg.sync(&cfg) => 0; + + // corrupt the destination + lfs_mount(&lfs, &cfg) => 0; + lfs_dir_open(&lfs, &dir, "c") => 0; + block = dir.m.pair[0]; + lfs_dir_close(&lfs, &dir) => 0; + lfs_unmount(&lfs) => 0; + cfg.read(&cfg, block, 0, bbuffer, LFS_BLOCK_SIZE) => 0; + off = LFS_BLOCK_SIZE-1; + while (off >= 0 && bbuffer[off] == LFS_ERASE_VALUE) { + off -= 1; + } + memset(&bbuffer[off-3], LFS_BLOCK_SIZE, 3); + cfg.erase(&cfg, block) => 0; + cfg.prog(&cfg, block, 0, bbuffer, LFS_BLOCK_SIZE) => 0; + cfg.sync(&cfg) => 0; + + // continue move + lfs_mount(&lfs, &cfg) => 0; + lfs_rename(&lfs, "a/hello", "c/hello") => 0; + lfs_unmount(&lfs) => 0; + + lfs_mount(&lfs, &cfg) => 0; + lfs_dir_open(&lfs, &dir, "a") => 0; + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, ".") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "..") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 0; + lfs_dir_close(&lfs, &dir) => 0; + lfs_dir_open(&lfs, &dir, "c") => 0; + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, ".") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "..") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "hello") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == 5+8+6); + lfs_dir_read(&lfs, &dir, &info) => 0; + lfs_dir_close(&lfs, &dir) => 0; + + lfs_file_open(&lfs, &file, "a/hello", LFS_O_RDONLY) => LFS_ERR_NOENT; + lfs_file_open(&lfs, &file, "b/hello", LFS_O_RDONLY) => LFS_ERR_NOENT; + lfs_file_open(&lfs, &file, "c/hello", LFS_O_RDONLY) => 0; + lfs_file_read(&lfs, &file, buffer, 5) => 5; + memcmp(buffer, "hola\n", 5) => 0; + lfs_file_read(&lfs, &file, buffer, 8) => 8; + memcmp(buffer, "bonjour\n", 8) => 0; + lfs_file_read(&lfs, &file, buffer, 6) => 6; + memcmp(buffer, "ohayo\n", 6) => 0; + lfs_file_close(&lfs, &file) => 0; + lfs_file_open(&lfs, &file, "d/hello", LFS_O_RDONLY) => LFS_ERR_NOENT; + lfs_unmount(&lfs) => 0; +''' + +[[case]] # simple reentrant move file +reentrant = true +code = ''' + err = lfs_mount(&lfs, &cfg); + if (err) { + lfs_format(&lfs, &cfg) => 0; + lfs_mount(&lfs, &cfg) => 0; + } + err = lfs_mkdir(&lfs, "a"); + assert(!err || err == LFS_ERR_EXIST); + err = lfs_mkdir(&lfs, "b"); + assert(!err || err == LFS_ERR_EXIST); + err = lfs_mkdir(&lfs, "c"); + assert(!err || err == LFS_ERR_EXIST); + err = lfs_mkdir(&lfs, "d"); + assert(!err || err == LFS_ERR_EXIST); + lfs_unmount(&lfs) => 0; + + while (true) { + lfs_mount(&lfs, &cfg) => 0; + // there should never exist _2_ hello files + int count = 0; + if (lfs_stat(&lfs, "a/hello", &info) == 0) { + assert(strcmp(info.name, "hello") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == 5+8+6 || info.size == 0); + count += 1; + } + if (lfs_stat(&lfs, "b/hello", &info) == 0) { + assert(strcmp(info.name, "hello") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == 5+8+6); + count += 1; + } + if (lfs_stat(&lfs, "c/hello", &info) == 0) { + assert(strcmp(info.name, "hello") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == 5+8+6); + count += 1; + } + if (lfs_stat(&lfs, "d/hello", &info) == 0) { + assert(strcmp(info.name, "hello") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == 5+8+6); + count += 1; + } + assert(count <= 1); + lfs_unmount(&lfs) => 0; + + lfs_mount(&lfs, &cfg) => 0; + if (lfs_stat(&lfs, "a/hello", &info) == 0 && info.size > 0) { + lfs_rename(&lfs, "a/hello", "b/hello") => 0; + } else if (lfs_stat(&lfs, "b/hello", &info) == 0) { + lfs_rename(&lfs, "b/hello", "c/hello") => 0; + } else if (lfs_stat(&lfs, "c/hello", &info) == 0) { + lfs_rename(&lfs, "c/hello", "d/hello") => 0; + } else if (lfs_stat(&lfs, "d/hello", &info) == 0) { + // success + lfs_unmount(&lfs) => 0; + break; + } else { + // create file + lfs_file_open(&lfs, &file, "a/hello", + LFS_O_WRONLY | LFS_O_CREAT) => 0; + lfs_file_write(&lfs, &file, "hola\n", 5) => 5; + lfs_file_write(&lfs, &file, "bonjour\n", 8) => 8; + lfs_file_write(&lfs, &file, "ohayo\n", 6) => 6; + lfs_file_close(&lfs, &file) => 0; + } + lfs_unmount(&lfs) => 0; + } + + lfs_mount(&lfs, &cfg) => 0; + lfs_dir_open(&lfs, &dir, "a") => 0; + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, ".") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "..") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 0; + lfs_dir_close(&lfs, &dir) => 0; + lfs_dir_open(&lfs, &dir, "d") => 0; + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, ".") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "..") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "hello") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == 5+8+6); + lfs_dir_read(&lfs, &dir, &info) => 0; + lfs_dir_close(&lfs, &dir) => 0; + + lfs_file_open(&lfs, &file, "a/hello", LFS_O_RDONLY) => LFS_ERR_NOENT; + lfs_file_open(&lfs, &file, "b/hello", LFS_O_RDONLY) => LFS_ERR_NOENT; + lfs_file_open(&lfs, &file, "c/hello", LFS_O_RDONLY) => LFS_ERR_NOENT; + lfs_file_open(&lfs, &file, "d/hello", LFS_O_RDONLY) => 0; + lfs_file_read(&lfs, &file, buffer, 5) => 5; + memcmp(buffer, "hola\n", 5) => 0; + lfs_file_read(&lfs, &file, buffer, 8) => 8; + memcmp(buffer, "bonjour\n", 8) => 0; + lfs_file_read(&lfs, &file, buffer, 6) => 6; + memcmp(buffer, "ohayo\n", 6) => 0; + lfs_file_close(&lfs, &file) => 0; + lfs_unmount(&lfs) => 0; +''' + +[[case]] # move dir +code = ''' + lfs_format(&lfs, &cfg) => 0; + lfs_mount(&lfs, &cfg) => 0; + lfs_mkdir(&lfs, "a") => 0; + lfs_mkdir(&lfs, "b") => 0; + lfs_mkdir(&lfs, "c") => 0; + lfs_mkdir(&lfs, "d") => 0; + lfs_mkdir(&lfs, "a/hi") => 0; + lfs_mkdir(&lfs, "a/hi/hola") => 0; + lfs_mkdir(&lfs, "a/hi/bonjour") => 0; + lfs_mkdir(&lfs, "a/hi/ohayo") => 0; + lfs_unmount(&lfs) => 0; + + lfs_mount(&lfs, &cfg) => 0; + lfs_rename(&lfs, "a/hi", "c/hi") => 0; + lfs_unmount(&lfs) => 0; + + lfs_mount(&lfs, &cfg) => 0; + lfs_dir_open(&lfs, &dir, "a") => 0; + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, ".") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "..") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 0; + lfs_dir_close(&lfs, &dir) => 0; + lfs_dir_open(&lfs, &dir, "c") => 0; + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, ".") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "..") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "hi") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 0; + lfs_dir_close(&lfs, &dir) => 0; + + lfs_dir_open(&lfs, &dir, "a/hi") => LFS_ERR_NOENT; + lfs_dir_open(&lfs, &dir, "b/hi") => LFS_ERR_NOENT; + lfs_dir_open(&lfs, &dir, "c/hi") => 0; + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, ".") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "..") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "bonjour") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "hola") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "ohayo") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 0; + lfs_dir_close(&lfs, &dir) => 0; + lfs_dir_open(&lfs, &dir, "d/hi") => LFS_ERR_NOENT; + lfs_unmount(&lfs) => 0; +''' + +[[case]] # move dir corrupt source +in = "lfs.c" +code = ''' + lfs_format(&lfs, &cfg) => 0; + lfs_mount(&lfs, &cfg) => 0; + lfs_mkdir(&lfs, "a") => 0; + lfs_mkdir(&lfs, "b") => 0; + lfs_mkdir(&lfs, "c") => 0; + lfs_mkdir(&lfs, "d") => 0; + lfs_mkdir(&lfs, "a/hi") => 0; + lfs_mkdir(&lfs, "a/hi/hola") => 0; + lfs_mkdir(&lfs, "a/hi/bonjour") => 0; + lfs_mkdir(&lfs, "a/hi/ohayo") => 0; + lfs_unmount(&lfs) => 0; + + lfs_mount(&lfs, &cfg) => 0; + lfs_rename(&lfs, "a/hi", "c/hi") => 0; + lfs_unmount(&lfs) => 0; + + // corrupt the source + lfs_mount(&lfs, &cfg) => 0; + lfs_dir_open(&lfs, &dir, "a") => 0; + lfs_block_t block = dir.m.pair[0]; + lfs_dir_close(&lfs, &dir) => 0; + lfs_unmount(&lfs) => 0; + uint8_t bbuffer[LFS_BLOCK_SIZE]; + cfg.read(&cfg, block, 0, bbuffer, LFS_BLOCK_SIZE) => 0; + int off = LFS_BLOCK_SIZE-1; + while (off >= 0 && bbuffer[off] == LFS_ERASE_VALUE) { + off -= 1; + } + memset(&bbuffer[off-3], LFS_BLOCK_SIZE, 3); + cfg.erase(&cfg, block) => 0; + cfg.prog(&cfg, block, 0, bbuffer, LFS_BLOCK_SIZE) => 0; + cfg.sync(&cfg) => 0; + + lfs_mount(&lfs, &cfg) => 0; + lfs_dir_open(&lfs, &dir, "a") => 0; + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, ".") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "..") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 0; + lfs_dir_close(&lfs, &dir) => 0; + lfs_dir_open(&lfs, &dir, "c") => 0; + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, ".") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "..") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "hi") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 0; + lfs_dir_close(&lfs, &dir) => 0; + + lfs_dir_open(&lfs, &dir, "a/hi") => LFS_ERR_NOENT; + lfs_dir_open(&lfs, &dir, "b/hi") => LFS_ERR_NOENT; + lfs_dir_open(&lfs, &dir, "c/hi") => 0; + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, ".") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "..") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "bonjour") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "hola") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "ohayo") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 0; + lfs_dir_close(&lfs, &dir) => 0; + lfs_dir_open(&lfs, &dir, "d/hi") => LFS_ERR_NOENT; + lfs_unmount(&lfs) => 0; +''' + +[[case]] # move dir corrupt source and dest +in = "lfs.c" +if = 'LFS_PROG_SIZE <= 0x3fe' # only works with one crc per commit +code = ''' + lfs_format(&lfs, &cfg) => 0; + lfs_mount(&lfs, &cfg) => 0; + lfs_mkdir(&lfs, "a") => 0; + lfs_mkdir(&lfs, "b") => 0; + lfs_mkdir(&lfs, "c") => 0; + lfs_mkdir(&lfs, "d") => 0; + lfs_mkdir(&lfs, "a/hi") => 0; + lfs_mkdir(&lfs, "a/hi/hola") => 0; + lfs_mkdir(&lfs, "a/hi/bonjour") => 0; + lfs_mkdir(&lfs, "a/hi/ohayo") => 0; + lfs_unmount(&lfs) => 0; + + lfs_mount(&lfs, &cfg) => 0; + lfs_rename(&lfs, "a/hi", "c/hi") => 0; + lfs_unmount(&lfs) => 0; + + // corrupt the source + lfs_mount(&lfs, &cfg) => 0; + lfs_dir_open(&lfs, &dir, "a") => 0; + lfs_block_t block = dir.m.pair[0]; + lfs_dir_close(&lfs, &dir) => 0; + lfs_unmount(&lfs) => 0; + uint8_t bbuffer[LFS_BLOCK_SIZE]; + cfg.read(&cfg, block, 0, bbuffer, LFS_BLOCK_SIZE) => 0; + int off = LFS_BLOCK_SIZE-1; + while (off >= 0 && bbuffer[off] == LFS_ERASE_VALUE) { + off -= 1; + } + memset(&bbuffer[off-3], LFS_BLOCK_SIZE, 3); + cfg.erase(&cfg, block) => 0; + cfg.prog(&cfg, block, 0, bbuffer, LFS_BLOCK_SIZE) => 0; + cfg.sync(&cfg) => 0; + + // corrupt the destination + lfs_mount(&lfs, &cfg) => 0; + lfs_dir_open(&lfs, &dir, "c") => 0; + block = dir.m.pair[0]; + lfs_dir_close(&lfs, &dir) => 0; + lfs_unmount(&lfs) => 0; + cfg.read(&cfg, block, 0, bbuffer, LFS_BLOCK_SIZE) => 0; + off = LFS_BLOCK_SIZE-1; + while (off >= 0 && bbuffer[off] == LFS_ERASE_VALUE) { + off -= 1; + } + memset(&bbuffer[off-3], LFS_BLOCK_SIZE, 3); + cfg.erase(&cfg, block) => 0; + cfg.prog(&cfg, block, 0, bbuffer, LFS_BLOCK_SIZE) => 0; + cfg.sync(&cfg) => 0; + + lfs_mount(&lfs, &cfg) => 0; + lfs_dir_open(&lfs, &dir, "a") => 0; + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, ".") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "..") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "hi") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 0; + lfs_dir_close(&lfs, &dir) => 0; + lfs_dir_open(&lfs, &dir, "c") => 0; + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, ".") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "..") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 0; + lfs_dir_close(&lfs, &dir) => 0; + + lfs_dir_open(&lfs, &dir, "a/hi") => 0; + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, ".") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "..") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "bonjour") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "hola") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "ohayo") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 0; + lfs_dir_close(&lfs, &dir) => 0; + lfs_dir_open(&lfs, &dir, "b/hi") => LFS_ERR_NOENT; + lfs_dir_open(&lfs, &dir, "c/hi") => LFS_ERR_NOENT; + lfs_dir_open(&lfs, &dir, "d/hi") => LFS_ERR_NOENT; + lfs_unmount(&lfs) => 0; +''' + +[[case]] # move dir after corrupt +in = "lfs.c" +if = 'LFS_PROG_SIZE <= 0x3fe' # only works with one crc per commit +code = ''' + lfs_format(&lfs, &cfg) => 0; + lfs_mount(&lfs, &cfg) => 0; + lfs_mkdir(&lfs, "a") => 0; + lfs_mkdir(&lfs, "b") => 0; + lfs_mkdir(&lfs, "c") => 0; + lfs_mkdir(&lfs, "d") => 0; + lfs_mkdir(&lfs, "a/hi") => 0; + lfs_mkdir(&lfs, "a/hi/hola") => 0; + lfs_mkdir(&lfs, "a/hi/bonjour") => 0; + lfs_mkdir(&lfs, "a/hi/ohayo") => 0; + lfs_unmount(&lfs) => 0; + + lfs_mount(&lfs, &cfg) => 0; + lfs_rename(&lfs, "a/hi", "c/hi") => 0; + lfs_unmount(&lfs) => 0; + + // corrupt the source + lfs_mount(&lfs, &cfg) => 0; + lfs_dir_open(&lfs, &dir, "a") => 0; + lfs_block_t block = dir.m.pair[0]; + lfs_dir_close(&lfs, &dir) => 0; + lfs_unmount(&lfs) => 0; + uint8_t bbuffer[LFS_BLOCK_SIZE]; + cfg.read(&cfg, block, 0, bbuffer, LFS_BLOCK_SIZE) => 0; + int off = LFS_BLOCK_SIZE-1; + while (off >= 0 && bbuffer[off] == LFS_ERASE_VALUE) { + off -= 1; + } + memset(&bbuffer[off-3], LFS_BLOCK_SIZE, 3); + cfg.erase(&cfg, block) => 0; + cfg.prog(&cfg, block, 0, bbuffer, LFS_BLOCK_SIZE) => 0; + cfg.sync(&cfg) => 0; + + // corrupt the destination + lfs_mount(&lfs, &cfg) => 0; + lfs_dir_open(&lfs, &dir, "c") => 0; + block = dir.m.pair[0]; + lfs_dir_close(&lfs, &dir) => 0; + lfs_unmount(&lfs) => 0; + cfg.read(&cfg, block, 0, bbuffer, LFS_BLOCK_SIZE) => 0; + off = LFS_BLOCK_SIZE-1; + while (off >= 0 && bbuffer[off] == LFS_ERASE_VALUE) { + off -= 1; + } + memset(&bbuffer[off-3], LFS_BLOCK_SIZE, 3); + cfg.erase(&cfg, block) => 0; + cfg.prog(&cfg, block, 0, bbuffer, LFS_BLOCK_SIZE) => 0; + cfg.sync(&cfg) => 0; + + // continue move + lfs_mount(&lfs, &cfg) => 0; + lfs_rename(&lfs, "a/hi", "c/hi") => 0; + lfs_unmount(&lfs) => 0; + + lfs_mount(&lfs, &cfg) => 0; + lfs_dir_open(&lfs, &dir, "a") => 0; + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, ".") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "..") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 0; + lfs_dir_close(&lfs, &dir) => 0; + lfs_dir_open(&lfs, &dir, "c") => 0; + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, ".") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "..") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "hi") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 0; + lfs_dir_close(&lfs, &dir) => 0; + + lfs_dir_open(&lfs, &dir, "a/hi") => LFS_ERR_NOENT; + lfs_dir_open(&lfs, &dir, "b/hi") => LFS_ERR_NOENT; + lfs_dir_open(&lfs, &dir, "c/hi") => 0; + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, ".") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "..") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "bonjour") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "hola") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "ohayo") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 0; + lfs_dir_close(&lfs, &dir) => 0; + lfs_dir_open(&lfs, &dir, "d/hi") => LFS_ERR_NOENT; + lfs_unmount(&lfs) => 0; +''' + +[[case]] # simple reentrant move dir +reentrant = true +code = ''' + err = lfs_mount(&lfs, &cfg); + if (err) { + lfs_format(&lfs, &cfg) => 0; + lfs_mount(&lfs, &cfg) => 0; + } + err = lfs_mkdir(&lfs, "a"); + assert(!err || err == LFS_ERR_EXIST); + err = lfs_mkdir(&lfs, "b"); + assert(!err || err == LFS_ERR_EXIST); + err = lfs_mkdir(&lfs, "c"); + assert(!err || err == LFS_ERR_EXIST); + err = lfs_mkdir(&lfs, "d"); + assert(!err || err == LFS_ERR_EXIST); + lfs_unmount(&lfs) => 0; + + while (true) { + lfs_mount(&lfs, &cfg) => 0; + // there should never exist _2_ hi directories + int count = 0; + if (lfs_stat(&lfs, "a/hi", &info) == 0) { + assert(strcmp(info.name, "hi") == 0); + assert(info.type == LFS_TYPE_DIR); + count += 1; + } + if (lfs_stat(&lfs, "b/hi", &info) == 0) { + assert(strcmp(info.name, "hi") == 0); + assert(info.type == LFS_TYPE_DIR); + count += 1; + } + if (lfs_stat(&lfs, "c/hi", &info) == 0) { + assert(strcmp(info.name, "hi") == 0); + assert(info.type == LFS_TYPE_DIR); + count += 1; + } + if (lfs_stat(&lfs, "d/hi", &info) == 0) { + assert(strcmp(info.name, "hi") == 0); + assert(info.type == LFS_TYPE_DIR); + count += 1; + } + assert(count <= 1); + lfs_unmount(&lfs) => 0; + + lfs_mount(&lfs, &cfg) => 0; + if (lfs_stat(&lfs, "a/hi", &info) == 0) { + lfs_rename(&lfs, "a/hi", "b/hi") => 0; + } else if (lfs_stat(&lfs, "b/hi", &info) == 0) { + lfs_rename(&lfs, "b/hi", "c/hi") => 0; + } else if (lfs_stat(&lfs, "c/hi", &info) == 0) { + lfs_rename(&lfs, "c/hi", "d/hi") => 0; + } else if (lfs_stat(&lfs, "d/hi", &info) == 0) { + lfs_unmount(&lfs) => 0; + break; // success + } else { + // create dir and rename for atomicity + err = lfs_mkdir(&lfs, "temp"); + assert(!err || err == LFS_ERR_EXIST); + err = lfs_mkdir(&lfs, "temp/hola"); + assert(!err || err == LFS_ERR_EXIST); + err = lfs_mkdir(&lfs, "temp/bonjour"); + assert(!err || err == LFS_ERR_EXIST); + err = lfs_mkdir(&lfs, "temp/ohayo"); + assert(!err || err == LFS_ERR_EXIST); + lfs_rename(&lfs, "temp", "a/hi") => 0; + } + lfs_unmount(&lfs) => 0; + } + + lfs_mount(&lfs, &cfg) => 0; + lfs_dir_open(&lfs, &dir, "a") => 0; + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, ".") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "..") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 0; + lfs_dir_close(&lfs, &dir) => 0; + lfs_dir_open(&lfs, &dir, "d") => 0; + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, ".") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "..") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "hi") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 0; + lfs_dir_close(&lfs, &dir) => 0; + + lfs_dir_open(&lfs, &dir, "a/hi") => LFS_ERR_NOENT; + lfs_dir_open(&lfs, &dir, "b/hi") => LFS_ERR_NOENT; + lfs_dir_open(&lfs, &dir, "c/hi") => LFS_ERR_NOENT; + lfs_dir_open(&lfs, &dir, "d/hi") => 0; + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, ".") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "..") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "bonjour") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "hola") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "ohayo") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 0; + lfs_dir_close(&lfs, &dir) => 0; + lfs_unmount(&lfs) => 0; +''' + +[[case]] # move state stealing +code = ''' + lfs_format(&lfs, &cfg) => 0; + lfs_mount(&lfs, &cfg) => 0; + lfs_mkdir(&lfs, "a") => 0; + lfs_mkdir(&lfs, "b") => 0; + lfs_mkdir(&lfs, "c") => 0; + lfs_mkdir(&lfs, "d") => 0; + lfs_file_open(&lfs, &file, "a/hello", LFS_O_CREAT | LFS_O_WRONLY) => 0; + lfs_file_write(&lfs, &file, "hola\n", 5) => 5; + lfs_file_write(&lfs, &file, "bonjour\n", 8) => 8; + lfs_file_write(&lfs, &file, "ohayo\n", 6) => 6; + lfs_file_close(&lfs, &file) => 0; + lfs_unmount(&lfs) => 0; + + lfs_mount(&lfs, &cfg) => 0; + lfs_rename(&lfs, "a/hello", "b/hello") => 0; + lfs_unmount(&lfs) => 0; + lfs_mount(&lfs, &cfg) => 0; + lfs_rename(&lfs, "b/hello", "c/hello") => 0; + lfs_unmount(&lfs) => 0; + lfs_mount(&lfs, &cfg) => 0; + lfs_rename(&lfs, "c/hello", "d/hello") => 0; + lfs_unmount(&lfs) => 0; + + lfs_mount(&lfs, &cfg) => 0; + lfs_file_open(&lfs, &file, "a/hello", LFS_O_RDONLY) => LFS_ERR_NOENT; + lfs_file_open(&lfs, &file, "b/hello", LFS_O_RDONLY) => LFS_ERR_NOENT; + lfs_file_open(&lfs, &file, "c/hello", LFS_O_RDONLY) => LFS_ERR_NOENT; + lfs_file_open(&lfs, &file, "d/hello", LFS_O_RDONLY) => 0; + lfs_file_read(&lfs, &file, buffer, 5) => 5; + memcmp(buffer, "hola\n", 5) => 0; + lfs_file_read(&lfs, &file, buffer, 8) => 8; + memcmp(buffer, "bonjour\n", 8) => 0; + lfs_file_read(&lfs, &file, buffer, 6) => 6; + memcmp(buffer, "ohayo\n", 6) => 0; + lfs_file_close(&lfs, &file) => 0; + lfs_unmount(&lfs) => 0; + + lfs_mount(&lfs, &cfg) => 0; + lfs_remove(&lfs, "b") => 0; + lfs_remove(&lfs, "c") => 0; + lfs_unmount(&lfs) => 0; + + lfs_mount(&lfs, &cfg) => 0; + lfs_stat(&lfs, "a", &info) => 0; + lfs_stat(&lfs, "b", &info) => LFS_ERR_NOENT; + lfs_stat(&lfs, "c", &info) => LFS_ERR_NOENT; + lfs_stat(&lfs, "d", &info) => 0; + lfs_file_open(&lfs, &file, "a/hello", LFS_O_RDONLY) => LFS_ERR_NOENT; + lfs_file_open(&lfs, &file, "b/hello", LFS_O_RDONLY) => LFS_ERR_NOENT; + lfs_file_open(&lfs, &file, "c/hello", LFS_O_RDONLY) => LFS_ERR_NOENT; + lfs_file_open(&lfs, &file, "d/hello", LFS_O_RDONLY) => 0; + lfs_file_read(&lfs, &file, buffer, 5) => 5; + memcmp(buffer, "hola\n", 5) => 0; + lfs_file_read(&lfs, &file, buffer, 8) => 8; + memcmp(buffer, "bonjour\n", 8) => 0; + lfs_file_read(&lfs, &file, buffer, 6) => 6; + memcmp(buffer, "ohayo\n", 6) => 0; + lfs_file_close(&lfs, &file) => 0; + lfs_unmount(&lfs) => 0; +''' + +# Other specific corner cases +[[case]] # create + delete in same commit with neighbors +code = ''' + lfs_format(&lfs, &cfg) => 0; + lfs_mount(&lfs, &cfg) => 0; + + // littlefs keeps files sorted, so we know the order these will be in + lfs_file_open(&lfs, &file, "/1.move_me", + LFS_O_WRONLY | LFS_O_CREAT) => 0; + lfs_file_close(&lfs, &file) => 0; + + lfs_file_open(&lfs, &file, "/0.before", + LFS_O_WRONLY | LFS_O_CREAT) => 0; + lfs_file_write(&lfs, &file, "test.1", 7) => 7; + lfs_file_close(&lfs, &file) => 0; + + lfs_file_open(&lfs, &file, "/2.in_between", + LFS_O_WRONLY | LFS_O_CREAT) => 0; + lfs_file_write(&lfs, &file, "test.2", 7) => 7; + lfs_file_close(&lfs, &file) => 0; + + lfs_file_open(&lfs, &file, "/4.after", + LFS_O_WRONLY | LFS_O_CREAT) => 0; + lfs_file_write(&lfs, &file, "test.3", 7) => 7; + lfs_file_close(&lfs, &file) => 0; + + lfs_file_t files[3]; + lfs_file_open(&lfs, &files[0], "0.before", + LFS_O_WRONLY | LFS_O_TRUNC) => 0; + lfs_file_open(&lfs, &files[1], "2.in_between", + LFS_O_WRONLY | LFS_O_TRUNC) => 0; + lfs_file_open(&lfs, &files[2], "4.after", + LFS_O_WRONLY | LFS_O_TRUNC) => 0; + lfs_file_write(&lfs, &files[0], "test.4", 7) => 7; + lfs_file_write(&lfs, &files[1], "test.5", 7) => 7; + lfs_file_write(&lfs, &files[2], "test.6", 7) => 7; + + // rename file while everything is open, this triggers both + // a create and delete simultaneously + lfs_rename(&lfs, "/1.move_me", "/3.move_me") => 0; + + lfs_file_close(&lfs, &files[0]) => 0; + lfs_file_close(&lfs, &files[1]) => 0; + lfs_file_close(&lfs, &files[2]) => 0; + + // check that nothing was corrupted + lfs_dir_open(&lfs, &dir, "/") => 0; + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, ".") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "..") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "0.before") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == 7); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "2.in_between") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == 7); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "3.move_me") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == 0); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "4.after") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == 7); + lfs_dir_read(&lfs, &dir, &info) => 0; + lfs_dir_close(&lfs, &dir) => 0; + + lfs_file_open(&lfs, &file, "/0.before", LFS_O_RDONLY) => 0; + lfs_file_read(&lfs, &file, buffer, 7) => 7; + assert(strcmp((char*)buffer, "test.4") == 0); + lfs_file_close(&lfs, &file) => 0; + lfs_file_open(&lfs, &file, "/2.in_between", LFS_O_RDONLY) => 0; + lfs_file_read(&lfs, &file, buffer, 7) => 7; + assert(strcmp((char*)buffer, "test.5") == 0); + lfs_file_close(&lfs, &file) => 0; + lfs_file_open(&lfs, &file, "/4.after", LFS_O_RDONLY) => 0; + lfs_file_read(&lfs, &file, buffer, 7) => 7; + assert(strcmp((char*)buffer, "test.6") == 0); + lfs_file_close(&lfs, &file) => 0; + + // now move back + lfs_file_open(&lfs, &files[0], "0.before", + LFS_O_WRONLY | LFS_O_TRUNC) => 0; + lfs_file_open(&lfs, &files[1], "2.in_between", + LFS_O_WRONLY | LFS_O_TRUNC) => 0; + lfs_file_open(&lfs, &files[2], "4.after", + LFS_O_WRONLY | LFS_O_TRUNC) => 0; + lfs_file_write(&lfs, &files[0], "test.7", 7) => 7; + lfs_file_write(&lfs, &files[1], "test.8", 7) => 7; + lfs_file_write(&lfs, &files[2], "test.9", 7) => 7; + + // rename file while everything is open, this triggers both + // a create and delete simultaneously + lfs_rename(&lfs, "/3.move_me", "/1.move_me") => 0; + + lfs_file_close(&lfs, &files[0]) => 0; + lfs_file_close(&lfs, &files[1]) => 0; + lfs_file_close(&lfs, &files[2]) => 0; + + // and check that nothing was corrupted again + lfs_dir_open(&lfs, &dir, "/") => 0; + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, ".") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "..") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "0.before") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == 7); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "1.move_me") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == 0); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "2.in_between") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == 7); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "4.after") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == 7); + lfs_dir_read(&lfs, &dir, &info) => 0; + lfs_dir_close(&lfs, &dir) => 0; + + lfs_file_open(&lfs, &file, "/0.before", LFS_O_RDONLY) => 0; + lfs_file_read(&lfs, &file, buffer, 7) => 7; + assert(strcmp((char*)buffer, "test.7") == 0); + lfs_file_close(&lfs, &file) => 0; + lfs_file_open(&lfs, &file, "/2.in_between", LFS_O_RDONLY) => 0; + lfs_file_read(&lfs, &file, buffer, 7) => 7; + assert(strcmp((char*)buffer, "test.8") == 0); + lfs_file_close(&lfs, &file) => 0; + lfs_file_open(&lfs, &file, "/4.after", LFS_O_RDONLY) => 0; + lfs_file_read(&lfs, &file, buffer, 7) => 7; + assert(strcmp((char*)buffer, "test.9") == 0); + lfs_file_close(&lfs, &file) => 0; + lfs_unmount(&lfs) => 0; +''' + +# Other specific corner cases +[[case]] # create + delete + delete in same commit with neighbors +code = ''' + lfs_format(&lfs, &cfg) => 0; + lfs_mount(&lfs, &cfg) => 0; + + // littlefs keeps files sorted, so we know the order these will be in + lfs_file_open(&lfs, &file, "/1.move_me", + LFS_O_WRONLY | LFS_O_CREAT) => 0; + lfs_file_close(&lfs, &file) => 0; + lfs_file_open(&lfs, &file, "/3.move_me", + LFS_O_WRONLY | LFS_O_CREAT) => 0; + lfs_file_write(&lfs, &file, "remove me", + sizeof("remove me")) => sizeof("remove me"); + lfs_file_close(&lfs, &file) => 0; + + lfs_file_open(&lfs, &file, "/0.before", + LFS_O_WRONLY | LFS_O_CREAT) => 0; + lfs_file_write(&lfs, &file, "test.1", 7) => 7; + lfs_file_close(&lfs, &file) => 0; + + lfs_file_open(&lfs, &file, "/2.in_between", + LFS_O_WRONLY | LFS_O_CREAT) => 0; + lfs_file_write(&lfs, &file, "test.2", 7) => 7; + lfs_file_close(&lfs, &file) => 0; + + lfs_file_open(&lfs, &file, "/4.after", + LFS_O_WRONLY | LFS_O_CREAT) => 0; + lfs_file_write(&lfs, &file, "test.3", 7) => 7; + lfs_file_close(&lfs, &file) => 0; + + lfs_file_t files[3]; + lfs_file_open(&lfs, &files[0], "0.before", + LFS_O_WRONLY | LFS_O_TRUNC) => 0; + lfs_file_open(&lfs, &files[1], "2.in_between", + LFS_O_WRONLY | LFS_O_TRUNC) => 0; + lfs_file_open(&lfs, &files[2], "4.after", + LFS_O_WRONLY | LFS_O_TRUNC) => 0; + lfs_file_write(&lfs, &files[0], "test.4", 7) => 7; + lfs_file_write(&lfs, &files[1], "test.5", 7) => 7; + lfs_file_write(&lfs, &files[2], "test.6", 7) => 7; + + // rename file while everything is open, this triggers both + // a create and delete simultaneously + lfs_rename(&lfs, "/1.move_me", "/3.move_me") => 0; + + lfs_file_close(&lfs, &files[0]) => 0; + lfs_file_close(&lfs, &files[1]) => 0; + lfs_file_close(&lfs, &files[2]) => 0; + + // check that nothing was corrupted + lfs_dir_open(&lfs, &dir, "/") => 0; + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, ".") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "..") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "0.before") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == 7); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "2.in_between") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == 7); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "3.move_me") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == 0); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "4.after") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == 7); + lfs_dir_read(&lfs, &dir, &info) => 0; + lfs_dir_close(&lfs, &dir) => 0; + + lfs_file_open(&lfs, &file, "/0.before", LFS_O_RDONLY) => 0; + lfs_file_read(&lfs, &file, buffer, 7) => 7; + assert(strcmp((char*)buffer, "test.4") == 0); + lfs_file_close(&lfs, &file) => 0; + lfs_file_open(&lfs, &file, "/2.in_between", LFS_O_RDONLY) => 0; + lfs_file_read(&lfs, &file, buffer, 7) => 7; + assert(strcmp((char*)buffer, "test.5") == 0); + lfs_file_close(&lfs, &file) => 0; + lfs_file_open(&lfs, &file, "/4.after", LFS_O_RDONLY) => 0; + lfs_file_read(&lfs, &file, buffer, 7) => 7; + assert(strcmp((char*)buffer, "test.6") == 0); + lfs_file_close(&lfs, &file) => 0; + + // now move back + lfs_file_open(&lfs, &file, "/1.move_me", + LFS_O_WRONLY | LFS_O_CREAT) => 0; + lfs_file_write(&lfs, &file, "remove me", + sizeof("remove me")) => sizeof("remove me"); + lfs_file_close(&lfs, &file) => 0; + + lfs_file_open(&lfs, &files[0], "0.before", + LFS_O_WRONLY | LFS_O_TRUNC) => 0; + lfs_file_open(&lfs, &files[1], "2.in_between", + LFS_O_WRONLY | LFS_O_TRUNC) => 0; + lfs_file_open(&lfs, &files[2], "4.after", + LFS_O_WRONLY | LFS_O_TRUNC) => 0; + lfs_file_write(&lfs, &files[0], "test.7", 7) => 7; + lfs_file_write(&lfs, &files[1], "test.8", 7) => 7; + lfs_file_write(&lfs, &files[2], "test.9", 7) => 7; + + // rename file while everything is open, this triggers both + // a create and delete simultaneously + lfs_rename(&lfs, "/3.move_me", "/1.move_me") => 0; + + lfs_file_close(&lfs, &files[0]) => 0; + lfs_file_close(&lfs, &files[1]) => 0; + lfs_file_close(&lfs, &files[2]) => 0; + + // and check that nothing was corrupted again + lfs_dir_open(&lfs, &dir, "/") => 0; + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, ".") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "..") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "0.before") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == 7); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "1.move_me") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == 0); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "2.in_between") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == 7); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "4.after") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == 7); + lfs_dir_read(&lfs, &dir, &info) => 0; + lfs_dir_close(&lfs, &dir) => 0; + + lfs_file_open(&lfs, &file, "/0.before", LFS_O_RDONLY) => 0; + lfs_file_read(&lfs, &file, buffer, 7) => 7; + assert(strcmp((char*)buffer, "test.7") == 0); + lfs_file_close(&lfs, &file) => 0; + lfs_file_open(&lfs, &file, "/2.in_between", LFS_O_RDONLY) => 0; + lfs_file_read(&lfs, &file, buffer, 7) => 7; + assert(strcmp((char*)buffer, "test.8") == 0); + lfs_file_close(&lfs, &file) => 0; + lfs_file_open(&lfs, &file, "/4.after", LFS_O_RDONLY) => 0; + lfs_file_read(&lfs, &file, buffer, 7) => 7; + assert(strcmp((char*)buffer, "test.9") == 0); + lfs_file_close(&lfs, &file) => 0; + lfs_unmount(&lfs) => 0; +''' + +[[case]] # create + delete in different dirs with neighbors +code = ''' + lfs_format(&lfs, &cfg) => 0; + lfs_mount(&lfs, &cfg) => 0; + + // littlefs keeps files sorted, so we know the order these will be in + lfs_mkdir(&lfs, "/dir.1") => 0; + lfs_mkdir(&lfs, "/dir.2") => 0; + lfs_file_open(&lfs, &file, "/dir.1/1.move_me", + LFS_O_WRONLY | LFS_O_CREAT) => 0; + lfs_file_close(&lfs, &file) => 0; + lfs_file_open(&lfs, &file, "/dir.2/1.move_me", + LFS_O_WRONLY | LFS_O_CREAT) => 0; + lfs_file_write(&lfs, &file, "remove me", + sizeof("remove me")) => sizeof("remove me"); + lfs_file_close(&lfs, &file) => 0; + + lfs_file_open(&lfs, &file, "/dir.1/0.before", + LFS_O_WRONLY | LFS_O_CREAT) => 0; + lfs_file_write(&lfs, &file, "test.1", 7) => 7; + lfs_file_close(&lfs, &file) => 0; + lfs_file_open(&lfs, &file, "/dir.1/2.after", + LFS_O_WRONLY | LFS_O_CREAT) => 0; + lfs_file_write(&lfs, &file, "test.2", 7) => 7; + lfs_file_close(&lfs, &file) => 0; + + lfs_file_open(&lfs, &file, "/dir.2/0.before", + LFS_O_WRONLY | LFS_O_CREAT) => 0; + lfs_file_write(&lfs, &file, "test.3", 7) => 7; + lfs_file_close(&lfs, &file) => 0; + lfs_file_open(&lfs, &file, "/dir.2/2.after", + LFS_O_WRONLY | LFS_O_CREAT) => 0; + lfs_file_write(&lfs, &file, "test.4", 7) => 7; + lfs_file_close(&lfs, &file) => 0; + + lfs_file_t files[4]; + lfs_file_open(&lfs, &files[0], "/dir.1/0.before", + LFS_O_WRONLY | LFS_O_TRUNC) => 0; + lfs_file_open(&lfs, &files[1], "/dir.1/2.after", + LFS_O_WRONLY | LFS_O_TRUNC) => 0; + lfs_file_open(&lfs, &files[2], "/dir.2/0.before", + LFS_O_WRONLY | LFS_O_TRUNC) => 0; + lfs_file_open(&lfs, &files[3], "/dir.2/2.after", + LFS_O_WRONLY | LFS_O_TRUNC) => 0; + lfs_file_write(&lfs, &files[0], "test.5", 7) => 7; + lfs_file_write(&lfs, &files[1], "test.6", 7) => 7; + lfs_file_write(&lfs, &files[2], "test.7", 7) => 7; + lfs_file_write(&lfs, &files[3], "test.8", 7) => 7; + + // rename file while everything is open, this triggers both + // a create and delete as it overwrites the destination file + lfs_rename(&lfs, "/dir.1/1.move_me", "/dir.2/1.move_me") => 0; + + lfs_file_close(&lfs, &files[0]) => 0; + lfs_file_close(&lfs, &files[1]) => 0; + lfs_file_close(&lfs, &files[2]) => 0; + lfs_file_close(&lfs, &files[3]) => 0; + + // check that nothing was corrupted + lfs_dir_open(&lfs, &dir, "/") => 0; + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, ".") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "..") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "dir.1") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "dir.2") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 0; + lfs_dir_close(&lfs, &dir) => 0; + + lfs_dir_open(&lfs, &dir, "/dir.1") => 0; + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, ".") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "..") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "0.before") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == 7); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "2.after") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == 7); + lfs_dir_read(&lfs, &dir, &info) => 0; + lfs_dir_close(&lfs, &dir) => 0; + + lfs_dir_open(&lfs, &dir, "/dir.2") => 0; + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, ".") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "..") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "0.before") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == 7); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "1.move_me") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == 0); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "2.after") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == 7); + lfs_dir_read(&lfs, &dir, &info) => 0; + lfs_dir_close(&lfs, &dir) => 0; + + lfs_file_open(&lfs, &file, "/dir.1/0.before", LFS_O_RDONLY) => 0; + lfs_file_read(&lfs, &file, buffer, 7) => 7; + assert(strcmp((char*)buffer, "test.5") == 0); + lfs_file_close(&lfs, &file) => 0; + lfs_file_open(&lfs, &file, "/dir.1/2.after", LFS_O_RDONLY) => 0; + lfs_file_read(&lfs, &file, buffer, 7) => 7; + assert(strcmp((char*)buffer, "test.6") == 0); + lfs_file_close(&lfs, &file) => 0; + lfs_file_open(&lfs, &file, "/dir.2/0.before", LFS_O_RDONLY) => 0; + lfs_file_read(&lfs, &file, buffer, 7) => 7; + assert(strcmp((char*)buffer, "test.7") == 0); + lfs_file_close(&lfs, &file) => 0; + lfs_file_open(&lfs, &file, "/dir.2/2.after", LFS_O_RDONLY) => 0; + lfs_file_read(&lfs, &file, buffer, 7) => 7; + assert(strcmp((char*)buffer, "test.8") == 0); + lfs_file_close(&lfs, &file) => 0; + + // now move back + lfs_file_open(&lfs, &file, "/dir.1/1.move_me", + LFS_O_WRONLY | LFS_O_CREAT) => 0; + lfs_file_write(&lfs, &file, "remove me", + sizeof("remove me")) => sizeof("remove me"); + lfs_file_close(&lfs, &file) => 0; + + lfs_file_open(&lfs, &files[0], "/dir.1/0.before", + LFS_O_WRONLY | LFS_O_TRUNC) => 0; + lfs_file_open(&lfs, &files[1], "/dir.1/2.after", + LFS_O_WRONLY | LFS_O_TRUNC) => 0; + lfs_file_open(&lfs, &files[2], "/dir.2/0.before", + LFS_O_WRONLY | LFS_O_TRUNC) => 0; + lfs_file_open(&lfs, &files[3], "/dir.2/2.after", + LFS_O_WRONLY | LFS_O_TRUNC) => 0; + lfs_file_write(&lfs, &files[0], "test.9", 7) => 7; + lfs_file_write(&lfs, &files[1], "test.a", 7) => 7; + lfs_file_write(&lfs, &files[2], "test.b", 7) => 7; + lfs_file_write(&lfs, &files[3], "test.c", 7) => 7; + + // rename file while everything is open, this triggers both + // a create and delete simultaneously + lfs_rename(&lfs, "/dir.2/1.move_me", "/dir.1/1.move_me") => 0; + + lfs_file_close(&lfs, &files[0]) => 0; + lfs_file_close(&lfs, &files[1]) => 0; + lfs_file_close(&lfs, &files[2]) => 0; + lfs_file_close(&lfs, &files[3]) => 0; + + // and check that nothing was corrupted again + lfs_dir_open(&lfs, &dir, "/") => 0; + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, ".") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "..") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "dir.1") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "dir.2") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 0; + lfs_dir_close(&lfs, &dir) => 0; + + lfs_dir_open(&lfs, &dir, "/dir.1") => 0; + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, ".") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "..") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "0.before") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == 7); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "1.move_me") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == 0); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "2.after") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == 7); + lfs_dir_read(&lfs, &dir, &info) => 0; + lfs_dir_close(&lfs, &dir) => 0; + + lfs_dir_open(&lfs, &dir, "/dir.2") => 0; + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, ".") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "..") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "0.before") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == 7); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "2.after") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == 7); + lfs_dir_read(&lfs, &dir, &info) => 0; + lfs_dir_close(&lfs, &dir) => 0; + + lfs_file_open(&lfs, &file, "/dir.1/0.before", LFS_O_RDONLY) => 0; + lfs_file_read(&lfs, &file, buffer, 7) => 7; + assert(strcmp((char*)buffer, "test.9") == 0); + lfs_file_close(&lfs, &file) => 0; + lfs_file_open(&lfs, &file, "/dir.1/2.after", LFS_O_RDONLY) => 0; + lfs_file_read(&lfs, &file, buffer, 7) => 7; + assert(strcmp((char*)buffer, "test.a") == 0); + lfs_file_close(&lfs, &file) => 0; + lfs_file_open(&lfs, &file, "/dir.2/0.before", LFS_O_RDONLY) => 0; + lfs_file_read(&lfs, &file, buffer, 7) => 7; + assert(strcmp((char*)buffer, "test.b") == 0); + lfs_file_close(&lfs, &file) => 0; + lfs_file_open(&lfs, &file, "/dir.2/2.after", LFS_O_RDONLY) => 0; + lfs_file_read(&lfs, &file, buffer, 7) => 7; + assert(strcmp((char*)buffer, "test.c") == 0); + lfs_file_close(&lfs, &file) => 0; + lfs_unmount(&lfs) => 0; +''' + +[[case]] # move fix in relocation +in = "lfs.c" +define.RELOCATIONS = 'range(0x3+1)' +define.LFS_ERASE_CYCLES = 0xffffffff +code = ''' + lfs_format(&lfs, &cfg) => 0; + lfs_mount(&lfs, &cfg) => 0; + + lfs_mkdir(&lfs, "/parent") => 0; + lfs_mkdir(&lfs, "/parent/child") => 0; + + lfs_file_open(&lfs, &file, "/parent/1.move_me", + LFS_O_WRONLY | LFS_O_CREAT) => 0; + lfs_file_write(&lfs, &file, "move me", + sizeof("move me")) => sizeof("move me"); + lfs_file_close(&lfs, &file) => 0; + + lfs_file_open(&lfs, &file, "/parent/0.before", + LFS_O_WRONLY | LFS_O_CREAT) => 0; + lfs_file_write(&lfs, &file, "test.1", 7) => 7; + lfs_file_close(&lfs, &file) => 0; + lfs_file_open(&lfs, &file, "/parent/2.after", + LFS_O_WRONLY | LFS_O_CREAT) => 0; + lfs_file_write(&lfs, &file, "test.2", 7) => 7; + lfs_file_close(&lfs, &file) => 0; + lfs_file_open(&lfs, &file, "/parent/child/0.before", + LFS_O_WRONLY | LFS_O_CREAT) => 0; + lfs_file_write(&lfs, &file, "test.3", 7) => 7; + lfs_file_close(&lfs, &file) => 0; + lfs_file_open(&lfs, &file, "/parent/child/2.after", + LFS_O_WRONLY | LFS_O_CREAT) => 0; + lfs_file_write(&lfs, &file, "test.4", 7) => 7; + lfs_file_close(&lfs, &file) => 0; + + lfs_file_t files[4]; + lfs_file_open(&lfs, &files[0], "/parent/0.before", + LFS_O_WRONLY | LFS_O_TRUNC) => 0; + lfs_file_open(&lfs, &files[1], "/parent/2.after", + LFS_O_WRONLY | LFS_O_TRUNC) => 0; + lfs_file_open(&lfs, &files[2], "/parent/child/0.before", + LFS_O_WRONLY | LFS_O_TRUNC) => 0; + lfs_file_open(&lfs, &files[3], "/parent/child/2.after", + LFS_O_WRONLY | LFS_O_TRUNC) => 0; + lfs_file_write(&lfs, &files[0], "test.5", 7) => 7; + lfs_file_write(&lfs, &files[1], "test.6", 7) => 7; + lfs_file_write(&lfs, &files[2], "test.7", 7) => 7; + lfs_file_write(&lfs, &files[3], "test.8", 7) => 7; + + // force specific directories to relocate + if (RELOCATIONS & 0x1) { + lfs_dir_open(&lfs, &dir, "/parent"); + lfs_testbd_setwear(&cfg, dir.m.pair[0], 0xffffffff) => 0; + lfs_testbd_setwear(&cfg, dir.m.pair[1], 0xffffffff) => 0; + lfs_dir_close(&lfs, &dir) => 0; + } + if (RELOCATIONS & 0x2) { + lfs_dir_open(&lfs, &dir, "/parent/child"); + lfs_testbd_setwear(&cfg, dir.m.pair[0], 0xffffffff) => 0; + lfs_testbd_setwear(&cfg, dir.m.pair[1], 0xffffffff) => 0; + lfs_dir_close(&lfs, &dir) => 0; + } + + // ok, now we move the file, this creates a move that needs to be + // fixed, possibly in a metadata-pair that needs to be relocated + // + // the worst case is if we need to relocate and we need to implicit + // fix the move in our parent before it falls out of date + lfs_rename(&lfs, "/parent/1.move_me", "/parent/child/1.move_me") => 0; + + lfs_file_close(&lfs, &files[0]) => 0; + lfs_file_close(&lfs, &files[1]) => 0; + lfs_file_close(&lfs, &files[2]) => 0; + lfs_file_close(&lfs, &files[3]) => 0; + + // check that nothing was corrupted + lfs_dir_open(&lfs, &dir, "/parent") => 0; + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, ".") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "..") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "0.before") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == 7); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "2.after") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == 7); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "child") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 0; + lfs_dir_close(&lfs, &dir) => 0; + + lfs_dir_open(&lfs, &dir, "/parent/child") => 0; + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, ".") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "..") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "0.before") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == 7); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "1.move_me") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == sizeof("move me")); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "2.after") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == 7); + lfs_dir_read(&lfs, &dir, &info) => 0; + lfs_dir_close(&lfs, &dir) => 0; + + lfs_file_open(&lfs, &file, "/parent/0.before", LFS_O_RDONLY) => 0; + lfs_file_read(&lfs, &file, buffer, 7) => 7; + assert(strcmp((char*)buffer, "test.5") == 0); + lfs_file_close(&lfs, &file) => 0; + lfs_file_open(&lfs, &file, "/parent/2.after", LFS_O_RDONLY) => 0; + lfs_file_read(&lfs, &file, buffer, 7) => 7; + assert(strcmp((char*)buffer, "test.6") == 0); + lfs_file_close(&lfs, &file) => 0; + lfs_file_open(&lfs, &file, "/parent/child/0.before", LFS_O_RDONLY) => 0; + lfs_file_read(&lfs, &file, buffer, 7) => 7; + assert(strcmp((char*)buffer, "test.7") == 0); + lfs_file_close(&lfs, &file) => 0; + lfs_file_open(&lfs, &file, "/parent/child/2.after", LFS_O_RDONLY) => 0; + lfs_file_read(&lfs, &file, buffer, 7) => 7; + assert(strcmp((char*)buffer, "test.8") == 0); + lfs_file_close(&lfs, &file) => 0; + lfs_unmount(&lfs) => 0; +''' + +[[case]] # move fix in relocation with predecessor +in = "lfs.c" +define.RELOCATIONS = 'range(0x7+1)' +define.LFS_ERASE_CYCLES = 0xffffffff +code = ''' + lfs_format(&lfs, &cfg) => 0; + lfs_mount(&lfs, &cfg) => 0; + + lfs_mkdir(&lfs, "/parent") => 0; + lfs_mkdir(&lfs, "/parent/child") => 0; + lfs_mkdir(&lfs, "/parent/sibling") => 0; + + lfs_file_open(&lfs, &file, "/parent/sibling/1.move_me", + LFS_O_WRONLY | LFS_O_CREAT) => 0; + lfs_file_write(&lfs, &file, "move me", + sizeof("move me")) => sizeof("move me"); + lfs_file_close(&lfs, &file) => 0; + + lfs_file_open(&lfs, &file, "/parent/sibling/0.before", + LFS_O_WRONLY | LFS_O_CREAT) => 0; + lfs_file_write(&lfs, &file, "test.1", 7) => 7; + lfs_file_close(&lfs, &file) => 0; + lfs_file_open(&lfs, &file, "/parent/sibling/2.after", + LFS_O_WRONLY | LFS_O_CREAT) => 0; + lfs_file_write(&lfs, &file, "test.2", 7) => 7; + lfs_file_close(&lfs, &file) => 0; + lfs_file_open(&lfs, &file, "/parent/child/0.before", + LFS_O_WRONLY | LFS_O_CREAT) => 0; + lfs_file_write(&lfs, &file, "test.3", 7) => 7; + lfs_file_close(&lfs, &file) => 0; + lfs_file_open(&lfs, &file, "/parent/child/2.after", + LFS_O_WRONLY | LFS_O_CREAT) => 0; + lfs_file_write(&lfs, &file, "test.4", 7) => 7; + lfs_file_close(&lfs, &file) => 0; + + lfs_file_t files[4]; + lfs_file_open(&lfs, &files[0], "/parent/sibling/0.before", + LFS_O_WRONLY | LFS_O_TRUNC) => 0; + lfs_file_open(&lfs, &files[1], "/parent/sibling/2.after", + LFS_O_WRONLY | LFS_O_TRUNC) => 0; + lfs_file_open(&lfs, &files[2], "/parent/child/0.before", + LFS_O_WRONLY | LFS_O_TRUNC) => 0; + lfs_file_open(&lfs, &files[3], "/parent/child/2.after", + LFS_O_WRONLY | LFS_O_TRUNC) => 0; + lfs_file_write(&lfs, &files[0], "test.5", 7) => 7; + lfs_file_write(&lfs, &files[1], "test.6", 7) => 7; + lfs_file_write(&lfs, &files[2], "test.7", 7) => 7; + lfs_file_write(&lfs, &files[3], "test.8", 7) => 7; + + // force specific directories to relocate + if (RELOCATIONS & 0x1) { + lfs_dir_open(&lfs, &dir, "/parent"); + lfs_testbd_setwear(&cfg, dir.m.pair[0], 0xffffffff) => 0; + lfs_testbd_setwear(&cfg, dir.m.pair[1], 0xffffffff) => 0; + lfs_dir_close(&lfs, &dir) => 0; + } + if (RELOCATIONS & 0x2) { + lfs_dir_open(&lfs, &dir, "/parent/sibling"); + lfs_testbd_setwear(&cfg, dir.m.pair[0], 0xffffffff) => 0; + lfs_testbd_setwear(&cfg, dir.m.pair[1], 0xffffffff) => 0; + lfs_dir_close(&lfs, &dir) => 0; + } + if (RELOCATIONS & 0x4) { + lfs_dir_open(&lfs, &dir, "/parent/child"); + lfs_testbd_setwear(&cfg, dir.m.pair[0], 0xffffffff) => 0; + lfs_testbd_setwear(&cfg, dir.m.pair[1], 0xffffffff) => 0; + lfs_dir_close(&lfs, &dir) => 0; + } + + // ok, now we move the file, this creates a move that needs to be + // fixed, possibly in a metadata-pair that needs to be relocated + // + // and now relocations can force us to need to fix our move in either + // the parent or child before things break + lfs_rename(&lfs, + "/parent/sibling/1.move_me", + "/parent/child/1.move_me") => 0; + + lfs_file_close(&lfs, &files[0]) => 0; + lfs_file_close(&lfs, &files[1]) => 0; + lfs_file_close(&lfs, &files[2]) => 0; + lfs_file_close(&lfs, &files[3]) => 0; + + // check that nothing was corrupted + lfs_dir_open(&lfs, &dir, "/parent") => 0; + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, ".") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "..") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "child") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "sibling") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 0; + lfs_dir_close(&lfs, &dir) => 0; + + lfs_dir_open(&lfs, &dir, "/parent/sibling") => 0; + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, ".") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "..") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "0.before") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == 7); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "2.after") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == 7); + lfs_dir_read(&lfs, &dir, &info) => 0; + lfs_dir_close(&lfs, &dir) => 0; + + lfs_dir_open(&lfs, &dir, "/parent/child") => 0; + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, ".") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "..") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "0.before") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == 7); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "1.move_me") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == sizeof("move me")); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(strcmp(info.name, "2.after") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == 7); + lfs_dir_read(&lfs, &dir, &info) => 0; + lfs_dir_close(&lfs, &dir) => 0; + + lfs_file_open(&lfs, &file, "/parent/sibling/0.before", LFS_O_RDONLY) => 0; + lfs_file_read(&lfs, &file, buffer, 7) => 7; + assert(strcmp((char*)buffer, "test.5") == 0); + lfs_file_close(&lfs, &file) => 0; + lfs_file_open(&lfs, &file, "/parent/sibling/2.after", LFS_O_RDONLY) => 0; + lfs_file_read(&lfs, &file, buffer, 7) => 7; + assert(strcmp((char*)buffer, "test.6") == 0); + lfs_file_close(&lfs, &file) => 0; + lfs_file_open(&lfs, &file, "/parent/child/0.before", LFS_O_RDONLY) => 0; + lfs_file_read(&lfs, &file, buffer, 7) => 7; + assert(strcmp((char*)buffer, "test.7") == 0); + lfs_file_close(&lfs, &file) => 0; + lfs_file_open(&lfs, &file, "/parent/child/2.after", LFS_O_RDONLY) => 0; + lfs_file_read(&lfs, &file, buffer, 7) => 7; + assert(strcmp((char*)buffer, "test.8") == 0); + lfs_file_close(&lfs, &file) => 0; + lfs_unmount(&lfs) => 0; +''' diff --git a/tests/test_orphan.sh b/tests/test_orphan.sh deleted file mode 100755 index b0a8493f..00000000 --- a/tests/test_orphan.sh +++ /dev/null @@ -1,46 +0,0 @@ -#!/bin/bash -set -eu -export TEST_FILE=$0 -trap 'export TEST_LINE=$LINENO' DEBUG - -echo "=== Orphan tests ===" -rm -rf blocks -scripts/test.py << TEST - lfs_format(&lfs, &cfg) => 0; -TEST - -echo "--- Orphan test ---" -scripts/test.py << TEST - lfs_mount(&lfs, &cfg) => 0; - lfs_mkdir(&lfs, "parent") => 0; - lfs_mkdir(&lfs, "parent/orphan") => 0; - lfs_mkdir(&lfs, "parent/child") => 0; - lfs_remove(&lfs, "parent/orphan") => 0; -TEST -# corrupt most recent commit, this should be the update to the previous -# linked-list entry and should orphan the child -scripts/corrupt.py -scripts/test.py << TEST - lfs_mount(&lfs, &cfg) => 0; - - lfs_stat(&lfs, "parent/orphan", &info) => LFS_ERR_NOENT; - lfs_ssize_t before = lfs_fs_size(&lfs); - before => 8; - - lfs_unmount(&lfs) => 0; - lfs_mount(&lfs, &cfg) => 0; - - lfs_stat(&lfs, "parent/orphan", &info) => LFS_ERR_NOENT; - lfs_ssize_t orphaned = lfs_fs_size(&lfs); - orphaned => 8; - - lfs_mkdir(&lfs, "parent/otherchild") => 0; - - lfs_stat(&lfs, "parent/orphan", &info) => LFS_ERR_NOENT; - lfs_ssize_t deorphaned = lfs_fs_size(&lfs); - deorphaned => 8; - - lfs_unmount(&lfs) => 0; -TEST - -scripts/results.py diff --git a/tests/test_orphans.toml b/tests/test_orphans.toml new file mode 100644 index 00000000..241e273e --- /dev/null +++ b/tests/test_orphans.toml @@ -0,0 +1,120 @@ +[[case]] # orphan test +in = "lfs.c" +if = 'LFS_PROG_SIZE <= 0x3fe' # only works with one crc per commit +code = ''' + lfs_format(&lfs, &cfg) => 0; + lfs_mount(&lfs, &cfg) => 0; + lfs_mkdir(&lfs, "parent") => 0; + lfs_mkdir(&lfs, "parent/orphan") => 0; + lfs_mkdir(&lfs, "parent/child") => 0; + lfs_remove(&lfs, "parent/orphan") => 0; + lfs_unmount(&lfs) => 0; + + // corrupt the child's most recent commit, this should be the update + // to the linked-list entry, which should orphan the orphan. Note this + // makes a lot of assumptions about the remove operation. + lfs_mount(&lfs, &cfg) => 0; + lfs_dir_open(&lfs, &dir, "parent/child") => 0; + lfs_block_t block = dir.m.pair[0]; + lfs_dir_close(&lfs, &dir) => 0; + lfs_unmount(&lfs) => 0; + uint8_t bbuffer[LFS_BLOCK_SIZE]; + cfg.read(&cfg, block, 0, bbuffer, LFS_BLOCK_SIZE) => 0; + int off = LFS_BLOCK_SIZE-1; + while (off >= 0 && bbuffer[off] == LFS_ERASE_VALUE) { + off -= 1; + } + memset(&bbuffer[off-3], LFS_BLOCK_SIZE, 3); + cfg.erase(&cfg, block) => 0; + cfg.prog(&cfg, block, 0, bbuffer, LFS_BLOCK_SIZE) => 0; + cfg.sync(&cfg) => 0; + + lfs_mount(&lfs, &cfg) => 0; + lfs_stat(&lfs, "parent/orphan", &info) => LFS_ERR_NOENT; + lfs_stat(&lfs, "parent/child", &info) => 0; + lfs_fs_size(&lfs) => 8; + lfs_unmount(&lfs) => 0; + + lfs_mount(&lfs, &cfg) => 0; + lfs_stat(&lfs, "parent/orphan", &info) => LFS_ERR_NOENT; + lfs_stat(&lfs, "parent/child", &info) => 0; + lfs_fs_size(&lfs) => 8; + // this mkdir should both create a dir and deorphan, so size + // should be unchanged + lfs_mkdir(&lfs, "parent/otherchild") => 0; + lfs_stat(&lfs, "parent/orphan", &info) => LFS_ERR_NOENT; + lfs_stat(&lfs, "parent/child", &info) => 0; + lfs_stat(&lfs, "parent/otherchild", &info) => 0; + lfs_fs_size(&lfs) => 8; + lfs_unmount(&lfs) => 0; + + lfs_mount(&lfs, &cfg) => 0; + lfs_stat(&lfs, "parent/orphan", &info) => LFS_ERR_NOENT; + lfs_stat(&lfs, "parent/child", &info) => 0; + lfs_stat(&lfs, "parent/otherchild", &info) => 0; + lfs_fs_size(&lfs) => 8; + lfs_unmount(&lfs) => 0; +''' + +[[case]] # reentrant testing for orphans, basically just spam mkdir/remove +reentrant = true +# TODO fix this case, caused by non-DAG trees +if = '!(DEPTH == 3 && LFS_CACHE_SIZE != 64)' +define = [ + {FILES=6, DEPTH=1, CYCLES=20}, + {FILES=26, DEPTH=1, CYCLES=20}, + {FILES=3, DEPTH=3, CYCLES=20}, +] +code = ''' + err = lfs_mount(&lfs, &cfg); + if (err) { + lfs_format(&lfs, &cfg) => 0; + lfs_mount(&lfs, &cfg) => 0; + } + + srand(1); + const char alpha[] = "abcdefghijklmnopqrstuvwxyz"; + for (int i = 0; i < CYCLES; i++) { + // create random path + char full_path[256]; + for (int d = 0; d < DEPTH; d++) { + sprintf(&full_path[2*d], "/%c", alpha[rand() % FILES]); + } + + // if it does not exist, we create it, else we destroy + int res = lfs_stat(&lfs, full_path, &info); + if (res == LFS_ERR_NOENT) { + // create each directory in turn, ignore if dir already exists + for (int d = 0; d < DEPTH; d++) { + strcpy(path, full_path); + path[2*d+2] = '\0'; + err = lfs_mkdir(&lfs, path); + assert(!err || err == LFS_ERR_EXIST); + } + + for (int d = 0; d < DEPTH; d++) { + strcpy(path, full_path); + path[2*d+2] = '\0'; + lfs_stat(&lfs, path, &info) => 0; + assert(strcmp(info.name, &path[2*d+1]) == 0); + assert(info.type == LFS_TYPE_DIR); + } + } else { + // is valid dir? + assert(strcmp(info.name, &full_path[2*(DEPTH-1)+1]) == 0); + assert(info.type == LFS_TYPE_DIR); + + // try to delete path in reverse order, ignore if dir is not empty + for (int d = DEPTH-1; d >= 0; d--) { + strcpy(path, full_path); + path[2*d+2] = '\0'; + err = lfs_remove(&lfs, path); + assert(!err || err == LFS_ERR_NOTEMPTY); + } + + lfs_stat(&lfs, full_path, &info) => LFS_ERR_NOENT; + } + } + lfs_unmount(&lfs) => 0; +''' + diff --git a/tests/test_paths.sh b/tests/test_paths.sh deleted file mode 100755 index cfdcd989..00000000 --- a/tests/test_paths.sh +++ /dev/null @@ -1,202 +0,0 @@ -#!/bin/bash -set -eu -export TEST_FILE=$0 -trap 'export TEST_LINE=$LINENO' DEBUG - -echo "=== Path tests ===" -rm -rf blocks -scripts/test.py << TEST - lfs_format(&lfs, &cfg) => 0; -TEST - -scripts/test.py << TEST - lfs_mount(&lfs, &cfg) => 0; - lfs_mkdir(&lfs, "tea") => 0; - lfs_mkdir(&lfs, "coffee") => 0; - lfs_mkdir(&lfs, "soda") => 0; - lfs_mkdir(&lfs, "tea/hottea") => 0; - lfs_mkdir(&lfs, "tea/warmtea") => 0; - lfs_mkdir(&lfs, "tea/coldtea") => 0; - lfs_mkdir(&lfs, "coffee/hotcoffee") => 0; - lfs_mkdir(&lfs, "coffee/warmcoffee") => 0; - lfs_mkdir(&lfs, "coffee/coldcoffee") => 0; - lfs_mkdir(&lfs, "soda/hotsoda") => 0; - lfs_mkdir(&lfs, "soda/warmsoda") => 0; - lfs_mkdir(&lfs, "soda/coldsoda") => 0; - lfs_unmount(&lfs) => 0; -TEST - -echo "--- Root path tests ---" -scripts/test.py << TEST - lfs_mount(&lfs, &cfg) => 0; - lfs_stat(&lfs, "tea/hottea", &info) => 0; - strcmp(info.name, "hottea") => 0; - lfs_stat(&lfs, "/tea/hottea", &info) => 0; - strcmp(info.name, "hottea") => 0; - - lfs_mkdir(&lfs, "/milk1") => 0; - lfs_stat(&lfs, "/milk1", &info) => 0; - strcmp(info.name, "milk1") => 0; - lfs_unmount(&lfs) => 0; -TEST - -echo "--- Redundant slash path tests ---" -scripts/test.py << TEST - lfs_mount(&lfs, &cfg) => 0; - lfs_stat(&lfs, "/tea/hottea", &info) => 0; - strcmp(info.name, "hottea") => 0; - lfs_stat(&lfs, "//tea//hottea", &info) => 0; - strcmp(info.name, "hottea") => 0; - lfs_stat(&lfs, "///tea///hottea", &info) => 0; - strcmp(info.name, "hottea") => 0; - - lfs_mkdir(&lfs, "///milk2") => 0; - lfs_stat(&lfs, "///milk2", &info) => 0; - strcmp(info.name, "milk2") => 0; - lfs_unmount(&lfs) => 0; -TEST - -echo "--- Dot path tests ---" -scripts/test.py << TEST - lfs_mount(&lfs, &cfg) => 0; - lfs_stat(&lfs, "./tea/hottea", &info) => 0; - strcmp(info.name, "hottea") => 0; - lfs_stat(&lfs, "/./tea/hottea", &info) => 0; - strcmp(info.name, "hottea") => 0; - lfs_stat(&lfs, "/././tea/hottea", &info) => 0; - strcmp(info.name, "hottea") => 0; - lfs_stat(&lfs, "/./tea/./hottea", &info) => 0; - strcmp(info.name, "hottea") => 0; - - lfs_mkdir(&lfs, "/./milk3") => 0; - lfs_stat(&lfs, "/./milk3", &info) => 0; - strcmp(info.name, "milk3") => 0; - lfs_unmount(&lfs) => 0; -TEST - -echo "--- Dot dot path tests ---" -scripts/test.py << TEST - lfs_mount(&lfs, &cfg) => 0; - lfs_stat(&lfs, "coffee/../tea/hottea", &info) => 0; - strcmp(info.name, "hottea") => 0; - lfs_stat(&lfs, "tea/coldtea/../hottea", &info) => 0; - strcmp(info.name, "hottea") => 0; - lfs_stat(&lfs, "coffee/coldcoffee/../../tea/hottea", &info) => 0; - strcmp(info.name, "hottea") => 0; - lfs_stat(&lfs, "coffee/../soda/../tea/hottea", &info) => 0; - strcmp(info.name, "hottea") => 0; - - lfs_mkdir(&lfs, "coffee/../milk4") => 0; - lfs_stat(&lfs, "coffee/../milk4", &info) => 0; - strcmp(info.name, "milk4") => 0; - lfs_unmount(&lfs) => 0; -TEST - -echo "--- Trailing dot path tests ---" -scripts/test.py << TEST - lfs_mount(&lfs, &cfg) => 0; - lfs_stat(&lfs, "tea/hottea/", &info) => 0; - strcmp(info.name, "hottea") => 0; - lfs_stat(&lfs, "tea/hottea/.", &info) => 0; - strcmp(info.name, "hottea") => 0; - lfs_stat(&lfs, "tea/hottea/./.", &info) => 0; - strcmp(info.name, "hottea") => 0; - lfs_stat(&lfs, "tea/hottea/..", &info) => 0; - strcmp(info.name, "tea") => 0; - lfs_stat(&lfs, "tea/hottea/../.", &info) => 0; - strcmp(info.name, "tea") => 0; - lfs_unmount(&lfs) => 0; -TEST - -echo "--- Root dot dot path tests ---" -scripts/test.py << TEST - lfs_mount(&lfs, &cfg) => 0; - lfs_stat(&lfs, "coffee/../../../../../../tea/hottea", &info) => 0; - strcmp(info.name, "hottea") => 0; - - lfs_mkdir(&lfs, "coffee/../../../../../../milk5") => 0; - lfs_stat(&lfs, "coffee/../../../../../../milk5", &info) => 0; - strcmp(info.name, "milk5") => 0; - lfs_unmount(&lfs) => 0; -TEST - -echo "--- Root tests ---" -scripts/test.py << TEST - lfs_mount(&lfs, &cfg) => 0; - lfs_stat(&lfs, "/", &info) => 0; - info.type => LFS_TYPE_DIR; - strcmp(info.name, "/") => 0; - - lfs_mkdir(&lfs, "/") => LFS_ERR_EXIST; - lfs_file_open(&lfs, &file, "/", LFS_O_WRONLY | LFS_O_CREAT) - => LFS_ERR_ISDIR; - - // more corner cases - lfs_remove(&lfs, "") => LFS_ERR_INVAL; - lfs_remove(&lfs, ".") => LFS_ERR_INVAL; - lfs_remove(&lfs, "..") => LFS_ERR_INVAL; - lfs_remove(&lfs, "/") => LFS_ERR_INVAL; - lfs_remove(&lfs, "//") => LFS_ERR_INVAL; - lfs_remove(&lfs, "./") => LFS_ERR_INVAL; - lfs_unmount(&lfs) => 0; -TEST - -echo "--- Sketchy path tests ---" -scripts/test.py << TEST - lfs_mount(&lfs, &cfg) => 0; - lfs_mkdir(&lfs, "dirt/ground") => LFS_ERR_NOENT; - lfs_mkdir(&lfs, "dirt/ground/earth") => LFS_ERR_NOENT; - lfs_unmount(&lfs) => 0; -TEST - -echo "--- Superblock conflict test ---" -scripts/test.py << TEST - lfs_mount(&lfs, &cfg) => 0; - lfs_mkdir(&lfs, "littlefs") => 0; - lfs_remove(&lfs, "littlefs") => 0; - lfs_unmount(&lfs) => 0; -TEST - -echo "--- Max path test ---" -scripts/test.py << TEST - lfs_mount(&lfs, &cfg) => 0; - memset(path, 'w', LFS_NAME_MAX+1); - path[LFS_NAME_MAX+2] = '\0'; - lfs_mkdir(&lfs, path) => LFS_ERR_NAMETOOLONG; - lfs_file_open(&lfs, &file, path, - LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_NAMETOOLONG; - - memcpy(path, "coffee/", strlen("coffee/")); - memset(path+strlen("coffee/"), 'w', LFS_NAME_MAX+1); - path[strlen("coffee/")+LFS_NAME_MAX+2] = '\0'; - lfs_mkdir(&lfs, path) => LFS_ERR_NAMETOOLONG; - lfs_file_open(&lfs, &file, path, - LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_NAMETOOLONG; - lfs_unmount(&lfs) => 0; -TEST - -echo "--- Really big path test ---" -scripts/test.py << TEST - lfs_mount(&lfs, &cfg) => 0; - memset(path, 'w', LFS_NAME_MAX); - path[LFS_NAME_MAX] = '\0'; - lfs_mkdir(&lfs, path) => 0; - lfs_remove(&lfs, path) => 0; - lfs_file_open(&lfs, &file, path, - LFS_O_WRONLY | LFS_O_CREAT) => 0; - lfs_file_close(&lfs, &file) => 0; - lfs_remove(&lfs, path) => 0; - - memcpy(path, "coffee/", strlen("coffee/")); - memset(path+strlen("coffee/"), 'w', LFS_NAME_MAX); - path[strlen("coffee/")+LFS_NAME_MAX] = '\0'; - lfs_mkdir(&lfs, path) => 0; - lfs_remove(&lfs, path) => 0; - lfs_file_open(&lfs, &file, path, - LFS_O_WRONLY | LFS_O_CREAT) => 0; - lfs_file_close(&lfs, &file) => 0; - lfs_remove(&lfs, path) => 0; - lfs_unmount(&lfs) => 0; -TEST - -scripts/results.py diff --git a/tests/test_paths.toml b/tests/test_paths.toml new file mode 100644 index 00000000..a7474c0b --- /dev/null +++ b/tests/test_paths.toml @@ -0,0 +1,293 @@ + +[[case]] # simple path test +code = ''' + lfs_format(&lfs, &cfg) => 0; + lfs_mount(&lfs, &cfg) => 0; + lfs_mkdir(&lfs, "tea") => 0; + lfs_mkdir(&lfs, "tea/hottea") => 0; + lfs_mkdir(&lfs, "tea/warmtea") => 0; + lfs_mkdir(&lfs, "tea/coldtea") => 0; + + lfs_stat(&lfs, "tea/hottea", &info) => 0; + assert(strcmp(info.name, "hottea") == 0); + lfs_stat(&lfs, "/tea/hottea", &info) => 0; + assert(strcmp(info.name, "hottea") == 0); + + lfs_mkdir(&lfs, "/milk") => 0; + lfs_stat(&lfs, "/milk", &info) => 0; + assert(strcmp(info.name, "milk") == 0); + lfs_stat(&lfs, "milk", &info) => 0; + assert(strcmp(info.name, "milk") == 0); + lfs_unmount(&lfs) => 0; +''' + +[[case]] # redundant slashes +code = ''' + lfs_format(&lfs, &cfg) => 0; + lfs_mount(&lfs, &cfg) => 0; + lfs_mkdir(&lfs, "tea") => 0; + lfs_mkdir(&lfs, "tea/hottea") => 0; + lfs_mkdir(&lfs, "tea/warmtea") => 0; + lfs_mkdir(&lfs, "tea/coldtea") => 0; + + lfs_stat(&lfs, "/tea/hottea", &info) => 0; + assert(strcmp(info.name, "hottea") == 0); + lfs_stat(&lfs, "//tea//hottea", &info) => 0; + assert(strcmp(info.name, "hottea") == 0); + lfs_stat(&lfs, "///tea///hottea", &info) => 0; + assert(strcmp(info.name, "hottea") == 0); + + lfs_mkdir(&lfs, "////milk") => 0; + lfs_stat(&lfs, "////milk", &info) => 0; + assert(strcmp(info.name, "milk") == 0); + lfs_stat(&lfs, "milk", &info) => 0; + assert(strcmp(info.name, "milk") == 0); + lfs_unmount(&lfs) => 0; +''' + +[[case]] # dot path test +code = ''' + lfs_format(&lfs, &cfg) => 0; + lfs_mount(&lfs, &cfg) => 0; + lfs_mkdir(&lfs, "tea") => 0; + lfs_mkdir(&lfs, "tea/hottea") => 0; + lfs_mkdir(&lfs, "tea/warmtea") => 0; + lfs_mkdir(&lfs, "tea/coldtea") => 0; + + lfs_stat(&lfs, "./tea/hottea", &info) => 0; + assert(strcmp(info.name, "hottea") == 0); + lfs_stat(&lfs, "/./tea/hottea", &info) => 0; + assert(strcmp(info.name, "hottea") == 0); + lfs_stat(&lfs, "/././tea/hottea", &info) => 0; + assert(strcmp(info.name, "hottea") == 0); + lfs_stat(&lfs, "/./tea/./hottea", &info) => 0; + assert(strcmp(info.name, "hottea") == 0); + + lfs_mkdir(&lfs, "/./milk") => 0; + lfs_stat(&lfs, "/./milk", &info) => 0; + assert(strcmp(info.name, "milk") == 0); + lfs_stat(&lfs, "milk", &info) => 0; + assert(strcmp(info.name, "milk") == 0); + lfs_unmount(&lfs) => 0; +''' + +[[case]] # dot dot path test +code = ''' + lfs_format(&lfs, &cfg) => 0; + lfs_mount(&lfs, &cfg) => 0; + lfs_mkdir(&lfs, "tea") => 0; + lfs_mkdir(&lfs, "tea/hottea") => 0; + lfs_mkdir(&lfs, "tea/warmtea") => 0; + lfs_mkdir(&lfs, "tea/coldtea") => 0; + lfs_mkdir(&lfs, "coffee") => 0; + lfs_mkdir(&lfs, "coffee/hotcoffee") => 0; + lfs_mkdir(&lfs, "coffee/warmcoffee") => 0; + lfs_mkdir(&lfs, "coffee/coldcoffee") => 0; + + lfs_stat(&lfs, "coffee/../tea/hottea", &info) => 0; + assert(strcmp(info.name, "hottea") == 0); + lfs_stat(&lfs, "tea/coldtea/../hottea", &info) => 0; + assert(strcmp(info.name, "hottea") == 0); + lfs_stat(&lfs, "coffee/coldcoffee/../../tea/hottea", &info) => 0; + assert(strcmp(info.name, "hottea") == 0); + lfs_stat(&lfs, "coffee/../coffee/../tea/hottea", &info) => 0; + assert(strcmp(info.name, "hottea") == 0); + + lfs_mkdir(&lfs, "coffee/../milk") => 0; + lfs_stat(&lfs, "coffee/../milk", &info) => 0; + strcmp(info.name, "milk") => 0; + lfs_stat(&lfs, "milk", &info) => 0; + strcmp(info.name, "milk") => 0; + lfs_unmount(&lfs) => 0; +''' + +[[case]] # trailing dot path test +code = ''' + lfs_format(&lfs, &cfg) => 0; + lfs_mount(&lfs, &cfg) => 0; + lfs_mkdir(&lfs, "tea") => 0; + lfs_mkdir(&lfs, "tea/hottea") => 0; + lfs_mkdir(&lfs, "tea/warmtea") => 0; + lfs_mkdir(&lfs, "tea/coldtea") => 0; + + lfs_stat(&lfs, "tea/hottea/", &info) => 0; + assert(strcmp(info.name, "hottea") == 0); + lfs_stat(&lfs, "tea/hottea/.", &info) => 0; + assert(strcmp(info.name, "hottea") == 0); + lfs_stat(&lfs, "tea/hottea/./.", &info) => 0; + assert(strcmp(info.name, "hottea") == 0); + lfs_stat(&lfs, "tea/hottea/..", &info) => 0; + assert(strcmp(info.name, "tea") == 0); + lfs_stat(&lfs, "tea/hottea/../.", &info) => 0; + assert(strcmp(info.name, "tea") == 0); + lfs_unmount(&lfs) => 0; +''' + +[[case]] # leading dot path test +code = ''' + lfs_format(&lfs, &cfg) => 0; + lfs_mount(&lfs, &cfg) => 0; + lfs_mkdir(&lfs, ".milk") => 0; + lfs_stat(&lfs, ".milk", &info) => 0; + strcmp(info.name, ".milk") => 0; + lfs_stat(&lfs, "tea/.././.milk", &info) => 0; + strcmp(info.name, ".milk") => 0; + lfs_unmount(&lfs) => 0; +''' + +[[case]] # root dot dot path test +code = ''' + lfs_format(&lfs, &cfg) => 0; + lfs_mount(&lfs, &cfg) => 0; + lfs_mkdir(&lfs, "tea") => 0; + lfs_mkdir(&lfs, "tea/hottea") => 0; + lfs_mkdir(&lfs, "tea/warmtea") => 0; + lfs_mkdir(&lfs, "tea/coldtea") => 0; + lfs_mkdir(&lfs, "coffee") => 0; + lfs_mkdir(&lfs, "coffee/hotcoffee") => 0; + lfs_mkdir(&lfs, "coffee/warmcoffee") => 0; + lfs_mkdir(&lfs, "coffee/coldcoffee") => 0; + + lfs_stat(&lfs, "coffee/../../../../../../tea/hottea", &info) => 0; + strcmp(info.name, "hottea") => 0; + + lfs_mkdir(&lfs, "coffee/../../../../../../milk") => 0; + lfs_stat(&lfs, "coffee/../../../../../../milk", &info) => 0; + strcmp(info.name, "milk") => 0; + lfs_stat(&lfs, "milk", &info) => 0; + strcmp(info.name, "milk") => 0; + lfs_unmount(&lfs) => 0; +''' + +[[case]] # invalid path tests +code = ''' + lfs_format(&lfs, &cfg); + lfs_mount(&lfs, &cfg) => 0; + lfs_stat(&lfs, "dirt", &info) => LFS_ERR_NOENT; + lfs_stat(&lfs, "dirt/ground", &info) => LFS_ERR_NOENT; + lfs_stat(&lfs, "dirt/ground/earth", &info) => LFS_ERR_NOENT; + + lfs_remove(&lfs, "dirt") => LFS_ERR_NOENT; + lfs_remove(&lfs, "dirt/ground") => LFS_ERR_NOENT; + lfs_remove(&lfs, "dirt/ground/earth") => LFS_ERR_NOENT; + + lfs_mkdir(&lfs, "dirt/ground") => LFS_ERR_NOENT; + lfs_file_open(&lfs, &file, "dirt/ground", LFS_O_WRONLY | LFS_O_CREAT) + => LFS_ERR_NOENT; + lfs_mkdir(&lfs, "dirt/ground/earth") => LFS_ERR_NOENT; + lfs_file_open(&lfs, &file, "dirt/ground/earth", LFS_O_WRONLY | LFS_O_CREAT) + => LFS_ERR_NOENT; + lfs_unmount(&lfs) => 0; +''' + +[[case]] # root operations +code = ''' + lfs_format(&lfs, &cfg) => 0; + lfs_mount(&lfs, &cfg) => 0; + lfs_stat(&lfs, "/", &info) => 0; + assert(strcmp(info.name, "/") == 0); + assert(info.type == LFS_TYPE_DIR); + + lfs_mkdir(&lfs, "/") => LFS_ERR_EXIST; + lfs_file_open(&lfs, &file, "/", LFS_O_WRONLY | LFS_O_CREAT) + => LFS_ERR_ISDIR; + + lfs_remove(&lfs, "/") => LFS_ERR_INVAL; + lfs_unmount(&lfs) => 0; +''' + +[[case]] # root representations +code = ''' + lfs_format(&lfs, &cfg) => 0; + lfs_mount(&lfs, &cfg) => 0; + lfs_stat(&lfs, "/", &info) => 0; + assert(strcmp(info.name, "/") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_stat(&lfs, "", &info) => 0; + assert(strcmp(info.name, "/") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_stat(&lfs, ".", &info) => 0; + assert(strcmp(info.name, "/") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_stat(&lfs, "..", &info) => 0; + assert(strcmp(info.name, "/") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_stat(&lfs, "//", &info) => 0; + assert(strcmp(info.name, "/") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_stat(&lfs, "./", &info) => 0; + assert(strcmp(info.name, "/") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_unmount(&lfs) => 0; +''' + +[[case]] # superblock conflict test +code = ''' + lfs_format(&lfs, &cfg) => 0; + lfs_mount(&lfs, &cfg) => 0; + lfs_stat(&lfs, "littlefs", &info) => LFS_ERR_NOENT; + lfs_remove(&lfs, "littlefs") => LFS_ERR_NOENT; + + lfs_mkdir(&lfs, "littlefs") => 0; + lfs_stat(&lfs, "littlefs", &info) => 0; + assert(strcmp(info.name, "littlefs") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_remove(&lfs, "littlefs") => 0; + lfs_stat(&lfs, "littlefs", &info) => LFS_ERR_NOENT; + lfs_unmount(&lfs) => 0; +''' + +[[case]] # max path test +code = ''' + lfs_format(&lfs, &cfg) => 0; + lfs_mount(&lfs, &cfg) => 0; + lfs_mkdir(&lfs, "coffee") => 0; + lfs_mkdir(&lfs, "coffee/hotcoffee") => 0; + lfs_mkdir(&lfs, "coffee/warmcoffee") => 0; + lfs_mkdir(&lfs, "coffee/coldcoffee") => 0; + + memset(path, 'w', LFS_NAME_MAX+1); + path[LFS_NAME_MAX+1] = '\0'; + lfs_mkdir(&lfs, path) => LFS_ERR_NAMETOOLONG; + lfs_file_open(&lfs, &file, path, LFS_O_WRONLY | LFS_O_CREAT) + => LFS_ERR_NAMETOOLONG; + + memcpy(path, "coffee/", strlen("coffee/")); + memset(path+strlen("coffee/"), 'w', LFS_NAME_MAX+1); + path[strlen("coffee/")+LFS_NAME_MAX+1] = '\0'; + lfs_mkdir(&lfs, path) => LFS_ERR_NAMETOOLONG; + lfs_file_open(&lfs, &file, path, LFS_O_WRONLY | LFS_O_CREAT) + => LFS_ERR_NAMETOOLONG; + lfs_unmount(&lfs) => 0; +''' + +[[case]] # really big path test +code = ''' + lfs_format(&lfs, &cfg) => 0; + lfs_mount(&lfs, &cfg) => 0; + lfs_mkdir(&lfs, "coffee") => 0; + lfs_mkdir(&lfs, "coffee/hotcoffee") => 0; + lfs_mkdir(&lfs, "coffee/warmcoffee") => 0; + lfs_mkdir(&lfs, "coffee/coldcoffee") => 0; + + memset(path, 'w', LFS_NAME_MAX); + path[LFS_NAME_MAX] = '\0'; + lfs_mkdir(&lfs, path) => 0; + lfs_remove(&lfs, path) => 0; + lfs_file_open(&lfs, &file, path, + LFS_O_WRONLY | LFS_O_CREAT) => 0; + lfs_file_close(&lfs, &file) => 0; + lfs_remove(&lfs, path) => 0; + + memcpy(path, "coffee/", strlen("coffee/")); + memset(path+strlen("coffee/"), 'w', LFS_NAME_MAX); + path[strlen("coffee/")+LFS_NAME_MAX] = '\0'; + lfs_mkdir(&lfs, path) => 0; + lfs_remove(&lfs, path) => 0; + lfs_file_open(&lfs, &file, path, + LFS_O_WRONLY | LFS_O_CREAT) => 0; + lfs_file_close(&lfs, &file) => 0; + lfs_remove(&lfs, path) => 0; + lfs_unmount(&lfs) => 0; +''' + diff --git a/tests/test_relocations.sh b/tests/test_relocations.sh deleted file mode 100755 index 5244e5ea..00000000 --- a/tests/test_relocations.sh +++ /dev/null @@ -1,139 +0,0 @@ -#!/bin/bash -set -eu -export TEST_FILE=$0 -trap 'export TEST_LINE=$LINENO' DEBUG - -ITERATIONS=20 -COUNT=10 - -echo "=== Relocation tests ===" -rm -rf blocks -scripts/test.py << TEST - lfs_format(&lfs, &cfg) => 0; - // fill up filesystem so only ~16 blocks are left - lfs_mount(&lfs, &cfg) => 0; - lfs_file_open(&lfs, &file, "padding", LFS_O_CREAT | LFS_O_WRONLY) => 0; - memset(buffer, 0, 512); - while (LFS_BLOCK_COUNT - lfs_fs_size(&lfs) > 16) { - lfs_file_write(&lfs, &file, buffer, 512) => 512; - } - lfs_file_close(&lfs, &file) => 0; - // make a child dir to use in bounded space - lfs_mkdir(&lfs, "child") => 0; - lfs_unmount(&lfs) => 0; -TEST - -echo "--- Dangling split dir test ---" -scripts/test.py << TEST - lfs_mount(&lfs, &cfg) => 0; - for (int j = 0; j < $ITERATIONS; j++) { - for (int i = 0; i < $COUNT; i++) { - sprintf(path, "child/test%03d_loooooooooooooooooong_name", i); - lfs_file_open(&lfs, &file, path, LFS_O_CREAT | LFS_O_WRONLY) => 0; - lfs_file_close(&lfs, &file) => 0; - } - - lfs_dir_open(&lfs, &dir, "child") => 0; - lfs_dir_read(&lfs, &dir, &info) => 1; - lfs_dir_read(&lfs, &dir, &info) => 1; - for (int i = 0; i < $COUNT; i++) { - sprintf(path, "test%03d_loooooooooooooooooong_name", i); - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, path) => 0; - } - lfs_dir_read(&lfs, &dir, &info) => 0; - lfs_dir_close(&lfs, &dir) => 0; - - if (j == $ITERATIONS-1) { - break; - } - - for (int i = 0; i < $COUNT; i++) { - sprintf(path, "child/test%03d_loooooooooooooooooong_name", i); - lfs_remove(&lfs, path) => 0; - } - } - lfs_unmount(&lfs) => 0; -TEST -scripts/test.py << TEST - lfs_mount(&lfs, &cfg) => 0; - lfs_dir_open(&lfs, &dir, "child") => 0; - lfs_dir_read(&lfs, &dir, &info) => 1; - lfs_dir_read(&lfs, &dir, &info) => 1; - for (int i = 0; i < $COUNT; i++) { - sprintf(path, "test%03d_loooooooooooooooooong_name", i); - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, path) => 0; - } - lfs_dir_read(&lfs, &dir, &info) => 0; - lfs_dir_close(&lfs, &dir) => 0; - for (int i = 0; i < $COUNT; i++) { - sprintf(path, "child/test%03d_loooooooooooooooooong_name", i); - lfs_remove(&lfs, path) => 0; - } - lfs_unmount(&lfs) => 0; -TEST - -echo "--- Outdated head test ---" -scripts/test.py << TEST - lfs_mount(&lfs, &cfg) => 0; - for (int j = 0; j < $ITERATIONS; j++) { - for (int i = 0; i < $COUNT; i++) { - sprintf(path, "child/test%03d_loooooooooooooooooong_name", i); - lfs_file_open(&lfs, &file, path, LFS_O_CREAT | LFS_O_WRONLY) => 0; - lfs_file_close(&lfs, &file) => 0; - } - - lfs_dir_open(&lfs, &dir, "child") => 0; - lfs_dir_read(&lfs, &dir, &info) => 1; - lfs_dir_read(&lfs, &dir, &info) => 1; - for (int i = 0; i < $COUNT; i++) { - sprintf(path, "test%03d_loooooooooooooooooong_name", i); - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, path) => 0; - info.size => 0; - - sprintf(path, "child/test%03d_loooooooooooooooooong_name", i); - lfs_file_open(&lfs, &file, path, LFS_O_WRONLY) => 0; - lfs_file_write(&lfs, &file, "hi", 2) => 2; - lfs_file_close(&lfs, &file) => 0; - } - lfs_dir_read(&lfs, &dir, &info) => 0; - - lfs_dir_rewind(&lfs, &dir) => 0; - lfs_dir_read(&lfs, &dir, &info) => 1; - lfs_dir_read(&lfs, &dir, &info) => 1; - for (int i = 0; i < $COUNT; i++) { - sprintf(path, "test%03d_loooooooooooooooooong_name", i); - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, path) => 0; - info.size => 2; - - sprintf(path, "child/test%03d_loooooooooooooooooong_name", i); - lfs_file_open(&lfs, &file, path, LFS_O_WRONLY) => 0; - lfs_file_write(&lfs, &file, "hi", 2) => 2; - lfs_file_close(&lfs, &file) => 0; - } - lfs_dir_read(&lfs, &dir, &info) => 0; - - lfs_dir_rewind(&lfs, &dir) => 0; - lfs_dir_read(&lfs, &dir, &info) => 1; - lfs_dir_read(&lfs, &dir, &info) => 1; - for (int i = 0; i < $COUNT; i++) { - sprintf(path, "test%03d_loooooooooooooooooong_name", i); - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, path) => 0; - info.size => 2; - } - lfs_dir_read(&lfs, &dir, &info) => 0; - lfs_dir_close(&lfs, &dir) => 0; - - for (int i = 0; i < $COUNT; i++) { - sprintf(path, "child/test%03d_loooooooooooooooooong_name", i); - lfs_remove(&lfs, path) => 0; - } - } - lfs_unmount(&lfs) => 0; -TEST - -scripts/results.py diff --git a/tests/test_relocations.toml b/tests/test_relocations.toml new file mode 100644 index 00000000..71b10475 --- /dev/null +++ b/tests/test_relocations.toml @@ -0,0 +1,305 @@ +# specific corner cases worth explicitly testing for +[[case]] # dangling split dir test +define.ITERATIONS = 20 +define.COUNT = 10 +define.LFS_BLOCK_CYCLES = [8, 1] +code = ''' + lfs_format(&lfs, &cfg) => 0; + // fill up filesystem so only ~16 blocks are left + lfs_mount(&lfs, &cfg) => 0; + lfs_file_open(&lfs, &file, "padding", LFS_O_CREAT | LFS_O_WRONLY) => 0; + memset(buffer, 0, 512); + while (LFS_BLOCK_COUNT - lfs_fs_size(&lfs) > 16) { + lfs_file_write(&lfs, &file, buffer, 512) => 512; + } + lfs_file_close(&lfs, &file) => 0; + // make a child dir to use in bounded space + lfs_mkdir(&lfs, "child") => 0; + lfs_unmount(&lfs) => 0; + + lfs_mount(&lfs, &cfg) => 0; + for (int j = 0; j < ITERATIONS; j++) { + for (int i = 0; i < COUNT; i++) { + sprintf(path, "child/test%03d_loooooooooooooooooong_name", i); + lfs_file_open(&lfs, &file, path, LFS_O_CREAT | LFS_O_WRONLY) => 0; + lfs_file_close(&lfs, &file) => 0; + } + + lfs_dir_open(&lfs, &dir, "child") => 0; + lfs_dir_read(&lfs, &dir, &info) => 1; + lfs_dir_read(&lfs, &dir, &info) => 1; + for (int i = 0; i < COUNT; i++) { + sprintf(path, "test%03d_loooooooooooooooooong_name", i); + lfs_dir_read(&lfs, &dir, &info) => 1; + strcmp(info.name, path) => 0; + } + lfs_dir_read(&lfs, &dir, &info) => 0; + lfs_dir_close(&lfs, &dir) => 0; + + if (j == ITERATIONS-1) { + break; + } + + for (int i = 0; i < COUNT; i++) { + sprintf(path, "child/test%03d_loooooooooooooooooong_name", i); + lfs_remove(&lfs, path) => 0; + } + } + lfs_unmount(&lfs) => 0; + + lfs_mount(&lfs, &cfg) => 0; + lfs_dir_open(&lfs, &dir, "child") => 0; + lfs_dir_read(&lfs, &dir, &info) => 1; + lfs_dir_read(&lfs, &dir, &info) => 1; + for (int i = 0; i < COUNT; i++) { + sprintf(path, "test%03d_loooooooooooooooooong_name", i); + lfs_dir_read(&lfs, &dir, &info) => 1; + strcmp(info.name, path) => 0; + } + lfs_dir_read(&lfs, &dir, &info) => 0; + lfs_dir_close(&lfs, &dir) => 0; + for (int i = 0; i < COUNT; i++) { + sprintf(path, "child/test%03d_loooooooooooooooooong_name", i); + lfs_remove(&lfs, path) => 0; + } + lfs_unmount(&lfs) => 0; +''' + +[[case]] # outdated head test +define.ITERATIONS = 20 +define.COUNT = 10 +define.LFS_BLOCK_CYCLES = [8, 1] +code = ''' + lfs_format(&lfs, &cfg) => 0; + // fill up filesystem so only ~16 blocks are left + lfs_mount(&lfs, &cfg) => 0; + lfs_file_open(&lfs, &file, "padding", LFS_O_CREAT | LFS_O_WRONLY) => 0; + memset(buffer, 0, 512); + while (LFS_BLOCK_COUNT - lfs_fs_size(&lfs) > 16) { + lfs_file_write(&lfs, &file, buffer, 512) => 512; + } + lfs_file_close(&lfs, &file) => 0; + // make a child dir to use in bounded space + lfs_mkdir(&lfs, "child") => 0; + lfs_unmount(&lfs) => 0; + + lfs_mount(&lfs, &cfg) => 0; + for (int j = 0; j < ITERATIONS; j++) { + for (int i = 0; i < COUNT; i++) { + sprintf(path, "child/test%03d_loooooooooooooooooong_name", i); + lfs_file_open(&lfs, &file, path, LFS_O_CREAT | LFS_O_WRONLY) => 0; + lfs_file_close(&lfs, &file) => 0; + } + + lfs_dir_open(&lfs, &dir, "child") => 0; + lfs_dir_read(&lfs, &dir, &info) => 1; + lfs_dir_read(&lfs, &dir, &info) => 1; + for (int i = 0; i < COUNT; i++) { + sprintf(path, "test%03d_loooooooooooooooooong_name", i); + lfs_dir_read(&lfs, &dir, &info) => 1; + strcmp(info.name, path) => 0; + info.size => 0; + + sprintf(path, "child/test%03d_loooooooooooooooooong_name", i); + lfs_file_open(&lfs, &file, path, LFS_O_WRONLY) => 0; + lfs_file_write(&lfs, &file, "hi", 2) => 2; + lfs_file_close(&lfs, &file) => 0; + } + lfs_dir_read(&lfs, &dir, &info) => 0; + + lfs_dir_rewind(&lfs, &dir) => 0; + lfs_dir_read(&lfs, &dir, &info) => 1; + lfs_dir_read(&lfs, &dir, &info) => 1; + for (int i = 0; i < COUNT; i++) { + sprintf(path, "test%03d_loooooooooooooooooong_name", i); + lfs_dir_read(&lfs, &dir, &info) => 1; + strcmp(info.name, path) => 0; + info.size => 2; + + sprintf(path, "child/test%03d_loooooooooooooooooong_name", i); + lfs_file_open(&lfs, &file, path, LFS_O_WRONLY) => 0; + lfs_file_write(&lfs, &file, "hi", 2) => 2; + lfs_file_close(&lfs, &file) => 0; + } + lfs_dir_read(&lfs, &dir, &info) => 0; + + lfs_dir_rewind(&lfs, &dir) => 0; + lfs_dir_read(&lfs, &dir, &info) => 1; + lfs_dir_read(&lfs, &dir, &info) => 1; + for (int i = 0; i < COUNT; i++) { + sprintf(path, "test%03d_loooooooooooooooooong_name", i); + lfs_dir_read(&lfs, &dir, &info) => 1; + strcmp(info.name, path) => 0; + info.size => 2; + } + lfs_dir_read(&lfs, &dir, &info) => 0; + lfs_dir_close(&lfs, &dir) => 0; + + for (int i = 0; i < COUNT; i++) { + sprintf(path, "child/test%03d_loooooooooooooooooong_name", i); + lfs_remove(&lfs, path) => 0; + } + } + lfs_unmount(&lfs) => 0; +''' + +[[case]] # reentrant testing for relocations, this is the same as the + # orphan testing, except here we also set block_cycles so that + # almost every tree operation needs a relocation +reentrant = true +# TODO fix this case, caused by non-DAG trees +if = '!(DEPTH == 3 && LFS_CACHE_SIZE != 64)' +define = [ + {FILES=6, DEPTH=1, CYCLES=20, LFS_BLOCK_CYCLES=1}, + {FILES=26, DEPTH=1, CYCLES=20, LFS_BLOCK_CYCLES=1}, + {FILES=3, DEPTH=3, CYCLES=20, LFS_BLOCK_CYCLES=1}, +] +code = ''' + err = lfs_mount(&lfs, &cfg); + if (err) { + lfs_format(&lfs, &cfg) => 0; + lfs_mount(&lfs, &cfg) => 0; + } + + srand(1); + const char alpha[] = "abcdefghijklmnopqrstuvwxyz"; + for (int i = 0; i < CYCLES; i++) { + // create random path + char full_path[256]; + for (int d = 0; d < DEPTH; d++) { + sprintf(&full_path[2*d], "/%c", alpha[rand() % FILES]); + } + + // if it does not exist, we create it, else we destroy + int res = lfs_stat(&lfs, full_path, &info); + if (res == LFS_ERR_NOENT) { + // create each directory in turn, ignore if dir already exists + for (int d = 0; d < DEPTH; d++) { + strcpy(path, full_path); + path[2*d+2] = '\0'; + err = lfs_mkdir(&lfs, path); + assert(!err || err == LFS_ERR_EXIST); + } + + for (int d = 0; d < DEPTH; d++) { + strcpy(path, full_path); + path[2*d+2] = '\0'; + lfs_stat(&lfs, path, &info) => 0; + assert(strcmp(info.name, &path[2*d+1]) == 0); + assert(info.type == LFS_TYPE_DIR); + } + } else { + // is valid dir? + assert(strcmp(info.name, &full_path[2*(DEPTH-1)+1]) == 0); + assert(info.type == LFS_TYPE_DIR); + + // try to delete path in reverse order, ignore if dir is not empty + for (int d = DEPTH-1; d >= 0; d--) { + strcpy(path, full_path); + path[2*d+2] = '\0'; + err = lfs_remove(&lfs, path); + assert(!err || err == LFS_ERR_NOTEMPTY); + } + + lfs_stat(&lfs, full_path, &info) => LFS_ERR_NOENT; + } + } + lfs_unmount(&lfs) => 0; +''' + +[[case]] # reentrant testing for relocations, but now with random renames! +reentrant = true +# TODO fix this case, caused by non-DAG trees +if = '!(DEPTH == 3 && LFS_CACHE_SIZE != 64)' +define = [ + {FILES=6, DEPTH=1, CYCLES=20, LFS_BLOCK_CYCLES=1}, + {FILES=26, DEPTH=1, CYCLES=20, LFS_BLOCK_CYCLES=1}, + {FILES=3, DEPTH=3, CYCLES=20, LFS_BLOCK_CYCLES=1}, +] +code = ''' + err = lfs_mount(&lfs, &cfg); + if (err) { + lfs_format(&lfs, &cfg) => 0; + lfs_mount(&lfs, &cfg) => 0; + } + + srand(1); + const char alpha[] = "abcdefghijklmnopqrstuvwxyz"; + for (int i = 0; i < CYCLES; i++) { + // create random path + char full_path[256]; + for (int d = 0; d < DEPTH; d++) { + sprintf(&full_path[2*d], "/%c", alpha[rand() % FILES]); + } + + // if it does not exist, we create it, else we destroy + int res = lfs_stat(&lfs, full_path, &info); + assert(!res || res == LFS_ERR_NOENT); + if (res == LFS_ERR_NOENT) { + // create each directory in turn, ignore if dir already exists + for (int d = 0; d < DEPTH; d++) { + strcpy(path, full_path); + path[2*d+2] = '\0'; + err = lfs_mkdir(&lfs, path); + assert(!err || err == LFS_ERR_EXIST); + } + + for (int d = 0; d < DEPTH; d++) { + strcpy(path, full_path); + path[2*d+2] = '\0'; + lfs_stat(&lfs, path, &info) => 0; + assert(strcmp(info.name, &path[2*d+1]) == 0); + assert(info.type == LFS_TYPE_DIR); + } + } else { + assert(strcmp(info.name, &full_path[2*(DEPTH-1)+1]) == 0); + assert(info.type == LFS_TYPE_DIR); + + // create new random path + char new_path[256]; + for (int d = 0; d < DEPTH; d++) { + sprintf(&new_path[2*d], "/%c", alpha[rand() % FILES]); + } + + // if new path does not exist, rename, otherwise destroy + res = lfs_stat(&lfs, new_path, &info); + assert(!res || res == LFS_ERR_NOENT); + if (res == LFS_ERR_NOENT) { + // stop once some dir is renamed + for (int d = 0; d < DEPTH; d++) { + 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'; + err = lfs_rename(&lfs, path, path+128); + assert(!err || err == LFS_ERR_NOTEMPTY); + if (!err) { + strcpy(path, path+128); + } + } + + for (int d = 0; d < DEPTH; d++) { + strcpy(path, new_path); + path[2*d+2] = '\0'; + lfs_stat(&lfs, path, &info) => 0; + assert(strcmp(info.name, &path[2*d+1]) == 0); + assert(info.type == LFS_TYPE_DIR); + } + + lfs_stat(&lfs, full_path, &info) => LFS_ERR_NOENT; + } else { + // try to delete path in reverse order, + // ignore if dir is not empty + for (int d = DEPTH-1; d >= 0; d--) { + strcpy(path, full_path); + path[2*d+2] = '\0'; + err = lfs_remove(&lfs, path); + assert(!err || err == LFS_ERR_NOTEMPTY); + } + + lfs_stat(&lfs, full_path, &info) => LFS_ERR_NOENT; + } + } + } + lfs_unmount(&lfs) => 0; +''' diff --git a/tests/test_seek.sh b/tests/test_seek.sh deleted file mode 100755 index e136aa0d..00000000 --- a/tests/test_seek.sh +++ /dev/null @@ -1,505 +0,0 @@ -#!/bin/bash -set -eu -export TEST_FILE=$0 -trap 'export TEST_LINE=$LINENO' DEBUG - -echo "=== Seek tests ===" - -SMALLSIZE=4 -MEDIUMSIZE=128 -LARGESIZE=132 - -rm -rf blocks -scripts/test.py << TEST - lfs_format(&lfs, &cfg) => 0; - lfs_mount(&lfs, &cfg) => 0; - lfs_mkdir(&lfs, "hello") => 0; - for (int i = 0; i < $LARGESIZE; i++) { - sprintf(path, "hello/kitty%03d", i); - lfs_file_open(&lfs, &file, path, - LFS_O_WRONLY | LFS_O_CREAT | LFS_O_APPEND) => 0; - - lfs_size_t size = strlen("kittycatcat"); - memcpy(buffer, "kittycatcat", size); - for (int j = 0; j < $LARGESIZE; j++) { - lfs_file_write(&lfs, &file, buffer, size); - } - - lfs_file_close(&lfs, &file) => 0; - } - lfs_unmount(&lfs) => 0; -TEST - -echo "--- Simple dir seek ---" -scripts/test.py << TEST - lfs_mount(&lfs, &cfg) => 0; - lfs_dir_open(&lfs, &dir, "hello") => 0; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, ".") => 0; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, "..") => 0; - - lfs_soff_t pos; - int i; - for (i = 0; i < $SMALLSIZE; i++) { - sprintf(path, "kitty%03d", i); - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, path) => 0; - pos = lfs_dir_tell(&lfs, &dir); - } - pos >= 0 => 1; - - lfs_dir_seek(&lfs, &dir, pos) => 0; - sprintf(path, "kitty%03d", i); - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, path) => 0; - - lfs_dir_rewind(&lfs, &dir) => 0; - sprintf(path, "kitty%03d", 0); - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, ".") => 0; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, "..") => 0; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, path) => 0; - - lfs_dir_seek(&lfs, &dir, pos) => 0; - sprintf(path, "kitty%03d", i); - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, path) => 0; - - lfs_dir_close(&lfs, &dir) => 0; - lfs_unmount(&lfs) => 0; -TEST - -echo "--- Large dir seek ---" -scripts/test.py << TEST - lfs_mount(&lfs, &cfg) => 0; - lfs_dir_open(&lfs, &dir, "hello") => 0; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, ".") => 0; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, "..") => 0; - - lfs_soff_t pos; - int i; - for (i = 0; i < $MEDIUMSIZE; i++) { - sprintf(path, "kitty%03d", i); - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, path) => 0; - pos = lfs_dir_tell(&lfs, &dir); - } - pos >= 0 => 1; - - lfs_dir_seek(&lfs, &dir, pos) => 0; - sprintf(path, "kitty%03d", i); - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, path) => 0; - - lfs_dir_rewind(&lfs, &dir) => 0; - sprintf(path, "kitty%03d", 0); - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, ".") => 0; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, "..") => 0; - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, path) => 0; - - lfs_dir_seek(&lfs, &dir, pos) => 0; - sprintf(path, "kitty%03d", i); - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(info.name, path) => 0; - - lfs_dir_close(&lfs, &dir) => 0; - lfs_unmount(&lfs) => 0; -TEST - -echo "--- Simple file seek ---" -scripts/test.py << TEST - lfs_mount(&lfs, &cfg) => 0; - lfs_file_open(&lfs, &file, "hello/kitty042", LFS_O_RDONLY) => 0; - - lfs_soff_t pos; - lfs_size_t size = strlen("kittycatcat"); - for (int i = 0; i < $SMALLSIZE; i++) { - lfs_file_read(&lfs, &file, buffer, size) => size; - memcmp(buffer, "kittycatcat", size) => 0; - pos = lfs_file_tell(&lfs, &file); - } - pos >= 0 => 1; - - lfs_file_seek(&lfs, &file, pos, LFS_SEEK_SET) => pos; - lfs_file_read(&lfs, &file, buffer, size) => size; - memcmp(buffer, "kittycatcat", size) => 0; - - lfs_file_rewind(&lfs, &file) => 0; - lfs_file_read(&lfs, &file, buffer, size) => size; - memcmp(buffer, "kittycatcat", size) => 0; - - lfs_file_seek(&lfs, &file, 0, LFS_SEEK_CUR) => size; - lfs_file_read(&lfs, &file, buffer, size) => size; - memcmp(buffer, "kittycatcat", size) => 0; - - lfs_file_seek(&lfs, &file, size, LFS_SEEK_CUR) => 3*size; - lfs_file_read(&lfs, &file, buffer, size) => size; - memcmp(buffer, "kittycatcat", size) => 0; - - lfs_file_seek(&lfs, &file, pos, LFS_SEEK_SET) => pos; - lfs_file_read(&lfs, &file, buffer, size) => size; - memcmp(buffer, "kittycatcat", size) => 0; - - lfs_file_seek(&lfs, &file, -size, LFS_SEEK_CUR) => pos; - lfs_file_read(&lfs, &file, buffer, size) => size; - memcmp(buffer, "kittycatcat", size) => 0; - - lfs_file_seek(&lfs, &file, -size, LFS_SEEK_END) >= 0 => 1; - lfs_file_read(&lfs, &file, buffer, size) => size; - memcmp(buffer, "kittycatcat", size) => 0; - - size = lfs_file_size(&lfs, &file); - lfs_file_seek(&lfs, &file, 0, LFS_SEEK_CUR) => size; - - lfs_file_close(&lfs, &file) => 0; - lfs_unmount(&lfs) => 0; -TEST - -echo "--- Large file seek ---" -scripts/test.py << TEST - lfs_mount(&lfs, &cfg) => 0; - lfs_file_open(&lfs, &file, "hello/kitty042", LFS_O_RDONLY) => 0; - - lfs_soff_t pos; - lfs_size_t size = strlen("kittycatcat"); - for (int i = 0; i < $MEDIUMSIZE; i++) { - lfs_file_read(&lfs, &file, buffer, size) => size; - memcmp(buffer, "kittycatcat", size) => 0; - pos = lfs_file_tell(&lfs, &file); - } - pos >= 0 => 1; - - lfs_file_seek(&lfs, &file, pos, LFS_SEEK_SET) => pos; - lfs_file_read(&lfs, &file, buffer, size) => size; - memcmp(buffer, "kittycatcat", size) => 0; - - lfs_file_rewind(&lfs, &file) => 0; - lfs_file_read(&lfs, &file, buffer, size) => size; - memcmp(buffer, "kittycatcat", size) => 0; - - lfs_file_seek(&lfs, &file, 0, LFS_SEEK_CUR) => size; - lfs_file_read(&lfs, &file, buffer, size) => size; - memcmp(buffer, "kittycatcat", size) => 0; - - lfs_file_seek(&lfs, &file, size, LFS_SEEK_CUR) => 3*size; - lfs_file_read(&lfs, &file, buffer, size) => size; - memcmp(buffer, "kittycatcat", size) => 0; - - lfs_file_seek(&lfs, &file, pos, LFS_SEEK_SET) => pos; - lfs_file_read(&lfs, &file, buffer, size) => size; - memcmp(buffer, "kittycatcat", size) => 0; - - lfs_file_seek(&lfs, &file, -size, LFS_SEEK_CUR) => pos; - lfs_file_read(&lfs, &file, buffer, size) => size; - memcmp(buffer, "kittycatcat", size) => 0; - - lfs_file_seek(&lfs, &file, -size, LFS_SEEK_END) >= 0 => 1; - lfs_file_read(&lfs, &file, buffer, size) => size; - memcmp(buffer, "kittycatcat", size) => 0; - - size = lfs_file_size(&lfs, &file); - lfs_file_seek(&lfs, &file, 0, LFS_SEEK_CUR) => size; - - lfs_file_close(&lfs, &file) => 0; - lfs_unmount(&lfs) => 0; -TEST - -echo "--- Simple file seek and write ---" -scripts/test.py << TEST - lfs_mount(&lfs, &cfg) => 0; - lfs_file_open(&lfs, &file, "hello/kitty042", LFS_O_RDWR) => 0; - - lfs_soff_t pos; - lfs_size_t size = strlen("kittycatcat"); - for (int i = 0; i < $SMALLSIZE; i++) { - lfs_file_read(&lfs, &file, buffer, size) => size; - memcmp(buffer, "kittycatcat", size) => 0; - pos = lfs_file_tell(&lfs, &file); - } - pos >= 0 => 1; - - memcpy(buffer, "doggodogdog", size); - lfs_file_seek(&lfs, &file, pos, LFS_SEEK_SET) => pos; - lfs_file_write(&lfs, &file, buffer, size) => size; - - lfs_file_seek(&lfs, &file, pos, LFS_SEEK_SET) => pos; - lfs_file_read(&lfs, &file, buffer, size) => size; - memcmp(buffer, "doggodogdog", size) => 0; - - lfs_file_rewind(&lfs, &file) => 0; - lfs_file_read(&lfs, &file, buffer, size) => size; - memcmp(buffer, "kittycatcat", size) => 0; - - lfs_file_seek(&lfs, &file, pos, LFS_SEEK_SET) => pos; - lfs_file_read(&lfs, &file, buffer, size) => size; - memcmp(buffer, "doggodogdog", size) => 0; - - lfs_file_seek(&lfs, &file, -size, LFS_SEEK_END) >= 0 => 1; - lfs_file_read(&lfs, &file, buffer, size) => size; - memcmp(buffer, "kittycatcat", size) => 0; - - size = lfs_file_size(&lfs, &file); - lfs_file_seek(&lfs, &file, 0, LFS_SEEK_CUR) => size; - - lfs_file_close(&lfs, &file) => 0; - lfs_unmount(&lfs) => 0; -TEST - -echo "--- Large file seek and write ---" -scripts/test.py << TEST - lfs_mount(&lfs, &cfg) => 0; - lfs_file_open(&lfs, &file, "hello/kitty042", LFS_O_RDWR) => 0; - - lfs_soff_t pos; - lfs_size_t size = strlen("kittycatcat"); - for (int i = 0; i < $MEDIUMSIZE; i++) { - lfs_file_read(&lfs, &file, buffer, size) => size; - if (i != $SMALLSIZE) { - memcmp(buffer, "kittycatcat", size) => 0; - } - pos = lfs_file_tell(&lfs, &file); - } - pos >= 0 => 1; - - memcpy(buffer, "doggodogdog", size); - lfs_file_seek(&lfs, &file, pos, LFS_SEEK_SET) => pos; - lfs_file_write(&lfs, &file, buffer, size) => size; - - lfs_file_seek(&lfs, &file, pos, LFS_SEEK_SET) => pos; - lfs_file_read(&lfs, &file, buffer, size) => size; - memcmp(buffer, "doggodogdog", size) => 0; - - lfs_file_rewind(&lfs, &file) => 0; - lfs_file_read(&lfs, &file, buffer, size) => size; - memcmp(buffer, "kittycatcat", size) => 0; - - lfs_file_seek(&lfs, &file, pos, LFS_SEEK_SET) => pos; - lfs_file_read(&lfs, &file, buffer, size) => size; - memcmp(buffer, "doggodogdog", size) => 0; - - lfs_file_seek(&lfs, &file, -size, LFS_SEEK_END) >= 0 => 1; - lfs_file_read(&lfs, &file, buffer, size) => size; - memcmp(buffer, "kittycatcat", size) => 0; - - size = lfs_file_size(&lfs, &file); - lfs_file_seek(&lfs, &file, 0, LFS_SEEK_CUR) => size; - - lfs_file_close(&lfs, &file) => 0; - lfs_unmount(&lfs) => 0; -TEST - -echo "--- Boundary seek and write ---" -scripts/test.py << TEST - lfs_mount(&lfs, &cfg) => 0; - lfs_file_open(&lfs, &file, "hello/kitty042", LFS_O_RDWR) => 0; - - lfs_size_t size = strlen("hedgehoghog"); - const lfs_soff_t offsets[] = {512, 1020, 513, 1021, 511, 1019}; - - for (unsigned i = 0; i < sizeof(offsets) / sizeof(offsets[0]); i++) { - lfs_soff_t off = offsets[i]; - memcpy(buffer, "hedgehoghog", size); - lfs_file_seek(&lfs, &file, off, LFS_SEEK_SET) => off; - lfs_file_write(&lfs, &file, buffer, size) => size; - lfs_file_seek(&lfs, &file, off, LFS_SEEK_SET) => off; - lfs_file_read(&lfs, &file, buffer, size) => size; - memcmp(buffer, "hedgehoghog", size) => 0; - - lfs_file_seek(&lfs, &file, 0, LFS_SEEK_SET) => 0; - lfs_file_read(&lfs, &file, buffer, size) => size; - memcmp(buffer, "kittycatcat", size) => 0; - - lfs_file_sync(&lfs, &file) => 0; - } - - lfs_file_close(&lfs, &file) => 0; - lfs_unmount(&lfs) => 0; -TEST - -echo "--- Out-of-bounds seek ---" -scripts/test.py << TEST - lfs_mount(&lfs, &cfg) => 0; - lfs_file_open(&lfs, &file, "hello/kitty042", LFS_O_RDWR) => 0; - - lfs_size_t size = strlen("kittycatcat"); - lfs_file_size(&lfs, &file) => $LARGESIZE*size; - lfs_file_seek(&lfs, &file, ($LARGESIZE+$SMALLSIZE)*size, - LFS_SEEK_SET) => ($LARGESIZE+$SMALLSIZE)*size; - lfs_file_read(&lfs, &file, buffer, size) => 0; - - memcpy(buffer, "porcupineee", size); - lfs_file_write(&lfs, &file, buffer, size) => size; - - lfs_file_seek(&lfs, &file, ($LARGESIZE+$SMALLSIZE)*size, - LFS_SEEK_SET) => ($LARGESIZE+$SMALLSIZE)*size; - lfs_file_read(&lfs, &file, buffer, size) => size; - memcmp(buffer, "porcupineee", size) => 0; - - lfs_file_seek(&lfs, &file, $LARGESIZE*size, - LFS_SEEK_SET) => $LARGESIZE*size; - lfs_file_read(&lfs, &file, buffer, size) => size; - memcmp(buffer, "\0\0\0\0\0\0\0\0\0\0\0", size) => 0; - - lfs_file_seek(&lfs, &file, -(($LARGESIZE+$SMALLSIZE)*size), - LFS_SEEK_CUR) => LFS_ERR_INVAL; - lfs_file_tell(&lfs, &file) => ($LARGESIZE+1)*size; - - lfs_file_seek(&lfs, &file, -(($LARGESIZE+2*$SMALLSIZE)*size), - LFS_SEEK_END) => LFS_ERR_INVAL; - lfs_file_tell(&lfs, &file) => ($LARGESIZE+1)*size; - - lfs_file_close(&lfs, &file) => 0; - lfs_unmount(&lfs) => 0; -TEST - -echo "--- Inline write and seek ---" -for SIZE in $SMALLSIZE $MEDIUMSIZE $LARGESIZE -do -scripts/test.py << TEST - lfs_mount(&lfs, &cfg) => 0; - lfs_file_open(&lfs, &file, "hello/tinykitty$SIZE", - LFS_O_RDWR | LFS_O_CREAT) => 0; - int j = 0; - int k = 0; - - memcpy(buffer, "abcdefghijklmnopqrstuvwxyz", 26); - for (unsigned i = 0; i < $SIZE; i++) { - lfs_file_write(&lfs, &file, &buffer[j++ % 26], 1) => 1; - lfs_file_tell(&lfs, &file) => i+1; - lfs_file_size(&lfs, &file) => i+1; - } - - lfs_file_seek(&lfs, &file, 0, LFS_SEEK_SET) => 0; - lfs_file_tell(&lfs, &file) => 0; - lfs_file_size(&lfs, &file) => $SIZE; - for (unsigned i = 0; i < $SIZE; i++) { - uint8_t c; - lfs_file_read(&lfs, &file, &c, 1) => 1; - c => buffer[k++ % 26]; - } - - lfs_file_sync(&lfs, &file) => 0; - lfs_file_tell(&lfs, &file) => $SIZE; - lfs_file_size(&lfs, &file) => $SIZE; - - lfs_file_seek(&lfs, &file, 0, LFS_SEEK_SET) => 0; - for (unsigned i = 0; i < $SIZE; i++) { - lfs_file_write(&lfs, &file, &buffer[j++ % 26], 1) => 1; - lfs_file_tell(&lfs, &file) => i+1; - lfs_file_size(&lfs, &file) => $SIZE; - lfs_file_sync(&lfs, &file) => 0; - lfs_file_tell(&lfs, &file) => i+1; - lfs_file_size(&lfs, &file) => $SIZE; - if (i < $SIZE-2) { - uint8_t c[3]; - lfs_file_seek(&lfs, &file, -1, LFS_SEEK_CUR) => i; - lfs_file_read(&lfs, &file, &c, 3) => 3; - lfs_file_tell(&lfs, &file) => i+3; - lfs_file_size(&lfs, &file) => $SIZE; - lfs_file_seek(&lfs, &file, i+1, LFS_SEEK_SET) => i+1; - lfs_file_tell(&lfs, &file) => i+1; - lfs_file_size(&lfs, &file) => $SIZE; - } - } - - lfs_file_seek(&lfs, &file, 0, LFS_SEEK_SET) => 0; - lfs_file_tell(&lfs, &file) => 0; - lfs_file_size(&lfs, &file) => $SIZE; - for (unsigned i = 0; i < $SIZE; i++) { - uint8_t c; - lfs_file_read(&lfs, &file, &c, 1) => 1; - c => buffer[k++ % 26]; - } - - lfs_file_sync(&lfs, &file) => 0; - lfs_file_tell(&lfs, &file) => $SIZE; - lfs_file_size(&lfs, &file) => $SIZE; - - lfs_file_close(&lfs, &file) => 0; - lfs_unmount(&lfs) => 0; -TEST -done - -echo "--- Root seek test ---" -./scripts/test.py << TEST - lfs_mount(&lfs, &cfg) => 0; - for (int i = 3; i < $MEDIUMSIZE; i++) { - sprintf(path, "hi%03d", i); - lfs_mkdir(&lfs, path) => 0; - } - - lfs_dir_open(&lfs, &dir, "/") => 0; - for (int i = 0; i < $MEDIUMSIZE; i++) { - if (i == 0) { - sprintf(path, "."); - } else if (i == 1) { - sprintf(path, ".."); - } else if (i == 2) { - sprintf(path, "hello"); - } else { - sprintf(path, "hi%03d", i); - } - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(path, info.name) => 0; - } - lfs_dir_read(&lfs, &dir, &info) => 0; - lfs_dir_close(&lfs, &dir) => 0; - - for (int j = 0; j < $MEDIUMSIZE; j++) { - lfs_soff_t off = -1; - - lfs_dir_open(&lfs, &dir, "/") => 0; - for (int i = 0; i < $MEDIUMSIZE; i++) { - if (i == 0) { - sprintf(path, "."); - } else if (i == 1) { - sprintf(path, ".."); - } else if (i == 2) { - sprintf(path, "hello"); - } else { - sprintf(path, "hi%03d", i); - } - - if (i == j) { - off = lfs_dir_tell(&lfs, &dir); - off >= 0 => true; - } - - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(path, info.name) => 0; - } - lfs_dir_read(&lfs, &dir, &info) => 0; - lfs_dir_close(&lfs, &dir) => 0; - - lfs_dir_open(&lfs, &dir, "/") => 0; - lfs_dir_seek(&lfs, &dir, off) => 0; - for (int i = j; i < $MEDIUMSIZE; i++) { - if (i == 0) { - sprintf(path, "."); - } else if (i == 1) { - sprintf(path, ".."); - } else if (i == 2) { - sprintf(path, "hello"); - } else { - sprintf(path, "hi%03d", i); - } - - lfs_dir_read(&lfs, &dir, &info) => 1; - strcmp(path, info.name) => 0; - } - lfs_dir_read(&lfs, &dir, &info) => 0; - lfs_dir_close(&lfs, &dir) => 0; - } - - lfs_unmount(&lfs) => 0; -TEST - -scripts/results.py diff --git a/tests/test_seek.toml b/tests/test_seek.toml new file mode 100644 index 00000000..79d7728a --- /dev/null +++ b/tests/test_seek.toml @@ -0,0 +1,380 @@ + +[[case]] # simple file seek +define = [ + {COUNT=132, SKIP=4}, + {COUNT=132, SKIP=128}, + {COUNT=200, SKIP=10}, + {COUNT=200, SKIP=100}, + {COUNT=4, SKIP=1}, + {COUNT=4, SKIP=2}, +] +code = ''' + lfs_format(&lfs, &cfg) => 0; + lfs_mount(&lfs, &cfg) => 0; + lfs_file_open(&lfs, &file, "kitty", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_APPEND) => 0; + size = strlen("kittycatcat"); + memcpy(buffer, "kittycatcat", size); + for (int j = 0; j < COUNT; j++) { + lfs_file_write(&lfs, &file, buffer, size); + } + lfs_file_close(&lfs, &file) => 0; + lfs_unmount(&lfs) => 0; + + lfs_mount(&lfs, &cfg) => 0; + lfs_file_open(&lfs, &file, "kitty", LFS_O_RDONLY) => 0; + + lfs_soff_t pos = -1; + size = strlen("kittycatcat"); + for (int i = 0; i < SKIP; i++) { + lfs_file_read(&lfs, &file, buffer, size) => size; + memcmp(buffer, "kittycatcat", size) => 0; + pos = lfs_file_tell(&lfs, &file); + } + assert(pos >= 0); + + lfs_file_seek(&lfs, &file, pos, LFS_SEEK_SET) => pos; + lfs_file_read(&lfs, &file, buffer, size) => size; + memcmp(buffer, "kittycatcat", size) => 0; + + lfs_file_rewind(&lfs, &file) => 0; + lfs_file_read(&lfs, &file, buffer, size) => size; + memcmp(buffer, "kittycatcat", size) => 0; + + lfs_file_seek(&lfs, &file, 0, LFS_SEEK_CUR) => size; + lfs_file_read(&lfs, &file, buffer, size) => size; + memcmp(buffer, "kittycatcat", size) => 0; + + lfs_file_seek(&lfs, &file, size, LFS_SEEK_CUR) => 3*size; + lfs_file_read(&lfs, &file, buffer, size) => size; + memcmp(buffer, "kittycatcat", size) => 0; + + lfs_file_seek(&lfs, &file, pos, LFS_SEEK_SET) => pos; + lfs_file_read(&lfs, &file, buffer, size) => size; + memcmp(buffer, "kittycatcat", size) => 0; + + lfs_file_seek(&lfs, &file, -size, LFS_SEEK_CUR) => pos; + lfs_file_read(&lfs, &file, buffer, size) => size; + memcmp(buffer, "kittycatcat", size) => 0; + + lfs_file_seek(&lfs, &file, -size, LFS_SEEK_END) >= 0 => 1; + lfs_file_read(&lfs, &file, buffer, size) => size; + memcmp(buffer, "kittycatcat", size) => 0; + + size = lfs_file_size(&lfs, &file); + lfs_file_seek(&lfs, &file, 0, LFS_SEEK_CUR) => size; + + lfs_file_close(&lfs, &file) => 0; + lfs_unmount(&lfs) => 0; +''' + +[[case]] # simple file seek and write +define = [ + {COUNT=132, SKIP=4}, + {COUNT=132, SKIP=128}, + {COUNT=200, SKIP=10}, + {COUNT=200, SKIP=100}, + {COUNT=4, SKIP=1}, + {COUNT=4, SKIP=2}, +] +code = ''' + lfs_format(&lfs, &cfg) => 0; + lfs_mount(&lfs, &cfg) => 0; + lfs_file_open(&lfs, &file, "kitty", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_APPEND) => 0; + size = strlen("kittycatcat"); + memcpy(buffer, "kittycatcat", size); + for (int j = 0; j < COUNT; j++) { + lfs_file_write(&lfs, &file, buffer, size); + } + lfs_file_close(&lfs, &file) => 0; + lfs_unmount(&lfs) => 0; + + lfs_mount(&lfs, &cfg) => 0; + lfs_file_open(&lfs, &file, "kitty", LFS_O_RDWR) => 0; + + lfs_soff_t pos = -1; + size = strlen("kittycatcat"); + for (int i = 0; i < SKIP; i++) { + lfs_file_read(&lfs, &file, buffer, size) => size; + memcmp(buffer, "kittycatcat", size) => 0; + pos = lfs_file_tell(&lfs, &file); + } + assert(pos >= 0); + + memcpy(buffer, "doggodogdog", size); + lfs_file_seek(&lfs, &file, pos, LFS_SEEK_SET) => pos; + lfs_file_write(&lfs, &file, buffer, size) => size; + + lfs_file_seek(&lfs, &file, pos, LFS_SEEK_SET) => pos; + lfs_file_read(&lfs, &file, buffer, size) => size; + memcmp(buffer, "doggodogdog", size) => 0; + + lfs_file_rewind(&lfs, &file) => 0; + lfs_file_read(&lfs, &file, buffer, size) => size; + memcmp(buffer, "kittycatcat", size) => 0; + + lfs_file_seek(&lfs, &file, pos, LFS_SEEK_SET) => pos; + lfs_file_read(&lfs, &file, buffer, size) => size; + memcmp(buffer, "doggodogdog", size) => 0; + + lfs_file_seek(&lfs, &file, -size, LFS_SEEK_END) >= 0 => 1; + lfs_file_read(&lfs, &file, buffer, size) => size; + memcmp(buffer, "kittycatcat", size) => 0; + + size = lfs_file_size(&lfs, &file); + lfs_file_seek(&lfs, &file, 0, LFS_SEEK_CUR) => size; + + lfs_file_close(&lfs, &file) => 0; + lfs_unmount(&lfs) => 0; +''' + +[[case]] # boundary seek and writes +define.COUNT = 132 +define.OFFSETS = '"{512, 1020, 513, 1021, 511, 1019, 1441}"' +code = ''' + lfs_format(&lfs, &cfg) => 0; + lfs_mount(&lfs, &cfg) => 0; + lfs_file_open(&lfs, &file, "kitty", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_APPEND) => 0; + size = strlen("kittycatcat"); + memcpy(buffer, "kittycatcat", size); + for (int j = 0; j < COUNT; j++) { + lfs_file_write(&lfs, &file, buffer, size); + } + lfs_file_close(&lfs, &file) => 0; + lfs_unmount(&lfs) => 0; + + lfs_mount(&lfs, &cfg) => 0; + lfs_file_open(&lfs, &file, "kitty", LFS_O_RDWR) => 0; + + size = strlen("hedgehoghog"); + const lfs_soff_t offsets[] = OFFSETS; + + for (unsigned i = 0; i < sizeof(offsets) / sizeof(offsets[0]); i++) { + lfs_soff_t off = offsets[i]; + memcpy(buffer, "hedgehoghog", size); + lfs_file_seek(&lfs, &file, off, LFS_SEEK_SET) => off; + lfs_file_write(&lfs, &file, buffer, size) => size; + lfs_file_seek(&lfs, &file, off, LFS_SEEK_SET) => off; + lfs_file_read(&lfs, &file, buffer, size) => size; + memcmp(buffer, "hedgehoghog", size) => 0; + + lfs_file_seek(&lfs, &file, 0, LFS_SEEK_SET) => 0; + lfs_file_read(&lfs, &file, buffer, size) => size; + memcmp(buffer, "kittycatcat", size) => 0; + + lfs_file_seek(&lfs, &file, off, LFS_SEEK_SET) => off; + lfs_file_read(&lfs, &file, buffer, size) => size; + memcmp(buffer, "hedgehoghog", size) => 0; + + lfs_file_sync(&lfs, &file) => 0; + + lfs_file_seek(&lfs, &file, 0, LFS_SEEK_SET) => 0; + lfs_file_read(&lfs, &file, buffer, size) => size; + memcmp(buffer, "kittycatcat", size) => 0; + + lfs_file_seek(&lfs, &file, off, LFS_SEEK_SET) => off; + lfs_file_read(&lfs, &file, buffer, size) => size; + memcmp(buffer, "hedgehoghog", size) => 0; + } + + lfs_file_close(&lfs, &file) => 0; + lfs_unmount(&lfs) => 0; +''' + +[[case]] # out of bounds seek +define = [ + {COUNT=132, SKIP=4}, + {COUNT=132, SKIP=128}, + {COUNT=200, SKIP=10}, + {COUNT=200, SKIP=100}, + {COUNT=4, SKIP=2}, + {COUNT=4, SKIP=3}, +] +code = ''' + lfs_format(&lfs, &cfg) => 0; + lfs_mount(&lfs, &cfg) => 0; + lfs_file_open(&lfs, &file, "kitty", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_APPEND) => 0; + size = strlen("kittycatcat"); + memcpy(buffer, "kittycatcat", size); + for (int j = 0; j < COUNT; j++) { + lfs_file_write(&lfs, &file, buffer, size); + } + lfs_file_close(&lfs, &file) => 0; + lfs_unmount(&lfs) => 0; + lfs_mount(&lfs, &cfg) => 0; + lfs_file_open(&lfs, &file, "kitty", LFS_O_RDWR) => 0; + + size = strlen("kittycatcat"); + lfs_file_size(&lfs, &file) => COUNT*size; + lfs_file_seek(&lfs, &file, (COUNT+SKIP)*size, + LFS_SEEK_SET) => (COUNT+SKIP)*size; + lfs_file_read(&lfs, &file, buffer, size) => 0; + + memcpy(buffer, "porcupineee", size); + lfs_file_write(&lfs, &file, buffer, size) => size; + + lfs_file_seek(&lfs, &file, (COUNT+SKIP)*size, + LFS_SEEK_SET) => (COUNT+SKIP)*size; + lfs_file_read(&lfs, &file, buffer, size) => size; + memcmp(buffer, "porcupineee", size) => 0; + + lfs_file_seek(&lfs, &file, COUNT*size, + LFS_SEEK_SET) => COUNT*size; + lfs_file_read(&lfs, &file, buffer, size) => size; + memcmp(buffer, "\0\0\0\0\0\0\0\0\0\0\0", size) => 0; + + lfs_file_seek(&lfs, &file, -((COUNT+SKIP)*size), + LFS_SEEK_CUR) => LFS_ERR_INVAL; + lfs_file_tell(&lfs, &file) => (COUNT+1)*size; + + lfs_file_seek(&lfs, &file, -((COUNT+2*SKIP)*size), + LFS_SEEK_END) => LFS_ERR_INVAL; + lfs_file_tell(&lfs, &file) => (COUNT+1)*size; + + lfs_file_close(&lfs, &file) => 0; + lfs_unmount(&lfs) => 0; +''' + +[[case]] # inline write and seek +define.SIZE = [2, 4, 128, 132] +code = ''' + lfs_format(&lfs, &cfg) => 0; + lfs_mount(&lfs, &cfg) => 0; + lfs_file_open(&lfs, &file, "tinykitty", + LFS_O_RDWR | LFS_O_CREAT) => 0; + int j = 0; + int k = 0; + + memcpy(buffer, "abcdefghijklmnopqrstuvwxyz", 26); + for (unsigned i = 0; i < SIZE; i++) { + lfs_file_write(&lfs, &file, &buffer[j++ % 26], 1) => 1; + lfs_file_tell(&lfs, &file) => i+1; + lfs_file_size(&lfs, &file) => i+1; + } + + lfs_file_seek(&lfs, &file, 0, LFS_SEEK_SET) => 0; + lfs_file_tell(&lfs, &file) => 0; + lfs_file_size(&lfs, &file) => SIZE; + for (unsigned i = 0; i < SIZE; i++) { + uint8_t c; + lfs_file_read(&lfs, &file, &c, 1) => 1; + c => buffer[k++ % 26]; + } + + lfs_file_sync(&lfs, &file) => 0; + lfs_file_tell(&lfs, &file) => SIZE; + lfs_file_size(&lfs, &file) => SIZE; + + lfs_file_seek(&lfs, &file, 0, LFS_SEEK_SET) => 0; + for (unsigned i = 0; i < SIZE; i++) { + lfs_file_write(&lfs, &file, &buffer[j++ % 26], 1) => 1; + lfs_file_tell(&lfs, &file) => i+1; + lfs_file_size(&lfs, &file) => SIZE; + lfs_file_sync(&lfs, &file) => 0; + lfs_file_tell(&lfs, &file) => i+1; + lfs_file_size(&lfs, &file) => SIZE; + if (i < SIZE-2) { + uint8_t c[3]; + lfs_file_seek(&lfs, &file, -1, LFS_SEEK_CUR) => i; + lfs_file_read(&lfs, &file, &c, 3) => 3; + lfs_file_tell(&lfs, &file) => i+3; + lfs_file_size(&lfs, &file) => SIZE; + lfs_file_seek(&lfs, &file, i+1, LFS_SEEK_SET) => i+1; + lfs_file_tell(&lfs, &file) => i+1; + lfs_file_size(&lfs, &file) => SIZE; + } + } + + lfs_file_seek(&lfs, &file, 0, LFS_SEEK_SET) => 0; + lfs_file_tell(&lfs, &file) => 0; + lfs_file_size(&lfs, &file) => SIZE; + for (unsigned i = 0; i < SIZE; i++) { + uint8_t c; + lfs_file_read(&lfs, &file, &c, 1) => 1; + c => buffer[k++ % 26]; + } + + lfs_file_sync(&lfs, &file) => 0; + lfs_file_tell(&lfs, &file) => SIZE; + lfs_file_size(&lfs, &file) => SIZE; + + lfs_file_close(&lfs, &file) => 0; + lfs_unmount(&lfs) => 0; +''' + +[[case]] # file seek and write with power-loss +# must be power-of-2 for quadratic probing to be exhaustive +define.COUNT = [4, 64, 128] +reentrant = true +code = ''' + err = lfs_mount(&lfs, &cfg); + if (err) { + lfs_format(&lfs, &cfg) => 0; + lfs_mount(&lfs, &cfg) => 0; + } + err = lfs_file_open(&lfs, &file, "kitty", LFS_O_RDONLY); + assert(!err || err == LFS_ERR_NOENT); + if (!err) { + if (lfs_file_size(&lfs, &file) != 0) { + lfs_file_size(&lfs, &file) => 11*COUNT; + for (int j = 0; j < COUNT; j++) { + memset(buffer, 0, 11+1); + lfs_file_read(&lfs, &file, buffer, 11) => 11; + assert(memcmp(buffer, "kittycatcat", 11) == 0 || + memcmp(buffer, "doggodogdog", 11) == 0); + } + } + lfs_file_close(&lfs, &file) => 0; + } + + lfs_file_open(&lfs, &file, "kitty", LFS_O_WRONLY | LFS_O_CREAT) => 0; + if (lfs_file_size(&lfs, &file) == 0) { + for (int j = 0; j < COUNT; j++) { + strcpy((char*)buffer, "kittycatcat"); + size = strlen((char*)buffer); + lfs_file_write(&lfs, &file, buffer, size) => size; + } + } + lfs_file_close(&lfs, &file) => 0; + + strcpy((char*)buffer, "doggodogdog"); + size = strlen((char*)buffer); + + lfs_file_open(&lfs, &file, "kitty", LFS_O_RDWR) => 0; + lfs_file_size(&lfs, &file) => COUNT*size; + // seek and write using quadratic probing to touch all + // 11-byte words in the file + lfs_off_t off = 0; + for (int j = 0; j < COUNT; j++) { + off = (5*off + 1) % COUNT; + lfs_file_seek(&lfs, &file, off*size, LFS_SEEK_SET) => off*size; + lfs_file_read(&lfs, &file, buffer, size) => size; + assert(memcmp(buffer, "kittycatcat", size) == 0 || + memcmp(buffer, "doggodogdog", size) == 0); + if (memcmp(buffer, "doggodogdog", size) != 0) { + lfs_file_seek(&lfs, &file, off*size, LFS_SEEK_SET) => off*size; + strcpy((char*)buffer, "doggodogdog"); + lfs_file_write(&lfs, &file, buffer, size) => size; + lfs_file_seek(&lfs, &file, off*size, LFS_SEEK_SET) => off*size; + lfs_file_read(&lfs, &file, buffer, size) => size; + assert(memcmp(buffer, "doggodogdog", size) == 0); + lfs_file_sync(&lfs, &file) => 0; + lfs_file_seek(&lfs, &file, off*size, LFS_SEEK_SET) => off*size; + lfs_file_read(&lfs, &file, buffer, size) => size; + assert(memcmp(buffer, "doggodogdog", size) == 0); + } + } + lfs_file_close(&lfs, &file) => 0; + + lfs_file_open(&lfs, &file, "kitty", LFS_O_RDWR) => 0; + lfs_file_size(&lfs, &file) => COUNT*size; + for (int j = 0; j < COUNT; j++) { + lfs_file_read(&lfs, &file, buffer, size) => size; + assert(memcmp(buffer, "doggodogdog", size) == 0); + } + lfs_file_close(&lfs, &file) => 0; + lfs_unmount(&lfs) => 0; +''' diff --git a/tests/test_superblocks.toml b/tests/test_superblocks.toml new file mode 100644 index 00000000..407c8454 --- /dev/null +++ b/tests/test_superblocks.toml @@ -0,0 +1,127 @@ +[[case]] # simple formatting test +code = ''' + lfs_format(&lfs, &cfg) => 0; +''' + +[[case]] # mount/unmount +code = ''' + lfs_format(&lfs, &cfg) => 0; + lfs_mount(&lfs, &cfg) => 0; + lfs_unmount(&lfs) => 0; +''' + +[[case]] # reentrant format +reentrant = true +code = ''' + err = lfs_mount(&lfs, &cfg); + if (err) { + lfs_format(&lfs, &cfg) => 0; + lfs_mount(&lfs, &cfg) => 0; + } + lfs_unmount(&lfs) => 0; +''' + +[[case]] # invalid mount +code = ''' + lfs_mount(&lfs, &cfg) => LFS_ERR_CORRUPT; +''' + +[[case]] # expanding superblock +define.LFS_BLOCK_CYCLES = [32, 33, 1] +define.N = [10, 100, 1000] +code = ''' + lfs_format(&lfs, &cfg) => 0; + lfs_mount(&lfs, &cfg) => 0; + for (int i = 0; i < N; i++) { + lfs_file_open(&lfs, &file, "dummy", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + lfs_file_close(&lfs, &file) => 0; + lfs_stat(&lfs, "dummy", &info) => 0; + assert(strcmp(info.name, "dummy") == 0); + assert(info.type == LFS_TYPE_REG); + lfs_remove(&lfs, "dummy") => 0; + } + lfs_unmount(&lfs) => 0; + + // one last check after power-cycle + lfs_mount(&lfs, &cfg) => 0; + lfs_file_open(&lfs, &file, "dummy", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + lfs_file_close(&lfs, &file) => 0; + lfs_stat(&lfs, "dummy", &info) => 0; + assert(strcmp(info.name, "dummy") == 0); + assert(info.type == LFS_TYPE_REG); + lfs_unmount(&lfs) => 0; +''' + +[[case]] # expanding superblock with power cycle +define.LFS_BLOCK_CYCLES = [32, 33, 1] +define.N = [10, 100, 1000] +code = ''' + lfs_format(&lfs, &cfg) => 0; + for (int i = 0; i < N; i++) { + lfs_mount(&lfs, &cfg) => 0; + // remove lingering dummy? + err = lfs_stat(&lfs, "dummy", &info); + assert(err == 0 || (err == LFS_ERR_NOENT && i == 0)); + if (!err) { + assert(strcmp(info.name, "dummy") == 0); + assert(info.type == LFS_TYPE_REG); + lfs_remove(&lfs, "dummy") => 0; + } + + lfs_file_open(&lfs, &file, "dummy", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + lfs_file_close(&lfs, &file) => 0; + lfs_stat(&lfs, "dummy", &info) => 0; + assert(strcmp(info.name, "dummy") == 0); + assert(info.type == LFS_TYPE_REG); + lfs_unmount(&lfs) => 0; + } + + // one last check after power-cycle + lfs_mount(&lfs, &cfg) => 0; + lfs_stat(&lfs, "dummy", &info) => 0; + assert(strcmp(info.name, "dummy") == 0); + assert(info.type == LFS_TYPE_REG); + lfs_unmount(&lfs) => 0; +''' + +[[case]] # reentrant expanding superblock +define.LFS_BLOCK_CYCLES = [2, 1] +define.N = 24 +reentrant = true +code = ''' + err = lfs_mount(&lfs, &cfg); + if (err) { + lfs_format(&lfs, &cfg) => 0; + lfs_mount(&lfs, &cfg) => 0; + } + + for (int i = 0; i < N; i++) { + // remove lingering dummy? + err = lfs_stat(&lfs, "dummy", &info); + assert(err == 0 || (err == LFS_ERR_NOENT && i == 0)); + if (!err) { + assert(strcmp(info.name, "dummy") == 0); + assert(info.type == LFS_TYPE_REG); + lfs_remove(&lfs, "dummy") => 0; + } + + lfs_file_open(&lfs, &file, "dummy", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + lfs_file_close(&lfs, &file) => 0; + lfs_stat(&lfs, "dummy", &info) => 0; + assert(strcmp(info.name, "dummy") == 0); + assert(info.type == LFS_TYPE_REG); + } + + lfs_unmount(&lfs) => 0; + + // one last check after power-cycle + lfs_mount(&lfs, &cfg) => 0; + lfs_stat(&lfs, "dummy", &info) => 0; + assert(strcmp(info.name, "dummy") == 0); + assert(info.type == LFS_TYPE_REG); + lfs_unmount(&lfs) => 0; +''' diff --git a/tests/test_truncate.sh b/tests/test_truncate.sh deleted file mode 100755 index f33717df..00000000 --- a/tests/test_truncate.sh +++ /dev/null @@ -1,355 +0,0 @@ -#!/bin/bash -set -eu -export TEST_FILE=$0 -trap 'export TEST_LINE=$LINENO' DEBUG - -echo "=== Truncate tests ===" - -SMALLSIZE=32 -MEDIUMSIZE=2048 -LARGESIZE=8192 - -rm -rf blocks -scripts/test.py << TEST - lfs_format(&lfs, &cfg) => 0; -TEST - -echo "--- Simple truncate ---" -scripts/test.py << TEST - lfs_mount(&lfs, &cfg) => 0; - lfs_file_open(&lfs, &file, "baldynoop", - LFS_O_WRONLY | LFS_O_CREAT) => 0; - - strcpy((char*)buffer, "hair"); - lfs_size_t size = strlen((char*)buffer); - for (lfs_off_t j = 0; j < $LARGESIZE; j += size) { - lfs_file_write(&lfs, &file, buffer, size) => size; - } - lfs_file_size(&lfs, &file) => $LARGESIZE; - - lfs_file_close(&lfs, &file) => 0; - lfs_unmount(&lfs) => 0; -TEST -scripts/test.py << TEST - lfs_mount(&lfs, &cfg) => 0; - lfs_file_open(&lfs, &file, "baldynoop", LFS_O_RDWR) => 0; - lfs_file_size(&lfs, &file) => $LARGESIZE; - - lfs_file_truncate(&lfs, &file, $MEDIUMSIZE) => 0; - lfs_file_size(&lfs, &file) => $MEDIUMSIZE; - - lfs_file_close(&lfs, &file) => 0; - lfs_unmount(&lfs) => 0; -TEST -scripts/test.py << TEST - lfs_mount(&lfs, &cfg) => 0; - lfs_file_open(&lfs, &file, "baldynoop", LFS_O_RDONLY) => 0; - lfs_file_size(&lfs, &file) => $MEDIUMSIZE; - - lfs_size_t size = strlen("hair"); - for (lfs_off_t j = 0; j < $MEDIUMSIZE; j += size) { - lfs_file_read(&lfs, &file, buffer, size) => size; - memcmp(buffer, "hair", size) => 0; - } - lfs_file_read(&lfs, &file, buffer, size) => 0; - - lfs_file_close(&lfs, &file) => 0; - lfs_unmount(&lfs) => 0; -TEST - -echo "--- Truncate and read ---" -scripts/test.py << TEST - lfs_mount(&lfs, &cfg) => 0; - lfs_file_open(&lfs, &file, "baldyread", - LFS_O_WRONLY | LFS_O_CREAT) => 0; - - strcpy((char*)buffer, "hair"); - lfs_size_t size = strlen((char*)buffer); - for (lfs_off_t j = 0; j < $LARGESIZE; j += size) { - lfs_file_write(&lfs, &file, buffer, size) => size; - } - lfs_file_size(&lfs, &file) => $LARGESIZE; - - lfs_file_close(&lfs, &file) => 0; - lfs_unmount(&lfs) => 0; -TEST -scripts/test.py << TEST - lfs_mount(&lfs, &cfg) => 0; - lfs_file_open(&lfs, &file, "baldyread", LFS_O_RDWR) => 0; - lfs_file_size(&lfs, &file) => $LARGESIZE; - - lfs_file_truncate(&lfs, &file, $MEDIUMSIZE) => 0; - lfs_file_size(&lfs, &file) => $MEDIUMSIZE; - - lfs_size_t size = strlen("hair"); - for (lfs_off_t j = 0; j < $MEDIUMSIZE; j += size) { - lfs_file_read(&lfs, &file, buffer, size) => size; - memcmp(buffer, "hair", size) => 0; - } - lfs_file_read(&lfs, &file, buffer, size) => 0; - - lfs_file_close(&lfs, &file) => 0; - lfs_unmount(&lfs) => 0; -TEST -scripts/test.py << TEST - lfs_mount(&lfs, &cfg) => 0; - lfs_file_open(&lfs, &file, "baldyread", LFS_O_RDONLY) => 0; - lfs_file_size(&lfs, &file) => $MEDIUMSIZE; - - lfs_size_t size = strlen("hair"); - for (lfs_off_t j = 0; j < $MEDIUMSIZE; j += size) { - lfs_file_read(&lfs, &file, buffer, size) => size; - memcmp(buffer, "hair", size) => 0; - } - lfs_file_read(&lfs, &file, buffer, size) => 0; - - lfs_file_close(&lfs, &file) => 0; - lfs_unmount(&lfs) => 0; -TEST - -echo "--- Write, truncate, and read ---" -scripts/test.py << TEST - lfs_mount(&lfs, &cfg) => 0; - lfs_file_open(&lfs, &file, "sequence", - LFS_O_RDWR | LFS_O_CREAT | LFS_O_TRUNC) => 0; - - lfs_size_t size = lfs.cfg->cache_size; - lfs_size_t qsize = size / 4; - uint8_t *wb = buffer; - uint8_t *rb = buffer + size; - for (lfs_off_t j = 0; j < size; ++j) { - wb[j] = j; - } - - /* Spread sequence over size */ - lfs_file_write(&lfs, &file, wb, size) => size; - lfs_file_size(&lfs, &file) => size; - lfs_file_tell(&lfs, &file) => size; - - lfs_file_seek(&lfs, &file, 0, LFS_SEEK_SET) => 0; - lfs_file_tell(&lfs, &file) => 0; - - /* Chop off the last quarter */ - lfs_size_t trunc = size - qsize; - lfs_file_truncate(&lfs, &file, trunc) => 0; - lfs_file_tell(&lfs, &file) => 0; - lfs_file_size(&lfs, &file) => trunc; - - /* Read should produce first 3/4 */ - lfs_file_read(&lfs, &file, rb, size) => trunc; - memcmp(rb, wb, trunc) => 0; - - /* Move to 1/4 */ - lfs_file_size(&lfs, &file) => trunc; - lfs_file_seek(&lfs, &file, qsize, LFS_SEEK_SET) => qsize; - lfs_file_tell(&lfs, &file) => qsize; - - /* Chop to 1/2 */ - trunc -= qsize; - lfs_file_truncate(&lfs, &file, trunc) => 0; - lfs_file_tell(&lfs, &file) => qsize; - lfs_file_size(&lfs, &file) => trunc; - - /* Read should produce second quarter */ - lfs_file_read(&lfs, &file, rb, size) => trunc - qsize; - memcmp(rb, wb + qsize, trunc - qsize) => 0; - - lfs_file_close(&lfs, &file) => 0; - lfs_unmount(&lfs) => 0; -TEST - -echo "--- Truncate and write ---" -scripts/test.py << TEST - lfs_mount(&lfs, &cfg) => 0; - lfs_file_open(&lfs, &file, "baldywrite", - LFS_O_WRONLY | LFS_O_CREAT) => 0; - - strcpy((char*)buffer, "hair"); - lfs_size_t size = strlen((char*)buffer); - for (lfs_off_t j = 0; j < $LARGESIZE; j += size) { - lfs_file_write(&lfs, &file, buffer, size) => size; - } - lfs_file_size(&lfs, &file) => $LARGESIZE; - - lfs_file_close(&lfs, &file) => 0; - lfs_unmount(&lfs) => 0; -TEST -scripts/test.py << TEST - lfs_mount(&lfs, &cfg) => 0; - lfs_file_open(&lfs, &file, "baldywrite", LFS_O_RDWR) => 0; - lfs_file_size(&lfs, &file) => $LARGESIZE; - - lfs_file_truncate(&lfs, &file, $MEDIUMSIZE) => 0; - lfs_file_size(&lfs, &file) => $MEDIUMSIZE; - - strcpy((char*)buffer, "bald"); - lfs_size_t size = strlen((char*)buffer); - for (lfs_off_t j = 0; j < $MEDIUMSIZE; j += size) { - lfs_file_write(&lfs, &file, buffer, size) => size; - } - lfs_file_size(&lfs, &file) => $MEDIUMSIZE; - - lfs_file_close(&lfs, &file) => 0; - lfs_unmount(&lfs) => 0; -TEST -scripts/test.py << TEST - lfs_mount(&lfs, &cfg) => 0; - lfs_file_open(&lfs, &file, "baldywrite", LFS_O_RDONLY) => 0; - lfs_file_size(&lfs, &file) => $MEDIUMSIZE; - - lfs_size_t size = strlen("bald"); - for (lfs_off_t j = 0; j < $MEDIUMSIZE; j += size) { - lfs_file_read(&lfs, &file, buffer, size) => size; - memcmp(buffer, "bald", size) => 0; - } - lfs_file_read(&lfs, &file, buffer, size) => 0; - - lfs_file_close(&lfs, &file) => 0; - lfs_unmount(&lfs) => 0; -TEST - -# More aggressive general truncation tests -truncate_test() { -STARTSIZES="$1" -STARTSEEKS="$2" -HOTSIZES="$3" -COLDSIZES="$4" -scripts/test.py << TEST - static const lfs_off_t startsizes[] = {$STARTSIZES}; - static const lfs_off_t startseeks[] = {$STARTSEEKS}; - static const lfs_off_t hotsizes[] = {$HOTSIZES}; - - lfs_mount(&lfs, &cfg) => 0; - - for (unsigned i = 0; i < sizeof(startsizes)/sizeof(startsizes[0]); i++) { - sprintf(path, "hairyhead%d", i); - lfs_file_open(&lfs, &file, path, - LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0; - - strcpy((char*)buffer, "hair"); - lfs_size_t size = strlen((char*)buffer); - for (lfs_off_t j = 0; j < startsizes[i]; j += size) { - lfs_file_write(&lfs, &file, buffer, size) => size; - } - lfs_file_size(&lfs, &file) => startsizes[i]; - - if (startseeks[i] != startsizes[i]) { - lfs_file_seek(&lfs, &file, - startseeks[i], LFS_SEEK_SET) => startseeks[i]; - } - - lfs_file_truncate(&lfs, &file, hotsizes[i]) => 0; - lfs_file_size(&lfs, &file) => hotsizes[i]; - - lfs_file_close(&lfs, &file) => 0; - } - - lfs_unmount(&lfs) => 0; -TEST -scripts/test.py << TEST - static const lfs_off_t startsizes[] = {$STARTSIZES}; - static const lfs_off_t hotsizes[] = {$HOTSIZES}; - static const lfs_off_t coldsizes[] = {$COLDSIZES}; - - lfs_mount(&lfs, &cfg) => 0; - - for (unsigned i = 0; i < sizeof(startsizes)/sizeof(startsizes[0]); i++) { - sprintf(path, "hairyhead%d", i); - lfs_file_open(&lfs, &file, path, LFS_O_RDWR) => 0; - lfs_file_size(&lfs, &file) => hotsizes[i]; - - lfs_size_t size = strlen("hair"); - lfs_off_t j = 0; - for (; j < startsizes[i] && j < hotsizes[i]; j += size) { - lfs_file_read(&lfs, &file, buffer, size) => size; - memcmp(buffer, "hair", size) => 0; - } - - for (; j < hotsizes[i]; j += size) { - lfs_file_read(&lfs, &file, buffer, size) => size; - memcmp(buffer, "\0\0\0\0", size) => 0; - } - - lfs_file_truncate(&lfs, &file, coldsizes[i]) => 0; - lfs_file_size(&lfs, &file) => coldsizes[i]; - - lfs_file_close(&lfs, &file) => 0; - } - - lfs_unmount(&lfs) => 0; -TEST -scripts/test.py << TEST - static const lfs_off_t startsizes[] = {$STARTSIZES}; - static const lfs_off_t hotsizes[] = {$HOTSIZES}; - static const lfs_off_t coldsizes[] = {$COLDSIZES}; - - lfs_mount(&lfs, &cfg) => 0; - - for (unsigned i = 0; i < sizeof(startsizes)/sizeof(startsizes[0]); i++) { - sprintf(path, "hairyhead%d", i); - lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0; - lfs_file_size(&lfs, &file) => coldsizes[i]; - - lfs_size_t size = strlen("hair"); - lfs_off_t j = 0; - for (; j < startsizes[i] && j < hotsizes[i] && j < coldsizes[i]; - j += size) { - lfs_file_read(&lfs, &file, buffer, size) => size; - memcmp(buffer, "hair", size) => 0; - } - - for (; j < coldsizes[i]; j += size) { - lfs_file_read(&lfs, &file, buffer, size) => size; - memcmp(buffer, "\0\0\0\0", size) => 0; - } - - lfs_file_close(&lfs, &file) => 0; - } - - lfs_unmount(&lfs) => 0; -TEST -} - -echo "--- Cold shrinking truncate ---" -truncate_test \ - "2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE" \ - "2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE" \ - "2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE" \ - " 0, $SMALLSIZE, $MEDIUMSIZE, $LARGESIZE, 2*$LARGESIZE" - -echo "--- Cold expanding truncate ---" -truncate_test \ - " 0, $SMALLSIZE, $MEDIUMSIZE, $LARGESIZE, 2*$LARGESIZE" \ - " 0, $SMALLSIZE, $MEDIUMSIZE, $LARGESIZE, 2*$LARGESIZE" \ - " 0, $SMALLSIZE, $MEDIUMSIZE, $LARGESIZE, 2*$LARGESIZE" \ - "2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE" - -echo "--- Warm shrinking truncate ---" -truncate_test \ - "2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE" \ - "2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE" \ - " 0, $SMALLSIZE, $MEDIUMSIZE, $LARGESIZE, 2*$LARGESIZE" \ - " 0, 0, 0, 0, 0" - -echo "--- Warm expanding truncate ---" -truncate_test \ - " 0, $SMALLSIZE, $MEDIUMSIZE, $LARGESIZE, 2*$LARGESIZE" \ - " 0, $SMALLSIZE, $MEDIUMSIZE, $LARGESIZE, 2*$LARGESIZE" \ - "2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE" \ - "2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE" - -echo "--- Mid-file shrinking truncate ---" -truncate_test \ - "2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE" \ - " $LARGESIZE, $LARGESIZE, $LARGESIZE, $LARGESIZE, $LARGESIZE" \ - " 0, $SMALLSIZE, $MEDIUMSIZE, $LARGESIZE, 2*$LARGESIZE" \ - " 0, 0, 0, 0, 0" - -echo "--- Mid-file expanding truncate ---" -truncate_test \ - " 0, $SMALLSIZE, $MEDIUMSIZE, $LARGESIZE, 2*$LARGESIZE" \ - " 0, 0, $SMALLSIZE, $MEDIUMSIZE, $LARGESIZE" \ - "2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE" \ - "2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE" - -scripts/results.py diff --git a/tests/test_truncate.toml b/tests/test_truncate.toml new file mode 100644 index 00000000..c11285b7 --- /dev/null +++ b/tests/test_truncate.toml @@ -0,0 +1,394 @@ +[[case]] # simple truncate +define.MEDIUMSIZE = [32, 2048] +define.LARGESIZE = 8192 +code = ''' + lfs_format(&lfs, &cfg) => 0; + lfs_mount(&lfs, &cfg) => 0; + lfs_file_open(&lfs, &file, "baldynoop", + LFS_O_WRONLY | LFS_O_CREAT) => 0; + + strcpy((char*)buffer, "hair"); + size = strlen((char*)buffer); + for (lfs_off_t j = 0; j < LARGESIZE; j += size) { + lfs_file_write(&lfs, &file, buffer, size) => size; + } + lfs_file_size(&lfs, &file) => LARGESIZE; + + lfs_file_close(&lfs, &file) => 0; + lfs_unmount(&lfs) => 0; + + lfs_mount(&lfs, &cfg) => 0; + lfs_file_open(&lfs, &file, "baldynoop", LFS_O_RDWR) => 0; + lfs_file_size(&lfs, &file) => LARGESIZE; + + lfs_file_truncate(&lfs, &file, MEDIUMSIZE) => 0; + lfs_file_size(&lfs, &file) => MEDIUMSIZE; + + lfs_file_close(&lfs, &file) => 0; + lfs_unmount(&lfs) => 0; + + lfs_mount(&lfs, &cfg) => 0; + lfs_file_open(&lfs, &file, "baldynoop", LFS_O_RDONLY) => 0; + lfs_file_size(&lfs, &file) => MEDIUMSIZE; + + size = strlen("hair"); + for (lfs_off_t j = 0; j < MEDIUMSIZE; j += size) { + lfs_file_read(&lfs, &file, buffer, size) => size; + memcmp(buffer, "hair", size) => 0; + } + lfs_file_read(&lfs, &file, buffer, size) => 0; + + lfs_file_close(&lfs, &file) => 0; + lfs_unmount(&lfs) => 0; +''' + +[[case]] # truncate and read +define.MEDIUMSIZE = [32, 2048] +define.LARGESIZE = 8192 +code = ''' + lfs_format(&lfs, &cfg) => 0; + lfs_mount(&lfs, &cfg) => 0; + lfs_file_open(&lfs, &file, "baldyread", + LFS_O_WRONLY | LFS_O_CREAT) => 0; + + strcpy((char*)buffer, "hair"); + size = strlen((char*)buffer); + for (lfs_off_t j = 0; j < LARGESIZE; j += size) { + lfs_file_write(&lfs, &file, buffer, size) => size; + } + lfs_file_size(&lfs, &file) => LARGESIZE; + + lfs_file_close(&lfs, &file) => 0; + lfs_unmount(&lfs) => 0; + + lfs_mount(&lfs, &cfg) => 0; + lfs_file_open(&lfs, &file, "baldyread", LFS_O_RDWR) => 0; + lfs_file_size(&lfs, &file) => LARGESIZE; + + lfs_file_truncate(&lfs, &file, MEDIUMSIZE) => 0; + lfs_file_size(&lfs, &file) => MEDIUMSIZE; + + size = strlen("hair"); + for (lfs_off_t j = 0; j < MEDIUMSIZE; j += size) { + lfs_file_read(&lfs, &file, buffer, size) => size; + memcmp(buffer, "hair", size) => 0; + } + lfs_file_read(&lfs, &file, buffer, size) => 0; + + lfs_file_close(&lfs, &file) => 0; + lfs_unmount(&lfs) => 0; + + lfs_mount(&lfs, &cfg) => 0; + lfs_file_open(&lfs, &file, "baldyread", LFS_O_RDONLY) => 0; + lfs_file_size(&lfs, &file) => MEDIUMSIZE; + + size = strlen("hair"); + for (lfs_off_t j = 0; j < MEDIUMSIZE; j += size) { + lfs_file_read(&lfs, &file, buffer, size) => size; + memcmp(buffer, "hair", size) => 0; + } + lfs_file_read(&lfs, &file, buffer, size) => 0; + + lfs_file_close(&lfs, &file) => 0; + lfs_unmount(&lfs) => 0; +''' + +[[case]] # write, truncate, and read +code = ''' + lfs_format(&lfs, &cfg) => 0; + lfs_mount(&lfs, &cfg) => 0; + lfs_file_open(&lfs, &file, "sequence", + LFS_O_RDWR | LFS_O_CREAT | LFS_O_TRUNC) => 0; + + size = lfs_min(lfs.cfg->cache_size, sizeof(buffer)/2); + lfs_size_t qsize = size / 4; + uint8_t *wb = buffer; + uint8_t *rb = buffer + size; + for (lfs_off_t j = 0; j < size; ++j) { + wb[j] = j; + } + + /* Spread sequence over size */ + lfs_file_write(&lfs, &file, wb, size) => size; + lfs_file_size(&lfs, &file) => size; + lfs_file_tell(&lfs, &file) => size; + + lfs_file_seek(&lfs, &file, 0, LFS_SEEK_SET) => 0; + lfs_file_tell(&lfs, &file) => 0; + + /* Chop off the last quarter */ + lfs_size_t trunc = size - qsize; + lfs_file_truncate(&lfs, &file, trunc) => 0; + lfs_file_tell(&lfs, &file) => 0; + lfs_file_size(&lfs, &file) => trunc; + + /* Read should produce first 3/4 */ + lfs_file_read(&lfs, &file, rb, size) => trunc; + memcmp(rb, wb, trunc) => 0; + + /* Move to 1/4 */ + lfs_file_size(&lfs, &file) => trunc; + lfs_file_seek(&lfs, &file, qsize, LFS_SEEK_SET) => qsize; + lfs_file_tell(&lfs, &file) => qsize; + + /* Chop to 1/2 */ + trunc -= qsize; + lfs_file_truncate(&lfs, &file, trunc) => 0; + lfs_file_tell(&lfs, &file) => qsize; + lfs_file_size(&lfs, &file) => trunc; + + /* Read should produce second quarter */ + lfs_file_read(&lfs, &file, rb, size) => trunc - qsize; + memcmp(rb, wb + qsize, trunc - qsize) => 0; + + lfs_file_close(&lfs, &file) => 0; + lfs_unmount(&lfs) => 0; +''' + +[[case]] # truncate and write +define.MEDIUMSIZE = [32, 2048] +define.LARGESIZE = 8192 +code = ''' + lfs_format(&lfs, &cfg) => 0; + lfs_mount(&lfs, &cfg) => 0; + lfs_file_open(&lfs, &file, "baldywrite", + LFS_O_WRONLY | LFS_O_CREAT) => 0; + + strcpy((char*)buffer, "hair"); + size = strlen((char*)buffer); + for (lfs_off_t j = 0; j < LARGESIZE; j += size) { + lfs_file_write(&lfs, &file, buffer, size) => size; + } + lfs_file_size(&lfs, &file) => LARGESIZE; + + lfs_file_close(&lfs, &file) => 0; + lfs_unmount(&lfs) => 0; + + lfs_mount(&lfs, &cfg) => 0; + lfs_file_open(&lfs, &file, "baldywrite", LFS_O_RDWR) => 0; + lfs_file_size(&lfs, &file) => LARGESIZE; + + lfs_file_truncate(&lfs, &file, MEDIUMSIZE) => 0; + lfs_file_size(&lfs, &file) => MEDIUMSIZE; + + strcpy((char*)buffer, "bald"); + size = strlen((char*)buffer); + for (lfs_off_t j = 0; j < MEDIUMSIZE; j += size) { + lfs_file_write(&lfs, &file, buffer, size) => size; + } + lfs_file_size(&lfs, &file) => MEDIUMSIZE; + + lfs_file_close(&lfs, &file) => 0; + lfs_unmount(&lfs) => 0; + + lfs_mount(&lfs, &cfg) => 0; + lfs_file_open(&lfs, &file, "baldywrite", LFS_O_RDONLY) => 0; + lfs_file_size(&lfs, &file) => MEDIUMSIZE; + + size = strlen("bald"); + for (lfs_off_t j = 0; j < MEDIUMSIZE; j += size) { + lfs_file_read(&lfs, &file, buffer, size) => size; + memcmp(buffer, "bald", size) => 0; + } + lfs_file_read(&lfs, &file, buffer, size) => 0; + + lfs_file_close(&lfs, &file) => 0; + lfs_unmount(&lfs) => 0; +''' + +[[case]] # truncate write under powerloss +define.SMALLSIZE = [4, 512] +define.MEDIUMSIZE = [32, 1024] +define.LARGESIZE = 2048 +reentrant = true +code = ''' + err = lfs_mount(&lfs, &cfg); + if (err) { + lfs_format(&lfs, &cfg) => 0; + lfs_mount(&lfs, &cfg) => 0; + } + err = lfs_file_open(&lfs, &file, "baldy", LFS_O_RDONLY); + assert(!err || err == LFS_ERR_NOENT); + if (!err) { + size = lfs_file_size(&lfs, &file); + assert(size == 0 || + size == LARGESIZE || + size == MEDIUMSIZE || + size == SMALLSIZE); + for (lfs_off_t j = 0; j < size; j += 4) { + lfs_file_read(&lfs, &file, buffer, 4) => 4; + assert(memcmp(buffer, "hair", 4) == 0 || + memcmp(buffer, "bald", 4) == 0 || + memcmp(buffer, "comb", 4) == 0); + } + lfs_file_close(&lfs, &file) => 0; + } + + lfs_file_open(&lfs, &file, "baldy", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0; + lfs_file_size(&lfs, &file) => 0; + strcpy((char*)buffer, "hair"); + size = strlen((char*)buffer); + for (lfs_off_t j = 0; j < LARGESIZE; j += size) { + lfs_file_write(&lfs, &file, buffer, size) => size; + } + lfs_file_size(&lfs, &file) => LARGESIZE; + lfs_file_close(&lfs, &file) => 0; + + lfs_file_open(&lfs, &file, "baldy", LFS_O_RDWR) => 0; + lfs_file_size(&lfs, &file) => LARGESIZE; + lfs_file_truncate(&lfs, &file, MEDIUMSIZE) => 0; + lfs_file_size(&lfs, &file) => MEDIUMSIZE; + strcpy((char*)buffer, "bald"); + size = strlen((char*)buffer); + for (lfs_off_t j = 0; j < MEDIUMSIZE; j += size) { + lfs_file_write(&lfs, &file, buffer, size) => size; + } + lfs_file_size(&lfs, &file) => MEDIUMSIZE; + lfs_file_close(&lfs, &file) => 0; + + lfs_file_open(&lfs, &file, "baldy", LFS_O_RDWR) => 0; + lfs_file_size(&lfs, &file) => MEDIUMSIZE; + lfs_file_truncate(&lfs, &file, SMALLSIZE) => 0; + lfs_file_size(&lfs, &file) => SMALLSIZE; + strcpy((char*)buffer, "comb"); + size = strlen((char*)buffer); + for (lfs_off_t j = 0; j < SMALLSIZE; j += size) { + lfs_file_write(&lfs, &file, buffer, size) => size; + } + lfs_file_size(&lfs, &file) => SMALLSIZE; + lfs_file_close(&lfs, &file) => 0; + + lfs_unmount(&lfs) => 0; +''' + +[[case]] # more aggressive general truncation tests +define.CONFIG = 'range(6)' +define.SMALLSIZE = 32 +define.MEDIUMSIZE = 2048 +define.LARGESIZE = 8192 +code = ''' + #define COUNT 5 + const struct { + lfs_off_t startsizes[COUNT]; + lfs_off_t startseeks[COUNT]; + lfs_off_t hotsizes[COUNT]; + lfs_off_t coldsizes[COUNT]; + } configs[] = { + // cold shrinking + {{2*LARGESIZE, 2*LARGESIZE, 2*LARGESIZE, 2*LARGESIZE, 2*LARGESIZE}, + {2*LARGESIZE, 2*LARGESIZE, 2*LARGESIZE, 2*LARGESIZE, 2*LARGESIZE}, + {2*LARGESIZE, 2*LARGESIZE, 2*LARGESIZE, 2*LARGESIZE, 2*LARGESIZE}, + { 0, SMALLSIZE, MEDIUMSIZE, LARGESIZE, 2*LARGESIZE}}, + // cold expanding + {{2*LARGESIZE, 2*LARGESIZE, 2*LARGESIZE, 2*LARGESIZE, 2*LARGESIZE}, + {2*LARGESIZE, 2*LARGESIZE, 2*LARGESIZE, 2*LARGESIZE, 2*LARGESIZE}, + {2*LARGESIZE, 2*LARGESIZE, 2*LARGESIZE, 2*LARGESIZE, 2*LARGESIZE}, + { 0, SMALLSIZE, MEDIUMSIZE, LARGESIZE, 2*LARGESIZE}}, + // warm shrinking truncate + {{2*LARGESIZE, 2*LARGESIZE, 2*LARGESIZE, 2*LARGESIZE, 2*LARGESIZE}, + {2*LARGESIZE, 2*LARGESIZE, 2*LARGESIZE, 2*LARGESIZE, 2*LARGESIZE}, + { 0, SMALLSIZE, MEDIUMSIZE, LARGESIZE, 2*LARGESIZE}, + { 0, 0, 0, 0, 0}}, + // warm expanding truncate + {{ 0, SMALLSIZE, MEDIUMSIZE, LARGESIZE, 2*LARGESIZE}, + { 0, SMALLSIZE, MEDIUMSIZE, LARGESIZE, 2*LARGESIZE}, + {2*LARGESIZE, 2*LARGESIZE, 2*LARGESIZE, 2*LARGESIZE, 2*LARGESIZE}, + {2*LARGESIZE, 2*LARGESIZE, 2*LARGESIZE, 2*LARGESIZE, 2*LARGESIZE}}, + // mid-file shrinking truncate + {{2*LARGESIZE, 2*LARGESIZE, 2*LARGESIZE, 2*LARGESIZE, 2*LARGESIZE}, + { LARGESIZE, LARGESIZE, LARGESIZE, LARGESIZE, LARGESIZE}, + { 0, SMALLSIZE, MEDIUMSIZE, LARGESIZE, 2*LARGESIZE}, + { 0, 0, 0, 0, 0}}, + // mid-file expanding truncate + {{ 0, SMALLSIZE, MEDIUMSIZE, LARGESIZE, 2*LARGESIZE}, + { 0, 0, SMALLSIZE, MEDIUMSIZE, LARGESIZE}, + {2*LARGESIZE, 2*LARGESIZE, 2*LARGESIZE, 2*LARGESIZE, 2*LARGESIZE}, + {2*LARGESIZE, 2*LARGESIZE, 2*LARGESIZE, 2*LARGESIZE, 2*LARGESIZE}}, + }; + + const lfs_off_t *startsizes = configs[CONFIG].startsizes; + const lfs_off_t *startseeks = configs[CONFIG].startseeks; + const lfs_off_t *hotsizes = configs[CONFIG].hotsizes; + const lfs_off_t *coldsizes = configs[CONFIG].coldsizes; + + lfs_format(&lfs, &cfg) => 0; + lfs_mount(&lfs, &cfg) => 0; + + for (unsigned i = 0; i < COUNT; i++) { + sprintf(path, "hairyhead%d", i); + lfs_file_open(&lfs, &file, path, + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0; + + strcpy((char*)buffer, "hair"); + size = strlen((char*)buffer); + for (lfs_off_t j = 0; j < startsizes[i]; j += size) { + lfs_file_write(&lfs, &file, buffer, size) => size; + } + lfs_file_size(&lfs, &file) => startsizes[i]; + + if (startseeks[i] != startsizes[i]) { + lfs_file_seek(&lfs, &file, + startseeks[i], LFS_SEEK_SET) => startseeks[i]; + } + + lfs_file_truncate(&lfs, &file, hotsizes[i]) => 0; + lfs_file_size(&lfs, &file) => hotsizes[i]; + + lfs_file_close(&lfs, &file) => 0; + } + + lfs_unmount(&lfs) => 0; + + lfs_mount(&lfs, &cfg) => 0; + + for (unsigned i = 0; i < COUNT; i++) { + sprintf(path, "hairyhead%d", i); + lfs_file_open(&lfs, &file, path, LFS_O_RDWR) => 0; + lfs_file_size(&lfs, &file) => hotsizes[i]; + + size = strlen("hair"); + lfs_off_t j = 0; + for (; j < startsizes[i] && j < hotsizes[i]; j += size) { + lfs_file_read(&lfs, &file, buffer, size) => size; + memcmp(buffer, "hair", size) => 0; + } + + for (; j < hotsizes[i]; j += size) { + lfs_file_read(&lfs, &file, buffer, size) => size; + memcmp(buffer, "\0\0\0\0", size) => 0; + } + + lfs_file_truncate(&lfs, &file, coldsizes[i]) => 0; + lfs_file_size(&lfs, &file) => coldsizes[i]; + + lfs_file_close(&lfs, &file) => 0; + } + + lfs_unmount(&lfs) => 0; + + lfs_mount(&lfs, &cfg) => 0; + + for (unsigned i = 0; i < COUNT; i++) { + sprintf(path, "hairyhead%d", i); + lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0; + lfs_file_size(&lfs, &file) => coldsizes[i]; + + size = strlen("hair"); + lfs_off_t j = 0; + for (; j < startsizes[i] && j < hotsizes[i] && j < coldsizes[i]; + j += size) { + lfs_file_read(&lfs, &file, buffer, size) => size; + memcmp(buffer, "hair", size) => 0; + } + + for (; j < coldsizes[i]; j += size) { + lfs_file_read(&lfs, &file, buffer, size) => size; + memcmp(buffer, "\0\0\0\0", size) => 0; + } + + lfs_file_close(&lfs, &file) => 0; + } + + lfs_unmount(&lfs) => 0; +'''