Browse Source

Merge branch 'master' of https://github.com/geky/littlefs

xieyangrun 6 years ago
parent
commit
18d9f1ac40
28 changed files with 4039 additions and 1651 deletions
  1. 131 59
      .travis.yml
  2. 729 380
      DESIGN.md
  3. 5 2
      Makefile
  4. 115 59
      README.md
  5. 735 318
      SPEC.md
  6. 86 9
      emubd/lfs_emubd.c
  7. 4 0
      emubd/lfs_emubd.h
  8. 814 536
      lfs.c
  9. 283 138
      lfs.h
  10. 5 3
      lfs_util.c
  11. 39 5
      lfs_util.h
  12. 61 0
      scripts/prefix.py
  13. 44 0
      tests/corrupt.py
  14. 112 0
      tests/debug.py
  15. 2 2
      tests/stats.py
  16. 18 8
      tests/template.fmt
  17. 1 1
      tests/test.py
  18. 98 41
      tests/test_alloc.sh
  19. 286 0
      tests/test_attrs.sh
  20. 7 6
      tests/test_corrupt.sh
  21. 23 23
      tests/test_dirs.sh
  22. 221 0
      tests/test_entries.sh
  23. 7 7
      tests/test_files.sh
  24. 15 14
      tests/test_format.sh
  25. 110 14
      tests/test_move.sh
  26. 15 11
      tests/test_orphan.sh
  27. 58 0
      tests/test_paths.sh
  28. 15 15
      tests/test_seek.sh

+ 131 - 59
.travis.yml

@@ -18,10 +18,11 @@ script:
   - make test QUIET=1
   - make test QUIET=1
 
 
   # run tests with a few different configurations
   # 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 test QUIET=1 CFLAGS+="-DLFS_READ_SIZE=1      -DLFS_CACHE_SIZE=4"
+  - make test QUIET=1 CFLAGS+="-DLFS_READ_SIZE=512    -DLFS_CACHE_SIZE=512 -DLFS_BLOCK_CYCLES=16"
+  - make test QUIET=1 CFLAGS+="-DLFS_BLOCK_COUNT=1023 -DLFS_LOOKAHEAD_SIZE=256"
 
 
+  - make clean test QUIET=1 CFLAGS+="-DLFS_INLINE_MAX=0"
   - make clean test QUIET=1 CFLAGS+="-DLFS_NO_INTRINSICS"
   - make clean test QUIET=1 CFLAGS+="-DLFS_NO_INTRINSICS"
 
 
   # compile and find the code size with the smallest configuration
   # compile and find the code size with the smallest configuration
@@ -35,7 +36,7 @@ script:
     if [ "$TRAVIS_TEST_RESULT" -eq 0 ]
     if [ "$TRAVIS_TEST_RESULT" -eq 0 ]
     then
     then
         CURR=$(tail -n1 sizes | awk '{print $1}')
         CURR=$(tail -n1 sizes | awk '{print $1}')
-        PREV=$(curl -u $GEKY_BOT_STATUSES https://api.github.com/repos/$TRAVIS_REPO_SLUG/status/master \
+        PREV=$(curl -u "$GEKY_BOT_STATUSES" https://api.github.com/repos/$TRAVIS_REPO_SLUG/status/master \
             | jq -re "select(.sha != \"$TRAVIS_COMMIT\")
             | jq -re "select(.sha != \"$TRAVIS_COMMIT\")
                 | .statuses[] | select(.context == \"$STAGE/$NAME\").description
                 | .statuses[] | select(.context == \"$STAGE/$NAME\").description
                 | capture(\"code size is (?<size>[0-9]+)\").size" \
                 | capture(\"code size is (?<size>[0-9]+)\").size" \
@@ -100,9 +101,10 @@ jobs:
       env:
       env:
         - STAGE=test
         - STAGE=test
         - NAME=littlefs-fuse
         - NAME=littlefs-fuse
+      if: branch !~ -prefix$
       install:
       install:
         - sudo apt-get install libfuse-dev
         - sudo apt-get install libfuse-dev
-        - git clone --depth 1 https://github.com/geky/littlefs-fuse
+        - git clone --depth 1 https://github.com/geky/littlefs-fuse -b v2-alpha
         - fusermount -V
         - fusermount -V
         - gcc --version
         - gcc --version
       before_script:
       before_script:
@@ -112,7 +114,7 @@ jobs:
 
 
         - mkdir mount
         - mkdir mount
         - sudo chmod a+rw /dev/loop0
         - sudo chmod a+rw /dev/loop0
-        - dd if=/dev/zero bs=512 count=2048 of=disk
+        - dd if=/dev/zero bs=512 count=4096 of=disk
         - losetup /dev/loop0 disk
         - losetup /dev/loop0 disk
       script:
       script:
         # self-host test
         # self-host test
@@ -125,73 +127,143 @@ jobs:
         - mkdir mount/littlefs
         - mkdir mount/littlefs
         - cp -r $(git ls-tree --name-only HEAD) mount/littlefs
         - cp -r $(git ls-tree --name-only HEAD) mount/littlefs
         - cd mount/littlefs
         - cd mount/littlefs
-        - ls
+        - stat .
+        - ls -flh
         - make -B test_dirs test_files QUIET=1
         - make -B test_dirs test_files QUIET=1
 
 
-      # Automatically update releases
+    # self-host with littlefs-fuse for fuzz test
+    - stage: test
+      env:
+        - STAGE=test
+        - NAME=littlefs-migration
+      install:
+        - sudo apt-get install libfuse-dev
+        - git clone --depth 1 https://github.com/geky/littlefs-fuse -b v2-alpha v2
+        - git clone --depth 1 https://github.com/geky/littlefs-fuse -b v1 v1
+        - fusermount -V
+        - gcc --version
+      before_script:
+        # setup disk for littlefs-fuse
+        - rm -rf v2/littlefs/*
+        - cp -r $(git ls-tree --name-only HEAD) v2/littlefs
+
+        - mkdir mount
+        - sudo chmod a+rw /dev/loop0
+        - dd if=/dev/zero bs=512 count=4096 of=disk
+        - losetup /dev/loop0 disk
+      script:
+        # compile v1 and v2
+        - make -C v1
+        - make -C v2
+
+        # run self-host test with v1
+        - v1/lfs --format /dev/loop0
+        - v1/lfs /dev/loop0 mount
+
+        - ls mount
+        - mkdir mount/littlefs
+        - cp -r $(git ls-tree --name-only HEAD) mount/littlefs
+        - cd mount/littlefs
+        - stat .
+        - ls -flh
+        - make -B test_dirs test_files QUIET=1
+
+        # attempt to migrate
+        - cd ../..
+        - fusermount -u mount
+
+        - v2/lfs --migrate /dev/loop0
+        - v2/lfs /dev/loop0 mount
+
+        # run self-host test with v2 right where we left off
+        - ls mount
+        - cd mount/littlefs
+        - stat .
+        - ls -flh
+        - make -B test_dirs test_files QUIET=1
+
+    # Automatically create releases
     - stage: deploy
     - stage: deploy
       env:
       env:
         - STAGE=deploy
         - STAGE=deploy
         - NAME=deploy
         - NAME=deploy
       script:
       script:
-        # Find version defined in lfs.h
-        - LFS_VERSION=$(grep -ox '#define LFS_VERSION .*' lfs.h | cut -d ' ' -f3)
-        - LFS_VERSION_MAJOR=$((0xffff & ($LFS_VERSION >> 16)))
-        - LFS_VERSION_MINOR=$((0xffff & ($LFS_VERSION >>  0)))
-        # Grab latests patch from repo tags, default to 0, needs finagling to get past github's pagination api
-        - PREV_URL=https://api.github.com/repos/$TRAVIS_REPO_SLUG/git/refs/tags/v$LFS_VERSION_MAJOR.$LFS_VERSION_MINOR.
-        - PREV_URL=$(curl -u "$GEKY_BOT_RELEASES" "$PREV_URL" -I
-                | sed -n '/^Link/{s/.*<\(.*\)>; rel="last"/\1/;p;q0};$q1'
-                || echo $PREV_URL)
-        - LFS_VERSION_PATCH=$(curl -u "$GEKY_BOT_RELEASES" "$PREV_URL"
-                | jq 'map(.ref | match("\\bv.*\\..*\\.(.*)$";"g")
-                    .captures[].string | tonumber) | max + 1'
-                || echo 0)
-        # We have our new version
-        - LFS_VERSION="v$LFS_VERSION_MAJOR.$LFS_VERSION_MINOR.$LFS_VERSION_PATCH"
-        - echo "VERSION $LFS_VERSION"
         - |
         - |
+          bash << 'SCRIPT'
+          set -ev
+          # Find version defined in lfs.h
+          LFS_VERSION=$(grep -ox '#define LFS_VERSION .*' lfs.h | cut -d ' ' -f3)
+          LFS_VERSION_MAJOR=$((0xffff & ($LFS_VERSION >> 16)))
+          LFS_VERSION_MINOR=$((0xffff & ($LFS_VERSION >>  0)))
+          # Grab latests patch from repo tags, default to 0, needs finagling
+          # to get past github's pagination api
+          PREV_URL=https://api.github.com/repos/$TRAVIS_REPO_SLUG/git/refs/tags/v$LFS_VERSION_MAJOR.$LFS_VERSION_MINOR.
+          PREV_URL=$(curl -u "$GEKY_BOT_RELEASES" "$PREV_URL" -I \
+              | sed -n '/^Link/{s/.*<\(.*\)>; rel="last"/\1/;p;q0};$q1' \
+              || echo $PREV_URL)
+          LFS_VERSION_PATCH=$(curl -u "$GEKY_BOT_RELEASES" "$PREV_URL" \
+              | jq 'map(.ref | match("\\bv.*\\..*\\.(.*)$";"g")
+                  .captures[].string | tonumber) | max + 1' \
+              || echo 0)
+          # We have our new version
+          LFS_VERSION="v$LFS_VERSION_MAJOR.$LFS_VERSION_MINOR.$LFS_VERSION_PATCH"
+          echo "VERSION $LFS_VERSION"
           # Check that we're the most recent commit
           # Check that we're the most recent commit
           CURRENT_COMMIT=$(curl -f -u "$GEKY_BOT_RELEASES" \
           CURRENT_COMMIT=$(curl -f -u "$GEKY_BOT_RELEASES" \
-                https://api.github.com/repos/$TRAVIS_REPO_SLUG/commits/master \
-                | jq -re '.sha')
-          if [ "$TRAVIS_COMMIT" == "$CURRENT_COMMIT" ]
+              https://api.github.com/repos/$TRAVIS_REPO_SLUG/commits/master \
+              | jq -re '.sha')
+          [ "$TRAVIS_COMMIT" == "$CURRENT_COMMIT" ] || exit 0
+          # Create major branch
+          git branch v$LFS_VERSION_MAJOR HEAD
+          # Create major prefix branch
+          git config user.name "geky bot"
+          git config user.email "bot@geky.net"
+          git fetch https://github.com/$TRAVIS_REPO_SLUG.git \
+              --depth=50 v$LFS_VERSION_MAJOR-prefix || true
+          ./scripts/prefix.py lfs$LFS_VERSION_MAJOR
+          git branch v$LFS_VERSION_MAJOR-prefix $( \
+              git commit-tree $(git write-tree) \
+                  $(git rev-parse --verify -q FETCH_HEAD | sed -e 's/^/-p /') \
+                  -p HEAD \
+                  -m "Generated v$LFS_VERSION_MAJOR prefixes")
+          git reset --hard
+          # Update major version branches (vN and vN-prefix)
+          git push https://$GEKY_BOT_RELEASES@github.com/$TRAVIS_REPO_SLUG.git \
+              v$LFS_VERSION_MAJOR \
+              v$LFS_VERSION_MAJOR-prefix
+          # Create patch version tag (vN.N.N)
+          curl -f -u "$GEKY_BOT_RELEASES" -X POST \
+              https://api.github.com/repos/$TRAVIS_REPO_SLUG/git/refs \
+              -d "{
+                  \"ref\": \"refs/tags/$LFS_VERSION\",
+                  \"sha\": \"$TRAVIS_COMMIT\"
+              }"
+          # Create minor release?
+          [[ "$LFS_VERSION" == *.0 ]] || exit 0
+          # Build release notes
+          PREV=$(git tag --sort=-v:refname -l "v*.0" | head -1)
+          if [ ! -z "$PREV" ]
           then
           then
-            # Create a simple tag
-            curl -f -u "$GEKY_BOT_RELEASES" -X POST \
-                https://api.github.com/repos/$TRAVIS_REPO_SLUG/git/refs \
-                -d "{
-                    \"ref\": \"refs/tags/$LFS_VERSION\",
-                    \"sha\": \"$TRAVIS_COMMIT\"
-                }"
-            # Minor release?
-            if [[ "$LFS_VERSION" == *.0 ]]
-            then
-              # Build release notes
-              PREV=$(git tag --sort=-v:refname -l "v*.0" | head -1)
-              if [ ! -z "$PREV" ]
-              then
-                  echo "PREV $PREV"
-                  CHANGES=$'### Changes\n\n'$( \
-                      git log --oneline $PREV.. --grep='^Merge' --invert-grep)
-                  printf "CHANGES\n%s\n\n" "$CHANGES"
-              fi
-              # Create the release
-              curl -f -u "$GEKY_BOT_RELEASES" -X POST \
-                  https://api.github.com/repos/$TRAVIS_REPO_SLUG/releases \
-                  -d "{
-                      \"tag_name\": \"$LFS_VERSION\",
-                      \"name\": \"${LFS_VERSION%.0}\",
-                      \"draft\": true,
-                      \"body\": $(jq -sR '.' <<< "$CHANGES")
-                  }"
-            fi
+              echo "PREV $PREV"
+              CHANGES=$'### Changes\n\n'$( \
+                  git log --oneline $PREV.. --grep='^Merge' --invert-grep)
+              printf "CHANGES\n%s\n\n" "$CHANGES"
           fi
           fi
+          # Create the release
+          curl -f -u "$GEKY_BOT_RELEASES" -X POST \
+              https://api.github.com/repos/$TRAVIS_REPO_SLUG/releases \
+              -d "{
+                  \"tag_name\": \"$LFS_VERSION\",
+                  \"name\": \"${LFS_VERSION%.0}\",
+                  \"draft\": true,
+                  \"body\": $(jq -sR '.' <<< "$CHANGES")
+              }" #"
+          SCRIPT
 
 
 # Manage statuses
 # Manage statuses
 before_install:
 before_install:
   - |
   - |
-    curl -u $GEKY_BOT_STATUSES -X POST \
+    curl -u "$GEKY_BOT_STATUSES" -X POST \
         https://api.github.com/repos/$TRAVIS_REPO_SLUG/statuses/${TRAVIS_PULL_REQUEST_SHA:-$TRAVIS_COMMIT} \
         https://api.github.com/repos/$TRAVIS_REPO_SLUG/statuses/${TRAVIS_PULL_REQUEST_SHA:-$TRAVIS_COMMIT} \
         -d "{
         -d "{
             \"context\": \"$STAGE/$NAME\",
             \"context\": \"$STAGE/$NAME\",
@@ -202,7 +274,7 @@ before_install:
 
 
 after_failure:
 after_failure:
   - |
   - |
-    curl -u $GEKY_BOT_STATUSES -X POST \
+    curl -u "$GEKY_BOT_STATUSES" -X POST \
         https://api.github.com/repos/$TRAVIS_REPO_SLUG/statuses/${TRAVIS_PULL_REQUEST_SHA:-$TRAVIS_COMMIT} \
         https://api.github.com/repos/$TRAVIS_REPO_SLUG/statuses/${TRAVIS_PULL_REQUEST_SHA:-$TRAVIS_COMMIT} \
         -d "{
         -d "{
             \"context\": \"$STAGE/$NAME\",
             \"context\": \"$STAGE/$NAME\",
@@ -213,7 +285,7 @@ after_failure:
 
 
 after_success:
 after_success:
   - |
   - |
-    curl -u $GEKY_BOT_STATUSES -X POST \
+    curl -u "$GEKY_BOT_STATUSES" -X POST \
         https://api.github.com/repos/$TRAVIS_REPO_SLUG/statuses/${TRAVIS_PULL_REQUEST_SHA:-$TRAVIS_COMMIT} \
         https://api.github.com/repos/$TRAVIS_REPO_SLUG/statuses/${TRAVIS_PULL_REQUEST_SHA:-$TRAVIS_COMMIT} \
         -d "{
         -d "{
             \"context\": \"$STAGE/$NAME\",
             \"context\": \"$STAGE/$NAME\",

File diff suppressed because it is too large
+ 729 - 380
DESIGN.md


+ 5 - 2
Makefile

@@ -26,7 +26,9 @@ override CFLAGS += -m$(WORD)
 endif
 endif
 override CFLAGS += -I.
 override CFLAGS += -I.
 override CFLAGS += -std=c99 -Wall -pedantic
 override CFLAGS += -std=c99 -Wall -pedantic
-override CFLAGS += -Wshadow -Wunused-parameter -Wjump-misses-init -Wsign-compare
+override CFLAGS += -Wextra -Wshadow -Wjump-misses-init
+# Remove missing-field-initializers because of GCC bug
+override CFLAGS += -Wno-missing-field-initializers
 
 
 
 
 all: $(TARGET)
 all: $(TARGET)
@@ -38,7 +40,8 @@ size: $(OBJ)
 
 
 .SUFFIXES:
 .SUFFIXES:
 test: test_format test_dirs test_files test_seek test_truncate \
 test: test_format test_dirs test_files test_seek test_truncate \
-	test_interspersed test_alloc test_paths test_orphan test_move test_corrupt
+	test_entries test_interspersed test_alloc test_paths test_attrs \
+	test_move test_orphan test_corrupt
 	@rm test.c
 	@rm test.c
 test_%: tests/test_%.sh
 test_%: tests/test_%.sh
 
 

+ 115 - 59
README.md

@@ -1,6 +1,6 @@
-## The little filesystem
+## littlefs
 
 
-A little fail-safe filesystem designed for embedded systems.
+A little fail-safe filesystem designed for microcontrollers.
 
 
 ```
 ```
    | | |     .---._____
    | | |     .---._____
@@ -11,17 +11,19 @@ A little fail-safe filesystem designed for embedded systems.
    | | |
    | | |
 ```
 ```
 
 
-**Bounded RAM/ROM** - The littlefs is designed to work with a limited amount
-of memory. Recursion is avoided and dynamic memory is limited to configurable
-buffers that can be provided statically.
+**Power-loss resilience** - littlefs is designed to handle random power
+failures. All file operations have strong copy-on-write guarantees and if
+power is lost the filesystem will fall back to the last known good state.
 
 
-**Power-loss resilient** - The littlefs is designed for systems that may have
-random power failures. The littlefs has strong copy-on-write guarantees and
-storage on disk is always kept in a valid state.
+**Dynamic wear leveling** - littlefs is designed with flash in mind, and
+provides wear leveling over dynamic blocks. Additionally, littlefs can
+detect bad blocks and work around them.
 
 
-**Wear leveling** - Since the most common form of embedded storage is erodible
-flash memories, littlefs provides a form of dynamic wear leveling for systems
-that can not fit a full flash translation layer.
+**Bounded RAM/ROM** - littlefs is designed to work with a small amount of
+memory. RAM usage is strictly bounded, which means RAM consumption does not
+change as the filesystem grows. The filesystem contains no unbounded
+recursion and dynamic memory is limited to configurable buffers that can be
+provided statically.
 
 
 ## Example
 ## Example
 
 
@@ -49,7 +51,8 @@ const struct lfs_config cfg = {
     .prog_size = 16,
     .prog_size = 16,
     .block_size = 4096,
     .block_size = 4096,
     .block_count = 128,
     .block_count = 128,
-    .lookahead = 128,
+    .cache_size = 16,
+    .lookahead_size = 16,
 };
 };
 
 
 // entry point
 // entry point
@@ -90,11 +93,11 @@ int main(void) {
 Detailed documentation (or at least as much detail as is currently available)
 Detailed documentation (or at least as much detail as is currently available)
 can be found 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
-filesystem with the block device operations and dimensions, tweakable
-parameters that tradeoff memory usage for performance, and optional
-static buffers if the user wants to avoid dynamic memory.
+littlefs takes in a configuration structure that defines how the filesystem
+operates. The configuration struct provides the filesystem with the block
+device operations and dimensions, tweakable parameters that tradeoff memory
+usage for performance, and optional static buffers if the user wants to avoid
+dynamic memory.
 
 
 The state of the littlefs is stored in the `lfs_t` type which is left up
 The state of the littlefs is stored in the `lfs_t` type which is left up
 to the user to allocate, allowing multiple filesystems to be in use
 to the user to allocate, allowing multiple filesystems to be in use
@@ -106,13 +109,13 @@ directory functions, with the deviation that the allocation of filesystem
 structures must be provided by the user.
 structures must be provided by the user.
 
 
 All POSIX operations, such as remove and rename, are atomic, even in 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.
+of power-loss. Additionally, no file updates are not actually committed to
+the filesystem until sync or close is called on the file.
 
 
 ## Other notes
 ## Other notes
 
 
-All littlefs calls have the potential to return a negative error code. The 
-errors can be either one of those found in the `enum lfs_error` in 
+All littlefs calls have the potential to return a negative error code. The
+errors can be either one of those found in the `enum lfs_error` in
 [lfs.h](lfs.h), or an error returned by the user's block device operations.
 [lfs.h](lfs.h), or an error returned by the user's block device operations.
 
 
 In the configuration struct, the `prog` and `erase` function provided by the
 In the configuration struct, the `prog` and `erase` function provided by the
@@ -127,14 +130,60 @@ from memory, otherwise data integrity can not be guaranteed. If the `write`
 function does not perform caching, and therefore each `read` or `write` call
 function does not perform caching, and therefore each `read` or `write` call
 hits the memory, the `sync` function can simply return 0.
 hits the memory, the `sync` function can simply return 0.
 
 
-## Reference material
+## Design
 
 
-[DESIGN.md](DESIGN.md) - DESIGN.md contains a fully detailed dive into how
-littlefs actually works. I would encourage you to read it since the
-solutions and tradeoffs at work here are quite interesting.
+At a high level, littlefs is a block based filesystem that uses small logs to
+store metadata and larger copy-on-write (COW) structures to store file data.
 
 
-[SPEC.md](SPEC.md) - SPEC.md contains the on-disk specification of littlefs
-with all the nitty-gritty details. Can be useful for developing tooling.
+In littlefs, these ingredients form a sort of two-layered cake, with the small
+logs (called metadata pairs) providing fast updates to metadata anywhere on
+storage, while the COW structures store file data compactly and without any
+wear amplification cost.
+
+Both of these data structures are built out of blocks, which are fed by a
+common block allocator. By limiting the number of erases allowed on a block
+per allocation, the allocator provides dynamic wear leveling over the entire
+filesystem.
+
+```
+                    root
+                   .--------.--------.
+                   | A'| B'|         |
+                   |   |   |->       |
+                   |   |   |         |
+                   '--------'--------'
+                .----'   '--------------.
+       A       v                 B       v
+      .--------.--------.       .--------.--------.
+      | C'| D'|         |       | E'|new|         |
+      |   |   |->       |       |   | E'|->       |
+      |   |   |         |       |   |   |         |
+      '--------'--------'       '--------'--------'
+      .-'   '--.                  |   '------------------.
+     v          v              .-'                        v
+.--------.  .--------.        v                       .--------.
+|   C    |  |   D    |   .--------.       write       | new E  |
+|        |  |        |   |   E    |        ==>        |        |
+|        |  |        |   |        |                   |        |
+'--------'  '--------'   |        |                   '--------'
+                         '--------'                   .-'    |
+                         .-'    '-.    .-------------|------'
+                        v          v  v              v
+                   .--------.  .--------.       .--------.
+                   |   F    |  |   G    |       | new F  |
+                   |        |  |        |       |        |
+                   |        |  |        |       |        |
+                   '--------'  '--------'       '--------'
+```
+
+More details on how littlefs works can be found in [DESIGN.md](DESIGN.md) and
+[SPEC.md](SPEC.md).
+
+- [DESIGN.md](DESIGN.md) - A fully detailed dive into how littlefs works.
+  I would suggest reading it as the tradeoffs at work are quite interesting.
+
+- [SPEC.md](SPEC.md) - The on-disk specification of littlefs with all the
+  nitty-gritty details. May be useful for tooling development.
 
 
 ## Testing
 ## Testing
 
 
@@ -148,9 +197,9 @@ make test
 
 
 ## License
 ## License
 
 
-The littlefs is provided under the [BSD-3-Clause](https://spdx.org/licenses/BSD-3-Clause.html)
-license. See [LICENSE.md](LICENSE.md) for more information. Contributions to
-this project are accepted under the same license.
+The littlefs is provided under the [BSD-3-Clause] license. See
+[LICENSE.md](LICENSE.md) for more information. Contributions to this project
+are accepted under the same license.
 
 
 Individual files contain the following tag instead of the full license text.
 Individual files contain the following tag instead of the full license text.
 
 
@@ -161,32 +210,39 @@ License Identifiers that are here available: http://spdx.org/licenses/
 
 
 ## Related projects
 ## 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 on a
-Linux machine. Can be useful for debugging littlefs if you have an SD card
-handy.
-
-[littlefs-js](https://github.com/geky/littlefs-js) - A javascript wrapper for
-littlefs. I'm not sure why you would want this, but it is handy for demos.
-You can see it in action [here](http://littlefs.geky.net/demo.html).
-
-[mklfs](https://github.com/whitecatboard/Lua-RTOS-ESP32/tree/master/components/mklfs/src) -
-A command line tool built by the [Lua RTOS](https://github.com/whitecatboard/Lua-RTOS-ESP32)
-guys for making littlefs images from a host PC. Supports Windows, Mac OS,
-and Linux.
-
-[SPIFFS](https://github.com/pellepl/spiffs) - Another excellent embedded
-filesystem for NOR flash. As a more traditional logging filesystem with full
-static wear-leveling, SPIFFS will likely outperform littlefs on small
-memories such as the internal flash on microcontrollers.
-
-[Dhara](https://github.com/dlbeer/dhara) - An interesting NAND flash
-translation layer designed for small MCUs. It offers static wear-leveling and
-power-resilience with only a fixed O(|address|) pointer structure stored on
-each block and in RAM.
+- [littlefs-fuse] - A [FUSE] 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.
+
+- [littlefs-js] - A javascript wrapper for littlefs. I'm not sure why you would
+  want this, but it is handy for demos.  You can see it in action
+  [here][littlefs-js-demo].
+
+- [mklfs] - A command line tool built by the [Lua RTOS] guys for making
+  littlefs images from a host PC. Supports Windows, Mac OS, and Linux.
+
+- [Mbed OS] - The easiest way to get started with littlefs is to jump into Mbed
+  which already has block device drivers for most forms of embedded storage.
+  littlefs is available in Mbed OS as the [LittleFileSystem] class.
+
+- [SPIFFS] - Another excellent embedded filesystem for NOR flash. As a more
+  traditional logging filesystem with full static wear-leveling, SPIFFS will
+  likely outperform littlefs on small memories such as the internal flash on
+  microcontrollers.
+
+- [Dhara] - An interesting NAND flash translation layer designed for small
+  MCUs. It offers static wear-leveling and power-resilience with only a fixed
+  _O(|address|)_ pointer structure stored on each block and in RAM.
+
+
+[BSD-3-Clause]: https://spdx.org/licenses/BSD-3-Clause.html
+[littlefs-fuse]: https://github.com/geky/littlefs-fuse
+[FUSE]: https://github.com/libfuse/libfuse
+[littlefs-js]: https://github.com/geky/littlefs-js
+[littlefs-js-demo]:http://littlefs.geky.net/demo.html
+[mklfs]: https://github.com/whitecatboard/Lua-RTOS-ESP32/tree/master/components/mklfs/src
+[Lua RTOS]: https://github.com/whitecatboard/Lua-RTOS-ESP32
+[Mbed OS]: https://github.com/armmbed/mbed-os
+[LittleFileSystem]: https://os.mbed.com/docs/mbed-os/v5.12/apis/littlefilesystem.html
+[SPIFFS]: https://github.com/pellepl/spiffs
+[Dhara]: https://github.com/dlbeer/dhara

+ 735 - 318
SPEC.md

@@ -1,10 +1,10 @@
-## The little filesystem technical specification
+## littlefs technical specification
 
 
 This is the technical specification of the little filesystem. This document
 This is the technical specification of the little filesystem. This document
 covers the technical details of how the littlefs is stored on disk for
 covers the technical details of how the littlefs is stored on disk for
-introspection and tooling development. This document assumes you are
-familiar with the design of the littlefs, for more info on how littlefs
-works check out [DESIGN.md](DESIGN.md).
+introspection and tooling. This document assumes you are familiar with the
+design of the littlefs, for more info on how littlefs works check
+out [DESIGN.md](DESIGN.md).
 
 
 ```
 ```
    | | |     .---._____
    | | |     .---._____
@@ -15,356 +15,773 @@ works check out [DESIGN.md](DESIGN.md).
    | | |
    | | |
 ```
 ```
 
 
-## Some important details
+## Some quick notes
 
 
-- The littlefs is a block-based filesystem. This is, the disk is divided into
-  an array of evenly sized blocks that are used as the logical unit of storage
-  in littlefs. Block pointers are stored in 32 bits.
+- littlefs is a block-based filesystem. The disk is divided into an array of
+  evenly sized blocks that are used as the logical unit of storage.
 
 
-- There is no explicit free-list stored on disk, the littlefs only knows what
-  is in use in the filesystem.
+- Block pointers are stored in 32 bits, with the special value `0xffffffff`
+  representing a null block address.
 
 
-- The littlefs uses the value of 0xffffffff to represent a null block-pointer.
+- In addition to the logical block size (which usually matches the erase
+  block size), littlefs also uses a program block size and read block size.
+  These determine the alignment of block device operations, but don't need
+  to be consistent for portability.
 
 
-- All values in littlefs are stored in little-endian byte order.
+- By default, all values in littlefs are stored in little-endian byte order.
 
 
 ## Directories / Metadata pairs
 ## Directories / Metadata pairs
 
 
-Metadata pairs form the backbone of the littlefs and provide a system for
-atomic updates. Even the superblock is stored in a metadata pair.
+Metadata pairs form the backbone of littlefs and provide a system for
+distributed atomic updates. Even the superblock is stored in a metadata pair.
 
 
 As their name suggests, a metadata pair is stored in two blocks, with one block
 As their name suggests, a metadata pair is stored in two blocks, with one block
-acting as a redundant backup in case the other is corrupted. These two blocks
-could be anywhere in the disk and may not be next to each other, so any
-pointers to directory pairs need to be stored as two block pointers.
-
-Here's the layout of metadata blocks on disk:
-
-| offset | size          | description    |
-|--------|---------------|----------------|
-| 0x00   | 32 bits       | revision count |
-| 0x04   | 32 bits       | dir size       |
-| 0x08   | 64 bits       | tail pointer   |
-| 0x10   | size-16 bytes | dir entries    |
-| 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.
-Comparison between revision counts must use sequence comparison since the
-revision counts may overflow.
-
-**Dir size** - Size in bytes of the contents in the current metadata block,
-including the metadata-pair metadata. Additionally, the highest bit of the
-dir size may be set to indicate that the directory's contents continue on the
-next metadata-pair pointed to by the tail pointer.
-
-**Tail pointer** - Pointer to the next metadata-pair in the filesystem.
-A null pair-pointer (0xffffffff, 0xffffffff) indicates the end of the list.
-If the highest bit in the dir size is set, this points to the next
-metadata-pair in the current directory, otherwise it points to an arbitrary
-metadata-pair. Starting with the superblock, the tail-pointers form a
-linked-list containing all metadata-pairs in the filesystem.
-
-**CRC** - 32 bit CRC used to detect corruption from power-lost, from block
-end-of-life, or just from noise on the storage bus. The CRC is appended to
-the end of each metadata-block. The littlefs uses the standard CRC-32, which
-uses a polynomial of 0x04c11db7, initialized with 0xffffffff.
-
-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
-
-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"
-00000020: 08 00 06 07 00 00 00 06 00 00 00 63 6f 66 66 65  ...........coffe
-00000030: 65 22 08 00 04 09 00 00 00 08 00 00 00 73 6f 64  e"...........sod
-00000040: 61 22 08 00 05 1d 00 00 00 1c 00 00 00 6d 69 6c  a"...........mil
-00000050: 6b 31 22 08 00 05 1f 00 00 00 1e 00 00 00 6d 69  k1"...........mi
-00000060: 6c 6b 32 22 08 00 05 21 00 00 00 20 00 00 00 6d  lk2"...!... ...m
-00000070: 69 6c 6b 33 22 08 00 05 23 00 00 00 22 00 00 00  ilk3"...#..."...
-00000080: 6d 69 6c 6b 34 22 08 00 05 25 00 00 00 24 00 00  milk4"...%...$..
-00000090: 00 6d 69 6c 6b 35 06 31 6e c8                    .milk5.1n.
-```
-
-A note about the tail pointer linked-list: Normally, this linked-list is
-threaded through the entire filesystem. However, after power-loss this
-linked-list may become out of sync with the rest of the filesystem.
-- The linked-list may contain a directory that has actually been removed
-- The linked-list may contain a metadata pair that has not been updated after
-  a block in the pair has gone bad.
-
-The threaded linked-list must be checked for these errors before it can be
-used reliably. Fortunately, the threaded linked-list can simply be ignored
-if littlefs is mounted read-only.
-
-## Entries
-
-Each metadata block contains a series of entries that follow a standard
-layout. An entry contains the type of the entry, along with a section for
-entry-specific data, attributes, and a name.
-
-Here's the layout of entries on disk:
-
-| offset  | size                   | description                |
-|---------|------------------------|----------------------------|
-| 0x0     | 8 bits                 | entry type                 |
-| 0x1     | 8 bits                 | entry length               |
-| 0x2     | 8 bits                 | attribute length           |
-| 0x3     | 8 bits                 | name length                |
-| 0x4     | entry length bytes     | entry-specific data        |
-| 0x4+e   | attribute length bytes | system-specific attributes |
-| 0x4+e+a | name length bytes      | entry name                 |
-
-**Entry type** - Type of the entry, currently this is limited to the following:
-- 0x11 - file entry
-- 0x22 - directory entry
-- 0x2e - superblock entry
-
-Additionally, the type is broken into two 4 bit nibbles, with the upper nibble
-specifying the type's data structure used when scanning the filesystem. The
-lower nibble clarifies the type further when multiple entries share the same
-data structure.
-
-The highest bit is reserved for marking the entry as "moved". If an entry
-is marked as "moved", the entry may also exist somewhere else in the
-filesystem. If the entry exists elsewhere, this entry must be treated as
-though it does not exist.
-
-**Entry length** - Length in bytes of the entry-specific data. This does
-not include the entry type size, attributes, or name. The full size in bytes
-of the entry is 4 + entry length + attribute length + name length.
-
-**Attribute length** - Length of system-specific attributes in bytes. Since
-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,
-although most systems will probably only support ASCII. Entry names can not
-contain '/' and can not be '.' or '..' as these are a part of the syntax of
-filesystem paths.
-
-Here's an example of a simple entry stored on disk:
-```
-(8 bits)   entry type       = file     (0x11)
-(8 bits)   entry length     = 8 bytes  (0x08)
-(8 bits)   attribute length = 0 bytes  (0x00)
-(8 bits)   name length      = 12 bytes (0x0c)
-(8 bytes)  entry data       = 05 00 00 00 20 00 00 00
-(12 bytes) entry name       = smallavacado
-
-00000000: 11 08 00 0c 05 00 00 00 20 00 00 00 73 6d 61 6c  ........ ...smal
-00000010: 6c 61 76 61 63 61 64 6f                          lavacado
-```
-
-## Superblock
-
-The superblock is the anchor for the littlefs. The superblock is stored as
-a metadata pair containing a single superblock entry. It is through the
-superblock that littlefs can access the rest of the filesystem.
-
-The superblock can always be found in blocks 0 and 1, however fetching the
-superblock requires knowing the block size. The block size can be guessed by
-searching the beginning of disk for the string "littlefs", although currently
-the filesystems relies on the user providing the correct block size.
-
-The superblock is the most valuable block in the filesystem. It is updated
-very rarely, only during format or when the root directory must be moved. It
-is encouraged to always write out both superblock pairs even though it is not
-required.
-
-Here's the layout of the superblock entry:
-
-| offset | size                   | description                            |
-|--------|------------------------|----------------------------------------|
-| 0x00   | 8 bits                 | entry type (0x2e for superblock entry) |
-| 0x01   | 8 bits                 | entry length (20 bytes)                |
-| 0x02   | 8 bits                 | attribute length                       |
-| 0x03   | 8 bits                 | name length (8 bytes)                  |
-| 0x04   | 64 bits                | root directory                         |
-| 0x0c   | 32 bits                | block size                             |
-| 0x10   | 32 bits                | block count                            |
-| 0x14   | 32 bits                | version                                |
-| 0x18   | attribute length bytes | system-specific attributes             |
-| 0x18+a | 8 bytes                | magic string ("littlefs")              |
-
-**Root directory** - Pointer to the root directory's metadata pair.
-
-**Block size** - Size of the logical block size used by the filesystem.
-
-**Block count** - Number of blocks in the filesystem.
-
-**Version** - The littlefs version encoded as a 32 bit value. The upper 16 bits
-encodes the major version, which is incremented when a breaking-change is
-introduced in the filesystem specification. The lower 16 bits encodes the
-minor version, which is incremented when a backwards-compatible change is
-introduced. Non-standard Attribute changes do not change the version. This
-specification describes version 1.1 (0x00010001), which is the first version
-of littlefs.
-
-**Magic string** - The magic string "littlefs" takes the place of an entry
-name.
-
-Here's an example of a complete superblock:
-```
-(32 bits) revision count   = 3                    (0x00000003)
-(32 bits) dir size         = 52 bytes, end of dir (0x00000034)
-(64 bits) tail pointer     = 3, 2                 (0x00000003, 0x00000002)
-(8 bits)  entry type       = superblock           (0x2e)
-(8 bits)  entry length     = 20 bytes             (0x14)
-(8 bits)  attribute length = 0 bytes              (0x00)
-(8 bits)  name length      = 8 bytes              (0x08)
-(64 bits) root directory   = 3, 2                 (0x00000003, 0x00000002)
-(32 bits) block size       = 512 bytes            (0x00000200)
-(32 bits) block count      = 1024 blocks          (0x00000400)
-(32 bits) version          = 1.1                  (0x00010001)
-(8 bytes) magic string     = littlefs
-(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  ................
-00000020: 00 04 00 00 01 00 01 00 6c 69 74 74 6c 65 66 73  ........littlefs
-00000030: fa 74 0b c5                                      .t..
-```
-
-## Directory entries
-
-Directories are stored in entries with a pointer to the first metadata pair
-in the directory. Keep in mind that a directory may be composed of multiple
-metadata pairs connected by the tail pointer when the highest bit in the dir
-size is set.
+providing a backup during erase cycles in case power is lost. These two blocks
+are not necessarily sequential and may be anywhere on disk, so a "pointer" to a
+metadata pair is stored as two block pointers.
 
 
-Here's the layout of a directory entry:
-
-| offset | size                   | description                             |
-|--------|------------------------|-----------------------------------------|
-| 0x0    | 8 bits                 | entry type (0x22 for directory entries) |
-| 0x1    | 8 bits                 | entry length (8 bytes)                  |
-| 0x2    | 8 bits                 | attribute length                        |
-| 0x3    | 8 bits                 | name length                             |
-| 0x4    | 64 bits                | directory pointer                       |
-| 0xc    | attribute length bytes | system-specific attributes              |
-| 0xc+a  | name length bytes      | directory name                          |
+On top of this, each metadata block behaves as an appendable log, containing a
+variable number of commits. Commits can be appended to the metadata log in
+order to update the metadata without requiring an erase cycles. Note that
+successive commits may supersede the metadata in previous commits. Only the
+most recent metadata should be considered valid.
+
+The high-level layout of a metadata block is fairly simple:
+
+```
+  .---------------------------------------.
+.-|  revision count   |      entries      |  \
+| |-------------------+                   |  |
+| |                                       |  |
+| |                                       |  +-- 1st commit
+| |                                       |  |
+| |                   +-------------------|  |
+| |                   |        CRC        |  /
+| |-------------------+-------------------|
+| |                entries                |  \
+| |                                       |  |
+| |                                       |  +-- 2nd commit
+| |    +-------------------+--------------|  |
+| |    |        CRC        |    padding   |  /
+| |----+-------------------+--------------|
+| |                entries                |  \
+| |                                       |  |
+| |                                       |  +-- 3rd commit
+| |         +-------------------+---------|  |
+| |         |        CRC        |         |  /
+| |---------+-------------------+         |
+| |           unwritten storage           |  more commits
+| |                                       |       |
+| |                                       |       v
+| |                                       |
+| |                                       |
+| '---------------------------------------'
+'---------------------------------------'
+```
+
+Each metadata block contains a 32-bit revision count followed by a number of
+commits. Each commit contains a variable number of metadata entries followed
+by a 32-bit CRC.
+
+Note also that entries aren't necessarily word-aligned. This allows us to
+store metadata more compactly, however we can only write to addresses that are
+aligned to our program block size. This means each commit may have padding for
+alignment.
+
+Metadata block fields:
+
+1. **Revision count (32-bits)** - Incremented every erase cycle. If both blocks
+   contain valid commits, only the block with the most recent revision count
+   should be used. Sequence comparison must be used to avoid issues with
+   integer overflow.
+
+2. **CRC (32-bits)** - Detects corruption from power-loss or other write
+   issues.  Uses a CRC-32 with a polynomial of `0x04c11db7` initialized
+   with `0xffffffff`.
+
+Entries themselves are stored as a 32-bit tag followed by a variable length
+blob of data. But exactly how these tags are stored is a little bit tricky.
+
+Metadata blocks support both forward and backward iteration. In order to do
+this without duplicating the space for each tag, neighboring entries have their
+tags XORed together, starting with `0xffffffff`.
 
 
-**Directory pointer** - Pointer to the first metadata pair in the directory.
-
-Here's an example of a directory entry:
 ```
 ```
-(8 bits)  entry type        = directory (0x22)
-(8 bits)  entry length      = 8 bytes   (0x08)
-(8 bits)  attribute length  = 0 bytes   (0x00)
-(8 bits)  name length       = 3 bytes   (0x03)
-(64 bits) directory pointer = 5, 4      (0x00000005, 0x00000004)
-(3 bytes) name              = tea
-
-00000000: 22 08 00 03 05 00 00 00 04 00 00 00 74 65 61     "...........tea
-```
-
-## File entries
-
-Files are stored in entries with a pointer to the head of the file and the
-size of the file. This is enough information to determine the state of the
-CTZ skip-list that is being referenced.
-
-How files are actually stored on disk is a bit complicated. The full
-explanation of CTZ skip-lists can be found in [DESIGN.md](DESIGN.md#ctz-skip-lists).
-
-A terribly quick summary: For every nth block where n is divisible by 2^x,
-the block contains a pointer to block n-2^x. These pointers are stored in
-increasing order of x in each block of the file preceding the data in the
+ Forward iteration                        Backward iteration
+
+.-------------------.  0xffffffff        .-------------------.
+|  revision count   |      |             |  revision count   |
+|-------------------|      v             |-------------------|
+|      tag ~A       |---> xor -> tag A   |      tag ~A       |---> xor -> 0xffffffff
+|-------------------|      |             |-------------------|      ^
+|       data A      |      |             |       data A      |      |
+|                   |      |             |                   |      |
+|                   |      |             |                   |      |
+|-------------------|      v             |-------------------|      |
+|      tag AxB      |---> xor -> tag B   |      tag AxB      |---> xor -> tag A
+|-------------------|      |             |-------------------|      ^
+|       data B      |      |             |       data B      |      |
+|                   |      |             |                   |      |
+|                   |      |             |                   |      |
+|-------------------|      v             |-------------------|      |
+|      tag BxC      |---> xor -> tag C   |      tag BxC      |---> xor -> tag B
+|-------------------|                    |-------------------|      ^
+|       data C      |                    |       data C      |      |
+|                   |                    |                   |    tag C
+|                   |                    |                   |
+|                   |                    |                   |
+'-------------------'                    '-------------------'
+```
+
+One last thing to note before we get into the details around tag encoding. Each
+tag contains a valid bit used to indicate if the tag and containing commit is
+valid. This valid bit is the first bit found in the tag and the commit and can
+be used to tell if we've attempted to write to the remaining space in the
 block.
 block.
 
 
-The maximum number of pointers in a block is bounded by the maximum file size
-divided by the block size. With 32 bits for file size, this results in a
-minimum block size of 104 bytes.
+Here's a more complete example of metadata block containing 4 entries:
 
 
-Here's the layout of a file entry:
+```
+  .---------------------------------------.
+.-|  revision count   |      tag ~A       |        \
+| |-------------------+-------------------|        |
+| |                 data A                |        |
+| |                                       |        |
+| |-------------------+-------------------|        |
+| |      tag AxB      |       data B      | <--.   |
+| |-------------------+                   |    |   |
+| |                                       |    |   +-- 1st commit
+| |         +-------------------+---------|    |   |
+| |         |      tag BxC      |         | <-.|   |
+| |---------+-------------------+         |   ||   |
+| |                 data C                |   ||   |
+| |                                       |   ||   |
+| |-------------------+-------------------|   ||   |
+| |     tag CxCRC     |        CRC        |   ||   /
+| |-------------------+-------------------|   ||
+| |     tag CRCxA'    |      data A'      |   ||   \
+| |-------------------+                   |   ||   |
+| |                                       |   ||   |
+| |              +-------------------+----|   ||   +-- 2nd commit
+| |              |     tag CRCxA'    |    |   ||   |
+| |--------------+-------------------+----|   ||   |
+| | CRC          |        padding         |   ||   /
+| |--------------+----+-------------------|   ||
+| |     tag CRCxA''   |      data A''     | <---.  \
+| |-------------------+                   |   |||  |
+| |                                       |   |||  |
+| |         +-------------------+---------|   |||  |
+| |         |     tag A''xD     |         | < |||  |
+| |---------+-------------------+         |  ||||  +-- 3rd commit
+| |                data D                 |  ||||  |
+| |                             +---------|  ||||  |
+| |                             |   tag Dx|  ||||  |
+| |---------+-------------------+---------|  ||||  |
+| |CRC      |        CRC        |         |  ||||  /
+| |---------+-------------------+         |  ||||
+| |           unwritten storage           |  ||||  more commits
+| |                                       |  ||||       |
+| |                                       |  ||||       v
+| |                                       |  ||||
+| |                                       |  ||||
+| '---------------------------------------'  ||||
+'---------------------------------------'    |||'- most recent A
+                                             ||'-- most recent B
+                                             |'--- most recent C
+                                             '---- most recent D
+```
 
 
-| offset | size                   | description                        |
-|--------|------------------------|------------------------------------|
-| 0x0    | 8 bits                 | entry type (0x11 for file entries) |
-| 0x1    | 8 bits                 | entry length (8 bytes)             |
-| 0x2    | 8 bits                 | attribute length                   |
-| 0x3    | 8 bits                 | name length                        |
-| 0x4    | 32 bits                | file head                          |
-| 0x8    | 32 bits                | file size                          |
-| 0xc    | attribute length bytes | system-specific attributes         |
-| 0xc+a  | name length bytes      | directory name                     |
+## Metadata tags
 
 
-**File head** - Pointer to the block that is the head of the file's CTZ
-skip-list.
+So in littlefs, 32-bit tags describe every type of metadata. And this means
+_every_ type of metadata, including file entries, directory fields, and
+global state. Even the CRCs used to mark the end of commits get their own tag.
 
 
-**File size** - Size of file in bytes.
+Because of this, the tag format contains some densely packed information. Note
+that there are multiple levels of types which break down into more info:
 
 
-Here's an example of a file entry:
 ```
 ```
-(8 bits)   entry type       = file     (0x11)
-(8 bits)   entry length     = 8 bytes  (0x08)
-(8 bits)   attribute length = 0 bytes  (0x00)
-(8 bits)   name length      = 12 bytes (0x03)
-(32 bits)  file head        = 543      (0x0000021f)
-(32 bits)  file size        = 256 KB   (0x00040000)
-(12 bytes) name             = largeavacado
+[----            32             ----]
+[1|--  11   --|--  10  --|--  10  --]
+ ^.     ^     .     ^          ^- length
+ |.     |     .     '------------ id
+ |.     '-----.------------------ type (type3)
+ '.-----------.------------------ valid bit
+  [-3-|-- 8 --]
+    ^     ^- chunk
+    '------- type (type1)
+```
+
+
+Before we go further, there's one important thing to note. These tags are
+**not** stored in little-endian. Tags stored in commits are actually stored
+in big-endian (and is the only thing in littlefs stored in big-endian). This
+little bit of craziness comes from the fact that the valid bit must be the
+first bit in a commit, and when converted to little-endian, the valid bit finds
+itself in byte 4. We could restructure the tag to store the valid bit lower,
+but, because none of the fields are byte-aligned, this would be more
+complicated than just storing the tag in big-endian.
+
+Another thing to note is that both the tags `0x00000000` and `0xffffffff` are
+invalid and can be used for null values.
+
+Metadata tag fields:
+
+1. **Valid bit (1-bit)** - Indicates if the tag is valid.
+
+2. **Type3 (11-bits)** - Type of the tag. This field is broken down further
+   into a 3-bit abstract type and an 8-bit chunk field. Note that the value
+   `0x000` is invalid and not assigned a type.
 
 
-00000000: 11 08 00 0c 1f 02 00 00 00 00 04 00 6c 61 72 67  ............larg
-00000010: 65 61 76 61 63 61 64 6f                          eavacado
+3. **Type1 (3-bits)** - Abstract type of the tag. Groups the tags into
+   8 categories that facilitate bitmasked lookups.
+
+4. **Chunk (8-bits)** - Chunk field used for various purposes by the different
+   abstract types.  type1+chunk+id form a unique identifier for each tag in the
+   metadata block.
+
+5. **Id (10-bits)** - File id associated with the tag. Each file in a metadata
+   block gets a unique id which is used to associate tags with that file. The
+   special value `0x3ff` is used for any tags that are not associated with a
+   file, such as directory and global metadata.
+
+6. **Length (10-bits)** - Length of the data in bytes. The special value
+   `0x3ff` indicates that this tag has been deleted.
+
+## Metadata types
+
+What follows is an exhaustive list of metadata in littlefs.
+
+---
+#### `0x401` LFS_TYPE_CREATE
+
+Creates a new file with this id. Note that files in a metadata block
+don't necessarily need a create tag. All a create does is move over any
+files using this id. In this sense a create is similar to insertion into
+an imaginary array of files.
+
+The create and delete tags allow littlefs to keep files in a directory
+ordered alphabetically by filename.
+
+---
+#### `0x4ff` LFS_TYPE_DELETE
+
+Deletes the file with this id. An inverse to create, this tag moves over
+any files neighboring this id similar to a deletion from an imaginary
+array of files.
+
+---
+#### `0x0xx` LFS_TYPE_NAME
+
+Associates the id with a file name and file type.
+
+The data contains the file name stored as an ASCII string (may be expanded to
+UTF8 in the future).
+
+The chunk field in this tag indicates an 8-bit file type which can be one of
+the following.
+
+Currently, the name tag must precede any other tags associated with the id and
+can not be reassigned without deleting the file.
+
+Layout of the name tag:
+
+```
+        tag                          data
+[--      32      --][---        variable length        ---]
+[1| 3| 8 | 10 | 10 ][---            (size)             ---]
+ ^  ^  ^    ^    ^- size               ^- file name
+ |  |  |    '------ id
+ |  |  '----------- file type
+ |  '-------------- type1 (0x0)
+ '----------------- valid bit
 ```
 ```
 
 
-## Entry attributes
+Name fields:
+
+1. **file type (8-bits)** - Type of the file.
+
+2. **file name** - File name stored as an ASCII string.
+
+---
+#### `0x001` LFS_TYPE_REG
 
 
-Each dir entry can have up to 256 bytes of system-specific attributes. Since
-these attributes are system-specific, they may not be portable between
-different systems. For this reason, all attributes must be optional. A minimal
-littlefs driver must be able to get away with supporting no attributes at all.
+Initializes the id + name as a regular file.
 
 
-For some level of portability, littlefs has a simple scheme for attributes.
-Each attribute is prefixes with an 8-bit type that indicates what the attribute
-is. The length of attributes may also be determined from this type. Attributes
-in an entry should be sorted based on portability, since attribute parsing
-will end when it hits the first attribute it does not understand.
+How each file is stored depends on its struct tag, which is described below.
 
 
-Each system should choose a 4-bit value to prefix all attribute types with to
-avoid conflicts with other systems. Additionally, littlefs drivers that support
-attributes should provide a "ignore attributes" flag to users in case attribute
-conflicts do occur.
+---
+#### `0x002` LFS_TYPE_DIR
 
 
-Attribute types prefixes with 0x0 and 0xf are currently reserved for future
-standard attributes. Standard attributes will be added to this document in
-that case.
+Initializes the id + name as a directory.
 
 
-Here's an example of non-standard time attribute:
+Directories in littlefs are stored on disk as a linked-list of metadata pairs,
+each pair containing any number of files in alphabetical order. A pointer to
+the directory is stored in the struct tag, which is described below.
+
+---
+#### `0x0ff` LFS_TYPE_SUPERBLOCK
+
+Initializes the id as a superblock entry.
+
+The superblock entry is a special entry used to store format-time configuration
+and identify the filesystem.
+
+The name is a bit of a misnomer. While the superblock entry serves the same
+purpose as a superblock found in other filesystems, in littlefs the superblock
+does not get a dedicated block. Instead, the superblock entry is duplicated
+across a linked-list of metadata pairs rooted on the blocks 0 and 1. The last
+metadata pair doubles as the root directory of the filesystem.
+
+```
+ .--------.  .--------.  .--------.  .--------.  .--------.
+.| super  |->| super  |->| super  |->| super  |->| file B |
+|| block  | || block  | || block  | || block  | || file C |
+||        | ||        | ||        | || file A | || file D |
+|'--------' |'--------' |'--------' |'--------' |'--------'
+'--------'  '--------'  '--------'  '--------'  '--------'
+
+\----------------+----------------/ \----------+----------/
+          superblock pairs               root directory
 ```
 ```
-(8 bits)  attribute type  = time       (0xc1)
-(72 bits) time in seconds = 1506286115 (0x0059c81a23)
 
 
-00000000: c1 23 1a c8 59 00                                .#..Y.
+The filesystem starts with only the root directory. The superblock metadata
+pairs grow every time the root pair is compacted in order to prolong the
+life of the device exponentially.
+
+The contents of the superblock entry are stored in a name tag with the
+superblock type and an inline-struct tag. The name tag contains the magic
+string "littlefs", while the inline-struct tag contains version and
+configuration information.
+
+Layout of the superblock name tag and inline-struct tag:
+
 ```
 ```
+        tag                          data
+[--      32      --][--      32      --|--      32      --]
+[1|- 11 -| 10 | 10 ][---              64               ---]
+ ^    ^     ^    ^- size (8)           ^- magic string ("littlefs")
+ |    |     '------ id (0)
+ |    '------------ type (0x0ff)
+ '----------------- valid bit
+
+        tag                          data
+[--      32      --][--      32      --|--      32      --|--      32      --]
+[1|- 11 -| 10 | 10 ][--      32      --|--      32      --|--      32      --]
+ ^    ^     ^    ^            ^- version         ^- block size      ^- block count
+ |    |     |    |  [--      32      --|--      32      --|--      32      --]
+ |    |     |    |  [--      32      --|--      32      --|--      32      --]
+ |    |     |    |            ^- name max        ^- file max        ^- attr max
+ |    |     |    '- size (24)
+ |    |     '------ id (0)
+ |    '------------ type (0x201)
+ '----------------- valid bit
+```
+
+Superblock fields:
+
+1. **Magic string (8-bytes)** - Magic string indicating the presence of
+   littlefs on the device. Must be the string "littlefs".
+
+2. **Version (32-bits)** - The version of littlefs at format time. The version
+   is encoded in a 32-bit value with the upper 16-bits containing the major
+   version, and the lower 16-bits containing the minor version.
+
+   This specification describes version 2.0 (`0x00020000`).
+
+3. **Block size (32-bits)** - Size of the logical block size used by the
+   filesystem in bytes.
+
+4. **Block count (32-bits)** - Number of blocks in the filesystem.
+
+5. **Name max (32-bits)** - Maximum size of file names in bytes.
+
+6. **File max (32-bits)** - Maximum size of files in bytes.
+
+7. **Attr max (32-bits)** - Maximum size of file attributes in bytes.
+
+The superblock must always be the first entry (id 0) in a metadata pair as well
+as be the first entry written to the block. This means that the superblock
+entry can be read from a device using offsets alone.
+
+---
+#### `0x2xx` LFS_TYPE_STRUCT
+
+Associates the id with an on-disk data structure.
 
 
-Here's an example of non-standard permissions attribute:
+The exact layout of the data depends on the data structure type stored in the
+chunk field and can be one of the following.
+
+Any type of struct supersedes all other structs associated with the id. For
+example, appending a ctz-struct replaces an inline-struct on the same file.
+
+---
+#### `0x200` LFS_TYPE_DIRSTRUCT
+
+Gives the id a directory data structure.
+
+Directories in littlefs are stored on disk as a linked-list of metadata pairs,
+each pair containing any number of files in alphabetical order.
+
+```
+     |
+     v
+ .--------.  .--------.  .--------.  .--------.  .--------.  .--------.
+.| file A |->| file D |->| file G |->| file I |->| file J |->| file M |
+|| file B | || file E | || file H | ||        | || file K | || file N |
+|| file C | || file F | ||        | ||        | || file L | ||        |
+|'--------' |'--------' |'--------' |'--------' |'--------' |'--------'
+'--------'  '--------'  '--------'  '--------'  '--------'  '--------'
 ```
 ```
-(8 bits)  attribute type  = permissions (0xc2)
-(16 bits) permission bits = rw-rw-r--   (0x01b4)
 
 
-00000000: c2 b4 01                                         ...
+The dir-struct tag contains only the pointer to the first metadata-pair in the
+directory. The directory size is not known without traversing the directory.
+
+The pointer to the next metadata-pair in the directory is stored in a tail tag,
+which is described below.
+
+Layout of the dir-struct tag:
+
 ```
 ```
+        tag                          data
+[--      32      --][--      32      --|--      32      --]
+[1|- 11 -| 10 | 10 ][---              64               ---]
+ ^    ^     ^    ^- size (8)           ^- metadata pair
+ |    |     '------ id
+ |    '------------ type (0x200)
+ '----------------- valid bit
+```
+
+Dir-struct fields:
+
+1. **Metadata pair (8-bytes)** - Pointer to the first metadata-pair
+   in the directory.
+
+---
+#### `0x201` LFS_TYPE_INLINESTRUCT
+
+Gives the id an inline data structure.
+
+Inline structs store small files that can fit in the metadata pair. In this
+case, the file data is stored directly in the tag's data area.
+
+Layout of the inline-struct tag:
 
 
-Here's what a dir entry may look like with these attributes:
 ```
 ```
-(8 bits)   entry type       = file         (0x11)
-(8 bits)   entry length     = 8 bytes      (0x08)
-(8 bits)   attribute length = 9 bytes      (0x09)
-(8 bits)   name length      = 12 bytes     (0x0c)
-(8 bytes)  entry data       = 05 00 00 00 20 00 00 00
-(8 bits)   attribute type   = time         (0xc1)
-(72 bits)  time in seconds  = 1506286115   (0x0059c81a23)
-(8 bits)   attribute type   = permissions  (0xc2)
-(16 bits)  permission bits  = rw-rw-r--    (0x01b4)
-(12 bytes) entry name       = smallavacado
+        tag                          data
+[--      32      --][---        variable length        ---]
+[1|- 11 -| 10 | 10 ][---            (size)             ---]
+ ^    ^     ^    ^- size               ^- inline data
+ |    |     '------ id
+ |    '------------ type (0x201)
+ '----------------- valid bit
+```
+
+Inline-struct fields:
+
+1. **Inline data** - File data stored directly in the metadata-pair.
+
+---
+#### `0x202` LFS_TYPE_CTZSTRUCT
+
+Gives the id a CTZ skip-list data structure.
+
+CTZ skip-lists store files that can not fit in the metadata pair. These files
+are stored in a skip-list in reverse, with a pointer to the head of the
+skip-list. Note that the head of the skip-list and the file size is enough
+information to read the file.
+
+How exactly CTZ skip-lists work is a bit complicated. A full explanation can be
+found in the [DESIGN.md](DESIGN.md#ctz-skip-lists).
+
+A quick summary: For every _n_&zwj;th block where _n_ is divisible by
+2&zwj;_&#739;_, that block contains a pointer to block _n_-2&zwj;_&#739;_.
+These pointers are stored in increasing order of _x_ in each block of the file
+before the actual data.
+
+```
+                                                               |
+                                                               v
+.--------.  .--------.  .--------.  .--------.  .--------.  .--------.
+| A      |<-| D      |<-| G      |<-| J      |<-| M      |<-| P      |
+| B      |<-| E      |--| H      |<-| K      |--| N      |  | Q      |
+| C      |<-| F      |--| I      |--| L      |--| O      |  |        |
+'--------'  '--------'  '--------'  '--------'  '--------'  '--------'
+  block 0     block 1     block 2     block 3     block 4     block 5
+              1 skip      2 skips     1 skip      3 skips     1 skip
+```
+
+Note that the maximum number of pointers in a block is bounded by the maximum
+file size divided by the block size. With 32 bits for file size, this results
+in a minimum block size of 104 bytes.
+
+Layout of the CTZ-struct tag:
+
+```
+        tag                          data
+[--      32      --][--      32      --|--      32      --]
+[1|- 11 -| 10 | 10 ][--      32      --|--      32      --]
+ ^    ^     ^    ^            ^                  ^- file size
+ |    |     |    |            '-------------------- file head
+ |    |     |    '- size (8)
+ |    |     '------ id
+ |    '------------ type (0x202)
+ '----------------- valid bit
+```
+
+CTZ-struct fields:
+
+1. **File head (32-bits)** - Pointer to the block that is the head of the
+   file's CTZ skip-list.
+
+2. **File size (32-bits)** - Size of the file in bytes.
+
+---
+#### `0x3xx` LFS_TYPE_USERATTR
+
+Attaches a user attribute to an id.
+
+littlefs has a concept of "user attributes". These are small user-provided
+attributes that can be used to store things like timestamps, hashes,
+permissions, etc.
+
+Each user attribute is uniquely identified by an 8-bit type which is stored in
+the chunk field, and the user attribute itself can be found in the tag's data.
+
+There are currently no standard user attributes and a portable littlefs
+implementation should work with any user attributes missing.
+
+Layout of the user-attr tag:
 
 
-00000000: 11 08 09 0c 05 00 00 00 20 00 00 00 c1 23 1a c8  ........ ....#..
-00000010: 59 00 c2 b4 01 73 6d 61 6c 6c 61 76 61 63 61 64  Y....smallavacad
-00000020: 6f                                               o
 ```
 ```
+        tag                          data
+[--      32      --][---        variable length        ---]
+[1| 3| 8 | 10 | 10 ][---            (size)             ---]
+ ^  ^  ^    ^    ^- size               ^- attr data
+ |  |  |    '------ id
+ |  |  '----------- attr type
+ |  '-------------- type1 (0x3)
+ '----------------- valid bit
+```
+
+User-attr fields:
+
+1. **Attr type (8-bits)** - Type of the user attributes.
+
+2. **Attr data** - The data associated with the user attribute.
+
+---
+#### `0x6xx` LFS_TYPE_TAIL
+
+Provides the tail pointer for the metadata pair itself.
+
+The metadata pair's tail pointer is used in littlefs for a linked-list
+containing all metadata pairs. The chunk field contains the type of the tail,
+which indicates if the following metadata pair is a part of the directory
+(hard-tail) or only used to traverse the filesystem (soft-tail).
+
+```
+         .--------.
+        .| dir A  |-.
+        ||softtail| |
+.--------|        |-'
+|       |'--------'
+|       '---|--|-'
+|        .-'    '-------------.
+|       v                      v
+|  .--------.  .--------.  .--------.
+'->| dir B  |->| dir B  |->| dir C  |
+  ||hardtail| ||softtail| ||        |
+  ||        | ||        | ||        |
+  |'--------' |'--------' |'--------'
+  '--------'  '--------'  '--------'
+```
+
+Currently any type supersedes any other preceding tails in the metadata pair,
+but this may change if additional metadata pair state is added.
+
+A note about the metadata pair linked-list: Normally, this linked-list contains
+every metadata pair in the filesystem. However, there are some operations that
+can cause this linked-list to become out of sync if a power-loss were to occur.
+When this happens, littlefs sets the "sync" flag in the global state. How
+exactly this flag is stored is described below.
+
+When the sync flag is set:
+
+1. The linked-list may contain an orphaned directory that has been removed in
+   the filesystem.
+2. The linked-list may contain a metadata pair with a bad block that has been
+   replaced in the filesystem.
+
+If the sync flag is set, the threaded linked-list must be checked for these
+errors before it can be used reliably. Note that the threaded linked-list can
+be ignored if littlefs is mounted read-only.
+
+Layout of the tail tag:
+
+```
+        tag                          data
+[--      32      --][--      32      --|--      32      --]
+[1| 3| 8 | 10 | 10 ][---              64               ---]
+ ^  ^  ^   ^    ^- size (8)            ^- metadata pair
+ |  |  |   '------ id
+ |  |  '---------- tail type
+ |  '------------- type1 (0x6)
+ '---------------- valid bit
+```
+
+Tail fields:
+
+1. **Tail type (8-bits)** - Type of the tail pointer.
+
+2. **Metadata pair (8-bytes)** - Pointer to the next metadata-pair.
+
+---
+#### `0x600` LFS_TYPE_SOFTTAIL
+
+Provides a tail pointer that points to the next metadata pair in the
+filesystem.
+
+In this case, the next metadata pair is not a part of our current directory
+and should only be followed when traversing the entire filesystem.
+
+---
+#### `0x601` LFS_TYPE_HARDTAIL
+
+Provides a tail pointer that points to the next metadata pair in the
+directory.
+
+In this case, the next metadata pair belongs to the current directory. Note
+that because directories in littlefs are sorted alphabetically, the next
+metadata pair should only contain filenames greater than any filename in the
+current pair.
+
+---
+#### `0x7xx` LFS_TYPE_GSTATE
+
+Provides delta bits for global state entries.
+
+littlefs has a concept of "global state". This is a small set of state that
+can be updated by a commit to _any_ metadata pair in the filesystem.
+
+The way this works is that the global state is stored as a set of deltas
+distributed across the filesystem such that the global state can be found by
+the xor-sum of these deltas.
+
+```
+ .--------.  .--------.  .--------.  .--------.  .--------.
+.|        |->| gdelta |->|        |->| gdelta |->| gdelta |
+||        | || 0x23   | ||        | || 0xff   | || 0xce   |
+||        | ||        | ||        | ||        | ||        |
+|'--------' |'--------' |'--------' |'--------' |'--------'
+'--------'  '----|---'  '--------'  '----|---'  '----|---'
+                 v                       v           v
+       0x00 --> xor ------------------> xor ------> xor --> gstate = 0x12
+```
+
+Note that storing globals this way is very expensive in terms of storage usage,
+so any global state should be kept very small.
+
+The size and format of each piece of global state depends on the type, which
+is stored in the chunk field. Currently, the only global state is move state,
+which is outlined below.
+
+---
+#### `0x7ff` LFS_TYPE_MOVESTATE
+
+Provides delta bits for the global move state.
+
+The move state in littlefs is used to store info about operations that could
+cause to filesystem to go out of sync if the power is lost. The operations
+where this could occur is moves of files between metadata pairs and any
+operation that changes metadata pairs on the threaded linked-list.
+
+In the case of moves, the move state contains a tag + metadata pair describing
+the source of the ongoing move. If this tag is non-zero, that means that power
+was lost during a move, and the file exists in two different locations. If this
+happens, the source of the move should be considered deleted, and the move
+should be completed (the source should be deleted) before any other write
+operations to the filesystem.
+
+In the case of operations to the threaded linked-list, a single "sync" bit is
+used to indicate that a modification is ongoing. If this sync flag is set, the
+threaded linked-list will need to be checked for errors before it can be used
+reliably. The exact cases to check for are described above in the tail tag.
+
+Layout of the move state:
+
+```
+        tag                                    data
+[--      32      --][--      32      --|--      32      --|--      32      --]
+[1|- 11 -| 10 | 10 ][1|- 11 -| 10 | 10 |---              64               ---]
+ ^    ^     ^    ^   ^    ^     ^    ^- padding (0)       ^- metadata pair
+ |    |     |    |   |    |     '------ move id
+ |    |     |    |   |    '------------ move type
+ |    |     |    |   '----------------- sync bit
+ |    |     |    |
+ |    |     |    '- size (12)
+ |    |     '------ id (0x3ff)
+ |    '------------ type (0x7ff)
+ '----------------- valid bit
+```
+
+Move state fields:
+
+1. **Sync bit (1-bit)** - Indicates if the metadata pair threaded linked-list
+   is in-sync. If set, the threaded linked-list should be checked for errors.
+
+2. **Move type (11-bits)** - Type of move being performed. Must be either
+   `0x000`, indicating no move, or `0x4ff` indicating the source file should
+   be deleted.
+
+3. **Move id (10-bits)** - The file id being moved.
+
+4. **Metadata pair (8-bytes)** - Pointer to the metadata-pair containing
+   the move.
+
+---
+#### `0x5xx` LFS_TYPE_CRC
+
+Last but not least, the CRC tag marks the end of a commit and provides a
+checksum for any commits to the metadata block.
+
+The first 32-bits of the data contain a CRC-32 with a polynomial of
+`0x04c11db7` initialized with `0xffffffff`. This CRC provides a checksum for
+all metadata since the previous CRC tag, including the CRC tag itself. For
+the first commit, this includes the revision count for the metadata block.
+
+However, the size of the data is not limited to 32-bits. The data field may
+larger to pad the commit to the next program-aligned boundary.
+
+In addition, the CRC tag's chunk field contains a set of flags which can
+change the behaviour of commits. Currently the only flag in use is the lowest
+bit, which determines the expected state of the valid bit for any following
+tags. This is used to guarantee that unwritten storage in a metadata block
+will be detected as invalid.
+
+Layout of the CRC tag:
+
+```
+        tag                                    data
+[--      32      --][--      32      --|---        variable length        ---]
+[1| 3| 8 | 10 | 10 ][--      32      --|---            (size)             ---]
+ ^  ^  ^    ^    ^            ^- crc                      ^- padding
+ |  |  |    |    '- size (12)
+ |  |  |    '------ id (0x3ff)
+ |  |  '----------- valid state
+ |  '-------------- type1 (0x5)
+ '----------------- valid bit
+```
+
+CRC fields:
+
+1. **Valid state (1-bit)** - Indicates the expected value of the valid bit for
+   any tags in the next commit.
+
+2. **CRC (32-bits)** - CRC-32 with a polynomial of `0x04c11db7` initialized
+   with `0xffffffff`.
+
+3. **Padding** - Padding to the next program-aligned boundary. No guarantees
+   are made about the contents.
+
+---

+ 86 - 9
emubd/lfs_emubd.c

@@ -19,6 +19,40 @@
 #include <inttypes.h>
 #include <inttypes.h>
 
 
 
 
+// Emulated block device utils
+static inline void lfs_emubd_tole32(lfs_emubd_t *emu) {
+    emu->cfg.read_size     = lfs_tole32(emu->cfg.read_size);
+    emu->cfg.prog_size     = lfs_tole32(emu->cfg.prog_size);
+    emu->cfg.block_size    = lfs_tole32(emu->cfg.block_size);
+    emu->cfg.block_count   = lfs_tole32(emu->cfg.block_count);
+
+    emu->stats.read_count  = lfs_tole32(emu->stats.read_count);
+    emu->stats.prog_count  = lfs_tole32(emu->stats.prog_count);
+    emu->stats.erase_count = lfs_tole32(emu->stats.erase_count);
+
+    for (unsigned i = 0; i < sizeof(emu->history.blocks) /
+            sizeof(emu->history.blocks[0]); i++) {
+        emu->history.blocks[i] = lfs_tole32(emu->history.blocks[i]);
+    }
+}
+
+static inline void lfs_emubd_fromle32(lfs_emubd_t *emu) {
+    emu->cfg.read_size     = lfs_fromle32(emu->cfg.read_size);
+    emu->cfg.prog_size     = lfs_fromle32(emu->cfg.prog_size);
+    emu->cfg.block_size    = lfs_fromle32(emu->cfg.block_size);
+    emu->cfg.block_count   = lfs_fromle32(emu->cfg.block_count);
+
+    emu->stats.read_count  = lfs_fromle32(emu->stats.read_count);
+    emu->stats.prog_count  = lfs_fromle32(emu->stats.prog_count);
+    emu->stats.erase_count = lfs_fromle32(emu->stats.erase_count);
+
+    for (unsigned i = 0; i < sizeof(emu->history.blocks) /
+            sizeof(emu->history.blocks[0]); i++) {
+        emu->history.blocks[i] = lfs_fromle32(emu->history.blocks[i]);
+    }
+}
+
+
 // Block device emulated on existing filesystem
 // Block device emulated on existing filesystem
 int lfs_emubd_create(const struct lfs_config *cfg, const char *path) {
 int lfs_emubd_create(const struct lfs_config *cfg, const char *path) {
     lfs_emubd_t *emu = cfg->context;
     lfs_emubd_t *emu = cfg->context;
@@ -46,17 +80,31 @@ int lfs_emubd_create(const struct lfs_config *cfg, const char *path) {
     }
     }
 
 
     // Load stats to continue incrementing
     // Load stats to continue incrementing
-    snprintf(emu->child, LFS_NAME_MAX, "stats");
-
+    snprintf(emu->child, LFS_NAME_MAX, ".stats");
     FILE *f = fopen(emu->path, "r");
     FILE *f = fopen(emu->path, "r");
-    if (!f && errno != ENOENT) {
-        return -errno;
+    if (!f) {
+        memset(&emu->stats, 0, sizeof(emu->stats));
+    } else {
+        size_t res = fread(&emu->stats, sizeof(emu->stats), 1, f);
+        lfs_emubd_fromle32(emu);
+        if (res < 1) {
+            return -errno;
+        }
+
+        err = fclose(f);
+        if (err) {
+            return -errno;
+        }
     }
     }
 
 
-    if (errno == ENOENT) {
-        memset(&emu->stats, 0x0, sizeof(emu->stats));
+    // Load history
+    snprintf(emu->child, LFS_NAME_MAX, ".history");
+    f = fopen(emu->path, "r");
+    if (!f) {
+        memset(&emu->history, 0, sizeof(emu->history));
     } else {
     } else {
-        size_t res = fread(&emu->stats, sizeof(emu->stats), 1, f);
+        size_t res = fread(&emu->history, sizeof(emu->history), 1, f);
+        lfs_emubd_fromle32(emu);
         if (res < 1) {
         if (res < 1) {
             return -errno;
             return -errno;
         }
         }
@@ -166,6 +214,13 @@ int lfs_emubd_prog(const struct lfs_config *cfg, lfs_block_t block,
         return -errno;
         return -errno;
     }
     }
 
 
+    // update history and stats
+    if (block != emu->history.blocks[0]) {
+        memcpy(&emu->history.blocks[1], &emu->history.blocks[0],
+                sizeof(emu->history) - sizeof(emu->history.blocks[0]));
+        emu->history.blocks[0] = block;
+    }
+
     emu->stats.prog_count += 1;
     emu->stats.prog_count += 1;
     return 0;
     return 0;
 }
 }
@@ -211,13 +266,15 @@ int lfs_emubd_sync(const struct lfs_config *cfg) {
     lfs_emubd_t *emu = cfg->context;
     lfs_emubd_t *emu = cfg->context;
 
 
     // Just write out info/stats for later lookup
     // Just write out info/stats for later lookup
-    snprintf(emu->child, LFS_NAME_MAX, "config");
+    snprintf(emu->child, LFS_NAME_MAX, ".config");
     FILE *f = fopen(emu->path, "w");
     FILE *f = fopen(emu->path, "w");
     if (!f) {
     if (!f) {
         return -errno;
         return -errno;
     }
     }
 
 
+    lfs_emubd_tole32(emu);
     size_t res = fwrite(&emu->cfg, sizeof(emu->cfg), 1, f);
     size_t res = fwrite(&emu->cfg, sizeof(emu->cfg), 1, f);
+    lfs_emubd_fromle32(emu);
     if (res < 1) {
     if (res < 1) {
         return -errno;
         return -errno;
     }
     }
@@ -227,13 +284,33 @@ int lfs_emubd_sync(const struct lfs_config *cfg) {
         return -errno;
         return -errno;
     }
     }
 
 
-    snprintf(emu->child, LFS_NAME_MAX, "stats");
+    snprintf(emu->child, LFS_NAME_MAX, ".stats");
     f = fopen(emu->path, "w");
     f = fopen(emu->path, "w");
     if (!f) {
     if (!f) {
         return -errno;
         return -errno;
     }
     }
 
 
+    lfs_emubd_tole32(emu);
     res = fwrite(&emu->stats, sizeof(emu->stats), 1, f);
     res = fwrite(&emu->stats, sizeof(emu->stats), 1, f);
+    lfs_emubd_fromle32(emu);
+    if (res < 1) {
+        return -errno;
+    }
+
+    err = fclose(f);
+    if (err) {
+        return -errno;
+    }
+
+    snprintf(emu->child, LFS_NAME_MAX, ".history");
+    f = fopen(emu->path, "w");
+    if (!f) {
+        return -errno;
+    }
+
+    lfs_emubd_tole32(emu);
+    res = fwrite(&emu->history, sizeof(emu->history), 1, f);
+    lfs_emubd_fromle32(emu);
     if (res < 1) {
     if (res < 1) {
         return -errno;
         return -errno;
     }
     }

+ 4 - 0
emubd/lfs_emubd.h

@@ -45,6 +45,10 @@ typedef struct lfs_emubd {
         uint64_t erase_count;
         uint64_t erase_count;
     } stats;
     } stats;
 
 
+    struct {
+        lfs_block_t blocks[4];
+    } history;
+
     struct {
     struct {
         uint32_t read_size;
         uint32_t read_size;
         uint32_t prog_size;
         uint32_t prog_size;

File diff suppressed because it is too large
+ 814 - 536
lfs.c


+ 283 - 138
lfs.h

@@ -21,14 +21,14 @@ extern "C"
 // Software library version
 // Software library version
 // Major (top-nibble), incremented on backwards incompatible changes
 // Major (top-nibble), incremented on backwards incompatible changes
 // Minor (bottom-nibble), incremented on feature additions
 // Minor (bottom-nibble), incremented on feature additions
-#define LFS_VERSION 0x00010007
+#define LFS_VERSION 0x00020000
 #define LFS_VERSION_MAJOR (0xffff & (LFS_VERSION >> 16))
 #define LFS_VERSION_MAJOR (0xffff & (LFS_VERSION >> 16))
 #define LFS_VERSION_MINOR (0xffff & (LFS_VERSION >>  0))
 #define LFS_VERSION_MINOR (0xffff & (LFS_VERSION >>  0))
 
 
 // Version of On-disk data structures
 // Version of On-disk data structures
 // Major (top-nibble), incremented on backwards incompatible changes
 // Major (top-nibble), incremented on backwards incompatible changes
 // Minor (bottom-nibble), incremented on feature additions
 // Minor (bottom-nibble), incremented on feature additions
-#define LFS_DISK_VERSION 0x00010001
+#define LFS_DISK_VERSION 0x00020000
 #define LFS_DISK_VERSION_MAJOR (0xffff & (LFS_DISK_VERSION >> 16))
 #define LFS_DISK_VERSION_MAJOR (0xffff & (LFS_DISK_VERSION >> 16))
 #define LFS_DISK_VERSION_MINOR (0xffff & (LFS_DISK_VERSION >>  0))
 #define LFS_DISK_VERSION_MINOR (0xffff & (LFS_DISK_VERSION >>  0))
 
 
@@ -44,57 +44,98 @@ typedef int32_t  lfs_soff_t;
 
 
 typedef uint32_t lfs_block_t;
 typedef uint32_t lfs_block_t;
 
 
-// Max name size in bytes
+// Maximum name size in bytes, may be redefined to reduce the size of the
+// info struct. Limited to <= 1022. Stored in superblock and must be
+// respected by other littlefs drivers.
 #ifndef LFS_NAME_MAX
 #ifndef LFS_NAME_MAX
 #define LFS_NAME_MAX 255
 #define LFS_NAME_MAX 255
 #endif
 #endif
 
 
-// Max file size in bytes
+// Maximum size of a file in bytes, may be redefined to limit to support other
+// drivers. Limited on disk to <= 4294967296. However, above 2147483647 the
+// functions lfs_file_seek, lfs_file_size, and lfs_file_tell will return
+// incorrect values due to using signed integers. Stored in superblock and
+// must be respected by other littlefs drivers.
 #ifndef LFS_FILE_MAX
 #ifndef LFS_FILE_MAX
 #define LFS_FILE_MAX 2147483647
 #define LFS_FILE_MAX 2147483647
 #endif
 #endif
 
 
+// Maximum size of custom attributes in bytes, may be redefined, but there is
+// no real benefit to using a smaller LFS_ATTR_MAX. Limited to <= 1022.
+#ifndef LFS_ATTR_MAX
+#define LFS_ATTR_MAX 1022
+#endif
+
 // Possible error codes, these are negative to allow
 // Possible error codes, these are negative to allow
 // valid positive return values
 // valid positive return values
 enum lfs_error {
 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_NOTEMPTY = -39,  // Dir is not empty
-    LFS_ERR_BADF     = -9,   // Bad file number
-    LFS_ERR_FBIG     = -27,  // File too large
-    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     = -84,  // 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_FBIG        = -27,  // File too large
+    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_NOATTR      = -61,  // No data/attr available
+    LFS_ERR_NAMETOOLONG = -36,  // File name too long
 };
 };
 
 
 // File types
 // File types
 enum lfs_type {
 enum lfs_type {
-    LFS_TYPE_REG        = 0x11,
-    LFS_TYPE_DIR        = 0x22,
-    LFS_TYPE_SUPERBLOCK = 0x2e,
+    // file types
+    LFS_TYPE_REG            = 0x001,
+    LFS_TYPE_DIR            = 0x002,
+
+    // internally used types
+    LFS_TYPE_SPLICE         = 0x400,
+    LFS_TYPE_NAME           = 0x000,
+    LFS_TYPE_STRUCT         = 0x200,
+    LFS_TYPE_USERATTR       = 0x300,
+    LFS_TYPE_FROM           = 0x100,
+    LFS_TYPE_TAIL           = 0x600,
+    LFS_TYPE_GLOBALS        = 0x700,
+    LFS_TYPE_CRC            = 0x500,
+
+    // internally used type specializations
+    LFS_TYPE_CREATE         = 0x401,
+    LFS_TYPE_DELETE         = 0x4ff,
+    LFS_TYPE_SUPERBLOCK     = 0x0ff,
+    LFS_TYPE_DIRSTRUCT      = 0x200,
+    LFS_TYPE_CTZSTRUCT      = 0x202,
+    LFS_TYPE_INLINESTRUCT   = 0x201,
+    LFS_TYPE_SOFTTAIL       = 0x600,
+    LFS_TYPE_HARDTAIL       = 0x601,
+    LFS_TYPE_MOVESTATE      = 0x7ff,
+
+    // internal chip sources
+    LFS_FROM_NOOP           = 0x000,
+    LFS_FROM_MOVE           = 0x101,
+    LFS_FROM_USERATTRS      = 0x102,
 };
 };
 
 
 // File open flags
 // File open flags
 enum lfs_open_flags {
 enum lfs_open_flags {
     // open flags
     // open flags
-    LFS_O_RDONLY = 1,        // Open a file as read only
-    LFS_O_WRONLY = 2,        // Open a file as write only
-    LFS_O_RDWR   = 3,        // Open a file as read and write
-    LFS_O_CREAT  = 0x0100,   // Create a file if it does not exist
-    LFS_O_EXCL   = 0x0200,   // Fail if a file already exists
-    LFS_O_TRUNC  = 0x0400,   // Truncate the existing file to zero size
-    LFS_O_APPEND = 0x0800,   // Move to end of file on every write
+    LFS_O_RDONLY = 1,         // Open a file as read only
+    LFS_O_WRONLY = 2,         // Open a file as write only
+    LFS_O_RDWR   = 3,         // Open a file as read and write
+    LFS_O_CREAT  = 0x0100,    // Create a file if it does not exist
+    LFS_O_EXCL   = 0x0200,    // Fail if a file already exists
+    LFS_O_TRUNC  = 0x0400,    // Truncate the existing file to zero size
+    LFS_O_APPEND = 0x0800,    // Move to end of file on every write
 
 
     // internally used flags
     // internally used flags
-    LFS_F_DIRTY   = 0x10000, // File does not match storage
-    LFS_F_WRITING = 0x20000, // File has been written since last flush
-    LFS_F_READING = 0x40000, // File has been read since last flush
-    LFS_F_ERRED   = 0x80000, // An error occured during write
+    LFS_F_DIRTY   = 0x010000, // File does not match storage
+    LFS_F_WRITING = 0x020000, // File has been written since last flush
+    LFS_F_READING = 0x040000, // File has been read since last flush
+    LFS_F_ERRED   = 0x080000, // An error occured during write
+    LFS_F_INLINE  = 0x100000, // Currently inlined in directory entry
 };
 };
 
 
 // File seek flags
 // File seek flags
@@ -132,52 +173,68 @@ struct lfs_config {
     // are propogated to the user.
     // are propogated to the user.
     int (*sync)(const struct lfs_config *c);
     int (*sync)(const struct lfs_config *c);
 
 
-    // Minimum size of a block read. This determines the size of read buffers.
-    // This may be larger than the physical read size to improve performance
-    // by caching more of the block device.
+    // Minimum size of a block read. All read operations will be a
+    // multiple of this value.
     lfs_size_t read_size;
     lfs_size_t read_size;
 
 
-    // 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.
+    // Minimum size of a block program. All program operations will be a
+    // multiple of this value.
     lfs_size_t prog_size;
     lfs_size_t prog_size;
 
 
     // Size of an erasable block. This does not impact ram consumption and
     // 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.
-    // Must be a multiple of the program size.
+    // may be larger than the physical erase size. However, non-inlined files
+    // take up at minimum one block. Must be a multiple of the read
+    // and program sizes.
     lfs_size_t block_size;
     lfs_size_t block_size;
 
 
     // Number of erasable blocks on the device.
     // Number of erasable blocks on the device.
     lfs_size_t block_count;
     lfs_size_t block_count;
 
 
-    // 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.
-    lfs_size_t lookahead;
-
-    // Optional, statically allocated read buffer. Must be read sized.
+    // Number of erase cycles before we should move data to another block.
+    // May be zero, in which case no block-level wear-leveling is performed.
+    uint32_t block_cycles;
+
+    // Size of block caches. Each cache buffers a portion of a block in RAM.
+    // The littlefs needs a read cache, a program cache, and one additional
+    // cache per file. Larger caches can improve performance by storing more
+    // data and reducing the number of disk accesses. Must be a multiple of
+    // the read and program sizes, and a factor of the block size.
+    lfs_size_t cache_size;
+
+    // Size of the lookahead buffer in bytes. A larger lookahead buffer
+    // increases the number of blocks found during an allocation pass. The
+    // lookahead buffer is stored as a compact bitmap, so each byte of RAM
+    // can track 8 blocks. Must be a multiple of 4.
+    lfs_size_t lookahead_size;
+
+    // Optional statically allocated read buffer. Must be cache_size.
+    // By default lfs_malloc is used to allocate this buffer.
     void *read_buffer;
     void *read_buffer;
 
 
-    // Optional, statically allocated program buffer. Must be program sized.
+    // Optional statically allocated program buffer. Must be cache_size.
+    // By default lfs_malloc is used to allocate this buffer.
     void *prog_buffer;
     void *prog_buffer;
 
 
-    // Optional, statically allocated lookahead buffer. Must be 1 bit per
-    // lookahead block.
+    // Optional statically allocated lookahead buffer. Must be lookahead_size
+    // and aligned to a 64-bit boundary. By default lfs_malloc is used to
+    // allocate this buffer.
     void *lookahead_buffer;
     void *lookahead_buffer;
 
 
-    // Optional, statically allocated buffer for files. Must be program sized.
-    // If enabled, only one file may be opened at a time.
-    void *file_buffer;
-};
-
-// Optional configuration provided during lfs_file_opencfg
-struct lfs_file_config {
-    // Optional, statically allocated buffer for files. Must be program sized.
-    // If NULL, malloc will be used by default.
-    void *buffer;
+    // Optional upper limit on length of file names in bytes. No downside for
+    // larger names except the size of the info struct which is controlled by
+    // the LFS_NAME_MAX define. Defaults to LFS_NAME_MAX when zero. Stored in
+    // superblock and must be respected by other littlefs drivers.
+    lfs_size_t name_max;
+
+    // Optional upper limit on files in bytes. No downside for larger files
+    // but must be <= LFS_FILE_MAX. Defaults to LFS_FILE_MAX when zero. Stored
+    // in superblock and must be respected by other littlefs drivers.
+    lfs_size_t file_max;
+
+    // Optional upper limit on custom attributes in bytes. No downside for
+    // larger attributes size but must be <= LFS_ATTR_MAX. Defaults to
+    // LFS_ATTR_MAX when zero.
+    lfs_size_t attr_max;
 };
 };
 
 
 // File info structure
 // File info structure
@@ -185,108 +242,149 @@ struct lfs_info {
     // Type of the file, either LFS_TYPE_REG or LFS_TYPE_DIR
     // Type of the file, either LFS_TYPE_REG or LFS_TYPE_DIR
     uint8_t type;
     uint8_t type;
 
 
-    // Size of the file, only valid for REG files
+    // Size of the file, only valid for REG files. Limited to 32-bits.
     lfs_size_t size;
     lfs_size_t size;
 
 
-    // Name of the file stored as a null-terminated string
+    // Name of the file stored as a null-terminated string. Limited to
+    // LFS_NAME_MAX+1, which can be changed by redefining LFS_NAME_MAX to
+    // reduce RAM. LFS_NAME_MAX is stored in superblock and must be
+    // respected by other littlefs drivers.
     char name[LFS_NAME_MAX+1];
     char name[LFS_NAME_MAX+1];
 };
 };
 
 
+// Custom attribute structure, used to describe custom attributes
+// committed atomically during file writes.
+struct lfs_attr {
+    // 8-bit type of attribute, provided by user and used to
+    // identify the attribute
+    uint8_t type;
 
 
-/// littlefs data structures ///
-typedef struct lfs_entry {
-    lfs_off_t off;
+    // Pointer to buffer containing the attribute
+    void *buffer;
+
+    // Size of attribute in bytes, limited to LFS_ATTR_MAX
+    lfs_size_t size;
+};
+
+// Optional configuration provided during lfs_file_opencfg
+struct lfs_file_config {
+    // Optional statically allocated file buffer. Must be cache_size.
+    // By default lfs_malloc is used to allocate this buffer.
+    void *buffer;
+
+    // Optional list of custom attributes related to the file. If the file
+    // is opened with read access, these attributes will be read from disk
+    // during the open call. If the file is opened with write access, the
+    // attributes will be written to disk every file sync or close. This
+    // write occurs atomically with update to the file's contents.
+    //
+    // Custom attributes are uniquely identified by an 8-bit type and limited
+    // to LFS_ATTR_MAX bytes. When read, if the stored attribute is smaller
+    // than the buffer, it will be padded with zeros. If the stored attribute
+    // is larger, then it will be silently truncated. If the attribute is not
+    // found, it will be created implicitly.
+    struct lfs_attr *attrs;
+
+    // Number of custom attributes in the list
+    lfs_size_t attr_count;
+};
 
 
-    struct lfs_disk_entry {
-        uint8_t type;
-        uint8_t elen;
-        uint8_t alen;
-        uint8_t nlen;
-        union {
-            struct {
-                lfs_block_t head;
-                lfs_size_t size;
-            } file;
-            lfs_block_t dir[2];
-        } u;
-    } d;
-} lfs_entry_t;
 
 
+/// internal littlefs data structures ///
 typedef struct lfs_cache {
 typedef struct lfs_cache {
     lfs_block_t block;
     lfs_block_t block;
     lfs_off_t off;
     lfs_off_t off;
+    lfs_size_t size;
     uint8_t *buffer;
     uint8_t *buffer;
 } lfs_cache_t;
 } lfs_cache_t;
 
 
+typedef struct lfs_mdir {
+    lfs_block_t pair[2];
+    uint32_t rev;
+    lfs_off_t off;
+    uint32_t etag;
+    uint16_t count;
+    bool erased;
+    bool split;
+    lfs_block_t tail[2];
+} lfs_mdir_t;
+
+// littlefs directory type
+typedef struct lfs_dir {
+    struct lfs_dir *next;
+    uint16_t id;
+    uint8_t type;
+    lfs_mdir_t m;
+
+    lfs_off_t pos;
+    lfs_block_t head[2];
+} lfs_dir_t;
+
+// littlefs file type
 typedef struct lfs_file {
 typedef struct lfs_file {
     struct lfs_file *next;
     struct lfs_file *next;
-    lfs_block_t pair[2];
-    lfs_off_t poff;
+    uint16_t id;
+    uint8_t type;
+    lfs_mdir_t m;
 
 
-    lfs_block_t head;
-    lfs_size_t size;
+    struct lfs_ctz {
+        lfs_block_t head;
+        lfs_size_t size;
+    } ctz;
 
 
-    const struct lfs_file_config *cfg;
     uint32_t flags;
     uint32_t flags;
     lfs_off_t pos;
     lfs_off_t pos;
     lfs_block_t block;
     lfs_block_t block;
     lfs_off_t off;
     lfs_off_t off;
     lfs_cache_t cache;
     lfs_cache_t cache;
-} lfs_file_t;
-
-typedef struct lfs_dir {
-    struct lfs_dir *next;
-    lfs_block_t pair[2];
-    lfs_off_t off;
-
-    lfs_block_t head[2];
-    lfs_off_t pos;
 
 
-    struct lfs_disk_dir {
-        uint32_t rev;
-        lfs_size_t size;
-        lfs_block_t tail[2];
-    } d;
-} lfs_dir_t;
+    const struct lfs_file_config *cfg;
+} lfs_file_t;
 
 
 typedef struct lfs_superblock {
 typedef struct lfs_superblock {
-    lfs_off_t off;
-
-    struct lfs_disk_superblock {
-        uint8_t type;
-        uint8_t elen;
-        uint8_t alen;
-        uint8_t nlen;
-        lfs_block_t root[2];
-        uint32_t block_size;
-        uint32_t block_count;
-        uint32_t version;
-        char magic[8];
-    } d;
+    uint32_t version;
+    lfs_size_t block_size;
+    lfs_size_t block_count;
+    lfs_size_t name_max;
+    lfs_size_t file_max;
+    lfs_size_t attr_max;
 } lfs_superblock_t;
 } lfs_superblock_t;
 
 
-typedef struct lfs_free {
-    lfs_block_t off;
-    lfs_block_t size;
-    lfs_block_t i;
-    lfs_block_t ack;
-    uint32_t *buffer;
-} lfs_free_t;
-
-// The littlefs type
+// The littlefs filesystem type
 typedef struct lfs {
 typedef struct lfs {
-    const struct lfs_config *cfg;
+    lfs_cache_t rcache;
+    lfs_cache_t pcache;
 
 
     lfs_block_t root[2];
     lfs_block_t root[2];
-    lfs_file_t *files;
-    lfs_dir_t *dirs;
+    struct lfs_mlist {
+        struct lfs_mlist *next;
+        uint16_t id;
+        uint8_t type;
+        lfs_mdir_t m;
+    } *mlist;
+    uint32_t seed;
+
+    struct lfs_gstate {
+        uint32_t tag;
+        lfs_block_t pair[2];
+    } gstate, gpending, gdelta;
+
+    struct lfs_free {
+        lfs_block_t off;
+        lfs_block_t size;
+        lfs_block_t i;
+        lfs_block_t ack;
+        uint32_t *buffer;
+    } free;
 
 
-    lfs_cache_t rcache;
-    lfs_cache_t pcache;
+    const struct lfs_config *cfg;
+    lfs_size_t name_max;
+    lfs_size_t file_max;
+    lfs_size_t attr_max;
 
 
-    lfs_free_t free;
-    bool deorphaned;
-    bool moving;
+#ifdef LFS_MIGRATE
+    struct lfs1 *lfs1;
+#endif
 } lfs_t;
 } lfs_t;
 
 
 
 
@@ -339,6 +437,38 @@ int lfs_rename(lfs_t *lfs, const char *oldpath, const char *newpath);
 // Returns a negative error code on failure.
 // Returns a negative error code on failure.
 int lfs_stat(lfs_t *lfs, const char *path, struct lfs_info *info);
 int lfs_stat(lfs_t *lfs, const char *path, struct lfs_info *info);
 
 
+// Get a custom attribute
+//
+// Custom attributes are uniquely identified by an 8-bit type and limited
+// to LFS_ATTR_MAX bytes. When read, if the stored attribute is smaller than
+// the buffer, it will be padded with zeros. If the stored attribute is larger,
+// then it will be silently truncated. If no attribute is found, the error
+// LFS_ERR_NOATTR is returned and the buffer is filled with zeros.
+//
+// Returns the size of the attribute, or a negative error code on failure.
+// Note, the returned size is the size of the attribute on disk, irrespective
+// of the size of the buffer. This can be used to dynamically allocate a buffer
+// or check for existance.
+lfs_ssize_t lfs_getattr(lfs_t *lfs, const char *path,
+        uint8_t type, void *buffer, lfs_size_t size);
+
+// Set custom attributes
+//
+// Custom attributes are uniquely identified by an 8-bit type and limited
+// to LFS_ATTR_MAX bytes. If an attribute is not found, it will be
+// implicitly created.
+//
+// Returns a negative error code on failure.
+int lfs_setattr(lfs_t *lfs, const char *path,
+        uint8_t type, const void *buffer, lfs_size_t size);
+
+// Removes a custom attribute
+//
+// If an attribute is not found, nothing happens.
+//
+// Returns a negative error code on failure.
+int lfs_removeattr(lfs_t *lfs, const char *path, uint8_t type);
+
 
 
 /// File operations ///
 /// File operations ///
 
 
@@ -448,7 +578,8 @@ int lfs_dir_close(lfs_t *lfs, lfs_dir_t *dir);
 // Read an entry in the directory
 // Read an entry in the directory
 //
 //
 // Fills out the info structure, based on the specified file or directory.
 // Fills out the info structure, based on the specified file or directory.
-// Returns a negative error code on failure.
+// Returns a positive value on success, 0 at the end of directory,
+// or a negative error code on failure.
 int lfs_dir_read(lfs_t *lfs, lfs_dir_t *dir, struct lfs_info *info);
 int lfs_dir_read(lfs_t *lfs, lfs_dir_t *dir, struct lfs_info *info);
 
 
 // Change the position of the directory
 // Change the position of the directory
@@ -473,7 +604,15 @@ lfs_soff_t lfs_dir_tell(lfs_t *lfs, lfs_dir_t *dir);
 int lfs_dir_rewind(lfs_t *lfs, lfs_dir_t *dir);
 int lfs_dir_rewind(lfs_t *lfs, lfs_dir_t *dir);
 
 
 
 
-/// Miscellaneous littlefs specific operations ///
+/// Filesystem-level filesystem operations
+
+// Finds the current size of the filesystem
+//
+// Note: Result is best effort. If files share COW structures, the returned
+// size may be larger than the filesystem actually is.
+//
+// Returns the number of allocated blocks, or a negative error code on failure.
+lfs_ssize_t lfs_fs_size(lfs_t *lfs);
 
 
 // Traverse through all blocks in use by the filesystem
 // Traverse through all blocks in use by the filesystem
 //
 //
@@ -482,16 +621,22 @@ int lfs_dir_rewind(lfs_t *lfs, lfs_dir_t *dir);
 // blocks are in use or how much of the storage is available.
 // blocks are in use or how much of the storage is available.
 //
 //
 // Returns a negative error code on failure.
 // Returns a negative error code on failure.
-int lfs_traverse(lfs_t *lfs, int (*cb)(void*, lfs_block_t), void *data);
+int lfs_fs_traverse(lfs_t *lfs, int (*cb)(void*, lfs_block_t), void *data);
 
 
-// Prunes any recoverable errors that may have occured in the filesystem
+#ifdef LFS_MIGRATE
+// Attempts to migrate a previous version of littlefs
 //
 //
-// Not needed to be called by user unless an operation is interrupted
-// but the filesystem is still mounted. This is already called on first
-// allocation.
+// Behaves similarly to the lfs_format function. Attempts to mount
+// the previous version of littlefs and update the filesystem so it can be
+// mounted with the current version of littlefs.
+//
+// Requires a littlefs object and config struct. This clobbers the littlefs
+// object, and does not leave the filesystem mounted. The config struct must
+// be zeroed for defaults and backwards compatibility.
 //
 //
 // Returns a negative error code on failure.
 // Returns a negative error code on failure.
-int lfs_deorphan(lfs_t *lfs);
+int lfs_migrate(lfs_t *lfs, const struct lfs_config *cfg);
+#endif
 
 
 
 
 #ifdef __cplusplus
 #ifdef __cplusplus

+ 5 - 3
lfs_util.c

@@ -11,7 +11,7 @@
 
 
 
 
 // Software CRC implementation with small lookup table
 // Software CRC implementation with small lookup table
-void lfs_crc(uint32_t *restrict crc, const void *buffer, size_t size) {
+uint32_t lfs_crc(uint32_t crc, const void *buffer, size_t size) {
     static const uint32_t rtable[16] = {
     static const uint32_t rtable[16] = {
         0x00000000, 0x1db71064, 0x3b6e20c8, 0x26d930ac,
         0x00000000, 0x1db71064, 0x3b6e20c8, 0x26d930ac,
         0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c,
         0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c,
@@ -22,9 +22,11 @@ void lfs_crc(uint32_t *restrict crc, const void *buffer, size_t size) {
     const uint8_t *data = buffer;
     const uint8_t *data = buffer;
 
 
     for (size_t i = 0; i < size; i++) {
     for (size_t i = 0; i < size; i++) {
-        *crc = (*crc >> 4) ^ rtable[(*crc ^ (data[i] >> 0)) & 0xf];
-        *crc = (*crc >> 4) ^ rtable[(*crc ^ (data[i] >> 4)) & 0xf];
+        crc = (crc >> 4) ^ rtable[(crc ^ (data[i] >> 0)) & 0xf];
+        crc = (crc >> 4) ^ rtable[(crc ^ (data[i] >> 4)) & 0xf];
     }
     }
+
+    return crc;
 }
 }
 
 
 
 

+ 39 - 5
lfs_util.h

@@ -11,8 +11,8 @@
 // LFS_CONFIG as a header file to include (-DLFS_CONFIG=lfs_config.h).
 // LFS_CONFIG as a header file to include (-DLFS_CONFIG=lfs_config.h).
 //
 //
 // If LFS_CONFIG is used, none of the default utils will be emitted and must be
 // If LFS_CONFIG is used, none of the default utils will be emitted and must be
-// provided by the config file. To start I would suggest copying lfs_util.h and
-// modifying as needed.
+// provided by the config file. To start, I would suggest copying lfs_util.h
+// and modifying as needed.
 #ifdef LFS_CONFIG
 #ifdef LFS_CONFIG
 #define LFS_STRINGIZE(x) LFS_STRINGIZE2(x)
 #define LFS_STRINGIZE(x) LFS_STRINGIZE2(x)
 #define LFS_STRINGIZE2(x) #x
 #define LFS_STRINGIZE2(x) #x
@@ -23,6 +23,7 @@
 #include <stdint.h>
 #include <stdint.h>
 #include <stdbool.h>
 #include <stdbool.h>
 #include <string.h>
 #include <string.h>
+#include <inttypes.h>
 
 
 #ifndef LFS_NO_MALLOC
 #ifndef LFS_NO_MALLOC
 #include <stdlib.h>
 #include <stdlib.h>
@@ -87,6 +88,15 @@ static inline uint32_t lfs_min(uint32_t a, uint32_t b) {
     return (a < b) ? a : b;
     return (a < b) ? a : b;
 }
 }
 
 
+// Align to nearest multiple of a size
+static inline uint32_t lfs_aligndown(uint32_t a, uint32_t alignment) {
+    return a - (a % alignment);
+}
+
+static inline uint32_t lfs_alignup(uint32_t a, uint32_t alignment) {
+    return lfs_aligndown(a + alignment-1, alignment);
+}
+
 // Find the next smallest power of 2 less than or equal to a
 // Find the next smallest power of 2 less than or equal to a
 static inline uint32_t lfs_npw2(uint32_t a) {
 static inline uint32_t lfs_npw2(uint32_t a) {
 #if !defined(LFS_NO_INTRINSICS) && (defined(__GNUC__) || defined(__CC_ARM))
 #if !defined(LFS_NO_INTRINSICS) && (defined(__GNUC__) || defined(__CC_ARM))
@@ -130,7 +140,7 @@ static inline int lfs_scmp(uint32_t a, uint32_t b) {
     return (int)(unsigned)(a - b);
     return (int)(unsigned)(a - b);
 }
 }
 
 
-// Convert from 32-bit little-endian to native order
+// Convert between 32-bit little-endian and native order
 static inline uint32_t lfs_fromle32(uint32_t a) {
 static inline uint32_t lfs_fromle32(uint32_t a) {
 #if !defined(LFS_NO_INTRINSICS) && ( \
 #if !defined(LFS_NO_INTRINSICS) && ( \
     (defined(  BYTE_ORDER  ) &&   BYTE_ORDER   ==   ORDER_LITTLE_ENDIAN  ) || \
     (defined(  BYTE_ORDER  ) &&   BYTE_ORDER   ==   ORDER_LITTLE_ENDIAN  ) || \
@@ -150,15 +160,39 @@ static inline uint32_t lfs_fromle32(uint32_t a) {
 #endif
 #endif
 }
 }
 
 
-// Convert to 32-bit little-endian from native order
 static inline uint32_t lfs_tole32(uint32_t a) {
 static inline uint32_t lfs_tole32(uint32_t a) {
     return lfs_fromle32(a);
     return lfs_fromle32(a);
 }
 }
 
 
+// Convert between 32-bit big-endian and native order
+static inline uint32_t lfs_frombe32(uint32_t a) {
+#if !defined(LFS_NO_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 __builtin_bswap32(a);
+#elif !defined(LFS_NO_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 a;
+#else
+    return (((uint8_t*)&a)[0] << 24) |
+           (((uint8_t*)&a)[1] << 16) |
+           (((uint8_t*)&a)[2] <<  8) |
+           (((uint8_t*)&a)[3] <<  0);
+#endif
+}
+
+static inline uint32_t lfs_tobe32(uint32_t a) {
+    return lfs_frombe32(a);
+}
+
 // Calculate CRC-32 with polynomial = 0x04c11db7
 // Calculate CRC-32 with polynomial = 0x04c11db7
-void lfs_crc(uint32_t *crc, const void *buffer, size_t size);
+uint32_t lfs_crc(uint32_t crc, const void *buffer, size_t size);
 
 
 // Allocate memory, only used if buffers are not provided to littlefs
 // Allocate memory, only used if buffers are not provided to littlefs
+// Note, memory must be 64-bit aligned
 static inline void *lfs_malloc(size_t size) {
 static inline void *lfs_malloc(size_t size) {
 #ifndef LFS_NO_MALLOC
 #ifndef LFS_NO_MALLOC
     return malloc(size);
     return malloc(size);

+ 61 - 0
scripts/prefix.py

@@ -0,0 +1,61 @@
+#!/usr/bin/env python
+
+# This script replaces prefixes of files, and symbols in that file.
+# Useful for creating different versions of the codebase that don't
+# conflict at compile time.
+#
+# example:
+# $ ./scripts/prefix.py lfs2
+
+import os
+import os.path
+import re
+import glob
+import itertools
+import tempfile
+import shutil
+import subprocess
+
+DEFAULT_PREFIX = "lfs"
+
+def subn(from_prefix, to_prefix, name):
+    name, count1 = re.subn('\\b'+from_prefix, to_prefix, name)
+    name, count2 = re.subn('\\b'+from_prefix.upper(), to_prefix.upper(), name)
+    name, count3 = re.subn('\\B-D'+from_prefix.upper(),
+            '-D'+to_prefix.upper(), name)
+    return name, count1+count2+count3
+
+def main(from_prefix, to_prefix=None, files=None):
+    if not to_prefix:
+        from_prefix, to_prefix = DEFAULT_PREFIX, from_prefix
+
+    if not files:
+        files = subprocess.check_output([
+                'git', 'ls-tree', '-r', '--name-only', 'HEAD']).split()
+
+    for oldname in files:
+        # Rename any matching file names
+        newname, namecount = subn(from_prefix, to_prefix, oldname)
+        if namecount:
+            subprocess.check_call(['git', 'mv', oldname, newname])
+
+        # Rename any prefixes in file
+        count = 0
+        with open(newname+'~', 'w') as tempf:
+            with open(newname) as newf:
+                for line in newf:
+                    line, n = subn(from_prefix, to_prefix, line)
+                    count += n
+                    tempf.write(line)
+        shutil.copystat(newname, newname+'~')
+        os.rename(newname+'~', newname)
+        subprocess.check_call(['git', 'add', newname])
+
+        # Summary
+        print '%s: %d replacements' % (
+                '%s -> %s' % (oldname, newname) if namecount else oldname,
+                count)
+
+if __name__ == "__main__":
+    import sys
+    sys.exit(main(*sys.argv[1:]))

+ 44 - 0
tests/corrupt.py

@@ -0,0 +1,44 @@
+#!/usr/bin/env python
+
+import struct
+import sys
+import os
+import argparse
+
+def corrupt(block):
+    with open(block, 'r+b') as file:
+        # skip rev
+        file.read(4)
+
+        # go to last commit
+        tag = 0xffffffff
+        while True:
+            try:
+                ntag, = struct.unpack('>I', file.read(4))
+            except struct.error:
+                break
+
+            tag ^= ntag
+            size = (tag & 0x3ff) if (tag & 0x3ff) != 0x3ff else 0
+            file.seek(size, os.SEEK_CUR)
+
+        # lob off last 3 bytes
+        file.seek(-(size + 3), os.SEEK_CUR)
+        file.truncate()
+
+def main(args):
+    if args.n or not args.blocks:
+        with open('blocks/.history', 'rb') as file:
+            for i in range(int(args.n or 1)):
+                last, = struct.unpack('<I', file.read(4))
+                args.blocks.append('blocks/%x' % last)
+
+    for block in args.blocks:
+        print 'corrupting %s' % block
+        corrupt(block)
+
+if __name__ == "__main__":
+    parser = argparse.ArgumentParser()
+    parser.add_argument('-n')
+    parser.add_argument('blocks', nargs='*')
+    main(parser.parse_args())

+ 112 - 0
tests/debug.py

@@ -0,0 +1,112 @@
+#!/usr/bin/env python2
+
+import struct
+import binascii
+
+TYPES = {
+    (0x700, 0x400): 'splice',
+    (0x7ff, 0x401): 'create',
+    (0x7ff, 0x4ff): 'delete',
+    (0x700, 0x000): 'name',
+    (0x7ff, 0x001): 'name reg',
+    (0x7ff, 0x002): 'name dir',
+    (0x7ff, 0x0ff): 'name superblock',
+    (0x700, 0x200): 'struct',
+    (0x7ff, 0x200): 'struct dir',
+    (0x7ff, 0x202): 'struct ctz',
+    (0x7ff, 0x201): 'struct inline',
+    (0x700, 0x300): 'userattr',
+    (0x700, 0x600): 'tail',
+    (0x7ff, 0x600): 'tail soft',
+    (0x7ff, 0x601): 'tail hard',
+    (0x700, 0x700): 'gstate',
+    (0x7ff, 0x7ff): 'gstate move',
+    (0x700, 0x500): 'crc',
+}
+
+def typeof(type):
+    for prefix in range(12):
+        mask = 0x7ff & ~((1 << prefix)-1)
+        if (mask, type & mask) in TYPES:
+            return TYPES[mask, type & mask] + (
+                ' %0*x' % (prefix/4, type & ((1 << prefix)-1))
+                if prefix else '')
+    else:
+        return '%02x' % type
+
+def main(*blocks):
+    # find most recent block
+    file = None
+    rev = None
+    crc = None
+    versions = []
+
+    for block in blocks:
+        try:
+            nfile = open(block, 'rb')
+            ndata = nfile.read(4)
+            ncrc = binascii.crc32(ndata)
+            nrev, = struct.unpack('<I', ndata)
+
+            assert rev != nrev
+            if not file or ((rev - nrev) & 0x80000000):
+                file = nfile
+                rev = nrev
+                crc = ncrc
+
+            versions.append((nrev, '%s (rev %d)' % (block, nrev)))
+        except (IOError, struct.error):
+            pass
+
+    if not file:
+        print 'Bad metadata pair {%s}' % ', '.join(blocks)
+        return 1
+
+    print "--- %s ---" % ', '.join(v for _,v in sorted(versions, reverse=True))
+
+    # go through each tag, print useful information
+    print "%-4s  %-8s  %-14s  %3s %4s  %s" % (
+        'off', 'tag', 'type', 'id', 'len', 'dump')
+
+    tag = 0xffffffff
+    off = 4
+    while True:
+        try:
+            data = file.read(4)
+            crc = binascii.crc32(data, crc)
+            ntag, = struct.unpack('>I', data)
+        except struct.error:
+            break
+
+        tag ^= ntag
+        off += 4
+
+        type = (tag & 0x7ff00000) >> 20
+        id   = (tag & 0x000ffc00) >> 10
+        size = (tag & 0x000003ff) >> 0
+        iscrc = (type & 0x700) == 0x500
+
+        data = file.read(size if size != 0x3ff else 0)
+        if iscrc:
+            crc = binascii.crc32(data[:4], crc)
+        else:
+            crc = binascii.crc32(data, crc)
+
+        print '%04x: %08x  %-15s %3s %4s  %-23s  %-8s' % (
+            off, tag,
+            typeof(type) + (' bad!' if iscrc and ~crc else ''),
+            id if id != 0x3ff else '.',
+            size if size != 0x3ff else 'x',
+            ' '.join('%02x' % ord(c) for c in data[:8]),
+            ''.join(c if c >= ' ' and c <= '~' else '.' for c in data[:8]))
+
+        off += size if size != 0x3ff else 0
+        if iscrc:
+            crc = 0
+            tag ^= (type & 1) << 31
+
+    return 0
+
+if __name__ == "__main__":
+    import sys
+    sys.exit(main(*sys.argv[1:]))

+ 2 - 2
tests/stats.py

@@ -7,7 +7,7 @@ import os
 import re
 import re
 
 
 def main():
 def main():
-    with open('blocks/config') as file:
+    with open('blocks/.config') as file:
         s = struct.unpack('<LLLL', file.read())
         s = struct.unpack('<LLLL', file.read())
         print 'read_size: %d' % s[0]
         print 'read_size: %d' % s[0]
         print 'prog_size: %d' % s[1]
         print 'prog_size: %d' % s[1]
@@ -18,7 +18,7 @@ def main():
         os.path.getsize(os.path.join('blocks', f))
         os.path.getsize(os.path.join('blocks', f))
         for f in os.listdir('blocks') if re.match('\d+', f))
         for f in os.listdir('blocks') if re.match('\d+', f))
 
 
-    with open('blocks/stats') as file:
+    with open('blocks/.stats') as file:
         s = struct.unpack('<QQQ', file.read())
         s = struct.unpack('<QQQ', file.read())
         print 'read_count: %d' % s[0]
         print 'read_count: %d' % s[0]
         print 'prog_count: %d' % s[1]
         print 'prog_count: %d' % s[1]

+ 18 - 8
tests/template.fmt

@@ -66,7 +66,7 @@ uintmax_t test;
 #endif
 #endif
 
 
 #ifndef LFS_PROG_SIZE
 #ifndef LFS_PROG_SIZE
-#define LFS_PROG_SIZE 16
+#define LFS_PROG_SIZE LFS_READ_SIZE
 #endif
 #endif
 
 
 #ifndef LFS_BLOCK_SIZE
 #ifndef LFS_BLOCK_SIZE
@@ -77,8 +77,16 @@ uintmax_t test;
 #define LFS_BLOCK_COUNT 1024
 #define LFS_BLOCK_COUNT 1024
 #endif
 #endif
 
 
-#ifndef LFS_LOOKAHEAD
-#define LFS_LOOKAHEAD 128
+#ifndef LFS_BLOCK_CYCLES
+#define LFS_BLOCK_CYCLES 1024
+#endif
+
+#ifndef LFS_CACHE_SIZE
+#define LFS_CACHE_SIZE 64
+#endif
+
+#ifndef LFS_LOOKAHEAD_SIZE
+#define LFS_LOOKAHEAD_SIZE 16
 #endif
 #endif
 
 
 const struct lfs_config cfg = {{
 const struct lfs_config cfg = {{
@@ -88,11 +96,13 @@ const struct lfs_config cfg = {{
     .erase = &lfs_emubd_erase,
     .erase = &lfs_emubd_erase,
     .sync  = &lfs_emubd_sync,
     .sync  = &lfs_emubd_sync,
 
 
-    .read_size   = LFS_READ_SIZE,
-    .prog_size   = LFS_PROG_SIZE,
-    .block_size  = LFS_BLOCK_SIZE,
-    .block_count = LFS_BLOCK_COUNT,
-    .lookahead   = LFS_LOOKAHEAD,
+    .read_size      = LFS_READ_SIZE,
+    .prog_size      = LFS_PROG_SIZE,
+    .block_size     = LFS_BLOCK_SIZE,
+    .block_count    = LFS_BLOCK_COUNT,
+    .block_cycles   = LFS_BLOCK_CYCLES,
+    .cache_size     = LFS_CACHE_SIZE,
+    .lookahead_size = LFS_LOOKAHEAD_SIZE,
 }};
 }};
 
 
 
 

+ 1 - 1
tests/test.py

@@ -10,7 +10,7 @@ def generate(test):
         template = file.read()
         template = file.read()
 
 
     lines = []
     lines = []
-    for line in re.split('(?<=[;{}])\n', test.read()):
+    for line in re.split('(?<=(?:.;| [{}]))\n', test.read()):
         match = re.match('(?: *\n)*( *)(.*)=>(.*);', line, re.DOTALL | re.MULTILINE)
         match = re.match('(?: *\n)*( *)(.*)=>(.*);', line, re.DOTALL | re.MULTILINE)
         if match:
         if match:
             tab, test, expect = match.groups()
             tab, test, expect = match.groups()

+ 98 - 41
tests/test_alloc.sh

@@ -194,76 +194,125 @@ tests/test.py << TEST
     lfs_file_read(&lfs, &file[0], buffer, size) => size;
     lfs_file_read(&lfs, &file[0], buffer, size) => size;
     memcmp(buffer, "exhaustion", size) => 0;
     memcmp(buffer, "exhaustion", size) => 0;
     lfs_file_close(&lfs, &file[0]) => 0;
     lfs_file_close(&lfs, &file[0]) => 0;
+    lfs_remove(&lfs, "exhaustion") => 0;
     lfs_unmount(&lfs) => 0;
     lfs_unmount(&lfs) => 0;
 TEST
 TEST
 
 
 echo "--- Dir exhaustion test ---"
 echo "--- Dir exhaustion test ---"
 tests/test.py << TEST
 tests/test.py << TEST
     lfs_mount(&lfs, &cfg) => 0;
     lfs_mount(&lfs, &cfg) => 0;
-    lfs_remove(&lfs, "exhaustion") => 0;
 
 
-    lfs_file_open(&lfs, &file[0], "exhaustion", LFS_O_WRONLY | LFS_O_CREAT);
+    // find out max file size
+    lfs_mkdir(&lfs, "exhaustiondir") => 0;
     size = strlen("blahblahblahblah");
     size = strlen("blahblahblahblah");
     memcpy(buffer, "blahblahblahblah", size);
     memcpy(buffer, "blahblahblahblah", size);
-    for (lfs_size_t i = 0;
-            i < (cfg.block_count-6)*(cfg.block_size-8);
-            i += size) {
+    lfs_file_open(&lfs, &file[0], "exhaustion", LFS_O_WRONLY | LFS_O_CREAT);
+    int count = 0;
+    int err;
+    while (true) {
+        err = lfs_file_write(&lfs, &file[0], buffer, size);
+        if (err < 0) {
+            break;
+        }
+
+        count += 1;
+    }
+    err => LFS_ERR_NOSPC;
+    lfs_file_close(&lfs, &file[0]) => 0;
+
+    lfs_remove(&lfs, "exhaustion") => 0;
+    lfs_remove(&lfs, "exhaustiondir") => 0;
+
+    // see if dir fits with max file size
+    lfs_file_open(&lfs, &file[0], "exhaustion", LFS_O_WRONLY | LFS_O_CREAT);
+    for (int i = 0; i < count; i++) {
         lfs_file_write(&lfs, &file[0], buffer, size) => size;
         lfs_file_write(&lfs, &file[0], buffer, size) => size;
     }
     }
     lfs_file_close(&lfs, &file[0]) => 0;
     lfs_file_close(&lfs, &file[0]) => 0;
 
 
     lfs_mkdir(&lfs, "exhaustiondir") => 0;
     lfs_mkdir(&lfs, "exhaustiondir") => 0;
     lfs_remove(&lfs, "exhaustiondir") => 0;
     lfs_remove(&lfs, "exhaustiondir") => 0;
+    lfs_remove(&lfs, "exhaustion") => 0;
 
 
-    lfs_file_open(&lfs, &file[0], "exhaustion", LFS_O_WRONLY | LFS_O_APPEND);
-    size = strlen("blahblahblahblah");
-    memcpy(buffer, "blahblahblahblah", size);
-    for (lfs_size_t i = 0;
-            i < (cfg.block_size-8);
-            i += size) {
+    // see if dir fits with > max file size
+    lfs_file_open(&lfs, &file[0], "exhaustion", LFS_O_WRONLY | LFS_O_CREAT);
+    for (int i = 0; i < count+1; i++) {
         lfs_file_write(&lfs, &file[0], buffer, size) => size;
         lfs_file_write(&lfs, &file[0], buffer, size) => size;
     }
     }
     lfs_file_close(&lfs, &file[0]) => 0;
     lfs_file_close(&lfs, &file[0]) => 0;
 
 
     lfs_mkdir(&lfs, "exhaustiondir") => LFS_ERR_NOSPC;
     lfs_mkdir(&lfs, "exhaustiondir") => LFS_ERR_NOSPC;
+
+    lfs_remove(&lfs, "exhaustion") => 0;
     lfs_unmount(&lfs) => 0;
     lfs_unmount(&lfs) => 0;
 TEST
 TEST
 
 
 echo "--- Chained dir exhaustion test ---"
 echo "--- Chained dir exhaustion test ---"
 tests/test.py << TEST
 tests/test.py << TEST
     lfs_mount(&lfs, &cfg) => 0;
     lfs_mount(&lfs, &cfg) => 0;
-    lfs_remove(&lfs, "exhaustion") => 0;
 
 
-    lfs_file_open(&lfs, &file[0], "exhaustion", LFS_O_WRONLY | LFS_O_CREAT);
+    // find out max file size
+    lfs_mkdir(&lfs, "exhaustiondir") => 0;
+    for (int i = 0; i < 10; i++) {
+        sprintf((char*)buffer, "dirwithanexhaustivelylongnameforpadding%d", i);
+        lfs_mkdir(&lfs, (char*)buffer) => 0;
+    }
     size = strlen("blahblahblahblah");
     size = strlen("blahblahblahblah");
     memcpy(buffer, "blahblahblahblah", size);
     memcpy(buffer, "blahblahblahblah", size);
-    for (lfs_size_t i = 0;
-            i < (cfg.block_count-24)*(cfg.block_size-8);
-            i += size) {
-        lfs_file_write(&lfs, &file[0], buffer, size) => size;
+    lfs_file_open(&lfs, &file[0], "exhaustion", LFS_O_WRONLY | LFS_O_CREAT);
+    int count = 0;
+    int err;
+    while (true) {
+        err = lfs_file_write(&lfs, &file[0], buffer, size);
+        if (err < 0) {
+            break;
+        }
+
+        count += 1;
     }
     }
+    err => LFS_ERR_NOSPC;
     lfs_file_close(&lfs, &file[0]) => 0;
     lfs_file_close(&lfs, &file[0]) => 0;
 
 
-    for (int i = 0; i < 9; i++) {
+    lfs_remove(&lfs, "exhaustion") => 0;
+    lfs_remove(&lfs, "exhaustiondir") => 0;
+    for (int i = 0; i < 10; i++) {
+        sprintf((char*)buffer, "dirwithanexhaustivelylongnameforpadding%d", i);
+        lfs_remove(&lfs, (char*)buffer) => 0;
+    }
+
+    // see that chained dir fails
+    lfs_file_open(&lfs, &file[0], "exhaustion", LFS_O_WRONLY | LFS_O_CREAT);
+    for (int i = 0; i < count+1; i++) {
+        lfs_file_write(&lfs, &file[0], buffer, size) => size;
+    }
+    lfs_file_sync(&lfs, &file[0]) => 0;
+
+    for (int i = 0; i < 10; i++) {
         sprintf((char*)buffer, "dirwithanexhaustivelylongnameforpadding%d", i);
         sprintf((char*)buffer, "dirwithanexhaustivelylongnameforpadding%d", i);
         lfs_mkdir(&lfs, (char*)buffer) => 0;
         lfs_mkdir(&lfs, (char*)buffer) => 0;
     }
     }
 
 
     lfs_mkdir(&lfs, "exhaustiondir") => LFS_ERR_NOSPC;
     lfs_mkdir(&lfs, "exhaustiondir") => LFS_ERR_NOSPC;
 
 
-    lfs_remove(&lfs, "exhaustion") => 0;
-    lfs_file_open(&lfs, &file[0], "exhaustion", LFS_O_WRONLY | LFS_O_CREAT);
-    size = strlen("blahblahblahblah");
-    memcpy(buffer, "blahblahblahblah", size);
-    for (lfs_size_t i = 0;
-            i < (cfg.block_count-26)*(cfg.block_size-8);
-            i += size) {
-        lfs_file_write(&lfs, &file[0], buffer, size) => size;
+    // shorten file to try a second chained dir
+    while (true) {
+        err = lfs_mkdir(&lfs, "exhaustiondir");
+        if (err != LFS_ERR_NOSPC) {
+            break;
+        }
+
+        lfs_ssize_t filesize = lfs_file_size(&lfs, &file[0]);
+        filesize > 0 => true;
+
+        lfs_file_truncate(&lfs, &file[0], filesize - size) => 0;
+        lfs_file_sync(&lfs, &file[0]) => 0;
     }
     }
-    lfs_file_close(&lfs, &file[0]) => 0;
+    err => 0;
 
 
-    lfs_mkdir(&lfs, "exhaustiondir") => 0;
     lfs_mkdir(&lfs, "exhaustiondir2") => LFS_ERR_NOSPC;
     lfs_mkdir(&lfs, "exhaustiondir2") => LFS_ERR_NOSPC;
+
+    lfs_file_close(&lfs, &file[0]) => 0;
+    lfs_unmount(&lfs) => 0;
 TEST
 TEST
 
 
 echo "--- Split dir test ---"
 echo "--- Split dir test ---"
@@ -274,28 +323,38 @@ TEST
 tests/test.py << TEST
 tests/test.py << TEST
     lfs_mount(&lfs, &cfg) => 0;
     lfs_mount(&lfs, &cfg) => 0;
 
 
-    // create one block whole for half a directory
+    // create one block hole for half a directory
     lfs_file_open(&lfs, &file[0], "bump", LFS_O_WRONLY | LFS_O_CREAT) => 0;
     lfs_file_open(&lfs, &file[0], "bump", LFS_O_WRONLY | LFS_O_CREAT) => 0;
-    lfs_file_write(&lfs, &file[0], (void*)"hi", 2) => 2;
+    for (lfs_size_t i = 0; i < cfg.block_size; i += 2) {
+        memcpy(&buffer[i], "hi", 2);
+    }
+    lfs_file_write(&lfs, &file[0], buffer, cfg.block_size) => cfg.block_size;
     lfs_file_close(&lfs, &file[0]) => 0;
     lfs_file_close(&lfs, &file[0]) => 0;
 
 
     lfs_file_open(&lfs, &file[0], "exhaustion", LFS_O_WRONLY | LFS_O_CREAT);
     lfs_file_open(&lfs, &file[0], "exhaustion", LFS_O_WRONLY | LFS_O_CREAT);
     size = strlen("blahblahblahblah");
     size = strlen("blahblahblahblah");
     memcpy(buffer, "blahblahblahblah", size);
     memcpy(buffer, "blahblahblahblah", size);
     for (lfs_size_t i = 0;
     for (lfs_size_t i = 0;
-            i < (cfg.block_count-6)*(cfg.block_size-8);
+            i < (cfg.block_count-4)*(cfg.block_size-8);
             i += size) {
             i += size) {
         lfs_file_write(&lfs, &file[0], buffer, size) => size;
         lfs_file_write(&lfs, &file[0], buffer, size) => size;
     }
     }
     lfs_file_close(&lfs, &file[0]) => 0;
     lfs_file_close(&lfs, &file[0]) => 0;
 
 
+    // remount to force reset of lookahead
+    lfs_unmount(&lfs) => 0;
+    lfs_mount(&lfs, &cfg) => 0;
+
     // open hole
     // open hole
     lfs_remove(&lfs, "bump") => 0;
     lfs_remove(&lfs, "bump") => 0;
 
 
     lfs_mkdir(&lfs, "splitdir") => 0;
     lfs_mkdir(&lfs, "splitdir") => 0;
     lfs_file_open(&lfs, &file[0], "splitdir/bump",
     lfs_file_open(&lfs, &file[0], "splitdir/bump",
             LFS_O_WRONLY | LFS_O_CREAT) => 0;
             LFS_O_WRONLY | LFS_O_CREAT) => 0;
-    lfs_file_write(&lfs, &file[0], buffer, size) => LFS_ERR_NOSPC;
+    for (lfs_size_t i = 0; i < cfg.block_size; i += 2) {
+        memcpy(&buffer[i], "hi", 2);
+    }
+    lfs_file_write(&lfs, &file[0], buffer, 2*cfg.block_size) => LFS_ERR_NOSPC;
     lfs_file_close(&lfs, &file[0]) => 0;
     lfs_file_close(&lfs, &file[0]) => 0;
 
 
     lfs_unmount(&lfs) => 0;
     lfs_unmount(&lfs) => 0;
@@ -314,7 +373,7 @@ tests/test.py << TEST
     size = strlen("blahblahblahblah");
     size = strlen("blahblahblahblah");
     memcpy(buffer, "blahblahblahblah", size);
     memcpy(buffer, "blahblahblahblah", size);
     for (lfs_size_t i = 0;
     for (lfs_size_t i = 0;
-            i < ((cfg.block_count-4)/2)*(cfg.block_size-8);
+            i < ((cfg.block_count-2)/2)*(cfg.block_size-8);
             i += size) {
             i += size) {
         lfs_file_write(&lfs, &file[0], buffer, size) => size;
         lfs_file_write(&lfs, &file[0], buffer, size) => size;
     }
     }
@@ -325,7 +384,7 @@ tests/test.py << TEST
     size = strlen("blahblahblahblah");
     size = strlen("blahblahblahblah");
     memcpy(buffer, "blahblahblahblah", size);
     memcpy(buffer, "blahblahblahblah", size);
     for (lfs_size_t i = 0;
     for (lfs_size_t i = 0;
-            i < ((cfg.block_count-4+1)/2)*(cfg.block_size-8);
+            i < ((cfg.block_count-2+1)/2)*(cfg.block_size-8);
             i += size) {
             i += size) {
         lfs_file_write(&lfs, &file[0], buffer, size) => size;
         lfs_file_write(&lfs, &file[0], buffer, size) => size;
     }
     }
@@ -333,7 +392,6 @@ tests/test.py << TEST
 
 
     // remount to force reset of lookahead
     // remount to force reset of lookahead
     lfs_unmount(&lfs) => 0;
     lfs_unmount(&lfs) => 0;
-
     lfs_mount(&lfs, &cfg) => 0;
     lfs_mount(&lfs, &cfg) => 0;
 
 
     // rewrite one file
     // rewrite one file
@@ -343,7 +401,7 @@ tests/test.py << TEST
     size = strlen("blahblahblahblah");
     size = strlen("blahblahblahblah");
     memcpy(buffer, "blahblahblahblah", size);
     memcpy(buffer, "blahblahblahblah", size);
     for (lfs_size_t i = 0;
     for (lfs_size_t i = 0;
-            i < ((cfg.block_count-4)/2)*(cfg.block_size-8);
+            i < ((cfg.block_count-2)/2)*(cfg.block_size-8);
             i += size) {
             i += size) {
         lfs_file_write(&lfs, &file[0], buffer, size) => size;
         lfs_file_write(&lfs, &file[0], buffer, size) => size;
     }
     }
@@ -357,7 +415,7 @@ tests/test.py << TEST
     size = strlen("blahblahblahblah");
     size = strlen("blahblahblahblah");
     memcpy(buffer, "blahblahblahblah", size);
     memcpy(buffer, "blahblahblahblah", size);
     for (lfs_size_t i = 0;
     for (lfs_size_t i = 0;
-            i < ((cfg.block_count-4+1)/2)*(cfg.block_size-8);
+            i < ((cfg.block_count-2+1)/2)*(cfg.block_size-8);
             i += size) {
             i += size) {
         lfs_file_write(&lfs, &file[0], buffer, size) => size;
         lfs_file_write(&lfs, &file[0], buffer, size) => size;
     }
     }
@@ -377,7 +435,7 @@ tests/test.py << TEST
     size = strlen("blahblahblahblah");
     size = strlen("blahblahblahblah");
     memcpy(buffer, "blahblahblahblah", size);
     memcpy(buffer, "blahblahblahblah", size);
     for (lfs_size_t i = 0;
     for (lfs_size_t i = 0;
-            i < ((cfg.block_count-4)/2)*(cfg.block_size-8);
+            i < ((cfg.block_count-2)/2)*(cfg.block_size-8);
             i += size) {
             i += size) {
         lfs_file_write(&lfs, &file[0], buffer, size) => size;
         lfs_file_write(&lfs, &file[0], buffer, size) => size;
     }
     }
@@ -388,7 +446,7 @@ tests/test.py << TEST
     size = strlen("blahblahblahblah");
     size = strlen("blahblahblahblah");
     memcpy(buffer, "blahblahblahblah", size);
     memcpy(buffer, "blahblahblahblah", size);
     for (lfs_size_t i = 0;
     for (lfs_size_t i = 0;
-            i < ((cfg.block_count-4+1)/2)*(cfg.block_size-8);
+            i < ((cfg.block_count-2+1)/2)*(cfg.block_size-8);
             i += size) {
             i += size) {
         lfs_file_write(&lfs, &file[0], buffer, size) => size;
         lfs_file_write(&lfs, &file[0], buffer, size) => size;
     }
     }
@@ -396,7 +454,6 @@ tests/test.py << TEST
 
 
     // remount to force reset of lookahead
     // remount to force reset of lookahead
     lfs_unmount(&lfs) => 0;
     lfs_unmount(&lfs) => 0;
-
     lfs_mount(&lfs, &cfg) => 0;
     lfs_mount(&lfs, &cfg) => 0;
 
 
     // rewrite one file with a hole of one block
     // rewrite one file with a hole of one block
@@ -406,7 +463,7 @@ tests/test.py << TEST
     size = strlen("blahblahblahblah");
     size = strlen("blahblahblahblah");
     memcpy(buffer, "blahblahblahblah", size);
     memcpy(buffer, "blahblahblahblah", size);
     for (lfs_size_t i = 0;
     for (lfs_size_t i = 0;
-            i < ((cfg.block_count-4)/2 - 1)*(cfg.block_size-8);
+            i < ((cfg.block_count-2)/2 - 1)*(cfg.block_size-8);
             i += size) {
             i += size) {
         lfs_file_write(&lfs, &file[0], buffer, size) => size;
         lfs_file_write(&lfs, &file[0], buffer, size) => size;
     }
     }

+ 286 - 0
tests/test_attrs.sh

@@ -0,0 +1,286 @@
+#!/bin/bash
+set -eu
+
+echo "=== Attr tests ==="
+rm -rf blocks
+tests/test.py << TEST
+    lfs_format(&lfs, &cfg) => 0;
+
+    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mkdir(&lfs, "hello") => 0;
+    lfs_file_open(&lfs, &file[0], "hello/hello",
+            LFS_O_WRONLY | LFS_O_CREAT) => 0;
+    lfs_file_write(&lfs, &file[0], "hello", strlen("hello"))
+            => strlen("hello");
+    lfs_file_close(&lfs, &file[0]);
+    lfs_unmount(&lfs) => 0;
+TEST
+
+echo "--- Set/get attribute ---"
+tests/test.py << TEST
+    lfs_mount(&lfs, &cfg) => 0;
+    lfs_setattr(&lfs, "hello", 'A', "aaaa",   4) => 0;
+    lfs_setattr(&lfs, "hello", 'B', "bbbbbb", 6) => 0;
+    lfs_setattr(&lfs, "hello", 'C', "ccccc",  5) => 0;
+    lfs_getattr(&lfs, "hello", 'A', buffer,    4) => 4;
+    lfs_getattr(&lfs, "hello", 'B', buffer+4,  6) => 6;
+    lfs_getattr(&lfs, "hello", 'C', buffer+10, 5) => 5;
+    memcmp(buffer,    "aaaa",   4) => 0;
+    memcmp(buffer+4,  "bbbbbb", 6) => 0;
+    memcmp(buffer+10, "ccccc",  5) => 0;
+
+    lfs_setattr(&lfs, "hello", 'B', "", 0) => 0;
+    lfs_getattr(&lfs, "hello", 'A', buffer,    4) => 4;
+    lfs_getattr(&lfs, "hello", 'B', buffer+4,  6) => 0;
+    lfs_getattr(&lfs, "hello", 'C', buffer+10, 5) => 5;
+    memcmp(buffer,    "aaaa",         4) => 0;
+    memcmp(buffer+4,  "\0\0\0\0\0\0", 6) => 0;
+    memcmp(buffer+10, "ccccc",        5) => 0;
+
+    lfs_removeattr(&lfs, "hello", 'B') => 0;
+    lfs_getattr(&lfs, "hello", 'A', buffer,    4) => 4;
+    lfs_getattr(&lfs, "hello", 'B', buffer+4,  6) => LFS_ERR_NOATTR;
+    lfs_getattr(&lfs, "hello", 'C', buffer+10, 5) => 5;
+    memcmp(buffer,    "aaaa",         4) => 0;
+    memcmp(buffer+4,  "\0\0\0\0\0\0", 6) => 0;
+    memcmp(buffer+10, "ccccc",        5) => 0;
+
+    lfs_setattr(&lfs, "hello", 'B', "dddddd", 6) => 0;
+    lfs_getattr(&lfs, "hello", 'A', buffer,    4) => 4;
+    lfs_getattr(&lfs, "hello", 'B', buffer+4,  6) => 6;
+    lfs_getattr(&lfs, "hello", 'C', buffer+10, 5) => 5;
+    memcmp(buffer,    "aaaa",   4) => 0;
+    memcmp(buffer+4,  "dddddd", 6) => 0;
+    memcmp(buffer+10, "ccccc",  5) => 0;
+
+    lfs_setattr(&lfs, "hello", 'B', "eee", 3) => 0;
+    lfs_getattr(&lfs, "hello", 'A', buffer,    4) => 4;
+    lfs_getattr(&lfs, "hello", 'B', buffer+4,  6) => 3;
+    lfs_getattr(&lfs, "hello", 'C', buffer+10, 5) => 5;
+    memcmp(buffer,    "aaaa",      4) => 0;
+    memcmp(buffer+4,  "eee\0\0\0", 6) => 0;
+    memcmp(buffer+10, "ccccc",     5) => 0;
+
+    lfs_setattr(&lfs, "hello", 'A', buffer, LFS_ATTR_MAX+1) => LFS_ERR_NOSPC;
+    lfs_setattr(&lfs, "hello", 'B', "fffffffff", 9) => 0;
+    lfs_getattr(&lfs, "hello", 'A', buffer,    4) => 4;
+    lfs_getattr(&lfs, "hello", 'B', buffer+4,  6) => 9;
+    lfs_getattr(&lfs, "hello", 'C', buffer+10, 5) => 5;
+
+    lfs_unmount(&lfs) => 0;
+TEST
+tests/test.py << TEST
+    lfs_mount(&lfs, &cfg) => 0;
+    lfs_getattr(&lfs, "hello", 'A', buffer,    4) => 4;
+    lfs_getattr(&lfs, "hello", 'B', buffer+4,  9) => 9;
+    lfs_getattr(&lfs, "hello", 'C', buffer+13, 5) => 5;
+    memcmp(buffer,    "aaaa",      4) => 0;
+    memcmp(buffer+4,  "fffffffff", 9) => 0;
+    memcmp(buffer+13, "ccccc",     5) => 0;
+
+    lfs_file_open(&lfs, &file[0], "hello/hello", LFS_O_RDONLY) => 0;
+    lfs_file_read(&lfs, &file[0], buffer, sizeof(buffer)) => strlen("hello");
+    memcmp(buffer, "hello", strlen("hello")) => 0;
+    lfs_file_close(&lfs, &file[0]);
+    lfs_unmount(&lfs) => 0;
+TEST
+
+echo "--- Set/get root attribute ---"
+tests/test.py << TEST
+    lfs_mount(&lfs, &cfg) => 0;
+    lfs_setattr(&lfs, "/", 'A', "aaaa",   4) => 0;
+    lfs_setattr(&lfs, "/", 'B', "bbbbbb", 6) => 0;
+    lfs_setattr(&lfs, "/", 'C', "ccccc",  5) => 0;
+    lfs_getattr(&lfs, "/", 'A', buffer,    4) => 4;
+    lfs_getattr(&lfs, "/", 'B', buffer+4,  6) => 6;
+    lfs_getattr(&lfs, "/", 'C', buffer+10, 5) => 5;
+    memcmp(buffer,    "aaaa",   4) => 0;
+    memcmp(buffer+4,  "bbbbbb", 6) => 0;
+    memcmp(buffer+10, "ccccc",  5) => 0;
+
+    lfs_setattr(&lfs, "/", 'B', "", 0) => 0;
+    lfs_getattr(&lfs, "/", 'A', buffer,    4) => 4;
+    lfs_getattr(&lfs, "/", 'B', buffer+4,  6) => 0;
+    lfs_getattr(&lfs, "/", 'C', buffer+10, 5) => 5;
+    memcmp(buffer,    "aaaa",         4) => 0;
+    memcmp(buffer+4,  "\0\0\0\0\0\0", 6) => 0;
+    memcmp(buffer+10, "ccccc",        5) => 0;
+
+    lfs_removeattr(&lfs, "/", 'B') => 0;
+    lfs_getattr(&lfs, "/", 'A', buffer,    4) => 4;
+    lfs_getattr(&lfs, "/", 'B', buffer+4,  6) => LFS_ERR_NOATTR;
+    lfs_getattr(&lfs, "/", 'C', buffer+10, 5) => 5;
+    memcmp(buffer,    "aaaa",         4) => 0;
+    memcmp(buffer+4,  "\0\0\0\0\0\0", 6) => 0;
+    memcmp(buffer+10, "ccccc",        5) => 0;
+
+    lfs_setattr(&lfs, "/", 'B', "dddddd", 6) => 0;
+    lfs_getattr(&lfs, "/", 'A', buffer,    4) => 4;
+    lfs_getattr(&lfs, "/", 'B', buffer+4,  6) => 6;
+    lfs_getattr(&lfs, "/", 'C', buffer+10, 5) => 5;
+    memcmp(buffer,    "aaaa",   4) => 0;
+    memcmp(buffer+4,  "dddddd", 6) => 0;
+    memcmp(buffer+10, "ccccc",  5) => 0;
+
+    lfs_setattr(&lfs, "/", 'B', "eee", 3) => 0;
+    lfs_getattr(&lfs, "/", 'A', buffer,    4) => 4;
+    lfs_getattr(&lfs, "/", 'B', buffer+4,  6) => 3;
+    lfs_getattr(&lfs, "/", 'C', buffer+10, 5) => 5;
+    memcmp(buffer,    "aaaa",      4) => 0;
+    memcmp(buffer+4,  "eee\0\0\0", 6) => 0;
+    memcmp(buffer+10, "ccccc",     5) => 0;
+
+    lfs_setattr(&lfs, "/", 'A', buffer, LFS_ATTR_MAX+1) => LFS_ERR_NOSPC;
+    lfs_setattr(&lfs, "/", 'B', "fffffffff", 9) => 0;
+    lfs_getattr(&lfs, "/", 'A', buffer,    4) => 4;
+    lfs_getattr(&lfs, "/", 'B', buffer+4,  6) => 9;
+    lfs_getattr(&lfs, "/", 'C', buffer+10, 5) => 5;
+    lfs_unmount(&lfs) => 0;
+TEST
+tests/test.py << TEST
+    lfs_mount(&lfs, &cfg) => 0;
+    lfs_getattr(&lfs, "/", 'A', buffer,    4) => 4;
+    lfs_getattr(&lfs, "/", 'B', buffer+4,  9) => 9;
+    lfs_getattr(&lfs, "/", 'C', buffer+13, 5) => 5;
+    memcmp(buffer,    "aaaa",      4) => 0;
+    memcmp(buffer+4,  "fffffffff", 9) => 0;
+    memcmp(buffer+13, "ccccc",     5) => 0;
+
+    lfs_file_open(&lfs, &file[0], "hello/hello", LFS_O_RDONLY) => 0;
+    lfs_file_read(&lfs, &file[0], buffer, sizeof(buffer)) => strlen("hello");
+    memcmp(buffer, "hello", strlen("hello")) => 0;
+    lfs_file_close(&lfs, &file[0]);
+    lfs_unmount(&lfs) => 0;
+TEST
+
+echo "--- Set/get file attribute ---"
+tests/test.py << TEST
+    lfs_mount(&lfs, &cfg) => 0;
+    struct lfs_attr attrs1[] = {
+        {'A', buffer,    4},
+        {'B', buffer+4,  6},
+        {'C', buffer+10, 5},
+    };
+    struct lfs_file_config cfg1 = {.attrs=attrs1, .attr_count=3};
+
+    lfs_file_opencfg(&lfs, &file[0], "hello/hello", LFS_O_WRONLY, &cfg1) => 0;
+    memcpy(buffer,    "aaaa",   4);
+    memcpy(buffer+4,  "bbbbbb", 6);
+    memcpy(buffer+10, "ccccc",  5);
+    lfs_file_close(&lfs, &file[0]) => 0;
+    memset(buffer, 0, 15);
+    lfs_file_opencfg(&lfs, &file[0], "hello/hello", LFS_O_RDONLY, &cfg1) => 0;
+    lfs_file_close(&lfs, &file[0]) => 0;
+    memcmp(buffer,    "aaaa",   4) => 0;
+    memcmp(buffer+4,  "bbbbbb", 6) => 0;
+    memcmp(buffer+10, "ccccc",  5) => 0;
+
+    attrs1[1].size = 0;
+    lfs_file_opencfg(&lfs, &file[0], "hello/hello", LFS_O_WRONLY, &cfg1) => 0;
+    lfs_file_close(&lfs, &file[0]) => 0;
+    memset(buffer, 0, 15);
+    attrs1[1].size = 6;
+    lfs_file_opencfg(&lfs, &file[0], "hello/hello", LFS_O_RDONLY, &cfg1) => 0;
+    lfs_file_close(&lfs, &file[0]) => 0;
+    memcmp(buffer,    "aaaa",         4) => 0;
+    memcmp(buffer+4,  "\0\0\0\0\0\0", 6) => 0;
+    memcmp(buffer+10, "ccccc",        5) => 0;
+
+    attrs1[1].size = 6;
+    lfs_file_opencfg(&lfs, &file[0], "hello/hello", LFS_O_WRONLY, &cfg1) => 0;
+    memcpy(buffer+4,  "dddddd", 6);
+    lfs_file_close(&lfs, &file[0]) => 0;
+    memset(buffer, 0, 15);
+    attrs1[1].size = 6;
+    lfs_file_opencfg(&lfs, &file[0], "hello/hello", LFS_O_RDONLY, &cfg1) => 0;
+    lfs_file_close(&lfs, &file[0]) => 0;
+    memcmp(buffer,    "aaaa",   4) => 0;
+    memcmp(buffer+4,  "dddddd", 6) => 0;
+    memcmp(buffer+10, "ccccc",  5) => 0;
+
+    attrs1[1].size = 3;
+    lfs_file_opencfg(&lfs, &file[0], "hello/hello", LFS_O_WRONLY, &cfg1) => 0;
+    memcpy(buffer+4,  "eee", 3);
+    lfs_file_close(&lfs, &file[0]) => 0;
+    memset(buffer, 0, 15);
+    attrs1[1].size = 6;
+    lfs_file_opencfg(&lfs, &file[0], "hello/hello", LFS_O_RDONLY, &cfg1) => 0;
+    lfs_file_close(&lfs, &file[0]) => 0;
+    memcmp(buffer,    "aaaa",      4) => 0;
+    memcmp(buffer+4,  "eee\0\0\0", 6) => 0;
+    memcmp(buffer+10, "ccccc",     5) => 0;
+
+    attrs1[0].size = LFS_ATTR_MAX+1;
+    lfs_file_opencfg(&lfs, &file[0], "hello/hello", LFS_O_WRONLY, &cfg1)
+        => LFS_ERR_NOSPC;
+
+    struct lfs_attr attrs2[] = {
+        {'A', buffer,    4},
+        {'B', buffer+4,  9},
+        {'C', buffer+13, 5},
+    };
+    struct lfs_file_config cfg2 = {.attrs=attrs2, .attr_count=3};
+    lfs_file_opencfg(&lfs, &file[0], "hello/hello", LFS_O_RDWR, &cfg2) => 0;
+    memcpy(buffer+4,  "fffffffff", 9);
+    lfs_file_close(&lfs, &file[0]) => 0;
+    attrs1[0].size = 4;
+    lfs_file_opencfg(&lfs, &file[0], "hello/hello", LFS_O_RDONLY, &cfg1) => 0;
+    lfs_file_close(&lfs, &file[0]) => 0;
+
+    lfs_unmount(&lfs) => 0;
+TEST
+tests/test.py << TEST
+    lfs_mount(&lfs, &cfg) => 0;
+    struct lfs_attr attrs2[] = {
+        {'A', buffer,    4},
+        {'B', buffer+4,  9},
+        {'C', buffer+13, 5},
+    };
+    struct lfs_file_config cfg2 = {.attrs=attrs2, .attr_count=3};
+
+    lfs_file_opencfg(&lfs, &file[0], "hello/hello", LFS_O_RDONLY, &cfg2) => 0;
+    lfs_file_close(&lfs, &file[0]) => 0;
+    memcmp(buffer,    "aaaa",      4) => 0;
+    memcmp(buffer+4,  "fffffffff", 9) => 0;
+    memcmp(buffer+13, "ccccc",     5) => 0;
+
+    lfs_file_open(&lfs, &file[0], "hello/hello", LFS_O_RDONLY) => 0;
+    lfs_file_read(&lfs, &file[0], buffer, sizeof(buffer)) => strlen("hello");
+    memcmp(buffer, "hello", strlen("hello")) => 0;
+    lfs_file_close(&lfs, &file[0]);
+    lfs_unmount(&lfs) => 0;
+TEST
+
+echo "--- Deferred file attributes ---"
+tests/test.py << TEST
+    lfs_mount(&lfs, &cfg) => 0;
+    struct lfs_attr attrs1[] = {
+        {'B', "gggg", 4},
+        {'C', "",     0},
+        {'D', "hhhh", 4},
+    };
+    struct lfs_file_config cfg1 = {.attrs=attrs1, .attr_count=3};
+
+    lfs_file_opencfg(&lfs, &file[0], "hello/hello", LFS_O_WRONLY, &cfg1) => 0;
+
+    lfs_getattr(&lfs, "hello/hello", 'B', buffer,    9) => 9;
+    lfs_getattr(&lfs, "hello/hello", 'C', buffer+9,  9) => 5;
+    lfs_getattr(&lfs, "hello/hello", 'D', buffer+18, 9) => LFS_ERR_NOATTR;
+    memcmp(buffer,    "fffffffff",          9) => 0;
+    memcmp(buffer+9,  "ccccc\0\0\0\0",      9) => 0;
+    memcmp(buffer+18, "\0\0\0\0\0\0\0\0\0", 9) => 0;
+
+    lfs_file_sync(&lfs, &file[0]) => 0;
+    lfs_getattr(&lfs, "hello/hello", 'B', buffer,    9) => 4;
+    lfs_getattr(&lfs, "hello/hello", 'C', buffer+9,  9) => 0;
+    lfs_getattr(&lfs, "hello/hello", 'D', buffer+18, 9) => 4;
+    memcmp(buffer,    "gggg\0\0\0\0\0",     9) => 0;
+    memcmp(buffer+9,  "\0\0\0\0\0\0\0\0\0", 9) => 0;
+    memcmp(buffer+18, "hhhh\0\0\0\0\0",     9) => 0;
+
+    lfs_file_close(&lfs, &file[0]) => 0;
+    lfs_unmount(&lfs) => 0;
+TEST
+
+echo "--- Results ---"
+tests/stats.py

+ 7 - 6
tests/test_corrupt.sh

@@ -71,24 +71,25 @@ echo "--- Sanity check ---"
 rm -rf blocks
 rm -rf blocks
 lfs_mktree
 lfs_mktree
 lfs_chktree
 lfs_chktree
+BLOCKS="$(ls blocks | grep -vw '[01]')"
 
 
 echo "--- Block corruption ---"
 echo "--- Block corruption ---"
-for i in {0..33}
+for b in $BLOCKS
 do 
 do 
     rm -rf blocks
     rm -rf blocks
     mkdir blocks
     mkdir blocks
-    ln -s /dev/zero blocks/$(printf '%x' $i)
+    ln -s /dev/zero blocks/$b
     lfs_mktree
     lfs_mktree
     lfs_chktree
     lfs_chktree
 done
 done
 
 
 echo "--- Block persistance ---"
 echo "--- Block persistance ---"
-for i in {0..33}
+for b in $BLOCKS
 do 
 do 
     rm -rf blocks
     rm -rf blocks
     mkdir blocks
     mkdir blocks
     lfs_mktree
     lfs_mktree
-    chmod a-w blocks/$(printf '%x' $i)
+    chmod a-w blocks/$b || true
     lfs_mktree
     lfs_mktree
     lfs_chktree
     lfs_chktree
 done
 done
@@ -96,7 +97,7 @@ done
 echo "--- Big region corruption ---"
 echo "--- Big region corruption ---"
 rm -rf blocks
 rm -rf blocks
 mkdir blocks
 mkdir blocks
-for i in {2..255}
+for i in {2..512}
 do
 do
     ln -s /dev/zero blocks/$(printf '%x' $i)
     ln -s /dev/zero blocks/$(printf '%x' $i)
 done
 done
@@ -106,7 +107,7 @@ lfs_chktree
 echo "--- Alternating corruption ---"
 echo "--- Alternating corruption ---"
 rm -rf blocks
 rm -rf blocks
 mkdir blocks
 mkdir blocks
-for i in {2..511..2}
+for i in {2..1024..2}
 do
 do
     ln -s /dev/zero blocks/$(printf '%x' $i)
     ln -s /dev/zero blocks/$(printf '%x' $i)
 done
 done

+ 23 - 23
tests/test_dirs.sh

@@ -43,11 +43,11 @@ tests/test.py << TEST
     strcmp(info.name, "..") => 0;
     strcmp(info.name, "..") => 0;
     info.type => LFS_TYPE_DIR;
     info.type => LFS_TYPE_DIR;
     lfs_dir_read(&lfs, &dir[0], &info) => 1;
     lfs_dir_read(&lfs, &dir[0], &info) => 1;
-    strcmp(info.name, "potato") => 0;
-    info.type => LFS_TYPE_DIR;
-    lfs_dir_read(&lfs, &dir[0], &info) => 1;
     strcmp(info.name, "burito") => 0;
     strcmp(info.name, "burito") => 0;
     info.type => LFS_TYPE_REG;
     info.type => LFS_TYPE_REG;
+    lfs_dir_read(&lfs, &dir[0], &info) => 1;
+    strcmp(info.name, "potato") => 0;
+    info.type => LFS_TYPE_DIR;
     lfs_dir_read(&lfs, &dir[0], &info) => 0;
     lfs_dir_read(&lfs, &dir[0], &info) => 0;
     lfs_dir_close(&lfs, &dir[0]) => 0;
     lfs_dir_close(&lfs, &dir[0]) => 0;
     lfs_unmount(&lfs) => 0;
     lfs_unmount(&lfs) => 0;
@@ -85,10 +85,10 @@ tests/test.py << TEST
     strcmp(info.name, "baked") => 0;
     strcmp(info.name, "baked") => 0;
     info.type => LFS_TYPE_DIR;
     info.type => LFS_TYPE_DIR;
     lfs_dir_read(&lfs, &dir[0], &info) => 1;
     lfs_dir_read(&lfs, &dir[0], &info) => 1;
-    strcmp(info.name, "sweet") => 0;
+    strcmp(info.name, "fried") => 0;
     info.type => LFS_TYPE_DIR;
     info.type => LFS_TYPE_DIR;
     lfs_dir_read(&lfs, &dir[0], &info) => 1;
     lfs_dir_read(&lfs, &dir[0], &info) => 1;
-    strcmp(info.name, "fried") => 0;
+    strcmp(info.name, "sweet") => 0;
     info.type => LFS_TYPE_DIR;
     info.type => LFS_TYPE_DIR;
     lfs_dir_read(&lfs, &dir[0], &info) => 0;
     lfs_dir_read(&lfs, &dir[0], &info) => 0;
     lfs_dir_close(&lfs, &dir[0]) => 0;
     lfs_dir_close(&lfs, &dir[0]) => 0;
@@ -100,7 +100,7 @@ tests/test.py << TEST
     lfs_mount(&lfs, &cfg) => 0;
     lfs_mount(&lfs, &cfg) => 0;
     lfs_mkdir(&lfs, "cactus") => 0;
     lfs_mkdir(&lfs, "cactus") => 0;
     for (int i = 0; i < $LARGESIZE; i++) {
     for (int i = 0; i < $LARGESIZE; i++) {
-        sprintf((char*)buffer, "cactus/test%d", i);
+        sprintf((char*)buffer, "cactus/test%03d", i);
         lfs_mkdir(&lfs, (char*)buffer) => 0;
         lfs_mkdir(&lfs, (char*)buffer) => 0;
     }
     }
     lfs_unmount(&lfs) => 0;
     lfs_unmount(&lfs) => 0;
@@ -115,7 +115,7 @@ tests/test.py << TEST
     strcmp(info.name, "..") => 0;
     strcmp(info.name, "..") => 0;
     info.type => LFS_TYPE_DIR;
     info.type => LFS_TYPE_DIR;
     for (int i = 0; i < $LARGESIZE; i++) {
     for (int i = 0; i < $LARGESIZE; i++) {
-        sprintf((char*)buffer, "test%d", i);
+        sprintf((char*)buffer, "test%03d", i);
         lfs_dir_read(&lfs, &dir[0], &info) => 1;
         lfs_dir_read(&lfs, &dir[0], &info) => 1;
         strcmp(info.name, (char*)buffer) => 0;
         strcmp(info.name, (char*)buffer) => 0;
         info.type => LFS_TYPE_DIR;
         info.type => LFS_TYPE_DIR;
@@ -208,10 +208,10 @@ tests/test.py << TEST
     strcmp(info.name, "baked") => 0;
     strcmp(info.name, "baked") => 0;
     info.type => LFS_TYPE_DIR;
     info.type => LFS_TYPE_DIR;
     lfs_dir_read(&lfs, &dir[0], &info) => 1;
     lfs_dir_read(&lfs, &dir[0], &info) => 1;
-    strcmp(info.name, "sweet") => 0;
+    strcmp(info.name, "fried") => 0;
     info.type => LFS_TYPE_DIR;
     info.type => LFS_TYPE_DIR;
     lfs_dir_read(&lfs, &dir[0], &info) => 1;
     lfs_dir_read(&lfs, &dir[0], &info) => 1;
-    strcmp(info.name, "fried") => 0;
+    strcmp(info.name, "sweet") => 0;
     info.type => LFS_TYPE_DIR;
     info.type => LFS_TYPE_DIR;
     lfs_dir_read(&lfs, &dir[0], &info) => 0;
     lfs_dir_read(&lfs, &dir[0], &info) => 0;
     lfs_dir_close(&lfs, &dir[0]) => 0;
     lfs_dir_close(&lfs, &dir[0]) => 0;
@@ -241,10 +241,10 @@ tests/test.py << TEST
     strcmp(info.name, "baked") => 0;
     strcmp(info.name, "baked") => 0;
     info.type => LFS_TYPE_DIR;
     info.type => LFS_TYPE_DIR;
     lfs_dir_read(&lfs, &dir[0], &info) => 1;
     lfs_dir_read(&lfs, &dir[0], &info) => 1;
-    strcmp(info.name, "sweet") => 0;
+    strcmp(info.name, "fried") => 0;
     info.type => LFS_TYPE_DIR;
     info.type => LFS_TYPE_DIR;
     lfs_dir_read(&lfs, &dir[0], &info) => 1;
     lfs_dir_read(&lfs, &dir[0], &info) => 1;
-    strcmp(info.name, "fried") => 0;
+    strcmp(info.name, "sweet") => 0;
     info.type => LFS_TYPE_DIR;
     info.type => LFS_TYPE_DIR;
     lfs_dir_read(&lfs, &dir[0], &info) => 0;
     lfs_dir_read(&lfs, &dir[0], &info) => 0;
     lfs_dir_close(&lfs, &dir[0]) => 0;
     lfs_dir_close(&lfs, &dir[0]) => 0;
@@ -273,10 +273,10 @@ tests/test.py << TEST
     strcmp(info.name, "baked") => 0;
     strcmp(info.name, "baked") => 0;
     info.type => LFS_TYPE_DIR;
     info.type => LFS_TYPE_DIR;
     lfs_dir_read(&lfs, &dir[0], &info) => 1;
     lfs_dir_read(&lfs, &dir[0], &info) => 1;
-    strcmp(info.name, "sweet") => 0;
+    strcmp(info.name, "fried") => 0;
     info.type => LFS_TYPE_DIR;
     info.type => LFS_TYPE_DIR;
     lfs_dir_read(&lfs, &dir[0], &info) => 1;
     lfs_dir_read(&lfs, &dir[0], &info) => 1;
-    strcmp(info.name, "fried") => 0;
+    strcmp(info.name, "sweet") => 0;
     info.type => LFS_TYPE_DIR;
     info.type => LFS_TYPE_DIR;
     lfs_dir_read(&lfs, &dir[0], &info) => 0;
     lfs_dir_read(&lfs, &dir[0], &info) => 0;
     lfs_dir_close(&lfs, &dir[0]) => 0;
     lfs_dir_close(&lfs, &dir[0]) => 0;
@@ -330,8 +330,8 @@ echo "--- Multi-block rename ---"
 tests/test.py << TEST
 tests/test.py << TEST
     lfs_mount(&lfs, &cfg) => 0;
     lfs_mount(&lfs, &cfg) => 0;
     for (int i = 0; i < $LARGESIZE; i++) {
     for (int i = 0; i < $LARGESIZE; i++) {
-        sprintf((char*)buffer, "cactus/test%d", i);
-        sprintf((char*)wbuffer, "cactus/tedd%d", i);
+        sprintf((char*)buffer, "cactus/test%03d", i);
+        sprintf((char*)wbuffer, "cactus/tedd%03d", i);
         lfs_rename(&lfs, (char*)buffer, (char*)wbuffer) => 0;
         lfs_rename(&lfs, (char*)buffer, (char*)wbuffer) => 0;
     }
     }
     lfs_unmount(&lfs) => 0;
     lfs_unmount(&lfs) => 0;
@@ -346,7 +346,7 @@ tests/test.py << TEST
     strcmp(info.name, "..") => 0;
     strcmp(info.name, "..") => 0;
     info.type => LFS_TYPE_DIR;
     info.type => LFS_TYPE_DIR;
     for (int i = 0; i < $LARGESIZE; i++) {
     for (int i = 0; i < $LARGESIZE; i++) {
-        sprintf((char*)buffer, "tedd%d", i);
+        sprintf((char*)buffer, "tedd%03d", i);
         lfs_dir_read(&lfs, &dir[0], &info) => 1;
         lfs_dir_read(&lfs, &dir[0], &info) => 1;
         strcmp(info.name, (char*)buffer) => 0;
         strcmp(info.name, (char*)buffer) => 0;
         info.type => LFS_TYPE_DIR;
         info.type => LFS_TYPE_DIR;
@@ -361,7 +361,7 @@ tests/test.py << TEST
     lfs_remove(&lfs, "cactus") => LFS_ERR_NOTEMPTY;
     lfs_remove(&lfs, "cactus") => LFS_ERR_NOTEMPTY;
 
 
     for (int i = 0; i < $LARGESIZE; i++) {
     for (int i = 0; i < $LARGESIZE; i++) {
-        sprintf((char*)buffer, "cactus/tedd%d", i);
+        sprintf((char*)buffer, "cactus/tedd%03d", i);
         lfs_remove(&lfs, (char*)buffer) => 0;
         lfs_remove(&lfs, (char*)buffer) => 0;
     }
     }
 
 
@@ -390,7 +390,7 @@ tests/test.py << TEST
     lfs_mount(&lfs, &cfg) => 0;
     lfs_mount(&lfs, &cfg) => 0;
     lfs_mkdir(&lfs, "prickly-pear") => 0;
     lfs_mkdir(&lfs, "prickly-pear") => 0;
     for (int i = 0; i < $LARGESIZE; i++) {
     for (int i = 0; i < $LARGESIZE; i++) {
-        sprintf((char*)buffer, "prickly-pear/test%d", i);
+        sprintf((char*)buffer, "prickly-pear/test%03d", i);
         lfs_file_open(&lfs, &file[0], (char*)buffer,
         lfs_file_open(&lfs, &file[0], (char*)buffer,
                 LFS_O_WRONLY | LFS_O_CREAT) => 0;
                 LFS_O_WRONLY | LFS_O_CREAT) => 0;
         size = 6;
         size = 6;
@@ -410,7 +410,7 @@ tests/test.py << TEST
     strcmp(info.name, "..") => 0;
     strcmp(info.name, "..") => 0;
     info.type => LFS_TYPE_DIR;
     info.type => LFS_TYPE_DIR;
     for (int i = 0; i < $LARGESIZE; i++) {
     for (int i = 0; i < $LARGESIZE; i++) {
-        sprintf((char*)buffer, "test%d", i);
+        sprintf((char*)buffer, "test%03d", i);
         lfs_dir_read(&lfs, &dir[0], &info) => 1;
         lfs_dir_read(&lfs, &dir[0], &info) => 1;
         strcmp(info.name, (char*)buffer) => 0;
         strcmp(info.name, (char*)buffer) => 0;
         info.type => LFS_TYPE_REG;
         info.type => LFS_TYPE_REG;
@@ -424,8 +424,8 @@ echo "--- Multi-block rename with files ---"
 tests/test.py << TEST
 tests/test.py << TEST
     lfs_mount(&lfs, &cfg) => 0;
     lfs_mount(&lfs, &cfg) => 0;
     for (int i = 0; i < $LARGESIZE; i++) {
     for (int i = 0; i < $LARGESIZE; i++) {
-        sprintf((char*)buffer, "prickly-pear/test%d", i);
-        sprintf((char*)wbuffer, "prickly-pear/tedd%d", i);
+        sprintf((char*)buffer, "prickly-pear/test%03d", i);
+        sprintf((char*)wbuffer, "prickly-pear/tedd%03d", i);
         lfs_rename(&lfs, (char*)buffer, (char*)wbuffer) => 0;
         lfs_rename(&lfs, (char*)buffer, (char*)wbuffer) => 0;
     }
     }
     lfs_unmount(&lfs) => 0;
     lfs_unmount(&lfs) => 0;
@@ -440,7 +440,7 @@ tests/test.py << TEST
     strcmp(info.name, "..") => 0;
     strcmp(info.name, "..") => 0;
     info.type => LFS_TYPE_DIR;
     info.type => LFS_TYPE_DIR;
     for (int i = 0; i < $LARGESIZE; i++) {
     for (int i = 0; i < $LARGESIZE; i++) {
-        sprintf((char*)buffer, "tedd%d", i);
+        sprintf((char*)buffer, "tedd%03d", i);
         lfs_dir_read(&lfs, &dir[0], &info) => 1;
         lfs_dir_read(&lfs, &dir[0], &info) => 1;
         strcmp(info.name, (char*)buffer) => 0;
         strcmp(info.name, (char*)buffer) => 0;
         info.type => LFS_TYPE_REG;
         info.type => LFS_TYPE_REG;
@@ -456,7 +456,7 @@ tests/test.py << TEST
     lfs_remove(&lfs, "prickly-pear") => LFS_ERR_NOTEMPTY;
     lfs_remove(&lfs, "prickly-pear") => LFS_ERR_NOTEMPTY;
 
 
     for (int i = 0; i < $LARGESIZE; i++) {
     for (int i = 0; i < $LARGESIZE; i++) {
-        sprintf((char*)buffer, "prickly-pear/tedd%d", i);
+        sprintf((char*)buffer, "prickly-pear/tedd%03d", i);
         lfs_remove(&lfs, (char*)buffer) => 0;
         lfs_remove(&lfs, (char*)buffer) => 0;
     }
     }
 
 

+ 221 - 0
tests/test_entries.sh

@@ -0,0 +1,221 @@
+#!/bin/bash
+set -eu
+
+# Note: These tests are intended for 512 byte inline size at different
+# inline sizes they should still pass, but won't be testing anything
+
+echo "=== Entry tests ==="
+rm -rf blocks
+function read_file {
+cat << TEST
+
+    size = $2;
+    lfs_file_open(&lfs, &file[0], "$1", LFS_O_RDONLY) => 0;
+    lfs_file_read(&lfs, &file[0], rbuffer, size) => size;
+    memcmp(rbuffer, wbuffer, size) => 0;
+    lfs_file_close(&lfs, &file[0]) => 0;
+TEST
+}
+
+function write_file {
+cat << TEST
+
+    size = $2;
+    lfs_file_open(&lfs, &file[0], "$1",
+            LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0;
+    memset(wbuffer, 'c', size);
+    lfs_file_write(&lfs, &file[0], wbuffer, size) => size;
+    lfs_file_close(&lfs, &file[0]) => 0;
+TEST
+}
+
+echo "--- Entry grow test ---"
+tests/test.py << TEST
+    lfs_format(&lfs, &cfg) => 0;
+
+    lfs_mount(&lfs, &cfg) => 0;
+    $(write_file "hi0" 20)
+    $(write_file "hi1" 20)
+    $(write_file "hi2" 20)
+    $(write_file "hi3" 20)
+
+    $(read_file "hi1" 20)
+    $(write_file "hi1" 200)
+
+    $(read_file "hi0" 20)
+    $(read_file "hi1" 200)
+    $(read_file "hi2" 20)
+    $(read_file "hi3" 20)
+    lfs_unmount(&lfs) => 0;
+TEST
+
+echo "--- Entry shrink test ---"
+tests/test.py << TEST
+    lfs_format(&lfs, &cfg) => 0;
+
+    lfs_mount(&lfs, &cfg) => 0;
+    $(write_file "hi0" 20)
+    $(write_file "hi1" 200)
+    $(write_file "hi2" 20)
+    $(write_file "hi3" 20)
+
+    $(read_file "hi1" 200)
+    $(write_file "hi1" 20)
+
+    $(read_file "hi0" 20)
+    $(read_file "hi1" 20)
+    $(read_file "hi2" 20)
+    $(read_file "hi3" 20)
+    lfs_unmount(&lfs) => 0;
+TEST
+
+echo "--- Entry spill test ---"
+tests/test.py << TEST
+    lfs_format(&lfs, &cfg) => 0;
+
+    lfs_mount(&lfs, &cfg) => 0;
+    $(write_file "hi0" 200)
+    $(write_file "hi1" 200)
+    $(write_file "hi2" 200)
+    $(write_file "hi3" 200)
+
+    $(read_file "hi0" 200)
+    $(read_file "hi1" 200)
+    $(read_file "hi2" 200)
+    $(read_file "hi3" 200)
+    lfs_unmount(&lfs) => 0;
+TEST
+
+echo "--- Entry push spill test ---"
+tests/test.py << TEST
+    lfs_format(&lfs, &cfg) => 0;
+
+    lfs_mount(&lfs, &cfg) => 0;
+    $(write_file "hi0" 200)
+    $(write_file "hi1" 20)
+    $(write_file "hi2" 200)
+    $(write_file "hi3" 200)
+
+    $(read_file "hi1" 20)
+    $(write_file "hi1" 200)
+
+    $(read_file "hi0" 200)
+    $(read_file "hi1" 200)
+    $(read_file "hi2" 200)
+    $(read_file "hi3" 200)
+    lfs_unmount(&lfs) => 0;
+TEST
+
+echo "--- Entry push spill two test ---"
+tests/test.py << TEST
+    lfs_format(&lfs, &cfg) => 0;
+
+    lfs_mount(&lfs, &cfg) => 0;
+    $(write_file "hi0" 200)
+    $(write_file "hi1" 20)
+    $(write_file "hi2" 200)
+    $(write_file "hi3" 200)
+    $(write_file "hi4" 200)
+
+    $(read_file "hi1" 20)
+    $(write_file "hi1" 200)
+
+    $(read_file "hi0" 200)
+    $(read_file "hi1" 200)
+    $(read_file "hi2" 200)
+    $(read_file "hi3" 200)
+    $(read_file "hi4" 200)
+    lfs_unmount(&lfs) => 0;
+TEST
+
+echo "--- Entry drop test ---"
+tests/test.py << TEST
+    lfs_format(&lfs, &cfg) => 0;
+
+    lfs_mount(&lfs, &cfg) => 0;
+    $(write_file "hi0" 200)
+    $(write_file "hi1" 200)
+    $(write_file "hi2" 200)
+    $(write_file "hi3" 200)
+
+    lfs_remove(&lfs, "hi1") => 0;
+    lfs_stat(&lfs, "hi1", &info) => LFS_ERR_NOENT;
+    $(read_file "hi0" 200)
+    $(read_file "hi2" 200)
+    $(read_file "hi3" 200)
+
+    lfs_remove(&lfs, "hi2") => 0;
+    lfs_stat(&lfs, "hi2", &info) => LFS_ERR_NOENT;
+    $(read_file "hi0" 200)
+    $(read_file "hi3" 200)
+
+    lfs_remove(&lfs, "hi3") => 0;
+    lfs_stat(&lfs, "hi3", &info) => LFS_ERR_NOENT;
+    $(read_file "hi0" 200)
+
+    lfs_remove(&lfs, "hi0") => 0;
+    lfs_stat(&lfs, "hi0", &info) => LFS_ERR_NOENT;
+    lfs_unmount(&lfs) => 0;
+TEST
+
+echo "--- Create too big ---"
+tests/test.py << TEST
+    lfs_format(&lfs, &cfg) => 0;
+
+    lfs_mount(&lfs, &cfg) => 0;
+    memset(buffer, 'm', 200);
+    buffer[200] = '\0';
+
+    size = 400;
+    lfs_file_open(&lfs, &file[0], (char*)buffer,
+            LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0;
+    memset(wbuffer, 'c', size);
+    lfs_file_write(&lfs, &file[0], wbuffer, size) => size;
+    lfs_file_close(&lfs, &file[0]) => 0;
+
+    size = 400;
+    lfs_file_open(&lfs, &file[0], (char*)buffer, LFS_O_RDONLY) => 0;
+    lfs_file_read(&lfs, &file[0], rbuffer, size) => size;
+    memcmp(rbuffer, wbuffer, size) => 0;
+    lfs_file_close(&lfs, &file[0]) => 0;
+    lfs_unmount(&lfs) => 0;
+TEST
+
+echo "--- Resize too big ---"
+tests/test.py << TEST
+    lfs_format(&lfs, &cfg) => 0;
+
+    lfs_mount(&lfs, &cfg) => 0;
+    memset(buffer, 'm', 200);
+    buffer[200] = '\0';
+
+    size = 40;
+    lfs_file_open(&lfs, &file[0], (char*)buffer,
+            LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0;
+    memset(wbuffer, 'c', size);
+    lfs_file_write(&lfs, &file[0], wbuffer, size) => size;
+    lfs_file_close(&lfs, &file[0]) => 0;
+
+    size = 40;
+    lfs_file_open(&lfs, &file[0], (char*)buffer, LFS_O_RDONLY) => 0;
+    lfs_file_read(&lfs, &file[0], rbuffer, size) => size;
+    memcmp(rbuffer, wbuffer, size) => 0;
+    lfs_file_close(&lfs, &file[0]) => 0;
+
+    size = 400;
+    lfs_file_open(&lfs, &file[0], (char*)buffer,
+            LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0;
+    memset(wbuffer, 'c', size);
+    lfs_file_write(&lfs, &file[0], wbuffer, size) => size;
+    lfs_file_close(&lfs, &file[0]) => 0;
+
+    size = 400;
+    lfs_file_open(&lfs, &file[0], (char*)buffer, LFS_O_RDONLY) => 0;
+    lfs_file_read(&lfs, &file[0], rbuffer, size) => size;
+    memcmp(rbuffer, wbuffer, size) => 0;
+    lfs_file_close(&lfs, &file[0]) => 0;
+    lfs_unmount(&lfs) => 0;
+TEST
+
+echo "--- Results ---"
+tests/stats.py

+ 7 - 7
tests/test_files.sh

@@ -29,7 +29,7 @@ tests/test.py << TEST
 TEST
 TEST
 
 
 w_test() {
 w_test() {
-tests/test.py << TEST
+tests/test.py ${4:-} << TEST
     size = $1;
     size = $1;
     lfs_size_t chunk = 31;
     lfs_size_t chunk = 31;
     srand(0);
     srand(0);
@@ -115,21 +115,21 @@ tests/test.py << TEST
     info.type => LFS_TYPE_REG;
     info.type => LFS_TYPE_REG;
     info.size => strlen("Hello World!\n");
     info.size => strlen("Hello World!\n");
     lfs_dir_read(&lfs, &dir[0], &info) => 1;
     lfs_dir_read(&lfs, &dir[0], &info) => 1;
-    strcmp(info.name, "smallavacado") => 0;
+    strcmp(info.name, "largeavacado") => 0;
     info.type => LFS_TYPE_REG;
     info.type => LFS_TYPE_REG;
-    info.size => $SMALLSIZE;
+    info.size => $LARGESIZE;
     lfs_dir_read(&lfs, &dir[0], &info) => 1;
     lfs_dir_read(&lfs, &dir[0], &info) => 1;
     strcmp(info.name, "mediumavacado") => 0;
     strcmp(info.name, "mediumavacado") => 0;
     info.type => LFS_TYPE_REG;
     info.type => LFS_TYPE_REG;
     info.size => $MEDIUMSIZE;
     info.size => $MEDIUMSIZE;
     lfs_dir_read(&lfs, &dir[0], &info) => 1;
     lfs_dir_read(&lfs, &dir[0], &info) => 1;
-    strcmp(info.name, "largeavacado") => 0;
-    info.type => LFS_TYPE_REG;
-    info.size => $LARGESIZE;
-    lfs_dir_read(&lfs, &dir[0], &info) => 1;
     strcmp(info.name, "noavacado") => 0;
     strcmp(info.name, "noavacado") => 0;
     info.type => LFS_TYPE_REG;
     info.type => LFS_TYPE_REG;
     info.size => 0;
     info.size => 0;
+    lfs_dir_read(&lfs, &dir[0], &info) => 1;
+    strcmp(info.name, "smallavacado") => 0;
+    info.type => LFS_TYPE_REG;
+    info.size => $SMALLSIZE;
     lfs_dir_read(&lfs, &dir[0], &info) => 0;
     lfs_dir_read(&lfs, &dir[0], &info) => 0;
     lfs_dir_close(&lfs, &dir[0]) => 0;
     lfs_dir_close(&lfs, &dir[0]) => 0;
     lfs_unmount(&lfs) => 0;
     lfs_unmount(&lfs) => 0;

+ 15 - 14
tests/test_format.sh

@@ -9,39 +9,40 @@ tests/test.py << TEST
     lfs_format(&lfs, &cfg) => 0;
     lfs_format(&lfs, &cfg) => 0;
 TEST
 TEST
 
 
-echo "--- Invalid superblocks ---"
-ln -f -s /dev/zero blocks/0
-ln -f -s /dev/zero blocks/1
-tests/test.py << TEST
-    lfs_format(&lfs, &cfg) => LFS_ERR_CORRUPT;
-TEST
-rm blocks/0 blocks/1
-
 echo "--- Basic mounting ---"
 echo "--- Basic mounting ---"
 tests/test.py << TEST
 tests/test.py << TEST
     lfs_format(&lfs, &cfg) => 0;
     lfs_format(&lfs, &cfg) => 0;
-TEST
-tests/test.py << TEST
+
     lfs_mount(&lfs, &cfg) => 0;
     lfs_mount(&lfs, &cfg) => 0;
     lfs_unmount(&lfs) => 0;
     lfs_unmount(&lfs) => 0;
 TEST
 TEST
 
 
-echo "--- Invalid mount ---"
+echo "--- Invalid superblocks ---"
+ln -f -s /dev/zero blocks/0
+ln -f -s /dev/zero blocks/1
 tests/test.py << TEST
 tests/test.py << TEST
-    lfs_format(&lfs, &cfg) => 0;
+    lfs_format(&lfs, &cfg) => LFS_ERR_NOSPC;
 TEST
 TEST
 rm blocks/0 blocks/1
 rm blocks/0 blocks/1
+
+echo "--- Invalid mount ---"
 tests/test.py << TEST
 tests/test.py << TEST
     lfs_mount(&lfs, &cfg) => LFS_ERR_CORRUPT;
     lfs_mount(&lfs, &cfg) => LFS_ERR_CORRUPT;
 TEST
 TEST
 
 
-echo "--- Valid corrupt mount ---"
+echo "--- Expanding superblock ---"
 tests/test.py << TEST
 tests/test.py << TEST
     lfs_format(&lfs, &cfg) => 0;
     lfs_format(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, &cfg) => 0;
+    for (int i = 0; i < 100; i++) {
+        lfs_mkdir(&lfs, "dummy") => 0;
+        lfs_remove(&lfs, "dummy") => 0;
+    }
+    lfs_unmount(&lfs) => 0;
 TEST
 TEST
-rm blocks/0
 tests/test.py << TEST
 tests/test.py << TEST
     lfs_mount(&lfs, &cfg) => 0;
     lfs_mount(&lfs, &cfg) => 0;
+    lfs_mkdir(&lfs, "dummy") => 0;
     lfs_unmount(&lfs) => 0;
     lfs_unmount(&lfs) => 0;
 TEST
 TEST
 
 

+ 110 - 14
tests/test_move.sh

@@ -59,7 +59,7 @@ tests/test.py << TEST
     lfs_rename(&lfs, "b/hello", "c/hello") => 0;
     lfs_rename(&lfs, "b/hello", "c/hello") => 0;
     lfs_unmount(&lfs) => 0;
     lfs_unmount(&lfs) => 0;
 TEST
 TEST
-rm -v blocks/7
+tests/corrupt.py -n 1
 tests/test.py << TEST
 tests/test.py << TEST
     lfs_mount(&lfs, &cfg) => 0;
     lfs_mount(&lfs, &cfg) => 0;
     lfs_dir_open(&lfs, &dir[0], "b") => 0;
     lfs_dir_open(&lfs, &dir[0], "b") => 0;
@@ -86,8 +86,7 @@ tests/test.py << TEST
     lfs_rename(&lfs, "c/hello", "d/hello") => 0;
     lfs_rename(&lfs, "c/hello", "d/hello") => 0;
     lfs_unmount(&lfs) => 0;
     lfs_unmount(&lfs) => 0;
 TEST
 TEST
-rm -v blocks/8
-rm -v blocks/a
+tests/corrupt.py -n 2
 tests/test.py << TEST
 tests/test.py << TEST
     lfs_mount(&lfs, &cfg) => 0;
     lfs_mount(&lfs, &cfg) => 0;
     lfs_dir_open(&lfs, &dir[0], "c") => 0;
     lfs_dir_open(&lfs, &dir[0], "c") => 0;
@@ -108,6 +107,32 @@ tests/test.py << TEST
     lfs_unmount(&lfs) => 0;
     lfs_unmount(&lfs) => 0;
 TEST
 TEST
 
 
+echo "--- Move file after corrupt ---"
+tests/test.py << TEST
+    lfs_mount(&lfs, &cfg) => 0;
+    lfs_rename(&lfs, "c/hello", "d/hello") => 0;
+    lfs_unmount(&lfs) => 0;
+TEST
+tests/test.py << TEST
+    lfs_mount(&lfs, &cfg) => 0;
+    lfs_dir_open(&lfs, &dir[0], "c") => 0;
+    lfs_dir_read(&lfs, &dir[0], &info) => 1;
+    strcmp(info.name, ".") => 0;
+    lfs_dir_read(&lfs, &dir[0], &info) => 1;
+    strcmp(info.name, "..") => 0;
+    lfs_dir_read(&lfs, &dir[0], &info) => 0;
+    lfs_dir_close(&lfs, &dir[0]) => 0;
+    lfs_dir_open(&lfs, &dir[0], "d") => 0;
+    lfs_dir_read(&lfs, &dir[0], &info) => 1;
+    strcmp(info.name, ".") => 0;
+    lfs_dir_read(&lfs, &dir[0], &info) => 1;
+    strcmp(info.name, "..") => 0;
+    lfs_dir_read(&lfs, &dir[0], &info) => 1;
+    strcmp(info.name, "hello") => 0;
+    lfs_dir_read(&lfs, &dir[0], &info) => 0;
+    lfs_unmount(&lfs) => 0;
+TEST
+
 echo "--- Move dir ---"
 echo "--- Move dir ---"
 tests/test.py << TEST
 tests/test.py << TEST
     lfs_mount(&lfs, &cfg) => 0;
     lfs_mount(&lfs, &cfg) => 0;
@@ -140,7 +165,7 @@ tests/test.py << TEST
     lfs_rename(&lfs, "b/hi", "c/hi") => 0;
     lfs_rename(&lfs, "b/hi", "c/hi") => 0;
     lfs_unmount(&lfs) => 0;
     lfs_unmount(&lfs) => 0;
 TEST
 TEST
-rm -v blocks/7
+tests/corrupt.py -n 1
 tests/test.py << TEST
 tests/test.py << TEST
     lfs_mount(&lfs, &cfg) => 0;
     lfs_mount(&lfs, &cfg) => 0;
     lfs_dir_open(&lfs, &dir[0], "b") => 0;
     lfs_dir_open(&lfs, &dir[0], "b") => 0;
@@ -156,8 +181,6 @@ tests/test.py << TEST
     lfs_dir_read(&lfs, &dir[0], &info) => 1;
     lfs_dir_read(&lfs, &dir[0], &info) => 1;
     strcmp(info.name, "..") => 0;
     strcmp(info.name, "..") => 0;
     lfs_dir_read(&lfs, &dir[0], &info) => 1;
     lfs_dir_read(&lfs, &dir[0], &info) => 1;
-    strcmp(info.name, "hello") => 0;
-    lfs_dir_read(&lfs, &dir[0], &info) => 1;
     strcmp(info.name, "hi") => 0;
     strcmp(info.name, "hi") => 0;
     lfs_dir_read(&lfs, &dir[0], &info) => 0;
     lfs_dir_read(&lfs, &dir[0], &info) => 0;
     lfs_unmount(&lfs) => 0;
     lfs_unmount(&lfs) => 0;
@@ -169,8 +192,7 @@ tests/test.py << TEST
     lfs_rename(&lfs, "c/hi", "d/hi") => 0;
     lfs_rename(&lfs, "c/hi", "d/hi") => 0;
     lfs_unmount(&lfs) => 0;
     lfs_unmount(&lfs) => 0;
 TEST
 TEST
-rm -v blocks/9
-rm -v blocks/a
+tests/corrupt.py -n 2
 tests/test.py << TEST
 tests/test.py << TEST
     lfs_mount(&lfs, &cfg) => 0;
     lfs_mount(&lfs, &cfg) => 0;
     lfs_dir_open(&lfs, &dir[0], "c") => 0;
     lfs_dir_open(&lfs, &dir[0], "c") => 0;
@@ -179,9 +201,33 @@ tests/test.py << TEST
     lfs_dir_read(&lfs, &dir[0], &info) => 1;
     lfs_dir_read(&lfs, &dir[0], &info) => 1;
     strcmp(info.name, "..") => 0;
     strcmp(info.name, "..") => 0;
     lfs_dir_read(&lfs, &dir[0], &info) => 1;
     lfs_dir_read(&lfs, &dir[0], &info) => 1;
+    strcmp(info.name, "hi") => 0;
+    lfs_dir_read(&lfs, &dir[0], &info) => 0;
+    lfs_dir_close(&lfs, &dir[0]) => 0;
+    lfs_dir_open(&lfs, &dir[0], "d") => 0;
+    lfs_dir_read(&lfs, &dir[0], &info) => 1;
+    strcmp(info.name, ".") => 0;
+    lfs_dir_read(&lfs, &dir[0], &info) => 1;
+    strcmp(info.name, "..") => 0;
+    lfs_dir_read(&lfs, &dir[0], &info) => 1;
     strcmp(info.name, "hello") => 0;
     strcmp(info.name, "hello") => 0;
+    lfs_dir_read(&lfs, &dir[0], &info) => 0;
+    lfs_unmount(&lfs) => 0;
+TEST
+
+echo "--- Move dir after corrupt ---"
+tests/test.py << TEST
+    lfs_mount(&lfs, &cfg) => 0;
+    lfs_rename(&lfs, "c/hi", "d/hi") => 0;
+    lfs_unmount(&lfs) => 0;
+TEST
+tests/test.py << TEST
+    lfs_mount(&lfs, &cfg) => 0;
+    lfs_dir_open(&lfs, &dir[0], "c") => 0;
     lfs_dir_read(&lfs, &dir[0], &info) => 1;
     lfs_dir_read(&lfs, &dir[0], &info) => 1;
-    strcmp(info.name, "hi") => 0;
+    strcmp(info.name, ".") => 0;
+    lfs_dir_read(&lfs, &dir[0], &info) => 1;
+    strcmp(info.name, "..") => 0;
     lfs_dir_read(&lfs, &dir[0], &info) => 0;
     lfs_dir_read(&lfs, &dir[0], &info) => 0;
     lfs_dir_close(&lfs, &dir[0]) => 0;
     lfs_dir_close(&lfs, &dir[0]) => 0;
     lfs_dir_open(&lfs, &dir[0], "d") => 0;
     lfs_dir_open(&lfs, &dir[0], "d") => 0;
@@ -189,6 +235,10 @@ tests/test.py << TEST
     strcmp(info.name, ".") => 0;
     strcmp(info.name, ".") => 0;
     lfs_dir_read(&lfs, &dir[0], &info) => 1;
     lfs_dir_read(&lfs, &dir[0], &info) => 1;
     strcmp(info.name, "..") => 0;
     strcmp(info.name, "..") => 0;
+    lfs_dir_read(&lfs, &dir[0], &info) => 1;
+    strcmp(info.name, "hello") => 0;
+    lfs_dir_read(&lfs, &dir[0], &info) => 1;
+    strcmp(info.name, "hi") => 0;
     lfs_dir_read(&lfs, &dir[0], &info) => 0;
     lfs_dir_read(&lfs, &dir[0], &info) => 0;
     lfs_unmount(&lfs) => 0;
     lfs_unmount(&lfs) => 0;
 TEST
 TEST
@@ -199,27 +249,73 @@ tests/test.py << TEST
 
 
     lfs_dir_open(&lfs, &dir[0], "a/hi") => LFS_ERR_NOENT;
     lfs_dir_open(&lfs, &dir[0], "a/hi") => LFS_ERR_NOENT;
     lfs_dir_open(&lfs, &dir[0], "b/hi") => LFS_ERR_NOENT;
     lfs_dir_open(&lfs, &dir[0], "b/hi") => LFS_ERR_NOENT;
-    lfs_dir_open(&lfs, &dir[0], "d/hi") => LFS_ERR_NOENT;
+    lfs_dir_open(&lfs, &dir[0], "c/hi") => LFS_ERR_NOENT;
 
 
-    lfs_dir_open(&lfs, &dir[0], "c/hi") => 0;
+    lfs_dir_open(&lfs, &dir[0], "d/hi") => 0;
     lfs_dir_read(&lfs, &dir[0], &info) => 1;
     lfs_dir_read(&lfs, &dir[0], &info) => 1;
     strcmp(info.name, ".") => 0;
     strcmp(info.name, ".") => 0;
     lfs_dir_read(&lfs, &dir[0], &info) => 1;
     lfs_dir_read(&lfs, &dir[0], &info) => 1;
     strcmp(info.name, "..") => 0;
     strcmp(info.name, "..") => 0;
     lfs_dir_read(&lfs, &dir[0], &info) => 1;
     lfs_dir_read(&lfs, &dir[0], &info) => 1;
+    strcmp(info.name, "bonjour") => 0;
+    lfs_dir_read(&lfs, &dir[0], &info) => 1;
     strcmp(info.name, "hola") => 0;
     strcmp(info.name, "hola") => 0;
     lfs_dir_read(&lfs, &dir[0], &info) => 1;
     lfs_dir_read(&lfs, &dir[0], &info) => 1;
+    strcmp(info.name, "ohayo") => 0;
+    lfs_dir_read(&lfs, &dir[0], &info) => 0;
+    lfs_dir_close(&lfs, &dir[0]) => 0;
+
+    lfs_dir_open(&lfs, &dir[0], "a/hello") => LFS_ERR_NOENT;
+    lfs_dir_open(&lfs, &dir[0], "b/hello") => LFS_ERR_NOENT;
+    lfs_dir_open(&lfs, &dir[0], "c/hello") => LFS_ERR_NOENT;
+
+    lfs_file_open(&lfs, &file[0], "d/hello", LFS_O_RDONLY) => 0;
+    lfs_file_read(&lfs, &file[0], buffer, 5) => 5;
+    memcmp(buffer, "hola\n", 5) => 0;
+    lfs_file_read(&lfs, &file[0], buffer, 8) => 8;
+    memcmp(buffer, "bonjour\n", 8) => 0;
+    lfs_file_read(&lfs, &file[0], buffer, 6) => 6;
+    memcmp(buffer, "ohayo\n", 6) => 0;
+    lfs_file_close(&lfs, &file[0]) => 0;
+
+    lfs_unmount(&lfs) => 0;
+TEST
+
+echo "--- Move state stealing ---"
+tests/test.py << TEST
+    lfs_mount(&lfs, &cfg) => 0;
+
+    lfs_remove(&lfs, "b") => 0;
+    lfs_remove(&lfs, "c") => 0;
+
+    lfs_unmount(&lfs) => 0;
+TEST
+tests/test.py << TEST
+    lfs_mount(&lfs, &cfg) => 0;
+
+    lfs_dir_open(&lfs, &dir[0], "a/hi") => LFS_ERR_NOENT;
+    lfs_dir_open(&lfs, &dir[0], "b") => LFS_ERR_NOENT;
+    lfs_dir_open(&lfs, &dir[0], "c") => LFS_ERR_NOENT;
+
+    lfs_dir_open(&lfs, &dir[0], "d/hi") => 0;
+    lfs_dir_read(&lfs, &dir[0], &info) => 1;
+    strcmp(info.name, ".") => 0;
+    lfs_dir_read(&lfs, &dir[0], &info) => 1;
+    strcmp(info.name, "..") => 0;
+    lfs_dir_read(&lfs, &dir[0], &info) => 1;
     strcmp(info.name, "bonjour") => 0;
     strcmp(info.name, "bonjour") => 0;
     lfs_dir_read(&lfs, &dir[0], &info) => 1;
     lfs_dir_read(&lfs, &dir[0], &info) => 1;
+    strcmp(info.name, "hola") => 0;
+    lfs_dir_read(&lfs, &dir[0], &info) => 1;
     strcmp(info.name, "ohayo") => 0;
     strcmp(info.name, "ohayo") => 0;
     lfs_dir_read(&lfs, &dir[0], &info) => 0;
     lfs_dir_read(&lfs, &dir[0], &info) => 0;
     lfs_dir_close(&lfs, &dir[0]) => 0;
     lfs_dir_close(&lfs, &dir[0]) => 0;
 
 
     lfs_dir_open(&lfs, &dir[0], "a/hello") => LFS_ERR_NOENT;
     lfs_dir_open(&lfs, &dir[0], "a/hello") => LFS_ERR_NOENT;
-    lfs_dir_open(&lfs, &dir[0], "b/hello") => LFS_ERR_NOENT;
-    lfs_dir_open(&lfs, &dir[0], "d/hello") => LFS_ERR_NOENT;
+    lfs_dir_open(&lfs, &dir[0], "b") => LFS_ERR_NOENT;
+    lfs_dir_open(&lfs, &dir[0], "c") => LFS_ERR_NOENT;
 
 
-    lfs_file_open(&lfs, &file[0], "c/hello", LFS_O_RDONLY) => 0;
+    lfs_file_open(&lfs, &file[0], "d/hello", LFS_O_RDONLY) => 0;
     lfs_file_read(&lfs, &file[0], buffer, 5) => 5;
     lfs_file_read(&lfs, &file[0], buffer, 5) => 5;
     memcmp(buffer, "hola\n", 5) => 0;
     memcmp(buffer, "hola\n", 5) => 0;
     lfs_file_read(&lfs, &file[0], buffer, 8) => 8;
     lfs_file_read(&lfs, &file[0], buffer, 8) => 8;

+ 15 - 11
tests/test_orphan.sh

@@ -15,25 +15,29 @@ tests/test.py << TEST
     lfs_mkdir(&lfs, "parent/child") => 0;
     lfs_mkdir(&lfs, "parent/child") => 0;
     lfs_remove(&lfs, "parent/orphan") => 0;
     lfs_remove(&lfs, "parent/orphan") => 0;
 TEST
 TEST
-# remove most recent file, this should be the update to the previous
+# corrupt most recent commit, this should be the update to the previous
 # linked-list entry and should orphan the child
 # linked-list entry and should orphan the child
-rm -v blocks/8
+tests/corrupt.py
 tests/test.py << TEST
 tests/test.py << TEST
     lfs_mount(&lfs, &cfg) => 0;
     lfs_mount(&lfs, &cfg) => 0;
+
+    lfs_stat(&lfs, "parent/orphan", &info) => LFS_ERR_NOENT;
+    lfs_ssize_t before = lfs_fs_size(&lfs);
+    before => 8;
+
+    lfs_unmount(&lfs) => 0;
+    lfs_mount(&lfs, &cfg) => 0;
+
     lfs_stat(&lfs, "parent/orphan", &info) => LFS_ERR_NOENT;
     lfs_stat(&lfs, "parent/orphan", &info) => LFS_ERR_NOENT;
-    unsigned before = 0;
-    lfs_traverse(&lfs, test_count, &before) => 0;
-    test_log("before", before);
+    lfs_ssize_t orphaned = lfs_fs_size(&lfs);
+    orphaned => 8;
 
 
-    lfs_deorphan(&lfs) => 0;
+    lfs_mkdir(&lfs, "parent/otherchild") => 0;
 
 
     lfs_stat(&lfs, "parent/orphan", &info) => LFS_ERR_NOENT;
     lfs_stat(&lfs, "parent/orphan", &info) => LFS_ERR_NOENT;
-    unsigned after = 0;
-    lfs_traverse(&lfs, test_count, &after) => 0;
-    test_log("after", after);
+    lfs_ssize_t deorphaned = lfs_fs_size(&lfs);
+    deorphaned => 8;
 
 
-    int diff = before - after;
-    diff => 2;
     lfs_unmount(&lfs) => 0;
     lfs_unmount(&lfs) => 0;
 TEST
 TEST
 
 

+ 58 - 0
tests/test_paths.sh

@@ -128,6 +128,14 @@ tests/test.py << TEST
     lfs_mkdir(&lfs, "/") => LFS_ERR_EXIST;
     lfs_mkdir(&lfs, "/") => LFS_ERR_EXIST;
     lfs_file_open(&lfs, &file[0], "/", LFS_O_WRONLY | LFS_O_CREAT)
     lfs_file_open(&lfs, &file[0], "/", LFS_O_WRONLY | LFS_O_CREAT)
         => LFS_ERR_ISDIR;
         => LFS_ERR_ISDIR;
+
+    // more corner cases
+    lfs_remove(&lfs, "") => LFS_ERR_INVAL;
+    lfs_remove(&lfs, ".") => LFS_ERR_INVAL;
+    lfs_remove(&lfs, "..") => LFS_ERR_INVAL;
+    lfs_remove(&lfs, "/") => LFS_ERR_INVAL;
+    lfs_remove(&lfs, "//") => LFS_ERR_INVAL;
+    lfs_remove(&lfs, "./") => LFS_ERR_INVAL;
     lfs_unmount(&lfs) => 0;
     lfs_unmount(&lfs) => 0;
 TEST
 TEST
 
 
@@ -139,5 +147,55 @@ tests/test.py << TEST
     lfs_unmount(&lfs) => 0;
     lfs_unmount(&lfs) => 0;
 TEST
 TEST
 
 
+echo "--- Superblock conflict test ---"
+tests/test.py << TEST
+    lfs_mount(&lfs, &cfg) => 0;
+    lfs_mkdir(&lfs, "littlefs") => 0;
+    lfs_remove(&lfs, "littlefs") => 0;
+    lfs_unmount(&lfs) => 0;
+TEST
+
+echo "--- Max path test ---"
+tests/test.py << TEST
+    lfs_mount(&lfs, &cfg) => 0;
+    memset(buffer, 'w', LFS_NAME_MAX+1);
+    buffer[LFS_NAME_MAX+2] = '\0';
+    lfs_mkdir(&lfs, (char*)buffer) => LFS_ERR_NAMETOOLONG;
+    lfs_file_open(&lfs, &file[0], (char*)buffer,
+            LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_NAMETOOLONG;
+
+    memcpy(buffer, "coffee/", strlen("coffee/"));
+    memset(buffer+strlen("coffee/"), 'w', LFS_NAME_MAX+1);
+    buffer[strlen("coffee/")+LFS_NAME_MAX+2] = '\0';
+    lfs_mkdir(&lfs, (char*)buffer) => LFS_ERR_NAMETOOLONG;
+    lfs_file_open(&lfs, &file[0], (char*)buffer,
+            LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_NAMETOOLONG;
+    lfs_unmount(&lfs) => 0;
+TEST
+
+echo "--- Really big path test ---"
+tests/test.py << TEST
+    lfs_mount(&lfs, &cfg) => 0;
+    memset(buffer, 'w', LFS_NAME_MAX);
+    buffer[LFS_NAME_MAX+1] = '\0';
+    lfs_mkdir(&lfs, (char*)buffer) => 0;
+    lfs_remove(&lfs, (char*)buffer) => 0;
+    lfs_file_open(&lfs, &file[0], (char*)buffer,
+            LFS_O_WRONLY | LFS_O_CREAT) => 0;
+    lfs_file_close(&lfs, &file[0]) => 0;
+    lfs_remove(&lfs, (char*)buffer) => 0;
+
+    memcpy(buffer, "coffee/", strlen("coffee/"));
+    memset(buffer+strlen("coffee/"), 'w', LFS_NAME_MAX);
+    buffer[strlen("coffee/")+LFS_NAME_MAX+1] = '\0';
+    lfs_mkdir(&lfs, (char*)buffer) => 0;
+    lfs_remove(&lfs, (char*)buffer) => 0;
+    lfs_file_open(&lfs, &file[0], (char*)buffer,
+            LFS_O_WRONLY | LFS_O_CREAT) => 0;
+    lfs_file_close(&lfs, &file[0]) => 0;
+    lfs_remove(&lfs, (char*)buffer) => 0;
+    lfs_unmount(&lfs) => 0;
+TEST
+
 echo "--- Results ---"
 echo "--- Results ---"
 tests/stats.py
 tests/stats.py

+ 15 - 15
tests/test_seek.sh

@@ -12,7 +12,7 @@ tests/test.py << TEST
     lfs_mount(&lfs, &cfg) => 0;
     lfs_mount(&lfs, &cfg) => 0;
     lfs_mkdir(&lfs, "hello") => 0;
     lfs_mkdir(&lfs, "hello") => 0;
     for (int i = 0; i < $LARGESIZE; i++) {
     for (int i = 0; i < $LARGESIZE; i++) {
-        sprintf((char*)buffer, "hello/kitty%d", i);
+        sprintf((char*)buffer, "hello/kitty%03d", i);
         lfs_file_open(&lfs, &file[0], (char*)buffer,
         lfs_file_open(&lfs, &file[0], (char*)buffer,
                 LFS_O_WRONLY | LFS_O_CREAT | LFS_O_APPEND) => 0;
                 LFS_O_WRONLY | LFS_O_CREAT | LFS_O_APPEND) => 0;
 
 
@@ -39,7 +39,7 @@ tests/test.py << TEST
     lfs_soff_t pos;
     lfs_soff_t pos;
     int i;
     int i;
     for (i = 0; i < $SMALLSIZE; i++) {
     for (i = 0; i < $SMALLSIZE; i++) {
-        sprintf((char*)buffer, "kitty%d", i);
+        sprintf((char*)buffer, "kitty%03d", i);
         lfs_dir_read(&lfs, &dir[0], &info) => 1;
         lfs_dir_read(&lfs, &dir[0], &info) => 1;
         strcmp(info.name, (char*)buffer) => 0;
         strcmp(info.name, (char*)buffer) => 0;
         pos = lfs_dir_tell(&lfs, &dir[0]);
         pos = lfs_dir_tell(&lfs, &dir[0]);
@@ -47,12 +47,12 @@ tests/test.py << TEST
     pos >= 0 => 1;
     pos >= 0 => 1;
 
 
     lfs_dir_seek(&lfs, &dir[0], pos) => 0;
     lfs_dir_seek(&lfs, &dir[0], pos) => 0;
-    sprintf((char*)buffer, "kitty%d", i);
+    sprintf((char*)buffer, "kitty%03d", i);
     lfs_dir_read(&lfs, &dir[0], &info) => 1;
     lfs_dir_read(&lfs, &dir[0], &info) => 1;
     strcmp(info.name, (char*)buffer) => 0;
     strcmp(info.name, (char*)buffer) => 0;
 
 
     lfs_dir_rewind(&lfs, &dir[0]) => 0;
     lfs_dir_rewind(&lfs, &dir[0]) => 0;
-    sprintf((char*)buffer, "kitty%d", 0);
+    sprintf((char*)buffer, "kitty%03d", 0);
     lfs_dir_read(&lfs, &dir[0], &info) => 1;
     lfs_dir_read(&lfs, &dir[0], &info) => 1;
     strcmp(info.name, ".") => 0;
     strcmp(info.name, ".") => 0;
     lfs_dir_read(&lfs, &dir[0], &info) => 1;
     lfs_dir_read(&lfs, &dir[0], &info) => 1;
@@ -61,7 +61,7 @@ tests/test.py << TEST
     strcmp(info.name, (char*)buffer) => 0;
     strcmp(info.name, (char*)buffer) => 0;
 
 
     lfs_dir_seek(&lfs, &dir[0], pos) => 0;
     lfs_dir_seek(&lfs, &dir[0], pos) => 0;
-    sprintf((char*)buffer, "kitty%d", i);
+    sprintf((char*)buffer, "kitty%03d", i);
     lfs_dir_read(&lfs, &dir[0], &info) => 1;
     lfs_dir_read(&lfs, &dir[0], &info) => 1;
     strcmp(info.name, (char*)buffer) => 0;
     strcmp(info.name, (char*)buffer) => 0;
 
 
@@ -81,7 +81,7 @@ tests/test.py << TEST
     lfs_soff_t pos;
     lfs_soff_t pos;
     int i;
     int i;
     for (i = 0; i < $MEDIUMSIZE; i++) {
     for (i = 0; i < $MEDIUMSIZE; i++) {
-        sprintf((char*)buffer, "kitty%d", i);
+        sprintf((char*)buffer, "kitty%03d", i);
         lfs_dir_read(&lfs, &dir[0], &info) => 1;
         lfs_dir_read(&lfs, &dir[0], &info) => 1;
         strcmp(info.name, (char*)buffer) => 0;
         strcmp(info.name, (char*)buffer) => 0;
         pos = lfs_dir_tell(&lfs, &dir[0]);
         pos = lfs_dir_tell(&lfs, &dir[0]);
@@ -89,12 +89,12 @@ tests/test.py << TEST
     pos >= 0 => 1;
     pos >= 0 => 1;
 
 
     lfs_dir_seek(&lfs, &dir[0], pos) => 0;
     lfs_dir_seek(&lfs, &dir[0], pos) => 0;
-    sprintf((char*)buffer, "kitty%d", i);
+    sprintf((char*)buffer, "kitty%03d", i);
     lfs_dir_read(&lfs, &dir[0], &info) => 1;
     lfs_dir_read(&lfs, &dir[0], &info) => 1;
     strcmp(info.name, (char*)buffer) => 0;
     strcmp(info.name, (char*)buffer) => 0;
 
 
     lfs_dir_rewind(&lfs, &dir[0]) => 0;
     lfs_dir_rewind(&lfs, &dir[0]) => 0;
-    sprintf((char*)buffer, "kitty%d", 0);
+    sprintf((char*)buffer, "kitty%03d", 0);
     lfs_dir_read(&lfs, &dir[0], &info) => 1;
     lfs_dir_read(&lfs, &dir[0], &info) => 1;
     strcmp(info.name, ".") => 0;
     strcmp(info.name, ".") => 0;
     lfs_dir_read(&lfs, &dir[0], &info) => 1;
     lfs_dir_read(&lfs, &dir[0], &info) => 1;
@@ -103,7 +103,7 @@ tests/test.py << TEST
     strcmp(info.name, (char*)buffer) => 0;
     strcmp(info.name, (char*)buffer) => 0;
 
 
     lfs_dir_seek(&lfs, &dir[0], pos) => 0;
     lfs_dir_seek(&lfs, &dir[0], pos) => 0;
-    sprintf((char*)buffer, "kitty%d", i);
+    sprintf((char*)buffer, "kitty%03d", i);
     lfs_dir_read(&lfs, &dir[0], &info) => 1;
     lfs_dir_read(&lfs, &dir[0], &info) => 1;
     strcmp(info.name, (char*)buffer) => 0;
     strcmp(info.name, (char*)buffer) => 0;
 
 
@@ -114,7 +114,7 @@ TEST
 echo "--- Simple file seek ---"
 echo "--- Simple file seek ---"
 tests/test.py << TEST
 tests/test.py << TEST
     lfs_mount(&lfs, &cfg) => 0;
     lfs_mount(&lfs, &cfg) => 0;
-    lfs_file_open(&lfs, &file[0], "hello/kitty42", LFS_O_RDONLY) => 0;
+    lfs_file_open(&lfs, &file[0], "hello/kitty042", LFS_O_RDONLY) => 0;
 
 
     lfs_soff_t pos;
     lfs_soff_t pos;
     size = strlen("kittycatcat");
     size = strlen("kittycatcat");
@@ -163,7 +163,7 @@ TEST
 echo "--- Large file seek ---"
 echo "--- Large file seek ---"
 tests/test.py << TEST
 tests/test.py << TEST
     lfs_mount(&lfs, &cfg) => 0;
     lfs_mount(&lfs, &cfg) => 0;
-    lfs_file_open(&lfs, &file[0], "hello/kitty42", LFS_O_RDONLY) => 0;
+    lfs_file_open(&lfs, &file[0], "hello/kitty042", LFS_O_RDONLY) => 0;
 
 
     lfs_soff_t pos;
     lfs_soff_t pos;
     size = strlen("kittycatcat");
     size = strlen("kittycatcat");
@@ -212,7 +212,7 @@ TEST
 echo "--- Simple file seek and write ---"
 echo "--- Simple file seek and write ---"
 tests/test.py << TEST
 tests/test.py << TEST
     lfs_mount(&lfs, &cfg) => 0;
     lfs_mount(&lfs, &cfg) => 0;
-    lfs_file_open(&lfs, &file[0], "hello/kitty42", LFS_O_RDWR) => 0;
+    lfs_file_open(&lfs, &file[0], "hello/kitty042", LFS_O_RDWR) => 0;
 
 
     lfs_soff_t pos;
     lfs_soff_t pos;
     size = strlen("kittycatcat");
     size = strlen("kittycatcat");
@@ -253,7 +253,7 @@ TEST
 echo "--- Large file seek and write ---"
 echo "--- Large file seek and write ---"
 tests/test.py << TEST
 tests/test.py << TEST
     lfs_mount(&lfs, &cfg) => 0;
     lfs_mount(&lfs, &cfg) => 0;
-    lfs_file_open(&lfs, &file[0], "hello/kitty42", LFS_O_RDWR) => 0;
+    lfs_file_open(&lfs, &file[0], "hello/kitty042", LFS_O_RDWR) => 0;
 
 
     lfs_soff_t pos;
     lfs_soff_t pos;
     size = strlen("kittycatcat");
     size = strlen("kittycatcat");
@@ -296,7 +296,7 @@ TEST
 echo "--- Boundary seek and write ---"
 echo "--- Boundary seek and write ---"
 tests/test.py << TEST
 tests/test.py << TEST
     lfs_mount(&lfs, &cfg) => 0;
     lfs_mount(&lfs, &cfg) => 0;
-    lfs_file_open(&lfs, &file[0], "hello/kitty42", LFS_O_RDWR) => 0;
+    lfs_file_open(&lfs, &file[0], "hello/kitty042", LFS_O_RDWR) => 0;
 
 
     size = strlen("hedgehoghog");
     size = strlen("hedgehoghog");
     const lfs_soff_t offsets[] = {512, 1020, 513, 1021, 511, 1019};
     const lfs_soff_t offsets[] = {512, 1020, 513, 1021, 511, 1019};
@@ -324,7 +324,7 @@ TEST
 echo "--- Out-of-bounds seek ---"
 echo "--- Out-of-bounds seek ---"
 tests/test.py << TEST
 tests/test.py << TEST
     lfs_mount(&lfs, &cfg) => 0;
     lfs_mount(&lfs, &cfg) => 0;
-    lfs_file_open(&lfs, &file[0], "hello/kitty42", LFS_O_RDWR) => 0;
+    lfs_file_open(&lfs, &file[0], "hello/kitty042", LFS_O_RDWR) => 0;
 
 
     size = strlen("kittycatcat");
     size = strlen("kittycatcat");
     lfs_file_size(&lfs, &file[0]) => $LARGESIZE*size;
     lfs_file_size(&lfs, &file[0]) => $LARGESIZE*size;

Some files were not shown because too many files changed in this diff