mirror of https://github.com/ARMmbed/mbed-os.git
				
				
				
			
						commit
						c4354fa645
					
				
							
								
								
									
										38
									
								
								.travis.yml
								
								
								
								
							
							
						
						
									
										38
									
								
								.travis.yml
								
								
								
								
							| 
						 | 
				
			
			@ -141,17 +141,20 @@ matrix:
 | 
			
		|||
        - sudo chmod a+rw /dev/loop0
 | 
			
		||||
        - dd if=/dev/zero bs=512 count=2048 of=DISK
 | 
			
		||||
        - losetup /dev/loop0 DISK
 | 
			
		||||
        - CFLAGS="-Werror -Wno-format"
 | 
			
		||||
      script:
 | 
			
		||||
        # Check that example compiles
 | 
			
		||||
        - export CFLAGS="-Werror -Wno-format"
 | 
			
		||||
        - sed -n '/``` c++/,/```/{/```/d;p;}' $LITTLEFS/README.md > main.cpp
 | 
			
		||||
        - python tools/make.py -t GCC_ARM -m K82F --source=. --build=BUILD/K82F/GCC_ARM -j0
 | 
			
		||||
        # Run local littlefs tests
 | 
			
		||||
        - CFLAGS="-Wno-format" make -C$LITTLEFS/littlefs test QUIET=1
 | 
			
		||||
        - make -C$LITTLEFS/littlefs test QUIET=1
 | 
			
		||||
        # Run local littlefs tests with set of variations
 | 
			
		||||
        - CFLAGS="-Wno-format -DLFS_READ_SIZE=64  -DLFS_PROG_SIZE=64"      make -C$LITTLEFS/littlefs test QUIET=1
 | 
			
		||||
        - CFLAGS="-Wno-format -DLFS_READ_SIZE=1   -DLFS_PROG_SIZE=1"       make -C$LITTLEFS/littlefs test QUIET=1
 | 
			
		||||
        - CFLAGS="-Wno-format -DLFS_READ_SIZE=512 -DLFS_PROG_SIZE=512"     make -C$LITTLEFS/littlefs test QUIET=1
 | 
			
		||||
        - CFLAGS="-Wno-format -DLFS_BLOCK_COUNT=1023 -DLFS_LOOKAHEAD=2048" make -C$LITTLEFS/littlefs test QUIET=1
 | 
			
		||||
        - make -C$LITTLEFS/littlefs test QUIET=1 CFLAGS+="-DLFS_READ_SIZE=64  -DLFS_PROG_SIZE=64"
 | 
			
		||||
        - make -C$LITTLEFS/littlefs test QUIET=1 CFLAGS+="-DLFS_READ_SIZE=1   -DLFS_PROG_SIZE=1"
 | 
			
		||||
        - make -C$LITTLEFS/littlefs test QUIET=1 CFLAGS+="-DLFS_READ_SIZE=512 -DLFS_PROG_SIZE=512"
 | 
			
		||||
        - make -C$LITTLEFS/littlefs test QUIET=1 CFLAGS+="-DLFS_BLOCK_COUNT=1023 -DLFS_LOOKAHEAD=2048"
 | 
			
		||||
        - make -C$LITTLEFS/littlefs clean test QUIET=1 CFLAGS+="-DLFS_NO_INTRINSICS"
 | 
			
		||||
        # Self-hosting littlefs fuzz test with littlefs-fuse
 | 
			
		||||
        - make -Clittlefs_fuse
 | 
			
		||||
        - littlefs_fuse/lfs --format /dev/loop0
 | 
			
		||||
| 
						 | 
				
			
			@ -160,7 +163,30 @@ matrix:
 | 
			
		|||
        - mkdir MOUNT/littlefs
 | 
			
		||||
        - cp -r $(git ls-tree --name-only HEAD $LITTLEFS/littlefs/) MOUNT/littlefs
 | 
			
		||||
        - ls MOUNT/littlefs
 | 
			
		||||
        - CFLAGS="-Wno-format" make -CMOUNT/littlefs -B test_dirs QUIET=1
 | 
			
		||||
        - CFLAGS="-Wno-format" make -CMOUNT/littlefs -B test_dirs test_files QUIET=1
 | 
			
		||||
        # Compile and find the code size with smallest configuration
 | 
			
		||||
        - cd $TRAVIS_BUILD_DIR/$LITTLEFS/littlefs
 | 
			
		||||
        - make clean size
 | 
			
		||||
            CC='arm-none-eabi-gcc -mthumb'
 | 
			
		||||
            OBJ="$(ls lfs*.o | tr '\n' ' ')"
 | 
			
		||||
            CFLAGS+="-DLFS_NO{ASSERT,DEBUG,WARN,ERROR}"
 | 
			
		||||
            | tee sizes
 | 
			
		||||
      after_success:
 | 
			
		||||
        # update status if we succeeded, compare with master if possible
 | 
			
		||||
        - |
 | 
			
		||||
          CURR=$(tail -n1 sizes | awk '{print $1}')
 | 
			
		||||
          PREV=$(curl https://api.github.com/repos/$TRAVIS_REPO_SLUG/status/master \
 | 
			
		||||
              | jq -re "select(.sha != \"$TRAVIS_COMMIT\")
 | 
			
		||||
                  | .statuses[] | select(.context == \"travis-ci/$NAME\").description
 | 
			
		||||
                  | capture(\"code size is (?<size>[0-9]+)\").size" \
 | 
			
		||||
              || echo 0)
 | 
			
		||||
 | 
			
		||||
          STATUSM="Passed, code size is ${CURR}B"
 | 
			
		||||
          if [ "$PREV" -ne 0 ]
 | 
			
		||||
          then
 | 
			
		||||
              STATUSM="$STATUSM ($(python -c "print '%+.2f' % (100*($CURR-$PREV)/$PREV.0)")%)"
 | 
			
		||||
          fi
 | 
			
		||||
        - bash -c "$STATUS" success "$STATUSM"
 | 
			
		||||
 | 
			
		||||
    - &mbed-2
 | 
			
		||||
      env: NAME=mbed2-NXP
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -18,7 +18,6 @@
 | 
			
		|||
#include "unity.h"
 | 
			
		||||
#include "utest.h"
 | 
			
		||||
#include <stdlib.h>
 | 
			
		||||
#include <errno.h>
 | 
			
		||||
 | 
			
		||||
using namespace utest::v1;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -354,7 +353,7 @@ void test_directory_remove()
 | 
			
		|||
        res = fs.mount(&bd);
 | 
			
		||||
        TEST_ASSERT_EQUAL(0, res);
 | 
			
		||||
        res = fs.remove("potato");
 | 
			
		||||
        TEST_ASSERT_EQUAL(-EINVAL, res);
 | 
			
		||||
        TEST_ASSERT_EQUAL(-ENOTEMPTY, res);
 | 
			
		||||
        res = fs.remove("potato/sweet");
 | 
			
		||||
        TEST_ASSERT_EQUAL(0, res);
 | 
			
		||||
        res = fs.remove("potato/baked");
 | 
			
		||||
| 
						 | 
				
			
			@ -536,7 +535,7 @@ void test_directory_rename()
 | 
			
		|||
        res = fs.mkdir("warmpotato/mushy", 0777);
 | 
			
		||||
        TEST_ASSERT_EQUAL(0, res);
 | 
			
		||||
        res = fs.rename("hotpotato", "warmpotato");
 | 
			
		||||
        TEST_ASSERT_EQUAL(-EINVAL, res);
 | 
			
		||||
        TEST_ASSERT_EQUAL(-ENOTEMPTY, res);
 | 
			
		||||
        res = fs.remove("warmpotato/mushy");
 | 
			
		||||
        TEST_ASSERT_EQUAL(0, res);
 | 
			
		||||
        res = fs.rename("hotpotato", "warmpotato");
 | 
			
		||||
| 
						 | 
				
			
			@ -600,7 +599,7 @@ void test_directory_rename()
 | 
			
		|||
        res = fs.rename("warmpotato/fried", "coldpotato/fried");
 | 
			
		||||
        TEST_ASSERT_EQUAL(0, res);
 | 
			
		||||
        res = fs.remove("coldpotato");
 | 
			
		||||
        TEST_ASSERT_EQUAL(-EINVAL, res);
 | 
			
		||||
        TEST_ASSERT_EQUAL(-ENOTEMPTY, res);
 | 
			
		||||
        res = fs.remove("warmpotato");
 | 
			
		||||
        TEST_ASSERT_EQUAL(0, res);
 | 
			
		||||
        res = fs.unmount();
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -13,12 +13,12 @@
 | 
			
		|||
 * See the License for the specific language governing permissions and
 | 
			
		||||
 * limitations under the License.
 | 
			
		||||
 */
 | 
			
		||||
#include <errno.h>
 | 
			
		||||
#include "mbed.h"
 | 
			
		||||
#include "greentea-client/test_env.h"
 | 
			
		||||
#include "unity.h"
 | 
			
		||||
#include "utest.h"
 | 
			
		||||
#include <stdlib.h>
 | 
			
		||||
#include <errno.h>
 | 
			
		||||
 | 
			
		||||
using namespace utest::v1;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -354,7 +354,7 @@ void test_directory_remove()
 | 
			
		|||
        res = fs.mount(&bd);
 | 
			
		||||
        TEST_ASSERT_EQUAL(0, res);
 | 
			
		||||
        res = remove("/fs/" "potato");
 | 
			
		||||
        TEST_ASSERT_EQUAL(EINVAL, errno);
 | 
			
		||||
        TEST_ASSERT_EQUAL(ENOTEMPTY, errno);
 | 
			
		||||
        res = remove("/fs/" "potato/sweet");
 | 
			
		||||
        TEST_ASSERT_EQUAL(0, res);
 | 
			
		||||
        res = remove("/fs/" "potato/baked");
 | 
			
		||||
| 
						 | 
				
			
			@ -536,7 +536,7 @@ void test_directory_rename()
 | 
			
		|||
        res = mkdir("/fs/" "warmpotato/mushy", 0777);
 | 
			
		||||
        TEST_ASSERT_EQUAL(0, res);
 | 
			
		||||
        res = rename("/fs/" "hotpotato", "/fs/" "warmpotato");
 | 
			
		||||
        TEST_ASSERT_EQUAL(EINVAL, errno);
 | 
			
		||||
        TEST_ASSERT_EQUAL(ENOTEMPTY, errno);
 | 
			
		||||
        res = remove("/fs/" "warmpotato/mushy");
 | 
			
		||||
        TEST_ASSERT_EQUAL(0, res);
 | 
			
		||||
        res = rename("/fs/" "hotpotato", "/fs/" "warmpotato");
 | 
			
		||||
| 
						 | 
				
			
			@ -600,7 +600,7 @@ void test_directory_rename()
 | 
			
		|||
        res = rename("/fs/" "warmpotato/fried", "/fs/" "coldpotato/fried");
 | 
			
		||||
        TEST_ASSERT_EQUAL(0, res);
 | 
			
		||||
        res = remove("/fs/" "coldpotato");
 | 
			
		||||
        TEST_ASSERT_EQUAL(EINVAL, errno);
 | 
			
		||||
        TEST_ASSERT_EQUAL(ENOTEMPTY, errno);
 | 
			
		||||
        res = remove("/fs/" "warmpotato");
 | 
			
		||||
        TEST_ASSERT_EQUAL(0, res);
 | 
			
		||||
        res = fs.unmount();
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,9 @@
 | 
			
		|||
# Compilation output
 | 
			
		||||
*.o
 | 
			
		||||
*.d
 | 
			
		||||
*.a
 | 
			
		||||
 | 
			
		||||
# Testing things
 | 
			
		||||
blocks/
 | 
			
		||||
lfs
 | 
			
		||||
test.c
 | 
			
		||||
| 
						 | 
				
			
			@ -1,47 +1,223 @@
 | 
			
		|||
# Environment variables
 | 
			
		||||
env:
 | 
			
		||||
  global:
 | 
			
		||||
    - CFLAGS=-Werror
 | 
			
		||||
 | 
			
		||||
# Common test script
 | 
			
		||||
script:
 | 
			
		||||
    # make sure example can at least compile
 | 
			
		||||
    - sed -n '/``` c/,/```/{/```/d; p;}' README.md > test.c &&
 | 
			
		||||
      CFLAGS='
 | 
			
		||||
  # make sure example can at least compile
 | 
			
		||||
  - 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 -Werror' make all size
 | 
			
		||||
        -include stdio.h"
 | 
			
		||||
 | 
			
		||||
    # run tests
 | 
			
		||||
    - make test QUIET=1
 | 
			
		||||
  # run tests
 | 
			
		||||
  - make test QUIET=1
 | 
			
		||||
 | 
			
		||||
    # run tests with a few different configurations
 | 
			
		||||
    - CFLAGS="-DLFS_READ_SIZE=1   -DLFS_PROG_SIZE=1"       make test QUIET=1
 | 
			
		||||
    - CFLAGS="-DLFS_READ_SIZE=512 -DLFS_PROG_SIZE=512"     make test QUIET=1
 | 
			
		||||
    - CFLAGS="-DLFS_BLOCK_COUNT=1023 -DLFS_LOOKAHEAD=2048" make test QUIET=1
 | 
			
		||||
  # run tests with a few different configurations
 | 
			
		||||
  - make test QUIET=1 CFLAGS+="-DLFS_READ_SIZE=1      -DLFS_PROG_SIZE=1"
 | 
			
		||||
  - make test QUIET=1 CFLAGS+="-DLFS_READ_SIZE=512    -DLFS_PROG_SIZE=512"
 | 
			
		||||
  - make test QUIET=1 CFLAGS+="-DLFS_BLOCK_COUNT=1023 -DLFS_LOOKAHEAD=2048"
 | 
			
		||||
 | 
			
		||||
  - make clean test QUIET=1 CFLAGS+="-DLFS_NO_INTRINSICS"
 | 
			
		||||
 | 
			
		||||
  # compile and find the code size with the smallest configuration
 | 
			
		||||
  - make clean size
 | 
			
		||||
        OBJ="$(ls lfs*.o | tr '\n' ' ')"
 | 
			
		||||
        CFLAGS+="-DLFS_NO{ASSERT,DEBUG,WARN,ERROR}"
 | 
			
		||||
        | tee sizes
 | 
			
		||||
 | 
			
		||||
  # update status if we succeeded, compare with master if possible
 | 
			
		||||
  - |
 | 
			
		||||
    if [ "$TRAVIS_TEST_RESULT" -eq 0 ]
 | 
			
		||||
    then
 | 
			
		||||
        CURR=$(tail -n1 sizes | awk '{print $1}')
 | 
			
		||||
        PREV=$(curl https://api.github.com/repos/$TRAVIS_REPO_SLUG/status/master \
 | 
			
		||||
            | jq -re "select(.sha != \"$TRAVIS_COMMIT\")
 | 
			
		||||
                | .statuses[] | select(.context == \"$STAGE/$NAME\").description
 | 
			
		||||
                | capture(\"code size is (?<size>[0-9]+)\").size" \
 | 
			
		||||
            || echo 0)
 | 
			
		||||
 | 
			
		||||
        STATUS="Passed, code size is ${CURR}B"
 | 
			
		||||
        if [ "$PREV" -ne 0 ]
 | 
			
		||||
        then
 | 
			
		||||
            STATUS="$STATUS ($(python -c "print '%+.2f' % (100*($CURR-$PREV)/$PREV.0)")%)"
 | 
			
		||||
        fi
 | 
			
		||||
    fi
 | 
			
		||||
 | 
			
		||||
# CI matrix
 | 
			
		||||
jobs:
 | 
			
		||||
  include:
 | 
			
		||||
    # native testing
 | 
			
		||||
    - stage: test
 | 
			
		||||
      env:
 | 
			
		||||
        - STAGE=test
 | 
			
		||||
        - NAME=littlefs-x86
 | 
			
		||||
 | 
			
		||||
    # 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 qemu-user
 | 
			
		||||
        - arm-linux-gnueabi-gcc --version
 | 
			
		||||
        - qemu-arm -version
 | 
			
		||||
 | 
			
		||||
    # 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 qemu-user
 | 
			
		||||
        - powerpc-linux-gnu-gcc --version
 | 
			
		||||
        - qemu-ppc -version
 | 
			
		||||
 | 
			
		||||
    # cross-compile with MIPS
 | 
			
		||||
    - stage: test
 | 
			
		||||
      env:
 | 
			
		||||
        - STAGE=test
 | 
			
		||||
        - NAME=littlefs-mips
 | 
			
		||||
        - CC="mips-linux-gnu-gcc --static"
 | 
			
		||||
        - EXEC="qemu-mips"
 | 
			
		||||
      install:
 | 
			
		||||
        - sudo add-apt-repository -y "deb http://archive.ubuntu.com/ubuntu/ xenial main universe"
 | 
			
		||||
        - sudo apt-get -qq update
 | 
			
		||||
        - sudo apt-get install gcc-mips-linux-gnu qemu-user
 | 
			
		||||
        - mips-linux-gnu-gcc --version
 | 
			
		||||
        - qemu-mips -version
 | 
			
		||||
 | 
			
		||||
    # self-host with littlefs-fuse for fuzz test
 | 
			
		||||
    - make -C littlefs-fuse
 | 
			
		||||
    - stage: test
 | 
			
		||||
      env:
 | 
			
		||||
        - STAGE=test
 | 
			
		||||
        - NAME=littlefs-fuse
 | 
			
		||||
      install:
 | 
			
		||||
        - sudo apt-get install libfuse-dev
 | 
			
		||||
        - git clone --depth 1 https://github.com/geky/littlefs-fuse
 | 
			
		||||
        - 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
 | 
			
		||||
 | 
			
		||||
    - littlefs-fuse/lfs --format /dev/loop0
 | 
			
		||||
    - littlefs-fuse/lfs /dev/loop0 mount
 | 
			
		||||
        - mkdir mount
 | 
			
		||||
        - sudo chmod a+rw /dev/loop0
 | 
			
		||||
        - dd if=/dev/zero bs=512 count=2048 of=disk
 | 
			
		||||
        - losetup /dev/loop0 disk
 | 
			
		||||
      script:
 | 
			
		||||
        # self-host test
 | 
			
		||||
        - make -C littlefs-fuse
 | 
			
		||||
 | 
			
		||||
    - ls mount
 | 
			
		||||
    - mkdir mount/littlefs
 | 
			
		||||
    - cp -r $(git ls-tree --name-only HEAD) mount/littlefs
 | 
			
		||||
    - cd mount/littlefs
 | 
			
		||||
    - ls
 | 
			
		||||
    - make -B test_dirs QUIET=1
 | 
			
		||||
        - 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
 | 
			
		||||
        - ls
 | 
			
		||||
        - make -B test_dirs test_files QUIET=1
 | 
			
		||||
 | 
			
		||||
      # Automatically update releases
 | 
			
		||||
    - stage: deploy
 | 
			
		||||
      env:
 | 
			
		||||
        - STAGE=deploy
 | 
			
		||||
        - NAME=deploy
 | 
			
		||||
      script:
 | 
			
		||||
        # Update tag for 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)))
 | 
			
		||||
        - LFS_VERSION="v$LFS_VERSION_MAJOR.$LFS_VERSION_MINOR"
 | 
			
		||||
        - echo "littlefs version $LFS_VERSION"
 | 
			
		||||
        - |
 | 
			
		||||
          curl -u $GEKY_BOT_RELEASES -X POST \
 | 
			
		||||
            https://api.github.com/repos/$TRAVIS_REPO_SLUG/git/refs \
 | 
			
		||||
            -d "{
 | 
			
		||||
              \"ref\": \"refs/tags/$LFS_VERSION\",
 | 
			
		||||
              \"sha\": \"$TRAVIS_COMMIT\"
 | 
			
		||||
            }"
 | 
			
		||||
        - |
 | 
			
		||||
          curl -f -u $GEKY_BOT_RELEASES -X PATCH \
 | 
			
		||||
            https://api.github.com/repos/$TRAVIS_REPO_SLUG/git/refs/tags/$LFS_VERSION \
 | 
			
		||||
            -d "{
 | 
			
		||||
              \"sha\": \"$TRAVIS_COMMIT\"
 | 
			
		||||
            }"
 | 
			
		||||
        # Create release notes from commits
 | 
			
		||||
        - LFS_PREV_VERSION="v$LFS_VERSION_MAJOR.$(($LFS_VERSION_MINOR-1))"
 | 
			
		||||
        - |
 | 
			
		||||
          if [ $(git tag -l "$LFS_PREV_VERSION") ]
 | 
			
		||||
          then
 | 
			
		||||
            curl -u $GEKY_BOT_RELEASES -X POST \
 | 
			
		||||
                https://api.github.com/repos/$TRAVIS_REPO_SLUG/releases \
 | 
			
		||||
                -d "{
 | 
			
		||||
                    \"tag_name\": \"$LFS_VERSION\",
 | 
			
		||||
                    \"name\": \"$LFS_VERSION\"
 | 
			
		||||
                }"
 | 
			
		||||
            RELEASE=$(
 | 
			
		||||
                curl -f https://api.github.com/repos/$TRAVIS_REPO_SLUG/releases/tags/$LFS_VERSION
 | 
			
		||||
            )
 | 
			
		||||
            CHANGES=$(
 | 
			
		||||
                git log --oneline $LFS_PREV_VERSION.. --grep='^Merge' --invert-grep
 | 
			
		||||
            )
 | 
			
		||||
            curl -f -u $GEKY_BOT_RELEASES -X PATCH \
 | 
			
		||||
                https://api.github.com/repos/$TRAVIS_REPO_SLUG/releases/$(
 | 
			
		||||
                    jq -r '.id' <<< "$RELEASE"
 | 
			
		||||
                ) \
 | 
			
		||||
                -d "$(
 | 
			
		||||
                    jq -s '{
 | 
			
		||||
                        "body": ((.[0] // "" | sub("(?<=\n)#+ Changes.*"; ""; "mi"))
 | 
			
		||||
                            + "### Changes\n\n" + .[1])
 | 
			
		||||
                    }' <(jq '.body' <<< "$RELEASE") <(jq -sR '.' <<< "$CHANGES")
 | 
			
		||||
                )"
 | 
			
		||||
          fi
 | 
			
		||||
 | 
			
		||||
# Manage statuses
 | 
			
		||||
before_install:
 | 
			
		||||
    - fusermount -V
 | 
			
		||||
    - gcc --version
 | 
			
		||||
  - |
 | 
			
		||||
    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\"
 | 
			
		||||
        }"
 | 
			
		||||
 | 
			
		||||
install:
 | 
			
		||||
    - sudo apt-get install libfuse-dev
 | 
			
		||||
    - git clone --depth 1 https://github.com/geky/littlefs-fuse
 | 
			
		||||
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\"
 | 
			
		||||
        }"
 | 
			
		||||
 | 
			
		||||
before_script:
 | 
			
		||||
    - rm -rf littlefs-fuse/littlefs/*
 | 
			
		||||
    - cp -r $(git ls-tree --name-only HEAD) littlefs-fuse/littlefs
 | 
			
		||||
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\"
 | 
			
		||||
        }"
 | 
			
		||||
 | 
			
		||||
    - mkdir mount
 | 
			
		||||
    - sudo chmod a+rw /dev/loop0
 | 
			
		||||
    - dd if=/dev/zero bs=512 count=2048 of=disk
 | 
			
		||||
    - losetup /dev/loop0 disk
 | 
			
		||||
# Job control
 | 
			
		||||
stages:
 | 
			
		||||
    - name: test
 | 
			
		||||
    - name: deploy
 | 
			
		||||
      if: branch = master
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -27,16 +27,17 @@ cheap and can be very granular. For NOR flash specifically, byte-level
 | 
			
		|||
programs are quite common. Erasing, however, requires an expensive operation
 | 
			
		||||
that forces the state of large blocks of memory to reset in a destructive
 | 
			
		||||
reaction that gives flash its name. The [Wikipedia entry](https://en.wikipedia.org/wiki/Flash_memory)
 | 
			
		||||
has more information if you are interesting in how this works.
 | 
			
		||||
has more information if you are interested in how this works.
 | 
			
		||||
 | 
			
		||||
This leaves us with an interesting set of limitations that can be simplified
 | 
			
		||||
to three strong requirements:
 | 
			
		||||
 | 
			
		||||
1. **Power-loss resilient** - This is the main goal of the littlefs and the
 | 
			
		||||
   focus of this project. Embedded systems are usually designed without a
 | 
			
		||||
   shutdown routine and a notable lack of user interface for recovery, so
 | 
			
		||||
   filesystems targeting embedded systems must be prepared to lose power at
 | 
			
		||||
   any given time.
 | 
			
		||||
   focus of this project.
 | 
			
		||||
 | 
			
		||||
   Embedded systems are usually designed without a shutdown routine and a
 | 
			
		||||
   notable lack of user interface for recovery, so filesystems targeting
 | 
			
		||||
   embedded systems must be prepared to lose power at any given time.
 | 
			
		||||
 | 
			
		||||
   Despite this state of things, there are very few embedded filesystems that
 | 
			
		||||
   handle power loss in a reasonable manner, and most can become corrupted if
 | 
			
		||||
| 
						 | 
				
			
			@ -52,7 +53,8 @@ to three strong requirements:
 | 
			
		|||
   which stores a file allocation table (FAT) at a specific offset from the
 | 
			
		||||
   beginning of disk. Every block allocation will update this table, and after
 | 
			
		||||
   100,000 updates, the block will likely go bad, rendering the filesystem
 | 
			
		||||
   unusable even if there are many more erase cycles available on the storage.
 | 
			
		||||
   unusable even if there are many more erase cycles available on the storage
 | 
			
		||||
   as a whole.
 | 
			
		||||
 | 
			
		||||
3. **Bounded RAM/ROM** - Even with the design difficulties presented by the
 | 
			
		||||
   previous two limitations, we have already seen several flash filesystems
 | 
			
		||||
| 
						 | 
				
			
			@ -72,7 +74,7 @@ to three strong requirements:
 | 
			
		|||
 | 
			
		||||
## Existing designs?
 | 
			
		||||
 | 
			
		||||
There are of course, many different existing filesystem. Heres a very rough
 | 
			
		||||
There are of course, many different existing filesystem. Here is a very rough
 | 
			
		||||
summary of the general ideas behind some of them.
 | 
			
		||||
 | 
			
		||||
Most of the existing filesystems fall into the one big category of filesystem
 | 
			
		||||
| 
						 | 
				
			
			@ -80,7 +82,7 @@ designed in the early days of spinny magnet disks. While there is a vast amount
 | 
			
		|||
of interesting technology and ideas in this area, the nature of spinny magnet
 | 
			
		||||
disks encourage properties, such as grouping writes near each other, that don't
 | 
			
		||||
make as much sense on recent storage types. For instance, on flash, write
 | 
			
		||||
locality is not important and can actually increase wear destructively.
 | 
			
		||||
locality is not important and can actually increase wear.
 | 
			
		||||
 | 
			
		||||
One of the most popular designs for flash filesystems is called the
 | 
			
		||||
[logging filesystem](https://en.wikipedia.org/wiki/Log-structured_file_system).
 | 
			
		||||
| 
						 | 
				
			
			@ -94,7 +96,7 @@ filesystem can easily be designed to be resilient to power loss. The
 | 
			
		|||
journaling component of most modern day filesystems is actually a reduced
 | 
			
		||||
form of a logging filesystem. However, logging filesystems have a difficulty
 | 
			
		||||
scaling as the size of storage increases. And most filesystems compensate by
 | 
			
		||||
caching large parts of the filesystem in RAM, a strategy that is unavailable
 | 
			
		||||
caching large parts of the filesystem in RAM, a strategy that is inappropriate
 | 
			
		||||
for embedded systems.
 | 
			
		||||
 | 
			
		||||
Another interesting filesystem design technique is that of [copy-on-write (COW)](https://en.wikipedia.org/wiki/Copy-on-write).
 | 
			
		||||
| 
						 | 
				
			
			@ -107,14 +109,14 @@ where the COW data structures are synchronized.
 | 
			
		|||
## Metadata pairs
 | 
			
		||||
 | 
			
		||||
The core piece of technology that provides the backbone for the littlefs is
 | 
			
		||||
the concept of metadata pairs. The key idea here, is that any metadata that
 | 
			
		||||
the concept of metadata pairs. The key idea here is that any metadata that
 | 
			
		||||
needs to be updated atomically is stored on a pair of blocks tagged with
 | 
			
		||||
a revision count and checksum. Every update alternates between these two
 | 
			
		||||
pairs, so that at any time there is always a backup containing the previous
 | 
			
		||||
state of the metadata.
 | 
			
		||||
 | 
			
		||||
Consider a small example where each metadata pair has a revision count,
 | 
			
		||||
a number as data and the xor of the block as a quick checksum. If
 | 
			
		||||
a number as data, and the XOR of the block as a quick checksum. If
 | 
			
		||||
we update the data to a value of 9, and then to a value of 5, here is
 | 
			
		||||
what the pair of blocks may look like after each update:
 | 
			
		||||
```
 | 
			
		||||
| 
						 | 
				
			
			@ -149,7 +151,7 @@ check our checksum, we notice that block 1 was corrupted. So we fall back to
 | 
			
		|||
block 2 and use the value 9.
 | 
			
		||||
 | 
			
		||||
Using this concept, the littlefs is able to update metadata blocks atomically.
 | 
			
		||||
There are a few other tweaks, such as using a 32 bit crc and using sequence
 | 
			
		||||
There are a few other tweaks, such as using a 32 bit CRC and using sequence
 | 
			
		||||
arithmetic to handle revision count overflow, but the basic concept
 | 
			
		||||
is the same. These metadata pairs define the backbone of the littlefs, and the
 | 
			
		||||
rest of the filesystem is built on top of these atomic updates.
 | 
			
		||||
| 
						 | 
				
			
			@ -184,7 +186,7 @@ Here is what updating a one-block file may look like:
 | 
			
		|||
            update data in file        update metadata pair
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
It doesn't matter if we lose power while writing block 5 with the new data
 | 
			
		||||
It doesn't matter if we lose power while writing new data to block 5,
 | 
			
		||||
because the old data remains unmodified in block 4. This example also
 | 
			
		||||
highlights how the atomic updates of the metadata blocks provide a
 | 
			
		||||
synchronization barrier for the rest of the littlefs.
 | 
			
		||||
| 
						 | 
				
			
			@ -206,10 +208,10 @@ files in filesystems. Of these, the littlefs uses a rather unique [COW](https://
 | 
			
		|||
data structure that allows the filesystem to reuse unmodified parts of the
 | 
			
		||||
file without additional metadata pairs.
 | 
			
		||||
 | 
			
		||||
First, let's consider storing files in a simple linked-list. What happens when
 | 
			
		||||
we append a block? We have to change the last block in the linked-list to point
 | 
			
		||||
to this new block, which means we have to copy out the last block and change
 | 
			
		||||
the second-to-last block and then the third-to-last and so on until we've
 | 
			
		||||
First lets consider storing files in a simple linked-list. What happens when we
 | 
			
		||||
append a block? We have to change the last block in the linked-list to point
 | 
			
		||||
to this new block, which means we have to copy out the last block, and change
 | 
			
		||||
the second-to-last block, and then the third-to-last, and so on until we've
 | 
			
		||||
copied out the entire file.
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
| 
						 | 
				
			
			@ -239,9 +241,9 @@ Exhibit B: A backwards linked-list
 | 
			
		|||
'--------'  '--------'  '--------'  '--------'  '--------'  '--------'
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
However, a backward linked-list does come with a rather glaring problem.
 | 
			
		||||
Iterating over a file _in order_ has a runtime of O(n^2). Gah! A quadratic
 | 
			
		||||
runtime to just _read_ a file? That's awful. Keep in mind reading files are
 | 
			
		||||
However, a backwards linked-list does come with a rather glaring problem.
 | 
			
		||||
Iterating over a file _in order_ has a runtime cost of O(n^2). Gah! A quadratic
 | 
			
		||||
runtime to just _read_ a file? That's awful. Keep in mind reading files is
 | 
			
		||||
usually the most common filesystem operation.
 | 
			
		||||
 | 
			
		||||
To avoid this problem, the littlefs uses a multilayered linked-list. For
 | 
			
		||||
| 
						 | 
				
			
			@ -266,7 +268,7 @@ Exhibit C: A backward CTZ skip-list
 | 
			
		|||
```
 | 
			
		||||
 | 
			
		||||
The additional pointers allow us to navigate the data-structure on disk
 | 
			
		||||
much more efficiently than in a single linked-list.
 | 
			
		||||
much more efficiently than in a singly linked-list.
 | 
			
		||||
 | 
			
		||||
Taking exhibit C, for example, here is the path from data block 5 to data
 | 
			
		||||
block 1. You can see how data block 3 was completely skipped:
 | 
			
		||||
| 
						 | 
				
			
			@ -289,15 +291,15 @@ The path to data block 0 is even quicker, requiring only two jumps:
 | 
			
		|||
 | 
			
		||||
We can find the runtime complexity by looking at the path to any block from
 | 
			
		||||
the block containing the most pointers. Every step along the path divides
 | 
			
		||||
the search space for the block in half. This gives us a runtime of O(logn).
 | 
			
		||||
the search space for the block in half. This gives us a runtime of O(log n).
 | 
			
		||||
To get to the block with the most pointers, we can perform the same steps
 | 
			
		||||
backward, which puts the runtime at O(2logn) = O(logn). The interesting
 | 
			
		||||
backwards, which puts the runtime at O(2 log n) = O(log n). The interesting
 | 
			
		||||
part about this data structure is that this optimal path occurs naturally
 | 
			
		||||
if we greedily choose the pointer that covers the most distance without passing
 | 
			
		||||
our target block.
 | 
			
		||||
 | 
			
		||||
So now we have a representation of files that can be appended trivially with
 | 
			
		||||
a runtime of O(1) and can be read with a worst case runtime of O(nlogn).
 | 
			
		||||
a runtime of O(1), and can be read with a worst case runtime of O(n log n).
 | 
			
		||||
Given that the the runtime is also divided by the amount of data we can store
 | 
			
		||||
in a block, this is pretty reasonable.
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -362,7 +364,7 @@ N = file size in bytes
 | 
			
		|||
 | 
			
		||||
And this works quite well but is not trivial to calculate. This equation
 | 
			
		||||
requires O(n) to compute, which brings the entire runtime of reading a file
 | 
			
		||||
to O(n^2logn). Fortunately, the additional O(n) does not need to touch disk,
 | 
			
		||||
to O(n^2 log n). Fortunately, the additional O(n) does not need to touch disk,
 | 
			
		||||
so it is not completely unreasonable. But if we could solve this equation into
 | 
			
		||||
a form that is easily computable, we can avoid a big slowdown.
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -379,8 +381,8 @@ unintuitive property:
 | 
			
		|||

 | 
			
		||||
 | 
			
		||||
where:  
 | 
			
		||||
ctz(i) = the number of trailing bits that are 0 in i  
 | 
			
		||||
popcount(i) = the number of bits that are 1 in i  
 | 
			
		||||
ctz(x) = the number of trailing bits that are 0 in x  
 | 
			
		||||
popcount(x) = the number of bits that are 1 in x  
 | 
			
		||||
 | 
			
		||||
It's a bit bewildering that these two seemingly unrelated bitwise instructions
 | 
			
		||||
are related by this property. But if we start to dissect this equation, we can
 | 
			
		||||
| 
						 | 
				
			
			@ -410,8 +412,7 @@ a bit to avoid integer overflow:
 | 
			
		|||

 | 
			
		||||
 | 
			
		||||
The solution involves quite a bit of math, but computers are very good at math.
 | 
			
		||||
We can now solve for the block index plus offset while only needed to store the
 | 
			
		||||
file size in O(1).
 | 
			
		||||
Now we can solve for both the block index and offset from the file size in O(1).
 | 
			
		||||
 | 
			
		||||
Here is what it might look like to update a file stored with a CTZ skip-list:
 | 
			
		||||
```
 | 
			
		||||
| 
						 | 
				
			
			@ -500,6 +501,7 @@ scanned to find the most recent free list, but once the list is found, the
 | 
			
		|||
state of all free blocks becomes known.
 | 
			
		||||
 | 
			
		||||
However, this approach had several issues:
 | 
			
		||||
 | 
			
		||||
- There was a lot of nuanced logic for adding blocks to the free list without
 | 
			
		||||
  modifying the blocks because the blocks remain active until the metadata is
 | 
			
		||||
  updated.
 | 
			
		||||
| 
						 | 
				
			
			@ -773,7 +775,7 @@ deorphan step that simply iterates through every directory in the linked-list
 | 
			
		|||
and checks it against every directory entry in the filesystem to see if it
 | 
			
		||||
has a parent. The deorphan step occurs on the first block allocation after
 | 
			
		||||
boot, so orphans should never cause the littlefs to run out of storage
 | 
			
		||||
prematurely. Note that the deorphan step never needs to run in a readonly
 | 
			
		||||
prematurely. Note that the deorphan step never needs to run in a read-only
 | 
			
		||||
filesystem.
 | 
			
		||||
 | 
			
		||||
## The move problem
 | 
			
		||||
| 
						 | 
				
			
			@ -883,7 +885,7 @@ a power loss will occur during filesystem activity. We still need to handle
 | 
			
		|||
the condition, but runtime during a power loss takes a back seat to the runtime
 | 
			
		||||
during normal operations.
 | 
			
		||||
 | 
			
		||||
So what littlefs does is unelegantly simple. When littlefs moves a file, it
 | 
			
		||||
So what littlefs does is inelegantly simple. When littlefs moves a file, it
 | 
			
		||||
marks the file as "moving". This is stored as a single bit in the directory
 | 
			
		||||
entry and doesn't take up much space. Then littlefs moves the directory,
 | 
			
		||||
finishing with the complete remove of the "moving" directory entry.
 | 
			
		||||
| 
						 | 
				
			
			@ -979,7 +981,7 @@ if it exists elsewhere in the filesystem.
 | 
			
		|||
So now that we have all of the pieces of a filesystem, we can look at a more
 | 
			
		||||
subtle attribute of embedded storage: The wear down of flash blocks.
 | 
			
		||||
 | 
			
		||||
The first concern for the littlefs is that prefectly valid blocks can suddenly
 | 
			
		||||
The first concern for the littlefs, is that perfectly valid blocks can suddenly
 | 
			
		||||
become unusable. As a nice side-effect of using a COW data-structure for files,
 | 
			
		||||
we can simply move on to a different block when a file write fails. All
 | 
			
		||||
modifications to files are performed in copies, so we will only replace the
 | 
			
		||||
| 
						 | 
				
			
			@ -1152,8 +1154,8 @@ develops errors and needs to be moved.
 | 
			
		|||
## Wear leveling
 | 
			
		||||
 | 
			
		||||
The second concern for the littlefs is that blocks in the filesystem may wear
 | 
			
		||||
unevenly. In this situation, a filesystem may meet an early demise whe,
 | 
			
		||||
there are no more noncorrupted blocks that aren't in use. It's common to
 | 
			
		||||
unevenly. In this situation, a filesystem may meet an early demise where
 | 
			
		||||
there are no more non-corrupted blocks that aren't in use. It's common to
 | 
			
		||||
have files that were written once and left unmodified, wasting the potential
 | 
			
		||||
erase cycles of the blocks it sits on.
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1171,7 +1173,7 @@ of wear leveling:
 | 
			
		|||
 | 
			
		||||
In littlefs's case, it's possible to use the revision count on metadata pairs
 | 
			
		||||
to approximate the wear of a metadata block. And combined with the COW nature
 | 
			
		||||
of files, littlefs could provide your usually implementation of dynamic wear
 | 
			
		||||
of files, littlefs could provide your usual implementation of dynamic wear
 | 
			
		||||
leveling.
 | 
			
		||||
 | 
			
		||||
However, the littlefs does not. This is for a few reasons. Most notably, even
 | 
			
		||||
| 
						 | 
				
			
			@ -1206,12 +1208,12 @@ So, to summarize:
 | 
			
		|||
1. The littlefs is composed of directory blocks.
 | 
			
		||||
2. Each directory is a linked-list of metadata pairs.
 | 
			
		||||
3. These metadata pairs can be updated atomically by alternating which
 | 
			
		||||
   metadata block is active.
 | 
			
		||||
4. Directory blocks contain either references to other directories or files.
 | 
			
		||||
5. Files are represented by copy-on-write CTZ skip-lists, which support O(1)
 | 
			
		||||
   append and O(nlogn) reading.
 | 
			
		||||
   metadata block is active
 | 
			
		||||
4. Directory blocks contain either references to other directories or files
 | 
			
		||||
5. Files are represented by copy-on-write CTZ skip-lists which support O(1)
 | 
			
		||||
   append and O(n log n) reading
 | 
			
		||||
6. Blocks are allocated by scanning the filesystem for used blocks in a
 | 
			
		||||
   fixed-size lookahead region is that stored in a bit-vector.
 | 
			
		||||
   fixed-size lookahead region that is stored in a bit-vector
 | 
			
		||||
7. To facilitate scanning the filesystem, all directories are part of a
 | 
			
		||||
   linked-list that is threaded through the entire filesystem.
 | 
			
		||||
8. If a block develops an error, the littlefs allocates a new block and
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,8 +1,8 @@
 | 
			
		|||
TARGET = lfs
 | 
			
		||||
 | 
			
		||||
CC = gcc
 | 
			
		||||
AR = ar
 | 
			
		||||
SIZE = size
 | 
			
		||||
CC ?= gcc
 | 
			
		||||
AR ?= ar
 | 
			
		||||
SIZE ?= size
 | 
			
		||||
 | 
			
		||||
SRC += $(wildcard *.c emubd/*.c)
 | 
			
		||||
OBJ := $(SRC:.c=.o)
 | 
			
		||||
| 
						 | 
				
			
			@ -14,15 +14,15 @@ TEST := $(patsubst tests/%.sh,%,$(wildcard tests/test_*))
 | 
			
		|||
SHELL = /bin/bash -o pipefail
 | 
			
		||||
 | 
			
		||||
ifdef DEBUG
 | 
			
		||||
CFLAGS += -O0 -g3
 | 
			
		||||
override CFLAGS += -O0 -g3
 | 
			
		||||
else
 | 
			
		||||
CFLAGS += -Os
 | 
			
		||||
override CFLAGS += -Os
 | 
			
		||||
endif
 | 
			
		||||
ifdef WORD
 | 
			
		||||
CFLAGS += -m$(WORD)
 | 
			
		||||
override CFLAGS += -m$(WORD)
 | 
			
		||||
endif
 | 
			
		||||
CFLAGS += -I.
 | 
			
		||||
CFLAGS += -std=c99 -Wall -pedantic
 | 
			
		||||
override CFLAGS += -I.
 | 
			
		||||
override CFLAGS += -std=c99 -Wall -pedantic
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
all: $(TARGET)
 | 
			
		||||
| 
						 | 
				
			
			@ -33,11 +33,11 @@ size: $(OBJ)
 | 
			
		|||
	$(SIZE) -t $^
 | 
			
		||||
 | 
			
		||||
.SUFFIXES:
 | 
			
		||||
test: test_format test_dirs test_files test_seek test_parallel \
 | 
			
		||||
test: test_format test_dirs test_files test_seek test_truncate test_parallel \
 | 
			
		||||
	test_alloc test_paths test_orphan test_move test_corrupt
 | 
			
		||||
test_%: tests/test_%.sh
 | 
			
		||||
ifdef QUIET
 | 
			
		||||
	./$< | sed -n '/^[-=]/p'
 | 
			
		||||
	@./$< | sed -n '/^[-=]/p'
 | 
			
		||||
else
 | 
			
		||||
	./$<
 | 
			
		||||
endif
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -16,7 +16,7 @@ of memory. Recursion is avoided, and dynamic memory is limited to configurable
 | 
			
		|||
buffers that can be provided statically.
 | 
			
		||||
 | 
			
		||||
**Power-loss resilient** - The littlefs is designed for systems that may have
 | 
			
		||||
random power failures. The littlefs has strong copy-on-write guaruntees, and
 | 
			
		||||
random power failures. The littlefs has strong copy-on-write guarantees, and
 | 
			
		||||
storage on disk is always kept in a valid state.
 | 
			
		||||
 | 
			
		||||
**Wear leveling** - Because the most common form of embedded storage is erodible
 | 
			
		||||
| 
						 | 
				
			
			@ -88,7 +88,7 @@ int main(void) {
 | 
			
		|||
## Usage
 | 
			
		||||
 | 
			
		||||
Detailed documentation (or at least as much detail as is currently available)
 | 
			
		||||
can be cound in the comments in [lfs.h](lfs.h).
 | 
			
		||||
can be found in the comments in [lfs.h](lfs.h).
 | 
			
		||||
 | 
			
		||||
As you may have noticed, littlefs takes in a configuration structure that
 | 
			
		||||
defines how the filesystem operates. The configuration struct provides the
 | 
			
		||||
| 
						 | 
				
			
			@ -105,7 +105,7 @@ Once mounted, the littlefs provides a full set of POSIX-like file and
 | 
			
		|||
directory functions, with the deviation that the allocation of filesystem
 | 
			
		||||
structures must be provided by the user.
 | 
			
		||||
 | 
			
		||||
All POSIX operations, such as remove and rename, are atomic, even in the event
 | 
			
		||||
All POSIX operations, such as remove and rename, are atomic, even in event
 | 
			
		||||
of power loss. Additionally, no file updates are actually committed to the
 | 
			
		||||
filesystem until sync or close is called on the file.
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -141,8 +141,14 @@ make test
 | 
			
		|||
 | 
			
		||||
## Related projects
 | 
			
		||||
 | 
			
		||||
[Mbed OS](https://github.com/ARMmbed/mbed-os/tree/master/features/filesystem/littlefs) -
 | 
			
		||||
The easiest way to get started with littlefs is to jump into [Mbed](https://os.mbed.com/),
 | 
			
		||||
which already has block device drivers for most forms of embedded storage. The
 | 
			
		||||
littlefs is available in Mbed OS as the [LittleFileSystem](https://os.mbed.com/docs/latest/reference/littlefilesystem.html)
 | 
			
		||||
class.
 | 
			
		||||
 | 
			
		||||
[littlefs-fuse](https://github.com/geky/littlefs-fuse) - A [FUSE](https://github.com/libfuse/libfuse)
 | 
			
		||||
wrapper for littlefs. The project allows you to mount littlefs directly in a
 | 
			
		||||
wrapper for littlefs. The project allows you to mount littlefs directly on a
 | 
			
		||||
Linux machine. Can be useful for debugging littlefs if you have an SD card
 | 
			
		||||
handy.
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -46,7 +46,7 @@ Here's the layout of metadata blocks on disk:
 | 
			
		|||
| 0x04   | 32 bits       | dir size       |
 | 
			
		||||
| 0x08   | 64 bits       | tail pointer   |
 | 
			
		||||
| 0x10   | size-16 bytes | dir entries    |
 | 
			
		||||
| 0x00+s | 32 bits       | crc            |
 | 
			
		||||
| 0x00+s | 32 bits       | CRC            |
 | 
			
		||||
 | 
			
		||||
**Revision count** - Incremented every update, only the uncorrupted
 | 
			
		||||
metadata-block with the most recent revision count contains the valid metadata.
 | 
			
		||||
| 
						 | 
				
			
			@ -75,7 +75,7 @@ Here's an example of a simple directory stored on disk:
 | 
			
		|||
(32 bits) revision count = 10                    (0x0000000a)
 | 
			
		||||
(32 bits) dir size       = 154 bytes, end of dir (0x0000009a)
 | 
			
		||||
(64 bits) tail pointer   = 37, 36                (0x00000025, 0x00000024)
 | 
			
		||||
(32 bits) crc            = 0xc86e3106
 | 
			
		||||
(32 bits) CRC            = 0xc86e3106
 | 
			
		||||
 | 
			
		||||
00000000: 0a 00 00 00 9a 00 00 00 25 00 00 00 24 00 00 00  ........%...$...
 | 
			
		||||
00000010: 22 08 00 03 05 00 00 00 04 00 00 00 74 65 61 22  "...........tea"
 | 
			
		||||
| 
						 | 
				
			
			@ -142,8 +142,8 @@ attributes are system specific, there is not much guarantee on the values in
 | 
			
		|||
this section, and systems are expected to work even when it is empty. See the
 | 
			
		||||
[attributes](#entry-attributes) section for more details.
 | 
			
		||||
 | 
			
		||||
**Name length** - Length of the entry name. Entry names are stored as utf8,
 | 
			
		||||
though most systems will probably only support ascii. Entry names can not
 | 
			
		||||
**Name length** - Length of the entry name. Entry names are stored as UTF8,
 | 
			
		||||
though most systems will probably only support ASCII. Entry names can not
 | 
			
		||||
contain '/' and can not be '.' or '..' because these are a part of the syntax of
 | 
			
		||||
filesystem paths.
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -222,7 +222,7 @@ Here's an example of a complete superblock:
 | 
			
		|||
(32 bits) block count      = 1024 blocks          (0x00000400)
 | 
			
		||||
(32 bits) version          = 1.1                  (0x00010001)
 | 
			
		||||
(8 bytes) magic string     = littlefs
 | 
			
		||||
(32 bits) crc              = 0xc50b74fa
 | 
			
		||||
(32 bits) CRC              = 0xc50b74fa
 | 
			
		||||
 | 
			
		||||
00000000: 03 00 00 00 34 00 00 00 03 00 00 00 02 00 00 00  ....4...........
 | 
			
		||||
00000010: 2e 14 00 08 03 00 00 00 02 00 00 00 00 02 00 00  ................
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -190,13 +190,13 @@ int lfs_emubd_erase(const struct lfs_config *cfg, lfs_block_t block) {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    if (!err && S_ISREG(st.st_mode) && (S_IWUSR & st.st_mode)) {
 | 
			
		||||
        int err = unlink(emu->path);
 | 
			
		||||
        err = unlink(emu->path);
 | 
			
		||||
        if (err) {
 | 
			
		||||
            return -errno;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (errno == ENOENT || (S_ISREG(st.st_mode) && (S_IWUSR & st.st_mode))) {
 | 
			
		||||
    if (err || (S_ISREG(st.st_mode) && (S_IWUSR & st.st_mode))) {
 | 
			
		||||
        FILE *f = fopen(emu->path, "w");
 | 
			
		||||
        if (!f) {
 | 
			
		||||
            return -errno;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -18,17 +18,13 @@
 | 
			
		|||
#include "lfs.h"
 | 
			
		||||
#include "lfs_util.h"
 | 
			
		||||
 | 
			
		||||
#include <string.h>
 | 
			
		||||
#include <stdlib.h>
 | 
			
		||||
#include <assert.h>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/// Caching block device operations ///
 | 
			
		||||
static int lfs_cache_read(lfs_t *lfs, lfs_cache_t *rcache,
 | 
			
		||||
        const lfs_cache_t *pcache, lfs_block_t block,
 | 
			
		||||
        lfs_off_t off, void *buffer, lfs_size_t size) {
 | 
			
		||||
    uint8_t *data = buffer;
 | 
			
		||||
    assert(block < lfs->cfg->block_count);
 | 
			
		||||
    LFS_ASSERT(block < lfs->cfg->block_count);
 | 
			
		||||
 | 
			
		||||
    while (size > 0) {
 | 
			
		||||
        if (pcache && block == pcache->block && off >= pcache->off &&
 | 
			
		||||
| 
						 | 
				
			
			@ -153,7 +149,7 @@ static int lfs_cache_prog(lfs_t *lfs, lfs_cache_t *pcache,
 | 
			
		|||
        lfs_cache_t *rcache, lfs_block_t block,
 | 
			
		||||
        lfs_off_t off, const void *buffer, lfs_size_t size) {
 | 
			
		||||
    const uint8_t *data = buffer;
 | 
			
		||||
    assert(block < lfs->cfg->block_count);
 | 
			
		||||
    LFS_ASSERT(block < lfs->cfg->block_count);
 | 
			
		||||
 | 
			
		||||
    while (size > 0) {
 | 
			
		||||
        if (block == pcache->block && off >= pcache->off &&
 | 
			
		||||
| 
						 | 
				
			
			@ -180,7 +176,7 @@ static int lfs_cache_prog(lfs_t *lfs, lfs_cache_t *pcache,
 | 
			
		|||
 | 
			
		||||
        // pcache must have been flushed, either by programming and
 | 
			
		||||
        // entire block or manually flushing the pcache
 | 
			
		||||
        assert(pcache->block == 0xffffffff);
 | 
			
		||||
        LFS_ASSERT(pcache->block == 0xffffffff);
 | 
			
		||||
 | 
			
		||||
        if (off % lfs->cfg->prog_size == 0 &&
 | 
			
		||||
                size >= lfs->cfg->prog_size) {
 | 
			
		||||
| 
						 | 
				
			
			@ -323,6 +319,48 @@ static void lfs_alloc_ack(lfs_t *lfs) {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/// Endian swapping functions ///
 | 
			
		||||
static void lfs_dir_fromle32(struct lfs_disk_dir *d) {
 | 
			
		||||
    d->rev     = lfs_fromle32(d->rev);
 | 
			
		||||
    d->size    = lfs_fromle32(d->size);
 | 
			
		||||
    d->tail[0] = lfs_fromle32(d->tail[0]);
 | 
			
		||||
    d->tail[1] = lfs_fromle32(d->tail[1]);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void lfs_dir_tole32(struct lfs_disk_dir *d) {
 | 
			
		||||
    d->rev     = lfs_tole32(d->rev);
 | 
			
		||||
    d->size    = lfs_tole32(d->size);
 | 
			
		||||
    d->tail[0] = lfs_tole32(d->tail[0]);
 | 
			
		||||
    d->tail[1] = lfs_tole32(d->tail[1]);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void lfs_entry_fromle32(struct lfs_disk_entry *d) {
 | 
			
		||||
    d->u.dir[0] = lfs_fromle32(d->u.dir[0]);
 | 
			
		||||
    d->u.dir[1] = lfs_fromle32(d->u.dir[1]);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void lfs_entry_tole32(struct lfs_disk_entry *d) {
 | 
			
		||||
    d->u.dir[0] = lfs_tole32(d->u.dir[0]);
 | 
			
		||||
    d->u.dir[1] = lfs_tole32(d->u.dir[1]);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void lfs_superblock_fromle32(struct lfs_disk_superblock *d) {
 | 
			
		||||
    d->root[0]     = lfs_fromle32(d->root[0]);
 | 
			
		||||
    d->root[1]     = lfs_fromle32(d->root[1]);
 | 
			
		||||
    d->block_size  = lfs_fromle32(d->block_size);
 | 
			
		||||
    d->block_count = lfs_fromle32(d->block_count);
 | 
			
		||||
    d->version     = lfs_fromle32(d->version);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void lfs_superblock_tole32(struct lfs_disk_superblock *d) {
 | 
			
		||||
    d->root[0]     = lfs_tole32(d->root[0]);
 | 
			
		||||
    d->root[1]     = lfs_tole32(d->root[1]);
 | 
			
		||||
    d->block_size  = lfs_tole32(d->block_size);
 | 
			
		||||
    d->block_count = lfs_tole32(d->block_count);
 | 
			
		||||
    d->version     = lfs_tole32(d->version);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/// Metadata pair and directory operations ///
 | 
			
		||||
static inline void lfs_pairswap(lfs_block_t pair[2]) {
 | 
			
		||||
    lfs_block_t t = pair[0];
 | 
			
		||||
| 
						 | 
				
			
			@ -364,6 +402,7 @@ static int lfs_dir_alloc(lfs_t *lfs, lfs_dir_t *dir) {
 | 
			
		|||
    // rather than clobbering one of the blocks we just pretend
 | 
			
		||||
    // the revision may be valid
 | 
			
		||||
    int err = lfs_bd_read(lfs, dir->pair[0], 0, &dir->d.rev, 4);
 | 
			
		||||
    dir->d.rev = lfs_fromle32(dir->d.rev);
 | 
			
		||||
    if (err) {
 | 
			
		||||
        return err;
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -389,6 +428,7 @@ static int lfs_dir_fetch(lfs_t *lfs,
 | 
			
		|||
    for (int i = 0; i < 2; i++) {
 | 
			
		||||
        struct lfs_disk_dir test;
 | 
			
		||||
        int err = lfs_bd_read(lfs, tpair[i], 0, &test, sizeof(test));
 | 
			
		||||
        lfs_dir_fromle32(&test);
 | 
			
		||||
        if (err) {
 | 
			
		||||
            return err;
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			@ -403,7 +443,9 @@ static int lfs_dir_fetch(lfs_t *lfs,
 | 
			
		|||
        }
 | 
			
		||||
 | 
			
		||||
        uint32_t crc = 0xffffffff;
 | 
			
		||||
        lfs_dir_tole32(&test);
 | 
			
		||||
        lfs_crc(&crc, &test, sizeof(test));
 | 
			
		||||
        lfs_dir_fromle32(&test);
 | 
			
		||||
        err = lfs_bd_crc(lfs, tpair[i], sizeof(test),
 | 
			
		||||
                (0x7fffffff & test.size) - sizeof(test), &crc);
 | 
			
		||||
        if (err) {
 | 
			
		||||
| 
						 | 
				
			
			@ -463,8 +505,10 @@ static int lfs_dir_commit(lfs_t *lfs, lfs_dir_t *dir,
 | 
			
		|||
            }
 | 
			
		||||
 | 
			
		||||
            uint32_t crc = 0xffffffff;
 | 
			
		||||
            lfs_dir_tole32(&dir->d);
 | 
			
		||||
            lfs_crc(&crc, &dir->d, sizeof(dir->d));
 | 
			
		||||
            err = lfs_bd_prog(lfs, dir->pair[0], 0, &dir->d, sizeof(dir->d));
 | 
			
		||||
            lfs_dir_fromle32(&dir->d);
 | 
			
		||||
            if (err) {
 | 
			
		||||
                if (err == LFS_ERR_CORRUPT) {
 | 
			
		||||
                    goto relocate;
 | 
			
		||||
| 
						 | 
				
			
			@ -478,7 +522,7 @@ static int lfs_dir_commit(lfs_t *lfs, lfs_dir_t *dir,
 | 
			
		|||
            while (newoff < (0x7fffffff & dir->d.size)-4) {
 | 
			
		||||
                if (i < count && regions[i].oldoff == oldoff) {
 | 
			
		||||
                    lfs_crc(&crc, regions[i].newdata, regions[i].newlen);
 | 
			
		||||
                    int err = lfs_bd_prog(lfs, dir->pair[0],
 | 
			
		||||
                    err = lfs_bd_prog(lfs, dir->pair[0],
 | 
			
		||||
                            newoff, regions[i].newdata, regions[i].newlen);
 | 
			
		||||
                    if (err) {
 | 
			
		||||
                        if (err == LFS_ERR_CORRUPT) {
 | 
			
		||||
| 
						 | 
				
			
			@ -492,7 +536,7 @@ static int lfs_dir_commit(lfs_t *lfs, lfs_dir_t *dir,
 | 
			
		|||
                    i += 1;
 | 
			
		||||
                } else {
 | 
			
		||||
                    uint8_t data;
 | 
			
		||||
                    int err = lfs_bd_read(lfs, oldpair[1], oldoff, &data, 1);
 | 
			
		||||
                    err = lfs_bd_read(lfs, oldpair[1], oldoff, &data, 1);
 | 
			
		||||
                    if (err) {
 | 
			
		||||
                        return err;
 | 
			
		||||
                    }
 | 
			
		||||
| 
						 | 
				
			
			@ -511,7 +555,9 @@ static int lfs_dir_commit(lfs_t *lfs, lfs_dir_t *dir,
 | 
			
		|||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            crc = lfs_tole32(crc);
 | 
			
		||||
            err = lfs_bd_prog(lfs, dir->pair[0], newoff, &crc, 4);
 | 
			
		||||
            crc = lfs_fromle32(crc);
 | 
			
		||||
            if (err) {
 | 
			
		||||
                if (err == LFS_ERR_CORRUPT) {
 | 
			
		||||
                    goto relocate;
 | 
			
		||||
| 
						 | 
				
			
			@ -584,11 +630,14 @@ relocate:
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
static int lfs_dir_update(lfs_t *lfs, lfs_dir_t *dir,
 | 
			
		||||
        const lfs_entry_t *entry, const void *data) {
 | 
			
		||||
    return lfs_dir_commit(lfs, dir, (struct lfs_region[]){
 | 
			
		||||
        lfs_entry_t *entry, const void *data) {
 | 
			
		||||
    lfs_entry_tole32(&entry->d);
 | 
			
		||||
    int err = lfs_dir_commit(lfs, dir, (struct lfs_region[]){
 | 
			
		||||
            {entry->off, sizeof(entry->d), &entry->d, sizeof(entry->d)},
 | 
			
		||||
            {entry->off+sizeof(entry->d), entry->d.nlen, data, entry->d.nlen}
 | 
			
		||||
        }, data ? 2 : 1);
 | 
			
		||||
    lfs_entry_fromle32(&entry->d);
 | 
			
		||||
    return err;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int lfs_dir_append(lfs_t *lfs, lfs_dir_t *dir,
 | 
			
		||||
| 
						 | 
				
			
			@ -597,10 +646,14 @@ static int lfs_dir_append(lfs_t *lfs, lfs_dir_t *dir,
 | 
			
		|||
    while (true) {
 | 
			
		||||
        if (dir->d.size + lfs_entry_size(entry) <= lfs->cfg->block_size) {
 | 
			
		||||
            entry->off = dir->d.size - 4;
 | 
			
		||||
            return lfs_dir_commit(lfs, dir, (struct lfs_region[]){
 | 
			
		||||
 | 
			
		||||
            lfs_entry_tole32(&entry->d);
 | 
			
		||||
            int err = lfs_dir_commit(lfs, dir, (struct lfs_region[]){
 | 
			
		||||
                    {entry->off, 0, &entry->d, sizeof(entry->d)},
 | 
			
		||||
                    {entry->off, 0, data, entry->d.nlen}
 | 
			
		||||
                }, 2);
 | 
			
		||||
            lfs_entry_fromle32(&entry->d);
 | 
			
		||||
            return err;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // we need to allocate a new dir block
 | 
			
		||||
| 
						 | 
				
			
			@ -614,10 +667,12 @@ static int lfs_dir_append(lfs_t *lfs, lfs_dir_t *dir,
 | 
			
		|||
            newdir.d.tail[0] = dir->d.tail[0];
 | 
			
		||||
            newdir.d.tail[1] = dir->d.tail[1];
 | 
			
		||||
            entry->off = newdir.d.size - 4;
 | 
			
		||||
            lfs_entry_tole32(&entry->d);
 | 
			
		||||
            err = lfs_dir_commit(lfs, &newdir, (struct lfs_region[]){
 | 
			
		||||
                    {entry->off, 0, &entry->d, sizeof(entry->d)},
 | 
			
		||||
                    {entry->off, 0, data, entry->d.nlen}
 | 
			
		||||
                }, 2);
 | 
			
		||||
            lfs_entry_fromle32(&entry->d);
 | 
			
		||||
            if (err) {
 | 
			
		||||
                return err;
 | 
			
		||||
            }
 | 
			
		||||
| 
						 | 
				
			
			@ -703,6 +758,7 @@ static int lfs_dir_next(lfs_t *lfs, lfs_dir_t *dir, lfs_entry_t *entry) {
 | 
			
		|||
 | 
			
		||||
    int err = lfs_bd_read(lfs, dir->pair[0], dir->off,
 | 
			
		||||
            &entry->d, sizeof(entry->d));
 | 
			
		||||
    lfs_entry_fromle32(&entry->d);
 | 
			
		||||
    if (err) {
 | 
			
		||||
        return err;
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -1006,7 +1062,7 @@ int lfs_dir_seek(lfs_t *lfs, lfs_dir_t *dir, lfs_off_t off) {
 | 
			
		|||
            return LFS_ERR_INVAL;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        int err = lfs_dir_fetch(lfs, dir, dir->d.tail);
 | 
			
		||||
        err = lfs_dir_fetch(lfs, dir, dir->d.tail);
 | 
			
		||||
        if (err) {
 | 
			
		||||
            return err;
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			@ -1017,6 +1073,7 @@ int lfs_dir_seek(lfs_t *lfs, lfs_dir_t *dir, lfs_off_t off) {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
lfs_soff_t lfs_dir_tell(lfs_t *lfs, lfs_dir_t *dir) {
 | 
			
		||||
    (void)lfs;
 | 
			
		||||
    return dir->pos;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1068,11 +1125,12 @@ static int lfs_ctz_find(lfs_t *lfs,
 | 
			
		|||
                lfs_ctz(current));
 | 
			
		||||
 | 
			
		||||
        int err = lfs_cache_read(lfs, rcache, pcache, head, 4*skip, &head, 4);
 | 
			
		||||
        head = lfs_fromle32(head);
 | 
			
		||||
        if (err) {
 | 
			
		||||
            return err;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        assert(head >= 2 && head <= lfs->cfg->block_count);
 | 
			
		||||
        LFS_ASSERT(head >= 2 && head <= lfs->cfg->block_count);
 | 
			
		||||
        current -= 1 << skip;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1092,7 +1150,7 @@ static int lfs_ctz_extend(lfs_t *lfs,
 | 
			
		|||
        if (err) {
 | 
			
		||||
            return err;
 | 
			
		||||
        }
 | 
			
		||||
        assert(nblock >= 2 && nblock <= lfs->cfg->block_count);
 | 
			
		||||
        LFS_ASSERT(nblock >= 2 && nblock <= lfs->cfg->block_count);
 | 
			
		||||
 | 
			
		||||
        if (true) {
 | 
			
		||||
            err = lfs_bd_erase(lfs, nblock);
 | 
			
		||||
| 
						 | 
				
			
			@ -1117,7 +1175,7 @@ static int lfs_ctz_extend(lfs_t *lfs,
 | 
			
		|||
            if (size != lfs->cfg->block_size) {
 | 
			
		||||
                for (lfs_off_t i = 0; i < size; i++) {
 | 
			
		||||
                    uint8_t data;
 | 
			
		||||
                    int err = lfs_cache_read(lfs, rcache, NULL,
 | 
			
		||||
                    err = lfs_cache_read(lfs, rcache, NULL,
 | 
			
		||||
                            head, i, &data, 1);
 | 
			
		||||
                    if (err) {
 | 
			
		||||
                        return err;
 | 
			
		||||
| 
						 | 
				
			
			@ -1143,8 +1201,10 @@ static int lfs_ctz_extend(lfs_t *lfs,
 | 
			
		|||
            lfs_size_t skips = lfs_ctz(index) + 1;
 | 
			
		||||
 | 
			
		||||
            for (lfs_off_t i = 0; i < skips; i++) {
 | 
			
		||||
                int err = lfs_cache_prog(lfs, pcache, rcache,
 | 
			
		||||
                head = lfs_tole32(head);
 | 
			
		||||
                err = lfs_cache_prog(lfs, pcache, rcache,
 | 
			
		||||
                        nblock, 4*i, &head, 4);
 | 
			
		||||
                head = lfs_fromle32(head);
 | 
			
		||||
                if (err) {
 | 
			
		||||
                    if (err == LFS_ERR_CORRUPT) {
 | 
			
		||||
                        goto relocate;
 | 
			
		||||
| 
						 | 
				
			
			@ -1155,12 +1215,13 @@ static int lfs_ctz_extend(lfs_t *lfs,
 | 
			
		|||
                if (i != skips-1) {
 | 
			
		||||
                    err = lfs_cache_read(lfs, rcache, NULL,
 | 
			
		||||
                            head, 4*i, &head, 4);
 | 
			
		||||
                    head = lfs_fromle32(head);
 | 
			
		||||
                    if (err) {
 | 
			
		||||
                        return err;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                assert(head >= 2 && head <= lfs->cfg->block_count);
 | 
			
		||||
                LFS_ASSERT(head >= 2 && head <= lfs->cfg->block_count);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            *block = nblock;
 | 
			
		||||
| 
						 | 
				
			
			@ -1196,12 +1257,24 @@ static int lfs_ctz_traverse(lfs_t *lfs,
 | 
			
		|||
            return 0;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        err = lfs_cache_read(lfs, rcache, pcache, head, 0, &head, 4);
 | 
			
		||||
        lfs_block_t heads[2];
 | 
			
		||||
        int count = 2 - (index & 1);
 | 
			
		||||
        err = lfs_cache_read(lfs, rcache, pcache, head, 0, &heads, count*4);
 | 
			
		||||
        heads[0] = lfs_fromle32(heads[0]);
 | 
			
		||||
        heads[1] = lfs_fromle32(heads[1]);
 | 
			
		||||
        if (err) {
 | 
			
		||||
            return err;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        index -= 1;
 | 
			
		||||
        for (int i = 0; i < count-1; i++) {
 | 
			
		||||
            err = cb(data, heads[i]);
 | 
			
		||||
            if (err) {
 | 
			
		||||
                return err;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        head = heads[count-1];
 | 
			
		||||
        index -= count;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1274,12 +1347,12 @@ int lfs_file_open(lfs_t *lfs, lfs_file_t *file,
 | 
			
		|||
    if (lfs->cfg->file_buffer) {
 | 
			
		||||
        file->cache.buffer = lfs->cfg->file_buffer;
 | 
			
		||||
    } else if ((file->flags & 3) == LFS_O_RDONLY) {
 | 
			
		||||
        file->cache.buffer = malloc(lfs->cfg->read_size);
 | 
			
		||||
        file->cache.buffer = lfs_malloc(lfs->cfg->read_size);
 | 
			
		||||
        if (!file->cache.buffer) {
 | 
			
		||||
            return LFS_ERR_NOMEM;
 | 
			
		||||
        }
 | 
			
		||||
    } else {
 | 
			
		||||
        file->cache.buffer = malloc(lfs->cfg->prog_size);
 | 
			
		||||
        file->cache.buffer = lfs_malloc(lfs->cfg->prog_size);
 | 
			
		||||
        if (!file->cache.buffer) {
 | 
			
		||||
            return LFS_ERR_NOMEM;
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			@ -1305,7 +1378,7 @@ int lfs_file_close(lfs_t *lfs, lfs_file_t *file) {
 | 
			
		|||
 | 
			
		||||
    // clean up memory
 | 
			
		||||
    if (!lfs->cfg->file_buffer) {
 | 
			
		||||
        free(file->cache.buffer);
 | 
			
		||||
        lfs_free(file->cache.buffer);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return err;
 | 
			
		||||
| 
						 | 
				
			
			@ -1441,7 +1514,7 @@ int lfs_file_sync(lfs_t *lfs, lfs_file_t *file) {
 | 
			
		|||
            !lfs_pairisnull(file->pair)) {
 | 
			
		||||
        // update dir entry
 | 
			
		||||
        lfs_dir_t cwd;
 | 
			
		||||
        int err = lfs_dir_fetch(lfs, &cwd, file->pair);
 | 
			
		||||
        err = lfs_dir_fetch(lfs, &cwd, file->pair);
 | 
			
		||||
        if (err) {
 | 
			
		||||
            return err;
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			@ -1449,15 +1522,12 @@ int lfs_file_sync(lfs_t *lfs, lfs_file_t *file) {
 | 
			
		|||
        lfs_entry_t entry = {.off = file->poff};
 | 
			
		||||
        err = lfs_bd_read(lfs, cwd.pair[0], entry.off,
 | 
			
		||||
                &entry.d, sizeof(entry.d));
 | 
			
		||||
        lfs_entry_fromle32(&entry.d);
 | 
			
		||||
        if (err) {
 | 
			
		||||
            return err;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (entry.d.type != LFS_TYPE_REG) {
 | 
			
		||||
            // sanity check valid entry
 | 
			
		||||
            return LFS_ERR_INVAL;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        LFS_ASSERT(entry.d.type == LFS_TYPE_REG);
 | 
			
		||||
        entry.d.u.file.head = file->head;
 | 
			
		||||
        entry.d.u.file.size = file->size;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1478,7 +1548,7 @@ lfs_ssize_t lfs_file_read(lfs_t *lfs, lfs_file_t *file,
 | 
			
		|||
    lfs_size_t nsize = size;
 | 
			
		||||
 | 
			
		||||
    if ((file->flags & 3) == LFS_O_WRONLY) {
 | 
			
		||||
        return LFS_ERR_INVAL;
 | 
			
		||||
        return LFS_ERR_BADF;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (file->flags & LFS_F_WRITING) {
 | 
			
		||||
| 
						 | 
				
			
			@ -1534,7 +1604,7 @@ lfs_ssize_t lfs_file_write(lfs_t *lfs, lfs_file_t *file,
 | 
			
		|||
    lfs_size_t nsize = size;
 | 
			
		||||
 | 
			
		||||
    if ((file->flags & 3) == LFS_O_RDONLY) {
 | 
			
		||||
        return LFS_ERR_INVAL;
 | 
			
		||||
        return LFS_ERR_BADF;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (file->flags & LFS_F_READING) {
 | 
			
		||||
| 
						 | 
				
			
			@ -1655,7 +1725,60 @@ lfs_soff_t lfs_file_seek(lfs_t *lfs, lfs_file_t *file,
 | 
			
		|||
    return file->pos;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int lfs_file_truncate(lfs_t *lfs, lfs_file_t *file, lfs_off_t size) {
 | 
			
		||||
    if ((file->flags & 3) == LFS_O_RDONLY) {
 | 
			
		||||
        return LFS_ERR_BADF;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    lfs_off_t oldsize = lfs_file_size(lfs, file);
 | 
			
		||||
    if (size < oldsize) {
 | 
			
		||||
        // need to flush since directly changing metadata
 | 
			
		||||
        int err = lfs_file_flush(lfs, file);
 | 
			
		||||
        if (err) {
 | 
			
		||||
            return err;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // lookup new head in ctz skip list
 | 
			
		||||
        err = lfs_ctz_find(lfs, &file->cache, NULL,
 | 
			
		||||
                file->head, file->size,
 | 
			
		||||
                size, &file->head, &(lfs_off_t){0});
 | 
			
		||||
        if (err) {
 | 
			
		||||
            return err;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        file->size = size;
 | 
			
		||||
        file->flags |= LFS_F_DIRTY;
 | 
			
		||||
    } else if (size > oldsize) {
 | 
			
		||||
        lfs_off_t pos = file->pos;
 | 
			
		||||
 | 
			
		||||
        // flush+seek if not already at end
 | 
			
		||||
        if (file->pos != oldsize) {
 | 
			
		||||
            int err = lfs_file_seek(lfs, file, 0, LFS_SEEK_END);
 | 
			
		||||
            if (err < 0) {
 | 
			
		||||
                return err;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // fill with zeros
 | 
			
		||||
        while (file->pos < size) {
 | 
			
		||||
            lfs_ssize_t res = lfs_file_write(lfs, file, &(uint8_t){0}, 1);
 | 
			
		||||
            if (res < 0) {
 | 
			
		||||
                return res;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // restore pos
 | 
			
		||||
        int err = lfs_file_seek(lfs, file, pos, LFS_SEEK_SET);
 | 
			
		||||
        if (err < 0) {
 | 
			
		||||
            return err;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
lfs_soff_t lfs_file_tell(lfs_t *lfs, lfs_file_t *file) {
 | 
			
		||||
    (void)lfs;
 | 
			
		||||
    return file->pos;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1669,11 +1792,16 @@ int lfs_file_rewind(lfs_t *lfs, lfs_file_t *file) {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
lfs_soff_t lfs_file_size(lfs_t *lfs, lfs_file_t *file) {
 | 
			
		||||
    return lfs_max(file->pos, file->size);
 | 
			
		||||
    (void)lfs;
 | 
			
		||||
    if (file->flags & LFS_F_WRITING) {
 | 
			
		||||
        return lfs_max(file->pos, file->size);
 | 
			
		||||
    } else {
 | 
			
		||||
        return file->size;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/// General fs oprations ///
 | 
			
		||||
/// General fs operations ///
 | 
			
		||||
int lfs_stat(lfs_t *lfs, const char *path, struct lfs_info *info) {
 | 
			
		||||
    lfs_dir_t cwd;
 | 
			
		||||
    int err = lfs_dir_fetch(lfs, &cwd, lfs->root);
 | 
			
		||||
| 
						 | 
				
			
			@ -1733,11 +1861,11 @@ int lfs_remove(lfs_t *lfs, const char *path) {
 | 
			
		|||
        // must be empty before removal, checking size
 | 
			
		||||
        // without masking top bit checks for any case where
 | 
			
		||||
        // dir is not empty
 | 
			
		||||
        int err = lfs_dir_fetch(lfs, &dir, entry.d.u.dir);
 | 
			
		||||
        err = lfs_dir_fetch(lfs, &dir, entry.d.u.dir);
 | 
			
		||||
        if (err) {
 | 
			
		||||
            return err;
 | 
			
		||||
        } else if (dir.d.size != sizeof(dir.d)+4) {
 | 
			
		||||
            return LFS_ERR_INVAL;
 | 
			
		||||
            return LFS_ERR_NOTEMPTY;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1754,11 +1882,11 @@ int lfs_remove(lfs_t *lfs, const char *path) {
 | 
			
		|||
            return res;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        assert(res); // must have pred
 | 
			
		||||
        LFS_ASSERT(res); // must have pred
 | 
			
		||||
        cwd.d.tail[0] = dir.d.tail[0];
 | 
			
		||||
        cwd.d.tail[1] = dir.d.tail[1];
 | 
			
		||||
 | 
			
		||||
        int err = lfs_dir_commit(lfs, &cwd, NULL, 0);
 | 
			
		||||
        err = lfs_dir_commit(lfs, &cwd, NULL, 0);
 | 
			
		||||
        if (err) {
 | 
			
		||||
            return err;
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			@ -1807,7 +1935,7 @@ int lfs_rename(lfs_t *lfs, const char *oldpath, const char *newpath) {
 | 
			
		|||
 | 
			
		||||
    // must have same type
 | 
			
		||||
    if (prevexists && preventry.d.type != oldentry.d.type) {
 | 
			
		||||
        return LFS_ERR_INVAL;
 | 
			
		||||
        return LFS_ERR_ISDIR;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    lfs_dir_t dir;
 | 
			
		||||
| 
						 | 
				
			
			@ -1815,11 +1943,11 @@ int lfs_rename(lfs_t *lfs, const char *oldpath, const char *newpath) {
 | 
			
		|||
        // must be empty before removal, checking size
 | 
			
		||||
        // without masking top bit checks for any case where
 | 
			
		||||
        // dir is not empty
 | 
			
		||||
        int err = lfs_dir_fetch(lfs, &dir, preventry.d.u.dir);
 | 
			
		||||
        err = lfs_dir_fetch(lfs, &dir, preventry.d.u.dir);
 | 
			
		||||
        if (err) {
 | 
			
		||||
            return err;
 | 
			
		||||
        } else if (dir.d.size != sizeof(dir.d)+4) {
 | 
			
		||||
            return LFS_ERR_INVAL;
 | 
			
		||||
            return LFS_ERR_NOTEMPTY;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1842,12 +1970,12 @@ int lfs_rename(lfs_t *lfs, const char *oldpath, const char *newpath) {
 | 
			
		|||
    newentry.d.nlen = strlen(newpath);
 | 
			
		||||
 | 
			
		||||
    if (prevexists) {
 | 
			
		||||
        int err = lfs_dir_update(lfs, &newcwd, &newentry, newpath);
 | 
			
		||||
        err = lfs_dir_update(lfs, &newcwd, &newentry, newpath);
 | 
			
		||||
        if (err) {
 | 
			
		||||
            return err;
 | 
			
		||||
        }
 | 
			
		||||
    } else {
 | 
			
		||||
        int err = lfs_dir_append(lfs, &newcwd, &newentry, newpath);
 | 
			
		||||
        err = lfs_dir_append(lfs, &newcwd, &newentry, newpath);
 | 
			
		||||
        if (err) {
 | 
			
		||||
            return err;
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			@ -1871,11 +1999,11 @@ int lfs_rename(lfs_t *lfs, const char *oldpath, const char *newpath) {
 | 
			
		|||
            return res;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        assert(res); // must have pred
 | 
			
		||||
        LFS_ASSERT(res); // must have pred
 | 
			
		||||
        newcwd.d.tail[0] = dir.d.tail[0];
 | 
			
		||||
        newcwd.d.tail[1] = dir.d.tail[1];
 | 
			
		||||
 | 
			
		||||
        int err = lfs_dir_commit(lfs, &newcwd, NULL, 0);
 | 
			
		||||
        err = lfs_dir_commit(lfs, &newcwd, NULL, 0);
 | 
			
		||||
        if (err) {
 | 
			
		||||
            return err;
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			@ -1894,7 +2022,7 @@ static int lfs_init(lfs_t *lfs, const struct lfs_config *cfg) {
 | 
			
		|||
    if (lfs->cfg->read_buffer) {
 | 
			
		||||
        lfs->rcache.buffer = lfs->cfg->read_buffer;
 | 
			
		||||
    } else {
 | 
			
		||||
        lfs->rcache.buffer = malloc(lfs->cfg->read_size);
 | 
			
		||||
        lfs->rcache.buffer = lfs_malloc(lfs->cfg->read_size);
 | 
			
		||||
        if (!lfs->rcache.buffer) {
 | 
			
		||||
            return LFS_ERR_NOMEM;
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			@ -1905,26 +2033,30 @@ static int lfs_init(lfs_t *lfs, const struct lfs_config *cfg) {
 | 
			
		|||
    if (lfs->cfg->prog_buffer) {
 | 
			
		||||
        lfs->pcache.buffer = lfs->cfg->prog_buffer;
 | 
			
		||||
    } else {
 | 
			
		||||
        lfs->pcache.buffer = malloc(lfs->cfg->prog_size);
 | 
			
		||||
        lfs->pcache.buffer = lfs_malloc(lfs->cfg->prog_size);
 | 
			
		||||
        if (!lfs->pcache.buffer) {
 | 
			
		||||
            return LFS_ERR_NOMEM;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // setup lookahead, round down to nearest 32-bits
 | 
			
		||||
    assert(lfs->cfg->lookahead % 32 == 0);
 | 
			
		||||
    assert(lfs->cfg->lookahead > 0);
 | 
			
		||||
    LFS_ASSERT(lfs->cfg->lookahead % 32 == 0);
 | 
			
		||||
    LFS_ASSERT(lfs->cfg->lookahead > 0);
 | 
			
		||||
    if (lfs->cfg->lookahead_buffer) {
 | 
			
		||||
        lfs->free.buffer = lfs->cfg->lookahead_buffer;
 | 
			
		||||
    } else {
 | 
			
		||||
        lfs->free.buffer = malloc(lfs->cfg->lookahead/8);
 | 
			
		||||
        lfs->free.buffer = lfs_malloc(lfs->cfg->lookahead/8);
 | 
			
		||||
        if (!lfs->free.buffer) {
 | 
			
		||||
            return LFS_ERR_NOMEM;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // check that program and read sizes are multiples of the block size
 | 
			
		||||
    LFS_ASSERT(lfs->cfg->prog_size % lfs->cfg->read_size == 0);
 | 
			
		||||
    LFS_ASSERT(lfs->cfg->block_size % lfs->cfg->prog_size == 0);
 | 
			
		||||
 | 
			
		||||
    // check that the block size is large enough to fit ctz pointers
 | 
			
		||||
    assert(4*lfs_npw2(0xffffffff / (lfs->cfg->block_size-2*4))
 | 
			
		||||
    LFS_ASSERT(4*lfs_npw2(0xffffffff / (lfs->cfg->block_size-2*4))
 | 
			
		||||
            <= lfs->cfg->block_size);
 | 
			
		||||
 | 
			
		||||
    // setup default state
 | 
			
		||||
| 
						 | 
				
			
			@ -1940,15 +2072,15 @@ static int lfs_init(lfs_t *lfs, const struct lfs_config *cfg) {
 | 
			
		|||
static int lfs_deinit(lfs_t *lfs) {
 | 
			
		||||
    // free allocated memory
 | 
			
		||||
    if (!lfs->cfg->read_buffer) {
 | 
			
		||||
        free(lfs->rcache.buffer);
 | 
			
		||||
        lfs_free(lfs->rcache.buffer);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!lfs->cfg->prog_buffer) {
 | 
			
		||||
        free(lfs->pcache.buffer);
 | 
			
		||||
        lfs_free(lfs->pcache.buffer);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!lfs->cfg->lookahead_buffer) {
 | 
			
		||||
        free(lfs->free.buffer);
 | 
			
		||||
        lfs_free(lfs->free.buffer);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return 0;
 | 
			
		||||
| 
						 | 
				
			
			@ -1995,7 +2127,7 @@ int lfs_format(lfs_t *lfs, const struct lfs_config *cfg) {
 | 
			
		|||
        .d.type = LFS_TYPE_SUPERBLOCK,
 | 
			
		||||
        .d.elen = sizeof(superblock.d) - sizeof(superblock.d.magic) - 4,
 | 
			
		||||
        .d.nlen = sizeof(superblock.d.magic),
 | 
			
		||||
        .d.version = 0x00010001,
 | 
			
		||||
        .d.version = LFS_DISK_VERSION,
 | 
			
		||||
        .d.magic = {"littlefs"},
 | 
			
		||||
        .d.block_size  = lfs->cfg->block_size,
 | 
			
		||||
        .d.block_count = lfs->cfg->block_count,
 | 
			
		||||
| 
						 | 
				
			
			@ -2006,9 +2138,10 @@ int lfs_format(lfs_t *lfs, const struct lfs_config *cfg) {
 | 
			
		|||
    superdir.d.size = sizeof(superdir.d) + sizeof(superblock.d) + 4;
 | 
			
		||||
 | 
			
		||||
    // write both pairs to be safe
 | 
			
		||||
    lfs_superblock_tole32(&superblock.d);
 | 
			
		||||
    bool valid = false;
 | 
			
		||||
    for (int i = 0; i < 2; i++) {
 | 
			
		||||
        int err = lfs_dir_commit(lfs, &superdir, (struct lfs_region[]){
 | 
			
		||||
        err = lfs_dir_commit(lfs, &superdir, (struct lfs_region[]){
 | 
			
		||||
                {sizeof(superdir.d), sizeof(superblock.d),
 | 
			
		||||
                 &superblock.d, sizeof(superblock.d)}
 | 
			
		||||
            }, 1);
 | 
			
		||||
| 
						 | 
				
			
			@ -2054,8 +2187,9 @@ int lfs_mount(lfs_t *lfs, const struct lfs_config *cfg) {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    if (!err) {
 | 
			
		||||
        int err = lfs_bd_read(lfs, dir.pair[0], sizeof(dir.d),
 | 
			
		||||
        err = lfs_bd_read(lfs, dir.pair[0], sizeof(dir.d),
 | 
			
		||||
                &superblock.d, sizeof(superblock.d));
 | 
			
		||||
        lfs_superblock_fromle32(&superblock.d);
 | 
			
		||||
        if (err) {
 | 
			
		||||
            return err;
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			@ -2069,10 +2203,11 @@ int lfs_mount(lfs_t *lfs, const struct lfs_config *cfg) {
 | 
			
		|||
        return LFS_ERR_CORRUPT;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (superblock.d.version > (0x00010001 | 0x0000ffff)) {
 | 
			
		||||
        LFS_ERROR("Invalid version %ld.%ld",
 | 
			
		||||
                0xffff & (superblock.d.version >> 16),
 | 
			
		||||
                0xffff & (superblock.d.version >> 0));
 | 
			
		||||
    uint16_t major_version = (0xffff & (superblock.d.version >> 16));
 | 
			
		||||
    uint16_t minor_version = (0xffff & (superblock.d.version >>  0));
 | 
			
		||||
    if ((major_version != LFS_DISK_VERSION_MAJOR ||
 | 
			
		||||
         minor_version > LFS_DISK_VERSION_MINOR)) {
 | 
			
		||||
        LFS_ERROR("Invalid version %ld.%ld", major_version, minor_version);
 | 
			
		||||
        return LFS_ERR_INVAL;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -2110,15 +2245,16 @@ int lfs_traverse(lfs_t *lfs, int (*cb)(void*, lfs_block_t), void *data) {
 | 
			
		|||
 | 
			
		||||
        // iterate over contents
 | 
			
		||||
        while (dir.off + sizeof(entry.d) <= (0x7fffffff & dir.d.size)-4) {
 | 
			
		||||
            int err = lfs_bd_read(lfs, dir.pair[0], dir.off,
 | 
			
		||||
            err = lfs_bd_read(lfs, dir.pair[0], dir.off,
 | 
			
		||||
                    &entry.d, sizeof(entry.d));
 | 
			
		||||
            lfs_entry_fromle32(&entry.d);
 | 
			
		||||
            if (err) {
 | 
			
		||||
                return err;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            dir.off += lfs_entry_size(&entry);
 | 
			
		||||
            if ((0x70 & entry.d.type) == (0x70 & LFS_TYPE_REG)) {
 | 
			
		||||
                int err = lfs_ctz_traverse(lfs, &lfs->rcache, NULL,
 | 
			
		||||
                err = lfs_ctz_traverse(lfs, &lfs->rcache, NULL,
 | 
			
		||||
                        entry.d.u.file.head, entry.d.u.file.size, cb, data);
 | 
			
		||||
                if (err) {
 | 
			
		||||
                    return err;
 | 
			
		||||
| 
						 | 
				
			
			@ -2152,7 +2288,7 @@ int lfs_traverse(lfs_t *lfs, int (*cb)(void*, lfs_block_t), void *data) {
 | 
			
		|||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -2172,7 +2308,7 @@ static int lfs_pred(lfs_t *lfs, const lfs_block_t dir[2], lfs_dir_t *pdir) {
 | 
			
		|||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        int err = lfs_dir_fetch(lfs, pdir, pdir->d.tail);
 | 
			
		||||
        err = lfs_dir_fetch(lfs, pdir, pdir->d.tail);
 | 
			
		||||
        if (err) {
 | 
			
		||||
            return err;
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			@ -2186,7 +2322,7 @@ static int lfs_parent(lfs_t *lfs, const lfs_block_t dir[2],
 | 
			
		|||
    if (lfs_pairisnull(lfs->root)) {
 | 
			
		||||
        return 0;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    parent->d.tail[0] = 0;
 | 
			
		||||
    parent->d.tail[1] = 1;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -2198,7 +2334,7 @@ static int lfs_parent(lfs_t *lfs, const lfs_block_t dir[2],
 | 
			
		|||
        }
 | 
			
		||||
 | 
			
		||||
        while (true) {
 | 
			
		||||
            int err = lfs_dir_next(lfs, parent, entry);
 | 
			
		||||
            err = lfs_dir_next(lfs, parent, entry);
 | 
			
		||||
            if (err && err != LFS_ERR_NOENT) {
 | 
			
		||||
                return err;
 | 
			
		||||
            }
 | 
			
		||||
| 
						 | 
				
			
			@ -2232,13 +2368,13 @@ static int lfs_moved(lfs_t *lfs, const void *e) {
 | 
			
		|||
    // iterate over all directory directory entries
 | 
			
		||||
    lfs_entry_t entry;
 | 
			
		||||
    while (!lfs_pairisnull(cwd.d.tail)) {
 | 
			
		||||
        int err = lfs_dir_fetch(lfs, &cwd, cwd.d.tail);
 | 
			
		||||
        err = lfs_dir_fetch(lfs, &cwd, cwd.d.tail);
 | 
			
		||||
        if (err) {
 | 
			
		||||
            return err;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        while (true) {
 | 
			
		||||
            int err = lfs_dir_next(lfs, &cwd, &entry);
 | 
			
		||||
            err = lfs_dir_next(lfs, &cwd, &entry);
 | 
			
		||||
            if (err && err != LFS_ERR_NOENT) {
 | 
			
		||||
                return err;
 | 
			
		||||
            }
 | 
			
		||||
| 
						 | 
				
			
			@ -2247,7 +2383,7 @@ static int lfs_moved(lfs_t *lfs, const void *e) {
 | 
			
		|||
                break;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (!(0x80 & entry.d.type) && 
 | 
			
		||||
            if (!(0x80 & entry.d.type) &&
 | 
			
		||||
                 memcmp(&entry.d.u, e, sizeof(entry.d.u)) == 0) {
 | 
			
		||||
                return true;
 | 
			
		||||
            }
 | 
			
		||||
| 
						 | 
				
			
			@ -2369,7 +2505,7 @@ int lfs_deorphan(lfs_t *lfs) {
 | 
			
		|||
        // check entries for moves
 | 
			
		||||
        lfs_entry_t entry;
 | 
			
		||||
        while (true) {
 | 
			
		||||
            int err = lfs_dir_next(lfs, &cwd, &entry);
 | 
			
		||||
            err = lfs_dir_next(lfs, &cwd, &entry);
 | 
			
		||||
            if (err && err != LFS_ERR_NOENT) {
 | 
			
		||||
                return err;
 | 
			
		||||
            }
 | 
			
		||||
| 
						 | 
				
			
			@ -2388,7 +2524,7 @@ int lfs_deorphan(lfs_t *lfs) {
 | 
			
		|||
                if (moved) {
 | 
			
		||||
                    LFS_DEBUG("Found move %ld %ld",
 | 
			
		||||
                            entry.d.u.dir[0], entry.d.u.dir[1]);
 | 
			
		||||
                    int err = lfs_dir_remove(lfs, &cwd, &entry);
 | 
			
		||||
                    err = lfs_dir_remove(lfs, &cwd, &entry);
 | 
			
		||||
                    if (err) {
 | 
			
		||||
                        return err;
 | 
			
		||||
                    }
 | 
			
		||||
| 
						 | 
				
			
			@ -2396,7 +2532,7 @@ int lfs_deorphan(lfs_t *lfs) {
 | 
			
		|||
                    LFS_DEBUG("Found partial move %ld %ld",
 | 
			
		||||
                            entry.d.u.dir[0], entry.d.u.dir[1]);
 | 
			
		||||
                    entry.d.type &= ~0x80;
 | 
			
		||||
                    int err = lfs_dir_update(lfs, &cwd, &entry, NULL);
 | 
			
		||||
                    err = lfs_dir_update(lfs, &cwd, &entry, NULL);
 | 
			
		||||
                    if (err) {
 | 
			
		||||
                        return err;
 | 
			
		||||
                    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -22,6 +22,23 @@
 | 
			
		|||
#include <stdbool.h>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/// Version info ///
 | 
			
		||||
 | 
			
		||||
// Software library version
 | 
			
		||||
// Major (top-nibble), incremented on backwards incompatible changes
 | 
			
		||||
// Minor (bottom-nibble), incremented on feature additions
 | 
			
		||||
#define LFS_VERSION 0x00010003
 | 
			
		||||
#define LFS_VERSION_MAJOR (0xffff & (LFS_VERSION >> 16))
 | 
			
		||||
#define LFS_VERSION_MINOR (0xffff & (LFS_VERSION >>  0))
 | 
			
		||||
 | 
			
		||||
// Version of On-disk data structures
 | 
			
		||||
// Major (top-nibble), incremented on backwards incompatible changes
 | 
			
		||||
// Minor (bottom-nibble), incremented on feature additions
 | 
			
		||||
#define LFS_DISK_VERSION 0x00010001
 | 
			
		||||
#define LFS_DISK_VERSION_MAJOR (0xffff & (LFS_DISK_VERSION >> 16))
 | 
			
		||||
#define LFS_DISK_VERSION_MINOR (0xffff & (LFS_DISK_VERSION >>  0))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/// Definitions ///
 | 
			
		||||
 | 
			
		||||
// Type definitions
 | 
			
		||||
| 
						 | 
				
			
			@ -41,16 +58,18 @@ typedef uint32_t lfs_block_t;
 | 
			
		|||
// Possible error codes, these are negative to allow
 | 
			
		||||
// valid positive return values
 | 
			
		||||
enum lfs_error {
 | 
			
		||||
    LFS_ERR_OK      = 0,    // No error
 | 
			
		||||
    LFS_ERR_IO      = -5,   // Error during device operation
 | 
			
		||||
    LFS_ERR_CORRUPT = -52,  // Corrupted
 | 
			
		||||
    LFS_ERR_NOENT   = -2,   // No directory entry
 | 
			
		||||
    LFS_ERR_EXIST   = -17,  // Entry already exists
 | 
			
		||||
    LFS_ERR_NOTDIR  = -20,  // Entry is not a dir
 | 
			
		||||
    LFS_ERR_ISDIR   = -21,  // Entry is a dir
 | 
			
		||||
    LFS_ERR_INVAL   = -22,  // Invalid parameter
 | 
			
		||||
    LFS_ERR_NOSPC   = -28,  // No space left on device
 | 
			
		||||
    LFS_ERR_NOMEM   = -12,  // No more memory available
 | 
			
		||||
    LFS_ERR_OK       = 0,    // No error
 | 
			
		||||
    LFS_ERR_IO       = -5,   // Error during device operation
 | 
			
		||||
    LFS_ERR_CORRUPT  = -52,  // Corrupted
 | 
			
		||||
    LFS_ERR_NOENT    = -2,   // No directory entry
 | 
			
		||||
    LFS_ERR_EXIST    = -17,  // Entry already exists
 | 
			
		||||
    LFS_ERR_NOTDIR   = -20,  // Entry is not a dir
 | 
			
		||||
    LFS_ERR_ISDIR    = -21,  // Entry is a dir
 | 
			
		||||
    LFS_ERR_NOTEMPTY = -39,  // Dir is not empty
 | 
			
		||||
    LFS_ERR_BADF     = -9,   // Bad file number
 | 
			
		||||
    LFS_ERR_INVAL    = -22,  // Invalid parameter
 | 
			
		||||
    LFS_ERR_NOSPC    = -28,  // No space left on device
 | 
			
		||||
    LFS_ERR_NOMEM    = -12,  // No more memory available
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// File types
 | 
			
		||||
| 
						 | 
				
			
			@ -99,14 +118,14 @@ struct lfs_config {
 | 
			
		|||
 | 
			
		||||
    // Program a region in a block. The block must have previously
 | 
			
		||||
    // been erased. Negative error codes are propogated to the user.
 | 
			
		||||
    // The prog function must return LFS_ERR_CORRUPT if the block should
 | 
			
		||||
    // be considered bad.
 | 
			
		||||
    // May return LFS_ERR_CORRUPT if the block should be considered bad.
 | 
			
		||||
    int (*prog)(const struct lfs_config *c, 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. Negative error codes
 | 
			
		||||
    // are propogated to the user.
 | 
			
		||||
    // May return LFS_ERR_CORRUPT if the block should be considered bad.
 | 
			
		||||
    int (*erase)(const struct lfs_config *c, lfs_block_t block);
 | 
			
		||||
 | 
			
		||||
    // Sync the state of the underlying block device. Negative error codes
 | 
			
		||||
| 
						 | 
				
			
			@ -121,11 +140,13 @@ struct lfs_config {
 | 
			
		|||
    // Minimum size of a block program. This determines the size of program
 | 
			
		||||
    // buffers. This may be larger than the physical program size to improve
 | 
			
		||||
    // performance by caching more of the block device.
 | 
			
		||||
    // Must be a multiple of the read size.
 | 
			
		||||
    lfs_size_t prog_size;
 | 
			
		||||
 | 
			
		||||
    // Size of an erasable block. This does not impact ram consumption and
 | 
			
		||||
    // may be larger than the physical erase size. However, this should be
 | 
			
		||||
    // kept small as each file currently takes up an entire block .
 | 
			
		||||
    // kept small as each file currently takes up an entire block.
 | 
			
		||||
    // Must be a multiple of the program size.
 | 
			
		||||
    lfs_size_t block_size;
 | 
			
		||||
 | 
			
		||||
    // Number of erasable blocks on the device.
 | 
			
		||||
| 
						 | 
				
			
			@ -362,6 +383,11 @@ lfs_ssize_t lfs_file_write(lfs_t *lfs, lfs_file_t *file,
 | 
			
		|||
lfs_soff_t lfs_file_seek(lfs_t *lfs, lfs_file_t *file,
 | 
			
		||||
        lfs_soff_t off, int whence);
 | 
			
		||||
 | 
			
		||||
// Truncates the size of the file to the specified size
 | 
			
		||||
//
 | 
			
		||||
// Returns a negative error code on failure.
 | 
			
		||||
int lfs_file_truncate(lfs_t *lfs, lfs_file_t *file, lfs_off_t size);
 | 
			
		||||
 | 
			
		||||
// Return the position of the file
 | 
			
		||||
//
 | 
			
		||||
// Equivalent to lfs_file_seek(lfs, file, 0, LFS_SEEK_CUR)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -18,16 +18,85 @@
 | 
			
		|||
#ifndef LFS_UTIL_H
 | 
			
		||||
#define LFS_UTIL_H
 | 
			
		||||
 | 
			
		||||
#include <stdlib.h>
 | 
			
		||||
#include <stdint.h>
 | 
			
		||||
#include <stdbool.h>
 | 
			
		||||
#include <string.h>
 | 
			
		||||
 | 
			
		||||
#ifndef LFS_NO_MALLOC
 | 
			
		||||
#include <stdlib.h>
 | 
			
		||||
#endif
 | 
			
		||||
#ifndef LFS_NO_ASSERT
 | 
			
		||||
#include <assert.h>
 | 
			
		||||
#endif
 | 
			
		||||
#if !defined(LFS_NO_INFO) || !defined(LFS_NO_DEBUG) || !defined(LFS_NO_WARN) || !defined(LFS_NO_ERROR)
 | 
			
		||||
#include <stdio.h>
 | 
			
		||||
#ifdef __ICCARM__
 | 
			
		||||
#include <intrinsics.h>
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
// Builtin functions, these may be replaced by more
 | 
			
		||||
// efficient implementations in the system
 | 
			
		||||
// Macros, may be replaced by system specific wrappers. Arguments to these
 | 
			
		||||
// macros must not have side-effects as the macros can be removed for a smaller
 | 
			
		||||
// code footprint
 | 
			
		||||
 | 
			
		||||
#ifdef __MBED__
 | 
			
		||||
#include "mbed_debug.h"
 | 
			
		||||
#include "mbed_assert.h"
 | 
			
		||||
#else
 | 
			
		||||
#define MBED_LFS_ENABLE_INFO   false
 | 
			
		||||
#define MBED_LFS_ENABLE_DEBUG  true
 | 
			
		||||
#define MBED_LFS_ENABLE_WARN   true
 | 
			
		||||
#define MBED_LFS_ENABLE_ERROR  true
 | 
			
		||||
#define MBED_LFS_ENABLE_ASSERT true
 | 
			
		||||
#define MBED_LFS_INTRINSICS    true
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
// Logging functions
 | 
			
		||||
#if !defined(LFS_NO_INFO) && MBED_LFS_ENABLE_INFO
 | 
			
		||||
#define LFS_INFO(fmt, ...)  printf("lfs info:%d: " fmt "\n", __LINE__, __VA_ARGS__)
 | 
			
		||||
#elif !defined(LFS_NO_INFO) && !defined(MBED_LFS_ENABLE_INFO)
 | 
			
		||||
#define LFS_INFO(fmt, ...)  debug("lfs info:%d: " fmt "\n", __LINE__, __VA_ARGS__)
 | 
			
		||||
#else
 | 
			
		||||
#define LFS_INFO(fmt, ...)
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#if !defined(LFS_NO_DEBUG) && MBED_LFS_ENABLE_DEBUG
 | 
			
		||||
#define LFS_DEBUG(fmt, ...) printf("lfs debug:%d: " fmt "\n", __LINE__, __VA_ARGS__)
 | 
			
		||||
#elif !defined(LFS_NO_DEBUG) && !defined(MBED_LFS_ENABLE_DEBUG)
 | 
			
		||||
#define LFS_DEBUG(fmt, ...) debug("lfs debug:%d: " fmt "\n", __LINE__, __VA_ARGS__)
 | 
			
		||||
#else
 | 
			
		||||
#define LFS_DEBUG(fmt, ...)
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#if !defined(LFS_NO_WARN) && MBED_LFS_ENABLE_WARN
 | 
			
		||||
#define LFS_WARN(fmt, ...)  printf("lfs warn:%d: " fmt "\n", __LINE__, __VA_ARGS__)
 | 
			
		||||
#elif !defined(LFS_NO_WARN) && !defined(MBED_LFS_ENABLE_WARN)
 | 
			
		||||
#define LFS_WARN(fmt, ...)  debug("lfs warn:%d: " fmt "\n", __LINE__, __VA_ARGS__)
 | 
			
		||||
#else
 | 
			
		||||
#define LFS_WARN(fmt, ...)
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#if !defined(LFS_NO_ERROR) && MBED_LFS_ENABLE_ERROR
 | 
			
		||||
#define LFS_ERROR(fmt, ...) printf("lfs error:%d: " fmt "\n", __LINE__, __VA_ARGS__)
 | 
			
		||||
#elif !defined(LFS_NO_ERROR) && !defined(MBED_LFS_ENABLE_ERROR)
 | 
			
		||||
#define LFS_ERROR(fmt, ...) debug("lfs error:%d: " fmt "\n", __LINE__, __VA_ARGS__)
 | 
			
		||||
#else
 | 
			
		||||
#define LFS_ERROR(fmt, ...)
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
// Runtime assertions
 | 
			
		||||
#if !defined(LFS_NO_ASSERT) && MBED_LFS_ENABLE_ASSERT
 | 
			
		||||
#define LFS_ASSERT(test) assert(test)
 | 
			
		||||
#elif !defined(LFS_NO_ASSERT) && !defined(MBED_LFS_ENABLE_ASSERT)
 | 
			
		||||
#define LFS_ASSERT(test) MBED_ASSERT(test)
 | 
			
		||||
#else
 | 
			
		||||
#define LFS_ASSERT(test)
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
// Builtin functions, these may be replaced by more efficient
 | 
			
		||||
// toolchain-specific implementations. LFS_NO_INTRINSICS falls back to a more
 | 
			
		||||
// expensive basic C implementation for debugging purposes
 | 
			
		||||
 | 
			
		||||
// Min/max functions for unsigned 32-bit numbers
 | 
			
		||||
static inline uint32_t lfs_max(uint32_t a, uint32_t b) {
 | 
			
		||||
    return (a > b) ? a : b;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -36,42 +105,38 @@ static inline uint32_t lfs_min(uint32_t a, uint32_t b) {
 | 
			
		|||
    return (a < b) ? a : b;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static inline uint32_t lfs_ctz(uint32_t a) {
 | 
			
		||||
#if defined(__GNUC__) || defined(__CC_ARM)
 | 
			
		||||
    return __builtin_ctz(a);
 | 
			
		||||
#elif defined(__ICCARM__) && defined(__CLZ)
 | 
			
		||||
    return __CLZ(__RBIT(a));
 | 
			
		||||
#else
 | 
			
		||||
    uint32_t r = 32;
 | 
			
		||||
    a &= -a;
 | 
			
		||||
    if (a) r -= 1;
 | 
			
		||||
    if (a & 0x0000ffff) r -= 16;
 | 
			
		||||
    if (a & 0x00ff00ff) r -= 8;
 | 
			
		||||
    if (a & 0x0f0f0f0f) r -= 4;
 | 
			
		||||
    if (a & 0x33333333) r -= 2;
 | 
			
		||||
    if (a & 0x55555555) r -= 1;
 | 
			
		||||
    return r;
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Find the next smallest power of 2 less than or equal to a
 | 
			
		||||
static inline uint32_t lfs_npw2(uint32_t a) {
 | 
			
		||||
#if defined(__GNUC__) || defined(__CC_ARM)
 | 
			
		||||
#if !defined(LFS_NO_INTRINSICS) && MBED_LFS_INTRINSICS && \
 | 
			
		||||
    (defined(__GNUC__) || defined(__CC_ARM))
 | 
			
		||||
    return 32 - __builtin_clz(a-1);
 | 
			
		||||
#elif defined(__ICCARM__) && defined(__CLZ)
 | 
			
		||||
    return 32 - __CLZ(a-1);
 | 
			
		||||
#else
 | 
			
		||||
    uint32_t r = 0;
 | 
			
		||||
    uint32_t s;
 | 
			
		||||
    a -= 1;
 | 
			
		||||
    s = (a > 0xffff) << 4; a >>= s; r |= s;
 | 
			
		||||
    s = (a > 0xff  ) << 3; a >>= s; r |= s;
 | 
			
		||||
    s = (a > 0xf   ) << 2; a >>= s; r |= s;
 | 
			
		||||
    s = (a > 0x3   ) << 1; a >>= s; r |= s;
 | 
			
		||||
    return r | (a >> 1);
 | 
			
		||||
    return (r | (a >> 1)) + 1;
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Count the number of trailing binary zeros in a
 | 
			
		||||
// lfs_ctz(0) may be undefined
 | 
			
		||||
static inline uint32_t lfs_ctz(uint32_t a) {
 | 
			
		||||
#if !defined(LFS_NO_INTRINSICS) && MBED_LFS_INTRINSICS && \
 | 
			
		||||
    defined(__GNUC__)
 | 
			
		||||
    return __builtin_ctz(a);
 | 
			
		||||
#else
 | 
			
		||||
    return lfs_npw2((a & -a) + 1) - 1;
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Count the number of binary ones in a
 | 
			
		||||
static inline uint32_t lfs_popc(uint32_t a) {
 | 
			
		||||
#if defined(__GNUC__) || defined(__CC_ARM)
 | 
			
		||||
#if !defined(LFS_NO_INTRINSICS) && MBED_LFS_INTRINSICS && \
 | 
			
		||||
    (defined(__GNUC__) || defined(__CC_ARM))
 | 
			
		||||
    return __builtin_popcount(a);
 | 
			
		||||
#else
 | 
			
		||||
    a = a - ((a >> 1) & 0x55555555);
 | 
			
		||||
| 
						 | 
				
			
			@ -80,55 +145,55 @@ static inline uint32_t lfs_popc(uint32_t a) {
 | 
			
		|||
#endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Find the sequence comparison of a and b, this is the distance
 | 
			
		||||
// between a and b ignoring overflow
 | 
			
		||||
static inline int lfs_scmp(uint32_t a, uint32_t b) {
 | 
			
		||||
    return (int)(unsigned)(a - b);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CRC-32 with polynomial = 0x04c11db7
 | 
			
		||||
// Convert from 32-bit little-endian to native order
 | 
			
		||||
static inline uint32_t lfs_fromle32(uint32_t a) {
 | 
			
		||||
#if !defined(LFS_NO_INTRINSICS) && MBED_LFS_INTRINSICS && ( \
 | 
			
		||||
    (defined(  BYTE_ORDER  ) &&   BYTE_ORDER   ==   ORDER_LITTLE_ENDIAN  ) || \
 | 
			
		||||
    (defined(__BYTE_ORDER  ) && __BYTE_ORDER   == __ORDER_LITTLE_ENDIAN  ) || \
 | 
			
		||||
    (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__))
 | 
			
		||||
    return a;
 | 
			
		||||
#elif !defined(LFS_NO_INTRINSICS) && MBED_LFS_INTRINSICS && ( \
 | 
			
		||||
    (defined(  BYTE_ORDER  ) &&   BYTE_ORDER   ==   ORDER_BIG_ENDIAN  ) || \
 | 
			
		||||
    (defined(__BYTE_ORDER  ) && __BYTE_ORDER   == __ORDER_BIG_ENDIAN  ) || \
 | 
			
		||||
    (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__))
 | 
			
		||||
    return __builtin_bswap32(a);
 | 
			
		||||
#else
 | 
			
		||||
    return (((uint8_t*)&a)[0] <<  0) |
 | 
			
		||||
           (((uint8_t*)&a)[1] <<  8) |
 | 
			
		||||
           (((uint8_t*)&a)[2] << 16) |
 | 
			
		||||
           (((uint8_t*)&a)[3] << 24);
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Convert to 32-bit little-endian from native order
 | 
			
		||||
static inline uint32_t lfs_tole32(uint32_t a) {
 | 
			
		||||
    return lfs_fromle32(a);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Calculate CRC-32 with polynomial = 0x04c11db7
 | 
			
		||||
void lfs_crc(uint32_t *crc, const void *buffer, size_t size);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
// Logging functions
 | 
			
		||||
#ifdef __MBED__
 | 
			
		||||
#include "mbed_debug.h"
 | 
			
		||||
// Allocate memory, only used if buffers are not provided to littlefs
 | 
			
		||||
static inline void *lfs_malloc(size_t size) {
 | 
			
		||||
#ifndef LFS_NO_MALLOC
 | 
			
		||||
    return malloc(size);
 | 
			
		||||
#else
 | 
			
		||||
#define MBED_LFS_ENABLE_INFO  false
 | 
			
		||||
#define MBED_LFS_ENABLE_DEBUG true
 | 
			
		||||
#define MBED_LFS_ENABLE_WARN  true
 | 
			
		||||
#define MBED_LFS_ENABLE_ERROR true
 | 
			
		||||
    return NULL;
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#if MBED_LFS_ENABLE_INFO
 | 
			
		||||
#define LFS_INFO(fmt, ...)  printf("lfs info: " fmt "\n", __VA_ARGS__)
 | 
			
		||||
#elif !defined(MBED_LFS_ENABLE_INFO)
 | 
			
		||||
#define LFS_INFO(fmt, ...)  debug("lfs info: " fmt "\n", __VA_ARGS__)
 | 
			
		||||
#else
 | 
			
		||||
#define LFS_INFO(fmt, ...)
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#if MBED_LFS_ENABLE_DEBUG
 | 
			
		||||
#define LFS_DEBUG(fmt, ...)  printf("lfs debug: " fmt "\n", __VA_ARGS__)
 | 
			
		||||
#elif !defined(MBED_LFS_ENABLE_DEBUG)
 | 
			
		||||
#define LFS_DEBUG(fmt, ...)  debug("lfs debug: " fmt "\n", __VA_ARGS__)
 | 
			
		||||
#else
 | 
			
		||||
#define LFS_DEBUG(fmt, ...)
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#if MBED_LFS_ENABLE_WARN
 | 
			
		||||
#define LFS_WARN(fmt, ...)  printf("lfs warn: " fmt "\n", __VA_ARGS__)
 | 
			
		||||
#elif !defined(MBED_LFS_ENABLE_WARN)
 | 
			
		||||
#define LFS_WARN(fmt, ...)  debug("lfs warn: " fmt "\n", __VA_ARGS__)
 | 
			
		||||
#else
 | 
			
		||||
#define LFS_WARN(fmt, ...)
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#if MBED_LFS_ENABLE_ERROR
 | 
			
		||||
#define LFS_ERROR(fmt, ...)  printf("lfs error: " fmt "\n", __VA_ARGS__)
 | 
			
		||||
#elif !defined(MBED_LFS_ENABLE_ERROR)
 | 
			
		||||
#define LFS_ERROR(fmt, ...)  debug("lfs error: " fmt "\n", __VA_ARGS__)
 | 
			
		||||
#else
 | 
			
		||||
#define LFS_ERROR(fmt, ...)
 | 
			
		||||
// Deallocate memory, only used if buffers are not provided to littlefs
 | 
			
		||||
static inline void lfs_free(void *p) {
 | 
			
		||||
#ifndef LFS_NO_MALLOC
 | 
			
		||||
    free(p);
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -7,11 +7,11 @@
 | 
			
		|||
 | 
			
		||||
 | 
			
		||||
// test stuff
 | 
			
		||||
void test_log(const char *s, uintmax_t v) {{
 | 
			
		||||
static void test_log(const char *s, uintmax_t v) {{
 | 
			
		||||
    printf("%s: %jd\n", s, v);
 | 
			
		||||
}}
 | 
			
		||||
 | 
			
		||||
void test_assert(const char *file, unsigned line,
 | 
			
		||||
static void test_assert(const char *file, unsigned line,
 | 
			
		||||
        const char *s, uintmax_t v, uintmax_t e) {{
 | 
			
		||||
    static const char *last[6] = {{0, 0}};
 | 
			
		||||
    if (v != e || !(last[0] == s || last[1] == s ||
 | 
			
		||||
| 
						 | 
				
			
			@ -37,7 +37,8 @@ void test_assert(const char *file, unsigned line,
 | 
			
		|||
 | 
			
		||||
 | 
			
		||||
// utility functions for traversals
 | 
			
		||||
int test_count(void *p, lfs_block_t b) {{
 | 
			
		||||
static int __attribute__((used)) test_count(void *p, lfs_block_t b) {{
 | 
			
		||||
    (void)b;
 | 
			
		||||
    unsigned *u = (unsigned*)p;
 | 
			
		||||
    *u += 1;
 | 
			
		||||
    return 0;
 | 
			
		||||
| 
						 | 
				
			
			@ -58,7 +59,7 @@ lfs_size_t size;
 | 
			
		|||
lfs_size_t wsize;
 | 
			
		||||
lfs_size_t rsize;
 | 
			
		||||
 | 
			
		||||
uintmax_t res;
 | 
			
		||||
uintmax_t test;
 | 
			
		||||
 | 
			
		||||
#ifndef LFS_READ_SIZE
 | 
			
		||||
#define LFS_READ_SIZE 16
 | 
			
		||||
| 
						 | 
				
			
			@ -96,7 +97,7 @@ const struct lfs_config cfg = {{
 | 
			
		|||
 | 
			
		||||
 | 
			
		||||
// Entry point
 | 
			
		||||
int main() {{
 | 
			
		||||
int main(void) {{
 | 
			
		||||
    lfs_emubd_create(&cfg, "blocks");
 | 
			
		||||
 | 
			
		||||
{tests}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -14,22 +14,34 @@ def generate(test):
 | 
			
		|||
        match = re.match('(?: *\n)*( *)(.*)=>(.*);', line, re.DOTALL | re.MULTILINE)
 | 
			
		||||
        if match:
 | 
			
		||||
            tab, test, expect = match.groups()
 | 
			
		||||
            lines.append(tab+'res = {test};'.format(test=test.strip()))
 | 
			
		||||
            lines.append(tab+'test_assert("{name}", res, {expect});'.format(
 | 
			
		||||
            lines.append(tab+'test = {test};'.format(test=test.strip()))
 | 
			
		||||
            lines.append(tab+'test_assert("{name}", test, {expect});'.format(
 | 
			
		||||
                    name = re.match('\w*', test.strip()).group(),
 | 
			
		||||
                    expect = expect.strip()))
 | 
			
		||||
        else:
 | 
			
		||||
            lines.append(line)
 | 
			
		||||
 | 
			
		||||
    # Create test file
 | 
			
		||||
    with open('test.c', 'w') as file:
 | 
			
		||||
        file.write(template.format(tests='\n'.join(lines)))
 | 
			
		||||
 | 
			
		||||
    # Remove build artifacts to force rebuild
 | 
			
		||||
    try:
 | 
			
		||||
        os.remove('test.o')
 | 
			
		||||
        os.remove('lfs')
 | 
			
		||||
    except OSError:
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
def compile():
 | 
			
		||||
    os.environ['CFLAGS'] = os.environ.get('CFLAGS', '') + ' -Werror'
 | 
			
		||||
    subprocess.check_call(['make', '--no-print-directory', '-s'], env=os.environ)
 | 
			
		||||
    subprocess.check_call([
 | 
			
		||||
            os.environ.get('MAKE', 'make'),
 | 
			
		||||
            '--no-print-directory', '-s'])
 | 
			
		||||
 | 
			
		||||
def execute():
 | 
			
		||||
    subprocess.check_call(["./lfs"])
 | 
			
		||||
    if 'EXEC' in os.environ:
 | 
			
		||||
        subprocess.check_call([os.environ['EXEC'], "./lfs"])
 | 
			
		||||
    else:
 | 
			
		||||
        subprocess.check_call(["./lfs"])
 | 
			
		||||
 | 
			
		||||
def main(test=None):
 | 
			
		||||
    if test and not test.startswith('-'):
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -126,7 +126,7 @@ TEST
 | 
			
		|||
echo "--- Directory remove ---"
 | 
			
		||||
tests/test.py << TEST
 | 
			
		||||
    lfs_mount(&lfs, &cfg) => 0;
 | 
			
		||||
    lfs_remove(&lfs, "potato") => LFS_ERR_INVAL;
 | 
			
		||||
    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;
 | 
			
		||||
| 
						 | 
				
			
			@ -220,7 +220,7 @@ tests/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_INVAL;
 | 
			
		||||
    lfs_rename(&lfs, "hotpotato", "warmpotato") => LFS_ERR_NOTEMPTY;
 | 
			
		||||
 | 
			
		||||
    lfs_remove(&lfs, "warmpotato/mushy") => 0;
 | 
			
		||||
    lfs_rename(&lfs, "hotpotato", "warmpotato") => 0;
 | 
			
		||||
| 
						 | 
				
			
			@ -255,7 +255,7 @@ tests/test.py << TEST
 | 
			
		|||
    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_INVAL;
 | 
			
		||||
    lfs_remove(&lfs, "coldpotato") => LFS_ERR_NOTEMPTY;
 | 
			
		||||
    lfs_remove(&lfs, "warmpotato") => 0;
 | 
			
		||||
    lfs_unmount(&lfs) => 0;
 | 
			
		||||
TEST
 | 
			
		||||
| 
						 | 
				
			
			@ -285,7 +285,7 @@ TEST
 | 
			
		|||
echo "--- Recursive remove ---"
 | 
			
		||||
tests/test.py << TEST
 | 
			
		||||
    lfs_mount(&lfs, &cfg) => 0;
 | 
			
		||||
    lfs_remove(&lfs, "coldpotato") => LFS_ERR_INVAL;
 | 
			
		||||
    lfs_remove(&lfs, "coldpotato") => LFS_ERR_NOTEMPTY;
 | 
			
		||||
 | 
			
		||||
    lfs_dir_open(&lfs, &dir[0], "coldpotato") => 0;
 | 
			
		||||
    lfs_dir_read(&lfs, &dir[0], &info) => 1;
 | 
			
		||||
| 
						 | 
				
			
			@ -328,7 +328,7 @@ TEST
 | 
			
		|||
echo "--- Multi-block remove ---"
 | 
			
		||||
tests/test.py << TEST
 | 
			
		||||
    lfs_mount(&lfs, &cfg) => 0;
 | 
			
		||||
    lfs_remove(&lfs, "cactus") => LFS_ERR_INVAL;
 | 
			
		||||
    lfs_remove(&lfs, "cactus") => LFS_ERR_NOTEMPTY;
 | 
			
		||||
 | 
			
		||||
    for (int i = 0; i < $LARGESIZE; i++) {
 | 
			
		||||
        sprintf((char*)buffer, "cactus/test%d", i);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,158 @@
 | 
			
		|||
#!/bin/bash
 | 
			
		||||
set -eu
 | 
			
		||||
 | 
			
		||||
SMALLSIZE=32
 | 
			
		||||
MEDIUMSIZE=2048
 | 
			
		||||
LARGESIZE=8192
 | 
			
		||||
 | 
			
		||||
echo "=== Truncate tests ==="
 | 
			
		||||
rm -rf blocks
 | 
			
		||||
tests/test.py << TEST
 | 
			
		||||
    lfs_format(&lfs, &cfg) => 0;
 | 
			
		||||
TEST
 | 
			
		||||
 | 
			
		||||
truncate_test() {
 | 
			
		||||
STARTSIZES="$1"
 | 
			
		||||
STARTSEEKS="$2"
 | 
			
		||||
HOTSIZES="$3"
 | 
			
		||||
COLDSIZES="$4"
 | 
			
		||||
tests/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 (int i = 0; i < sizeof(startsizes)/sizeof(startsizes[0]); i++) {
 | 
			
		||||
        sprintf((char*)buffer, "hairyhead%d", i);
 | 
			
		||||
        lfs_file_open(&lfs, &file[0], (const char*)buffer,
 | 
			
		||||
                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0;
 | 
			
		||||
 | 
			
		||||
        strcpy((char*)buffer, "hair");
 | 
			
		||||
        size = strlen((char*)buffer);
 | 
			
		||||
        for (int j = 0; j < startsizes[i]; j += size) {
 | 
			
		||||
            lfs_file_write(&lfs, &file[0], buffer, size) => size;
 | 
			
		||||
        }
 | 
			
		||||
        lfs_file_size(&lfs, &file[0]) => startsizes[i];
 | 
			
		||||
 | 
			
		||||
        if (startseeks[i] != startsizes[i]) {
 | 
			
		||||
            lfs_file_seek(&lfs, &file[0],
 | 
			
		||||
                    startseeks[i], LFS_SEEK_SET) => startseeks[i];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        lfs_file_truncate(&lfs, &file[0], hotsizes[i]) => 0;
 | 
			
		||||
        lfs_file_size(&lfs, &file[0]) => hotsizes[i];
 | 
			
		||||
 | 
			
		||||
        lfs_file_close(&lfs, &file[0]) => 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    lfs_unmount(&lfs) => 0;
 | 
			
		||||
TEST
 | 
			
		||||
tests/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 (int i = 0; i < sizeof(startsizes)/sizeof(startsizes[0]); i++) {
 | 
			
		||||
        sprintf((char*)buffer, "hairyhead%d", i);
 | 
			
		||||
        lfs_file_open(&lfs, &file[0], (const char*)buffer, LFS_O_RDWR) => 0;
 | 
			
		||||
        lfs_file_size(&lfs, &file[0]) => hotsizes[i];
 | 
			
		||||
 | 
			
		||||
        size = strlen("hair");
 | 
			
		||||
        int j = 0;
 | 
			
		||||
        for (; j < startsizes[i] && j < hotsizes[i]; j += size) {
 | 
			
		||||
            lfs_file_read(&lfs, &file[0], buffer, size) => size;
 | 
			
		||||
            memcmp(buffer, "hair", size) => 0;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        for (; j < hotsizes[i]; j += size) {
 | 
			
		||||
            lfs_file_read(&lfs, &file[0], buffer, size) => size;
 | 
			
		||||
            memcmp(buffer, "\0\0\0\0", size) => 0;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        lfs_file_truncate(&lfs, &file[0], coldsizes[i]) => 0;
 | 
			
		||||
        lfs_file_size(&lfs, &file[0]) => coldsizes[i];
 | 
			
		||||
 | 
			
		||||
        lfs_file_close(&lfs, &file[0]) => 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    lfs_unmount(&lfs) => 0;
 | 
			
		||||
TEST
 | 
			
		||||
tests/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 (int i = 0; i < sizeof(startsizes)/sizeof(startsizes[0]); i++) {
 | 
			
		||||
        sprintf((char*)buffer, "hairyhead%d", i);
 | 
			
		||||
        lfs_file_open(&lfs, &file[0], (const char*)buffer, LFS_O_RDONLY) => 0;
 | 
			
		||||
        lfs_file_size(&lfs, &file[0]) => coldsizes[i];
 | 
			
		||||
 | 
			
		||||
        size = strlen("hair");
 | 
			
		||||
        int j = 0;
 | 
			
		||||
        for (; j < startsizes[i] && j < hotsizes[i] && j < coldsizes[i];
 | 
			
		||||
                j += size) {
 | 
			
		||||
            lfs_file_read(&lfs, &file[0], buffer, size) => size;
 | 
			
		||||
            memcmp(buffer, "hair", size) => 0;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        for (; j < coldsizes[i]; j += size) {
 | 
			
		||||
            lfs_file_read(&lfs, &file[0], buffer, size) => size;
 | 
			
		||||
            memcmp(buffer, "\0\0\0\0", size) => 0;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        lfs_file_close(&lfs, &file[0]) => 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"
 | 
			
		||||
 | 
			
		||||
echo "--- Results ---"
 | 
			
		||||
tests/stats.py
 | 
			
		||||
| 
						 | 
				
			
			@ -21,6 +21,11 @@
 | 
			
		|||
        "value": 512,
 | 
			
		||||
        "help": "Number of blocks to lookahead during block allocation. A larger lookahead reduces the number of passes required to allocate a block. The lookahead buffer requires only 1 bit per block so it can be quite large with little ram impact. Should be a multiple of 32."
 | 
			
		||||
    },
 | 
			
		||||
    "intrinsics": {
 | 
			
		||||
        "macro_name": "MBED_LFS_INTRINSICS",
 | 
			
		||||
        "value": true,
 | 
			
		||||
        "help": "Enable intrinsics for bit operations such as ctz, popc, and le32 conversion. Can be disabled to help debug toolchain issues"
 | 
			
		||||
    },
 | 
			
		||||
    "enable_info": {
 | 
			
		||||
        "macro_name": "MBED_LFS_ENABLE_INFO",
 | 
			
		||||
        "value": false,
 | 
			
		||||
| 
						 | 
				
			
			@ -40,6 +45,11 @@
 | 
			
		|||
        "macro_name": "MBED_LFS_ENABLE_ERROR",
 | 
			
		||||
        "value": null,
 | 
			
		||||
        "help": "Enables error logging, true = enabled, false = disabled, null = disabled only in release builds"
 | 
			
		||||
    },
 | 
			
		||||
    "enable_assert": {
 | 
			
		||||
        "macro_name": "MBED_LFS_ENABLE_ASSERT",
 | 
			
		||||
        "value": null,
 | 
			
		||||
        "help": "Enables asserts, true = enabled, false = disabled, null = disabled only in release builds"
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue