Browse Source

Merge branch 'master' into HEAD

xieyangrun 8 năm trước cách đây
mục cha
commit
276efe486e
15 tập tin đã thay đổi với 600 bổ sung và 560 xóa
  1. 31 39
      .travis.yml
  2. 24 165
      LICENSE.md
  3. 10 4
      Makefile
  4. 13 0
      README.md
  5. 19 25
      emubd/lfs_emubd.c
  6. 11 13
      emubd/lfs_emubd.h
  7. 286 253
      lfs.c
  8. 41 25
      lfs.h
  9. 2 13
      lfs_util.c
  10. 14 13
      lfs_util.h
  11. 124 1
      tests/test_alloc.sh
  12. 2 2
      tests/test_files.sh
  13. 3 3
      tests/test_interspersed.sh
  14. 16 0
      tests/test_paths.sh
  15. 4 4
      tests/test_seek.sh

+ 31 - 39
.travis.yml

@@ -27,7 +27,7 @@ script:
   # compile and find the code size with the smallest configuration
   - make clean size
         OBJ="$(ls lfs*.o | tr '\n' ' ')"
-        CFLAGS+="-DLFS_NO{ASSERT,DEBUG,WARN,ERROR}"
+        CFLAGS+="-DLFS_NO_ASSERT -DLFS_NO_DEBUG -DLFS_NO_WARN -DLFS_NO_ERROR"
         | tee sizes
 
   # update status if we succeeded, compare with master if possible
@@ -35,7 +35,7 @@ script:
     if [ "$TRAVIS_TEST_RESULT" -eq 0 ]
     then
         CURR=$(tail -n1 sizes | awk '{print $1}')
-        PREV=$(curl https://api.github.com/repos/$TRAVIS_REPO_SLUG/status/master \
+        PREV=$(curl -u $GEKY_BOT_STATUSES https://api.github.com/repos/$TRAVIS_REPO_SLUG/status/master \
             | jq -re "select(.sha != \"$TRAVIS_COMMIT\")
                 | .statuses[] | select(.context == \"$STAGE/$NAME\").description
                 | capture(\"code size is (?<size>[0-9]+)\").size" \
@@ -134,52 +134,44 @@ jobs:
         - STAGE=deploy
         - NAME=deploy
       script:
-        # Update tag for version defined in lfs.h
+        # 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)))
-        - LFS_VERSION="v$LFS_VERSION_MAJOR.$LFS_VERSION_MINOR"
-        - echo "littlefs version $LFS_VERSION"
+        # Grab latests patch from repo tags, default to 0
+        - LFS_VERSION_PATCH=$(curl -f -u "$GEKY_BOT_RELEASES"
+                https://api.github.com/repos/$TRAVIS_REPO_SLUG/git/refs
+                | jq 'map(.ref | match(
+                    "refs/tags/v'"$LFS_VERSION_MAJOR"'\\.'"$LFS_VERSION_MINOR"'\\.(.*)$")
+                    .captures[].string | tonumber + 1) | max // 0')
+        # We have our new version
+        - LFS_VERSION="v$LFS_VERSION_MAJOR.$LFS_VERSION_MINOR.$LFS_VERSION_PATCH"
+        - echo "VERSION $LFS_VERSION"
         - |
-          curl -u $GEKY_BOT_RELEASES -X POST \
-            https://api.github.com/repos/$TRAVIS_REPO_SLUG/git/refs \
-            -d "{
-              \"ref\": \"refs/tags/$LFS_VERSION\",
-              \"sha\": \"$TRAVIS_COMMIT\"
-            }"
-        - |
-          curl -f -u $GEKY_BOT_RELEASES -X PATCH \
-            https://api.github.com/repos/$TRAVIS_REPO_SLUG/git/refs/tags/$LFS_VERSION \
-            -d "{
-              \"sha\": \"$TRAVIS_COMMIT\"
-            }"
-        # Create release notes from commits
-        - LFS_PREV_VERSION="v$LFS_VERSION_MAJOR.$(($LFS_VERSION_MINOR-1))"
-        - |
-          if [ $(git tag -l "$LFS_PREV_VERSION") ]
+          # Check that we're the most recent commit
+          CURRENT_COMMIT=$(curl -f -u "$GEKY_BOT_RELEASES" \
+                https://api.github.com/repos/$TRAVIS_REPO_SLUG/commits/master \
+                | jq -re '.sha')
+          if [ "$TRAVIS_COMMIT" == "$CURRENT_COMMIT" ]
           then
-            curl -u $GEKY_BOT_RELEASES -X POST \
+            # Build release notes
+            PREV=$(git tag --sort=-v:refname -l "v*" | 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\"
+                    \"target_commitish\": \"$TRAVIS_COMMIT\",
+                    \"name\": \"${LFS_VERSION%.0}\",
+                    \"body\": $(jq -sR '.' <<< "$CHANGES")
                 }"
-            RELEASE=$(
-                curl -f https://api.github.com/repos/$TRAVIS_REPO_SLUG/releases/tags/$LFS_VERSION
-            )
-            CHANGES=$(
-                git log --oneline $LFS_PREV_VERSION.. --grep='^Merge' --invert-grep
-            )
-            curl -f -u $GEKY_BOT_RELEASES -X PATCH \
-                https://api.github.com/repos/$TRAVIS_REPO_SLUG/releases/$(
-                    jq -r '.id' <<< "$RELEASE"
-                ) \
-                -d "$(
-                    jq -s '{
-                        "body": ((.[0] // "" | sub("(?<=\n)#+ Changes.*"; ""; "mi"))
-                            + "### Changes\n\n" + .[1])
-                    }' <(jq '.body' <<< "$RELEASE") <(jq -sR '.' <<< "$CHANGES")
-                )"
           fi
 
 # Manage statuses

+ 24 - 165
LICENSE.md

@@ -1,165 +1,24 @@
-Apache License
-Version 2.0, January 2004
-http://www.apache.org/licenses/
-
-TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
-1. Definitions.
-
-"License" shall mean the terms and conditions for use, reproduction, and
-distribution as defined by Sections 1 through 9 of this document.
-
-"Licensor" shall mean the copyright owner or entity authorized by the copyright
-owner that is granting the License.
-
-"Legal Entity" shall mean the union of the acting entity and all other entities
-that control, are controlled by, or are under common control with that entity.
-For the purposes of this definition, "control" means (i) the power, direct or
-indirect, to cause the direction or management of such entity, whether by
-contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
-outstanding shares, or (iii) beneficial ownership of such entity.
-
-"You" (or "Your") shall mean an individual or Legal Entity exercising
-permissions granted by this License.
-
-"Source" form shall mean the preferred form for making modifications, including
-but not limited to software source code, documentation source, and configuration
-files.
-
-"Object" form shall mean any form resulting from mechanical transformation or
-translation of a Source form, including but not limited to compiled object code,
-generated documentation, and conversions to other media types.
-
-"Work" shall mean the work of authorship, whether in Source or Object form, made
-available under the License, as indicated by a copyright notice that is included
-in or attached to the work (an example is provided in the Appendix below).
-
-"Derivative Works" shall mean any work, whether in Source or Object form, that
-is based on (or derived from) the Work and for which the editorial revisions,
-annotations, elaborations, or other modifications represent, as a whole, an
-original work of authorship. For the purposes of this License, Derivative Works
-shall not include works that remain separable from, or merely link (or bind by
-name) to the interfaces of, the Work and Derivative Works thereof.
-
-"Contribution" shall mean any work of authorship, including the original version
-of the Work and any modifications or additions to that Work or Derivative Works
-thereof, that is intentionally submitted to Licensor for inclusion in the Work
-by the copyright owner or by an individual or Legal Entity authorized to submit
-on behalf of the copyright owner. For the purposes of this definition,
-"submitted" means any form of electronic, verbal, or written communication sent
-to the Licensor or its representatives, including but not limited to
-communication on electronic mailing lists, source code control systems, and
-issue tracking systems that are managed by, or on behalf of, the Licensor for
-the purpose of discussing and improving the Work, but excluding communication
-that is conspicuously marked or otherwise designated in writing by the copyright
-owner as "Not a Contribution."
-
-"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
-of whom a Contribution has been received by Licensor and subsequently
-incorporated within the Work.
-
-2. Grant of Copyright License.
-
-Subject to the terms and conditions of this License, each Contributor hereby
-grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
-irrevocable copyright license to reproduce, prepare Derivative Works of,
-publicly display, publicly perform, sublicense, and distribute the Work and such
-Derivative Works in Source or Object form.
-
-3. Grant of Patent License.
-
-Subject to the terms and conditions of this License, each Contributor hereby
-grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
-irrevocable (except as stated in this section) patent license to make, have
-made, use, offer to sell, sell, import, and otherwise transfer the Work, where
-such license applies only to those patent claims licensable by such Contributor
-that are necessarily infringed by their Contribution(s) alone or by combination
-of their Contribution(s) with the Work to which such Contribution(s) was
-submitted. If You institute patent litigation against any entity (including a
-cross-claim or counterclaim in a lawsuit) alleging that the Work or a
-Contribution incorporated within the Work constitutes direct or contributory
-patent infringement, then any patent licenses granted to You under this License
-for that Work shall terminate as of the date such litigation is filed.
-
-4. Redistribution.
-
-You may reproduce and distribute copies of the Work or Derivative Works thereof
-in any medium, with or without modifications, and in Source or Object form,
-provided that You meet the following conditions:
-
-You must give any other recipients of the Work or Derivative Works a copy of
-this License; and
-You must cause any modified files to carry prominent notices stating that You
-changed the files; and
-You must retain, in the Source form of any Derivative Works that You distribute,
-all copyright, patent, trademark, and attribution notices from the Source form
-of the Work, excluding those notices that do not pertain to any part of the
-Derivative Works; and
-If the Work includes a "NOTICE" text file as part of its distribution, then any
-Derivative Works that You distribute must include a readable copy of the
-attribution notices contained within such NOTICE file, excluding those notices
-that do not pertain to any part of the Derivative Works, in at least one of the
-following places: within a NOTICE text file distributed as part of the
-Derivative Works; within the Source form or documentation, if provided along
-with the Derivative Works; or, within a display generated by the Derivative
-Works, if and wherever such third-party notices normally appear. The contents of
-the NOTICE file are for informational purposes only and do not modify the
-License. You may add Your own attribution notices within Derivative Works that
-You distribute, alongside or as an addendum to the NOTICE text from the Work,
-provided that such additional attribution notices cannot be construed as
-modifying the License.
-You may add Your own copyright statement to Your modifications and may provide
-additional or different license terms and conditions for use, reproduction, or
-distribution of Your modifications, or for any such Derivative Works as a whole,
-provided Your use, reproduction, and distribution of the Work otherwise complies
-with the conditions stated in this License.
-
-5. Submission of Contributions.
-
-Unless You explicitly state otherwise, any Contribution intentionally submitted
-for inclusion in the Work by You to the Licensor shall be under the terms and
-conditions of this License, without any additional terms or conditions.
-Notwithstanding the above, nothing herein shall supersede or modify the terms of
-any separate license agreement you may have executed with Licensor regarding
-such Contributions.
-
-6. Trademarks.
-
-This License does not grant permission to use the trade names, trademarks,
-service marks, or product names of the Licensor, except as required for
-reasonable and customary use in describing the origin of the Work and
-reproducing the content of the NOTICE file.
-
-7. Disclaimer of Warranty.
-
-Unless required by applicable law or agreed to in writing, Licensor provides the
-Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
-including, without limitation, any warranties or conditions of TITLE,
-NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
-solely responsible for determining the appropriateness of using or
-redistributing the Work and assume any risks associated with Your exercise of
-permissions under this License.
-
-8. Limitation of Liability.
-
-In no event and under no legal theory, whether in tort (including negligence),
-contract, or otherwise, unless required by applicable law (such as deliberate
-and grossly negligent acts) or agreed to in writing, shall any Contributor be
-liable to You for damages, including any direct, indirect, special, incidental,
-or consequential damages of any character arising as a result of this License or
-out of the use or inability to use the Work (including but not limited to
-damages for loss of goodwill, work stoppage, computer failure or malfunction, or
-any and all other commercial damages or losses), even if such Contributor has
-been advised of the possibility of such damages.
-
-9. Accepting Warranty or Additional Liability.
-
-While redistributing the Work or Derivative Works thereof, You may choose to
-offer, and charge a fee for, acceptance of support, warranty, indemnity, or
-other liability obligations and/or rights consistent with this License. However,
-in accepting such obligations, You may act only on Your own behalf and on Your
-sole responsibility, not on behalf of any other Contributor, and only if You
-agree to indemnify, defend, and hold each Contributor harmless for any liability
-incurred by, or claims asserted against, such Contributor by reason of your
-accepting any such warranty or additional liability.
+Copyright (c) 2017, Arm Limited. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+-  Redistributions of source code must retain the above copyright notice, this
+   list of conditions and the following disclaimer.
+-  Redistributions in binary form must reproduce the above copyright notice, this
+   list of conditions and the following disclaimer in the documentation and/or
+   other materials provided with the distribution.
+-  Neither the name of ARM nor the names of its contributors may be used to
+   endorse or promote products derived from this software without specific prior
+   written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+ 10 - 4
Makefile

@@ -1,4 +1,7 @@
-TARGET = lfs
+TARGET = lfs.a
+ifneq ($(wildcard test.c main.c),)
+override TARGET = lfs
+endif
 
 CC ?= gcc
 AR ?= ar
@@ -23,6 +26,7 @@ override CFLAGS += -m$(WORD)
 endif
 override CFLAGS += -I.
 override CFLAGS += -std=c99 -Wall -pedantic
+override CFLAGS += -Wshadow -Wunused-parameter -Wjump-misses-init
 
 
 all: $(TARGET)
@@ -33,9 +37,11 @@ size: $(OBJ)
 	$(SIZE) -t $^
 
 .SUFFIXES:
-test: test_format test_dirs test_files test_seek test_truncate test_parallel \
-	test_alloc test_paths test_orphan test_move test_corrupt
+test: test_format test_dirs test_files test_seek test_truncate \
+	test_interspersed test_alloc test_paths test_orphan test_move test_corrupt
+	@rm test.c
 test_%: tests/test_%.sh
+
 ifdef QUIET
 	@./$< | sed -n '/^[-=]/p'
 else
@@ -44,7 +50,7 @@ endif
 
 -include $(DEP)
 
-$(TARGET): $(OBJ)
+lfs: $(OBJ)
 	$(CC) $(CFLAGS) $^ $(LFLAGS) -o $@
 
 %.a: $(OBJ)

+ 13 - 0
README.md

@@ -146,6 +146,19 @@ The tests assume a Linux environment and can be started with make:
 make test
 ```
 
+## 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.
+
+Individual files contain the following tag instead of the full license text.
+
+    SPDX-License-Identifier:    BSD-3-Clause
+
+This enables machine processing of license information based on the SPDX
+License Identifiers that are here available: http://spdx.org/licenses/
+
 ## Related projects
 
 [Mbed OS](https://github.com/ARMmbed/mbed-os/tree/master/features/filesystem/littlefs) -

+ 19 - 25
emubd/lfs_emubd.c

@@ -1,19 +1,8 @@
 /*
  * Block device emulated on standard files
  *
- * Copyright (c) 2017 ARM Limited
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Copyright (c) 2017, Arm Limited. All rights reserved.
+ * SPDX-License-Identifier: BSD-3-Clause
  */
 #include "emubd/lfs_emubd.h"
 
@@ -27,6 +16,7 @@
 #include <unistd.h>
 #include <assert.h>
 #include <stdbool.h>
+#include <inttypes.h>
 
 
 // Block device emulated on existing filesystem
@@ -57,19 +47,24 @@ int lfs_emubd_create(const struct lfs_config *cfg, const char *path) {
 
     // Load stats to continue incrementing
     snprintf(emu->child, LFS_NAME_MAX, "stats");
+
     FILE *f = fopen(emu->path, "r");
-    if (!f) {
+    if (!f && errno != ENOENT) {
         return -errno;
     }
 
-    size_t res = fread(&emu->stats, sizeof(emu->stats), 1, f);
-    if (res < 1) {
-        return -errno;
-    }
+    if (errno == ENOENT) {
+        memset(&emu->stats, 0x0, sizeof(emu->stats));
+    } else {
+        size_t res = fread(&emu->stats, sizeof(emu->stats), 1, f);
+        if (res < 1) {
+            return -errno;
+        }
 
-    err = fclose(f);
-    if (err) {
-        return -errno;
+        err = fclose(f);
+        if (err) {
+            return -errno;
+        }
     }
 
     return 0;
@@ -96,7 +91,7 @@ int lfs_emubd_read(const struct lfs_config *cfg, lfs_block_t block,
     memset(data, 0, size);
 
     // Read data
-    snprintf(emu->child, LFS_NAME_MAX, "%x", block);
+    snprintf(emu->child, LFS_NAME_MAX, "%" PRIx32, block);
 
     FILE *f = fopen(emu->path, "rb");
     if (!f && errno != ENOENT) {
@@ -135,7 +130,7 @@ int lfs_emubd_prog(const struct lfs_config *cfg, lfs_block_t block,
     assert(block < cfg->block_count);
 
     // Program data
-    snprintf(emu->child, LFS_NAME_MAX, "%x", block);
+    snprintf(emu->child, LFS_NAME_MAX, "%" PRIx32, block);
 
     FILE *f = fopen(emu->path, "r+b");
     if (!f) {
@@ -182,7 +177,7 @@ int lfs_emubd_erase(const struct lfs_config *cfg, lfs_block_t block) {
     assert(block < cfg->block_count);
 
     // Erase the block
-    snprintf(emu->child, LFS_NAME_MAX, "%x", block);
+    snprintf(emu->child, LFS_NAME_MAX, "%" PRIx32, block);
     struct stat st;
     int err = stat(emu->path, &st);
     if (err && errno != ENOENT) {
@@ -250,4 +245,3 @@ int lfs_emubd_sync(const struct lfs_config *cfg) {
 
     return 0;
 }
-

+ 11 - 13
emubd/lfs_emubd.h

@@ -1,19 +1,8 @@
 /*
  * Block device emulated on standard files
  *
- * Copyright (c) 2017 ARM Limited
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Copyright (c) 2017, Arm Limited. All rights reserved.
+ * SPDX-License-Identifier: BSD-3-Clause
  */
 #ifndef LFS_EMUBD_H
 #define LFS_EMUBD_H
@@ -21,6 +10,11 @@
 #include "lfs.h"
 #include "lfs_util.h"
 
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
 
 // Config options
 #ifndef LFS_EMUBD_READ_SIZE
@@ -86,4 +80,8 @@ int lfs_emubd_erase(const struct lfs_config *cfg, lfs_block_t block);
 int lfs_emubd_sync(const struct lfs_config *cfg);
 
 
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
 #endif

+ 286 - 253
lfs.c

@@ -1,23 +1,14 @@
 /*
  * The little filesystem
  *
- * Copyright (c) 2017 ARM Limited
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Copyright (c) 2017, Arm Limited. All rights reserved.
+ * SPDX-License-Identifier: BSD-3-Clause
  */
 #include "lfs.h"
 #include "lfs_util.h"
 
+#include <inttypes.h>
+
 
 /// Caching block device operations ///
 static int lfs_cache_read(lfs_t *lfs, lfs_cache_t *rcache,
@@ -118,6 +109,19 @@ static int lfs_cache_crc(lfs_t *lfs, lfs_cache_t *rcache,
     return 0;
 }
 
+static inline void lfs_cache_drop(lfs_t *lfs, lfs_cache_t *rcache) {
+    // do not zero, cheaper if cache is readonly or only going to be
+    // written with identical data (during relocates)
+    (void)lfs;
+    rcache->block = 0xffffffff;
+}
+
+static inline void lfs_cache_zero(lfs_t *lfs, lfs_cache_t *pcache) {
+    // zero to avoid information leak
+    memset(pcache->buffer, 0xff, lfs->cfg->prog_size);
+    pcache->block = 0xffffffff;
+}
+
 static int lfs_cache_flush(lfs_t *lfs,
         lfs_cache_t *pcache, lfs_cache_t *rcache) {
     if (pcache->block != 0xffffffff) {
@@ -139,7 +143,7 @@ static int lfs_cache_flush(lfs_t *lfs,
             }
         }
 
-        pcache->block = 0xffffffff;
+        lfs_cache_zero(lfs, pcache);
     }
 
     return 0;
@@ -244,7 +248,7 @@ static int lfs_bd_erase(lfs_t *lfs, lfs_block_t block) {
 }
 
 static int lfs_bd_sync(lfs_t *lfs) {
-    lfs->rcache.block = 0xffffffff;
+    lfs_cache_drop(lfs, &lfs->rcache);
 
     int err = lfs_cache_flush(lfs, &lfs->pcache, NULL);
     if (err) {
@@ -270,8 +274,7 @@ int lfs_deorphan(lfs_t *lfs);
 static int lfs_alloc_lookahead(void *p, lfs_block_t block) {
     lfs_t *lfs = p;
 
-    lfs_block_t off = (((lfs_soff_t)(block - lfs->free.begin)
-                % (lfs_soff_t)(lfs->cfg->block_count))
+    lfs_block_t off = ((block - lfs->free.off)
             + lfs->cfg->block_count) % lfs->cfg->block_count;
 
     if (off < lfs->free.size) {
@@ -283,27 +286,39 @@ static int lfs_alloc_lookahead(void *p, lfs_block_t block) {
 
 static int lfs_alloc(lfs_t *lfs, lfs_block_t *block) {
     while (true) {
-        while (lfs->free.off != lfs->free.size) {
-            lfs_block_t off = lfs->free.off;
-            lfs->free.off += 1;
+        while (lfs->free.i != lfs->free.size) {
+            lfs_block_t off = lfs->free.i;
+            lfs->free.i += 1;
+            lfs->free.ack -= 1;
 
             if (!(lfs->free.buffer[off / 32] & (1U << (off % 32)))) {
                 // found a free block
-                *block = (lfs->free.begin + off) % lfs->cfg->block_count;
+                *block = (lfs->free.off + off) % lfs->cfg->block_count;
+
+                // eagerly find next off so an alloc ack can
+                // discredit old lookahead blocks
+                while (lfs->free.i != lfs->free.size &&
+                        (lfs->free.buffer[lfs->free.i / 32]
+                            & (1U << (lfs->free.i % 32)))) {
+                    lfs->free.i += 1;
+                    lfs->free.ack -= 1;
+                }
+
                 return 0;
             }
         }
 
         // check if we have looked at all blocks since last ack
-        if (lfs->free.off == lfs->free.ack - lfs->free.begin) {
-            LFS_WARN("No more free space %d", lfs->free.off + lfs->free.begin);
+        if (lfs->free.ack == 0) {
+            LFS_WARN("No more free space %" PRIu32,
+                    lfs->free.i + lfs->free.off);
             return LFS_ERR_NOSPC;
         }
 
-        lfs->free.begin += lfs->free.size;
-        lfs->free.size = lfs_min(lfs->cfg->lookahead,
-                lfs->free.ack - lfs->free.begin);
-        lfs->free.off = 0;
+        lfs->free.off = (lfs->free.off + lfs->free.size)
+                % lfs->cfg->block_count;
+        lfs->free.size = lfs_min(lfs->cfg->lookahead, lfs->free.ack);
+        lfs->free.i = 0;
 
         // find mask of free blocks from tree
         memset(lfs->free.buffer, 0, lfs->cfg->lookahead/8);
@@ -315,7 +330,7 @@ static int lfs_alloc(lfs_t *lfs, lfs_block_t *block) {
 }
 
 static void lfs_alloc_ack(lfs_t *lfs) {
-    lfs->free.ack = lfs->free.off-1 + lfs->free.begin + lfs->cfg->block_count;
+    lfs->free.ack = lfs->cfg->block_count;
 }
 
 
@@ -402,11 +417,14 @@ static int lfs_dir_alloc(lfs_t *lfs, lfs_dir_t *dir) {
     // rather than clobbering one of the blocks we just pretend
     // the revision may be valid
     int err = lfs_bd_read(lfs, dir->pair[0], 0, &dir->d.rev, 4);
-    dir->d.rev = lfs_fromle32(dir->d.rev);
-    if (err) {
+    if (err && err != LFS_ERR_CORRUPT) {
         return err;
     }
 
+    if (err != LFS_ERR_CORRUPT) {
+        dir->d.rev = lfs_fromle32(dir->d.rev);
+    }
+
     // set defaults
     dir->d.rev += 1;
     dir->d.size = sizeof(dir->d)+4;
@@ -430,6 +448,9 @@ static int lfs_dir_fetch(lfs_t *lfs,
         int err = lfs_bd_read(lfs, tpair[i], 0, &test, sizeof(test));
         lfs_dir_fromle32(&test);
         if (err) {
+            if (err == LFS_ERR_CORRUPT) {
+                continue;
+            }
             return err;
         }
 
@@ -449,6 +470,9 @@ static int lfs_dir_fetch(lfs_t *lfs,
         err = lfs_bd_crc(lfs, tpair[i], sizeof(test),
                 (0x7fffffff & test.size) - sizeof(test), &crc);
         if (err) {
+            if (err == LFS_ERR_CORRUPT) {
+                continue;
+            }
             return err;
         }
 
@@ -466,7 +490,8 @@ static int lfs_dir_fetch(lfs_t *lfs,
     }
 
     if (!valid) {
-        LFS_ERROR("Corrupted dir pair at %d %d", tpair[0], tpair[1]);
+        LFS_ERROR("Corrupted dir pair at %" PRIu32 " %" PRIu32 ,
+                tpair[0], tpair[1]);
         return LFS_ERR_CORRUPT;
     }
 
@@ -589,15 +614,16 @@ static int lfs_dir_commit(lfs_t *lfs, lfs_dir_t *dir,
         break;
 relocate:
         //commit was corrupted
-        LFS_DEBUG("Bad block at %d", dir->pair[0]);
+        LFS_DEBUG("Bad block at %" PRIu32, dir->pair[0]);
 
         // drop caches and prepare to relocate block
         relocated = true;
-        lfs->pcache.block = 0xffffffff;
+        lfs_cache_drop(lfs, &lfs->pcache);
 
         // can't relocate superblock, filesystem is now frozen
         if (lfs_paircmp(oldpair, (const lfs_block_t[2]){0, 1}) == 0) {
-            LFS_WARN("Superblock %d has become unwritable", oldpair[0]);
+            LFS_WARN("Superblock %" PRIu32 " has become unwritable",
+                    oldpair[0]);
             return LFS_ERR_CORRUPT;
         }
 
@@ -610,7 +636,7 @@ relocate:
 
     if (relocated) {
         // update references if we relocated
-        LFS_DEBUG("Relocating %d %d to %d %d",
+        LFS_DEBUG("Relocating %" PRIu32 " %" PRIu32 " to %" PRIu32 " %" PRIu32,
                 oldpair[0], oldpair[1], dir->pair[0], dir->pair[1]);
         int err = lfs_relocate(lfs, oldpair, dir->pair);
         if (err) {
@@ -773,26 +799,19 @@ static int lfs_dir_find(lfs_t *lfs, lfs_dir_t *dir,
         lfs_entry_t *entry, const char **path) {
     const char *pathname = *path;
     size_t pathlen;
+    entry->d.type = LFS_TYPE_DIR;
+    entry->d.elen = sizeof(entry->d) - 4;
+    entry->d.alen = 0;
+    entry->d.nlen = 0;
+    entry->d.u.dir[0] = lfs->root[0];
+    entry->d.u.dir[1] = lfs->root[1];
 
     while (true) {
-    nextname:
+nextname:
         // skip slashes
         pathname += strspn(pathname, "/");
         pathlen = strcspn(pathname, "/");
 
-        // special case for root dir
-        if (pathname[0] == '\0') {
-            *entry = (lfs_entry_t){
-                .d.type = LFS_TYPE_DIR,
-                .d.elen = sizeof(entry->d) - 4,
-                .d.alen = 0,
-                .d.nlen = 0,
-                .d.u.dir[0] = lfs->root[0],
-                .d.u.dir[1] = lfs->root[1],
-            };
-            return 0;
-        }
-
         // skip '.' and root '..'
         if ((pathlen == 1 && memcmp(pathname, ".", 1) == 0) ||
             (pathlen == 2 && memcmp(pathname, "..", 2) == 0)) {
@@ -824,12 +843,27 @@ static int lfs_dir_find(lfs_t *lfs, lfs_dir_t *dir,
             suffix += sufflen;
         }
 
+        // found path
+        if (pathname[0] == '\0') {
+            return 0;
+        }
+
         // update what we've found
         *path = pathname;
 
-        // find path
+        // continue on if we hit a directory
+        if (entry->d.type != LFS_TYPE_DIR) {
+            return LFS_ERR_NOTDIR;
+        }
+
+        int err = lfs_dir_fetch(lfs, dir, entry->d.u.dir);
+        if (err) {
+            return err;
+        }
+
+        // find entry matching name
         while (true) {
-            int err = lfs_dir_next(lfs, dir, entry);
+            err = lfs_dir_next(lfs, dir, entry);
             if (err) {
                 return err;
             }
@@ -863,21 +897,8 @@ static int lfs_dir_find(lfs_t *lfs, lfs_dir_t *dir,
             entry->d.type &= ~0x80;
         }
 
+        // to next name
         pathname += pathlen;
-        pathname += strspn(pathname, "/");
-        if (pathname[0] == '\0') {
-            return 0;
-        }
-
-        // continue on if we hit a directory
-        if (entry->d.type != LFS_TYPE_DIR) {
-            return LFS_ERR_NOTDIR;
-        }
-
-        int err = lfs_dir_fetch(lfs, dir, entry->d.u.dir);
-        if (err) {
-            return err;
-        }
     }
 }
 
@@ -894,13 +915,8 @@ int lfs_mkdir(lfs_t *lfs, const char *path) {
 
     // fetch parent directory
     lfs_dir_t cwd;
-    int err = lfs_dir_fetch(lfs, &cwd, lfs->root);
-    if (err) {
-        return err;
-    }
-
     lfs_entry_t entry;
-    err = lfs_dir_find(lfs, &cwd, &entry, &path);
+    int err = lfs_dir_find(lfs, &cwd, &entry, &path);
     if (err != LFS_ERR_NOENT || strchr(path, '/') != NULL) {
         return err ? err : LFS_ERR_EXIST;
     }
@@ -944,13 +960,8 @@ int lfs_dir_open(lfs_t *lfs, lfs_dir_t *dir, const char *path) {
     dir->pair[0] = lfs->root[0];
     dir->pair[1] = lfs->root[1];
 
-    int err = lfs_dir_fetch(lfs, dir, dir->pair);
-    if (err) {
-        return err;
-    }
-
     lfs_entry_t entry;
-    err = lfs_dir_find(lfs, dir, &entry, &path);
+    int err = lfs_dir_find(lfs, dir, &entry, &path);
     if (err) {
         return err;
     } else if (entry.d.type != LFS_TYPE_DIR) {
@@ -1230,10 +1241,10 @@ static int lfs_ctz_extend(lfs_t *lfs,
         }
 
 relocate:
-        LFS_DEBUG("Bad block at %d", nblock);
+        LFS_DEBUG("Bad block at %" PRIu32, nblock);
 
         // just clear cache and try a new block
-        pcache->block = 0xffffffff;
+        lfs_cache_drop(lfs, &lfs->pcache);
     }
 }
 
@@ -1280,8 +1291,9 @@ static int lfs_ctz_traverse(lfs_t *lfs,
 
 
 /// Top level file operations ///
-int lfs_file_open(lfs_t *lfs, lfs_file_t *file,
-        const char *path, int flags) {
+int lfs_file_opencfg(lfs_t *lfs, lfs_file_t *file,
+        const char *path, int flags,
+        const struct lfs_file_config *cfg) {
     // deorphan if we haven't yet, needed at most once after poweron
     if ((flags & 3) != LFS_O_RDONLY && !lfs->deorphaned) {
         int err = lfs_deorphan(lfs);
@@ -1292,13 +1304,8 @@ int lfs_file_open(lfs_t *lfs, lfs_file_t *file,
 
     // allocate entry for file if it doesn't exist
     lfs_dir_t cwd;
-    int err = lfs_dir_fetch(lfs, &cwd, lfs->root);
-    if (err) {
-        return err;
-    }
-
     lfs_entry_t entry;
-    err = lfs_dir_find(lfs, &cwd, &entry, &path);
+    int err = lfs_dir_find(lfs, &cwd, &entry, &path);
     if (err && (err != LFS_ERR_NOENT || strchr(path, '/') != NULL)) {
         return err;
     }
@@ -1326,6 +1333,7 @@ int lfs_file_open(lfs_t *lfs, lfs_file_t *file,
     }
 
     // setup file struct
+    file->cfg = cfg;
     file->pair[0] = cwd.pair[0];
     file->pair[1] = cwd.pair[1];
     file->poff = entry.off;
@@ -1344,7 +1352,13 @@ int lfs_file_open(lfs_t *lfs, lfs_file_t *file,
 
     // allocate buffer if needed
     file->cache.block = 0xffffffff;
-    if (lfs->cfg->file_buffer) {
+    if (file->cfg && file->cfg->buffer) {
+        file->cache.buffer = file->cfg->buffer;
+    } else if (lfs->cfg->file_buffer) {
+        if (lfs->files) {
+            // already in use
+            return LFS_ERR_NOMEM;
+        }
         file->cache.buffer = lfs->cfg->file_buffer;
     } else if ((file->flags & 3) == LFS_O_RDONLY) {
         file->cache.buffer = lfs_malloc(lfs->cfg->read_size);
@@ -1358,6 +1372,12 @@ int lfs_file_open(lfs_t *lfs, lfs_file_t *file,
         }
     }
 
+    // zero to avoid information leak
+    lfs_cache_drop(lfs, &file->cache);
+    if ((file->flags & 3) != LFS_O_RDONLY) {
+        lfs_cache_zero(lfs, &file->cache);
+    }
+
     // add to list of files
     file->next = lfs->files;
     lfs->files = file;
@@ -1365,6 +1385,11 @@ int lfs_file_open(lfs_t *lfs, lfs_file_t *file,
     return 0;
 }
 
+int lfs_file_open(lfs_t *lfs, lfs_file_t *file,
+        const char *path, int flags) {
+    return lfs_file_opencfg(lfs, file, path, flags, NULL);
+}
+
 int lfs_file_close(lfs_t *lfs, lfs_file_t *file) {
     int err = lfs_file_sync(lfs, file);
 
@@ -1377,7 +1402,7 @@ int lfs_file_close(lfs_t *lfs, lfs_file_t *file) {
     }
 
     // clean up memory
-    if (!lfs->cfg->file_buffer) {
+    if (!(file->cfg && file->cfg->buffer) && !lfs->cfg->file_buffer) {
         lfs_free(file->cache.buffer);
     }
 
@@ -1386,7 +1411,7 @@ int lfs_file_close(lfs_t *lfs, lfs_file_t *file) {
 
 static int lfs_file_relocate(lfs_t *lfs, lfs_file_t *file) {
 relocate:
-    LFS_DEBUG("Bad block at %d", file->block);
+    LFS_DEBUG("Bad block at %" PRIu32, file->block);
 
     // just relocate what exists into new block
     lfs_block_t nblock;
@@ -1426,7 +1451,7 @@ relocate:
     memcpy(file->cache.buffer, lfs->pcache.buffer, lfs->cfg->prog_size);
     file->cache.block = lfs->pcache.block;
     file->cache.off = lfs->pcache.off;
-    lfs->pcache.block = 0xffffffff;
+    lfs_cache_zero(lfs, &lfs->pcache);
 
     file->block = nblock;
     return 0;
@@ -1435,7 +1460,7 @@ relocate:
 static int lfs_file_flush(lfs_t *lfs, lfs_file_t *file) {
     if (file->flags & LFS_F_READING) {
         // just drop read cache
-        file->cache.block = 0xffffffff;
+        lfs_cache_drop(lfs, &file->cache);
         file->flags &= ~LFS_F_READING;
     }
 
@@ -1450,7 +1475,7 @@ static int lfs_file_flush(lfs_t *lfs, lfs_file_t *file) {
             .pos = file->pos,
             .cache = lfs->rcache,
         };
-        lfs->rcache.block = 0xffffffff;
+        lfs_cache_drop(lfs, &lfs->rcache);
 
         while (file->pos < file->size) {
             // copy over a byte at a time, leave it up to caching
@@ -1468,8 +1493,8 @@ static int lfs_file_flush(lfs_t *lfs, lfs_file_t *file) {
 
             // keep our reference to the rcache in sync
             if (lfs->rcache.block != 0xffffffff) {
-                orig.cache.block = 0xffffffff;
-                lfs->rcache.block = 0xffffffff;
+                lfs_cache_drop(lfs, &orig.cache);
+                lfs_cache_drop(lfs, &lfs->rcache);
             }
         }
 
@@ -1647,7 +1672,7 @@ lfs_ssize_t lfs_file_write(lfs_t *lfs, lfs_file_t *file,
                 }
 
                 // mark cache as dirty since we may have read data into it
-                file->cache.block = 0xffffffff;
+                lfs_cache_zero(lfs, &file->cache);
             }
 
             // extend file with new blocks
@@ -1804,13 +1829,8 @@ lfs_soff_t lfs_file_size(lfs_t *lfs, lfs_file_t *file) {
 /// General fs operations ///
 int lfs_stat(lfs_t *lfs, const char *path, struct lfs_info *info) {
     lfs_dir_t cwd;
-    int err = lfs_dir_fetch(lfs, &cwd, lfs->root);
-    if (err) {
-        return err;
-    }
-
     lfs_entry_t entry;
-    err = lfs_dir_find(lfs, &cwd, &entry, &path);
+    int err = lfs_dir_find(lfs, &cwd, &entry, &path);
     if (err) {
         return err;
     }
@@ -1845,13 +1865,8 @@ int lfs_remove(lfs_t *lfs, const char *path) {
     }
 
     lfs_dir_t cwd;
-    int err = lfs_dir_fetch(lfs, &cwd, lfs->root);
-    if (err) {
-        return err;
-    }
-
     lfs_entry_t entry;
-    err = lfs_dir_find(lfs, &cwd, &entry, &path);
+    int err = lfs_dir_find(lfs, &cwd, &entry, &path);
     if (err) {
         return err;
     }
@@ -1906,24 +1921,14 @@ int lfs_rename(lfs_t *lfs, const char *oldpath, const char *newpath) {
 
     // find old entry
     lfs_dir_t oldcwd;
-    int err = lfs_dir_fetch(lfs, &oldcwd, lfs->root);
-    if (err) {
-        return err;
-    }
-
     lfs_entry_t oldentry;
-    err = lfs_dir_find(lfs, &oldcwd, &oldentry, &oldpath);
+    int err = lfs_dir_find(lfs, &oldcwd, &oldentry, &oldpath);
     if (err) {
         return err;
     }
 
     // allocate new entry
     lfs_dir_t newcwd;
-    err = lfs_dir_fetch(lfs, &newcwd, lfs->root);
-    if (err) {
-        return err;
-    }
-
     lfs_entry_t preventry;
     err = lfs_dir_find(lfs, &newcwd, &preventry, &newpath);
     if (err && (err != LFS_ERR_NOENT || strchr(newpath, '/') != NULL)) {
@@ -2014,31 +2019,48 @@ int lfs_rename(lfs_t *lfs, const char *oldpath, const char *newpath) {
 
 
 /// Filesystem operations ///
+static void lfs_deinit(lfs_t *lfs) {
+    // free allocated memory
+    if (!lfs->cfg->read_buffer) {
+        lfs_free(lfs->rcache.buffer);
+    }
+
+    if (!lfs->cfg->prog_buffer) {
+        lfs_free(lfs->pcache.buffer);
+    }
+
+    if (!lfs->cfg->lookahead_buffer) {
+        lfs_free(lfs->free.buffer);
+    }
+}
+
 static int lfs_init(lfs_t *lfs, const struct lfs_config *cfg) {
     lfs->cfg = cfg;
 
     // setup read cache
-    lfs->rcache.block = 0xffffffff;
     if (lfs->cfg->read_buffer) {
         lfs->rcache.buffer = lfs->cfg->read_buffer;
     } else {
         lfs->rcache.buffer = lfs_malloc(lfs->cfg->read_size);
         if (!lfs->rcache.buffer) {
-            return LFS_ERR_NOMEM;
+            goto cleanup;
         }
     }
 
     // setup program cache
-    lfs->pcache.block = 0xffffffff;
     if (lfs->cfg->prog_buffer) {
         lfs->pcache.buffer = lfs->cfg->prog_buffer;
     } else {
         lfs->pcache.buffer = lfs_malloc(lfs->cfg->prog_size);
         if (!lfs->pcache.buffer) {
-            return LFS_ERR_NOMEM;
+            goto cleanup;
         }
     }
 
+    // zero to avoid information leaks
+    lfs_cache_zero(lfs, &lfs->pcache);
+    lfs_cache_drop(lfs, &lfs->rcache);
+
     // setup lookahead, round down to nearest 32-bits
     LFS_ASSERT(lfs->cfg->lookahead % 32 == 0);
     LFS_ASSERT(lfs->cfg->lookahead > 0);
@@ -2047,7 +2069,7 @@ static int lfs_init(lfs_t *lfs, const struct lfs_config *cfg) {
     } else {
         lfs->free.buffer = lfs_malloc(lfs->cfg->lookahead/8);
         if (!lfs->free.buffer) {
-            return LFS_ERR_NOMEM;
+            goto cleanup;
         }
     }
 
@@ -2067,155 +2089,160 @@ static int lfs_init(lfs_t *lfs, const struct lfs_config *cfg) {
     lfs->deorphaned = false;
 
     return 0;
-}
-
-static int lfs_deinit(lfs_t *lfs) {
-    // free allocated memory
-    if (!lfs->cfg->read_buffer) {
-        lfs_free(lfs->rcache.buffer);
-    }
-
-    if (!lfs->cfg->prog_buffer) {
-        lfs_free(lfs->pcache.buffer);
-    }
 
-    if (!lfs->cfg->lookahead_buffer) {
-        lfs_free(lfs->free.buffer);
-    }
-
-    return 0;
+cleanup:
+    lfs_deinit(lfs);
+    return LFS_ERR_NOMEM;
 }
 
 int lfs_format(lfs_t *lfs, const struct lfs_config *cfg) {
-    int err = lfs_init(lfs, cfg);
-    if (err) {
-        return err;
-    }
+    int err = 0;
+    if (true) {
+        err = lfs_init(lfs, cfg);
+        if (err) {
+            return err;
+        }
 
-    // create free lookahead
-    memset(lfs->free.buffer, 0, lfs->cfg->lookahead/8);
-    lfs->free.begin = 0;
-    lfs->free.size = lfs_min(lfs->cfg->lookahead, lfs->cfg->block_count);
-    lfs->free.off = 0;
-    lfs_alloc_ack(lfs);
+        // create free lookahead
+        memset(lfs->free.buffer, 0, lfs->cfg->lookahead/8);
+        lfs->free.off = 0;
+        lfs->free.size = lfs_min(lfs->cfg->lookahead, lfs->cfg->block_count);
+        lfs->free.i = 0;
+        lfs_alloc_ack(lfs);
 
-    // create superblock dir
-    lfs_dir_t superdir;
-    err = lfs_dir_alloc(lfs, &superdir);
-    if (err) {
-        return err;
-    }
+        // create superblock dir
+        lfs_dir_t superdir;
+        err = lfs_dir_alloc(lfs, &superdir);
+        if (err) {
+            goto cleanup;
+        }
 
-    // write root directory
-    lfs_dir_t root;
-    err = lfs_dir_alloc(lfs, &root);
-    if (err) {
-        return err;
-    }
+        // write root directory
+        lfs_dir_t root;
+        err = lfs_dir_alloc(lfs, &root);
+        if (err) {
+            goto cleanup;
+        }
 
-    err = lfs_dir_commit(lfs, &root, NULL, 0);
-    if (err) {
-        return err;
-    }
+        err = lfs_dir_commit(lfs, &root, NULL, 0);
+        if (err) {
+            goto cleanup;
+        }
+
+        lfs->root[0] = root.pair[0];
+        lfs->root[1] = root.pair[1];
+
+        // write superblocks
+        lfs_superblock_t superblock = {
+            .off = sizeof(superdir.d),
+            .d.type = LFS_TYPE_SUPERBLOCK,
+            .d.elen = sizeof(superblock.d) - sizeof(superblock.d.magic) - 4,
+            .d.nlen = sizeof(superblock.d.magic),
+            .d.version = LFS_DISK_VERSION,
+            .d.magic = {"littlefs"},
+            .d.block_size  = lfs->cfg->block_size,
+            .d.block_count = lfs->cfg->block_count,
+            .d.root = {lfs->root[0], lfs->root[1]},
+        };
+        superdir.d.tail[0] = root.pair[0];
+        superdir.d.tail[1] = root.pair[1];
+        superdir.d.size = sizeof(superdir.d) + sizeof(superblock.d) + 4;
 
-    lfs->root[0] = root.pair[0];
-    lfs->root[1] = root.pair[1];
-
-    // write superblocks
-    lfs_superblock_t superblock = {
-        .off = sizeof(superdir.d),
-        .d.type = LFS_TYPE_SUPERBLOCK,
-        .d.elen = sizeof(superblock.d) - sizeof(superblock.d.magic) - 4,
-        .d.nlen = sizeof(superblock.d.magic),
-        .d.version = LFS_DISK_VERSION,
-        .d.magic = {"littlefs"},
-        .d.block_size  = lfs->cfg->block_size,
-        .d.block_count = lfs->cfg->block_count,
-        .d.root = {lfs->root[0], lfs->root[1]},
-    };
-    superdir.d.tail[0] = root.pair[0];
-    superdir.d.tail[1] = root.pair[1];
-    superdir.d.size = sizeof(superdir.d) + sizeof(superblock.d) + 4;
-
-    // write both pairs to be safe
-    lfs_superblock_tole32(&superblock.d);
-    bool valid = false;
-    for (int i = 0; i < 2; i++) {
-        err = lfs_dir_commit(lfs, &superdir, (struct lfs_region[]){
-                {sizeof(superdir.d), sizeof(superblock.d),
-                 &superblock.d, sizeof(superblock.d)}
-            }, 1);
-        if (err && err != LFS_ERR_CORRUPT) {
-            return err;
+        // write both pairs to be safe
+        lfs_superblock_tole32(&superblock.d);
+        bool valid = false;
+        for (int i = 0; i < 2; i++) {
+            err = lfs_dir_commit(lfs, &superdir, (struct lfs_region[]){
+                    {sizeof(superdir.d), sizeof(superblock.d),
+                     &superblock.d, sizeof(superblock.d)}
+                }, 1);
+            if (err && err != LFS_ERR_CORRUPT) {
+                goto cleanup;
+            }
+
+            valid = valid || !err;
         }
 
-        valid = valid || !err;
-    }
+        if (!valid) {
+            err = LFS_ERR_CORRUPT;
+            goto cleanup;
+        }
 
-    if (!valid) {
-        return LFS_ERR_CORRUPT;
-    }
+        // sanity check that fetch works
+        err = lfs_dir_fetch(lfs, &superdir, (const lfs_block_t[2]){0, 1});
+        if (err) {
+            goto cleanup;
+        }
 
-    // sanity check that fetch works
-    err = lfs_dir_fetch(lfs, &superdir, (const lfs_block_t[2]){0, 1});
-    if (err) {
-        return err;
+        lfs_alloc_ack(lfs);
     }
 
-    lfs_alloc_ack(lfs);
-    return lfs_deinit(lfs);
+cleanup:
+    lfs_deinit(lfs);
+    return err;
 }
 
 int lfs_mount(lfs_t *lfs, const struct lfs_config *cfg) {
-    int err = lfs_init(lfs, cfg);
-    if (err) {
-        return err;
-    }
+    int err = 0;
+    if (true) {
+        err = lfs_init(lfs, cfg);
+        if (err) {
+            return err;
+        }
 
-    // setup free lookahead
-    lfs->free.begin = 0;
-    lfs->free.size = 0;
-    lfs->free.off = 0;
-    lfs_alloc_ack(lfs);
+        // setup free lookahead
+        lfs->free.off = 0;
+        lfs->free.size = 0;
+        lfs->free.i = 0;
+        lfs_alloc_ack(lfs);
 
-    // load superblock
-    lfs_dir_t dir;
-    lfs_superblock_t superblock;
-    err = lfs_dir_fetch(lfs, &dir, (const lfs_block_t[2]){0, 1});
-    if (err && err != LFS_ERR_CORRUPT) {
-        return err;
-    }
+        // load superblock
+        lfs_dir_t dir;
+        lfs_superblock_t superblock;
+        err = lfs_dir_fetch(lfs, &dir, (const lfs_block_t[2]){0, 1});
+        if (err && err != LFS_ERR_CORRUPT) {
+            goto cleanup;
+        }
 
-    if (!err) {
-        err = lfs_bd_read(lfs, dir.pair[0], sizeof(dir.d),
-                &superblock.d, sizeof(superblock.d));
-        lfs_superblock_fromle32(&superblock.d);
-        if (err) {
-            return err;
+        if (!err) {
+            err = lfs_bd_read(lfs, dir.pair[0], sizeof(dir.d),
+                    &superblock.d, sizeof(superblock.d));
+            lfs_superblock_fromle32(&superblock.d);
+            if (err) {
+                goto cleanup;
+            }
+
+            lfs->root[0] = superblock.d.root[0];
+            lfs->root[1] = superblock.d.root[1];
         }
 
-        lfs->root[0] = superblock.d.root[0];
-        lfs->root[1] = superblock.d.root[1];
-    }
+        if (err || memcmp(superblock.d.magic, "littlefs", 8) != 0) {
+            LFS_ERROR("Invalid superblock at %d %d", 0, 1);
+            err = LFS_ERR_CORRUPT;
+            goto cleanup;
+        }
 
-    if (err || memcmp(superblock.d.magic, "littlefs", 8) != 0) {
-        LFS_ERROR("Invalid superblock at %d %d", 0, 1);
-        return LFS_ERR_CORRUPT;
-    }
+        uint16_t major_version = (0xffff & (superblock.d.version >> 16));
+        uint16_t minor_version = (0xffff & (superblock.d.version >>  0));
+        if ((major_version != LFS_DISK_VERSION_MAJOR ||
+             minor_version > LFS_DISK_VERSION_MINOR)) {
+            LFS_ERROR("Invalid version %d.%d", major_version, minor_version);
+            err = LFS_ERR_INVAL;
+            goto cleanup;
+        }
 
-    uint16_t major_version = (0xffff & (superblock.d.version >> 16));
-    uint16_t minor_version = (0xffff & (superblock.d.version >>  0));
-    if ((major_version != LFS_DISK_VERSION_MAJOR ||
-         minor_version > LFS_DISK_VERSION_MINOR)) {
-        LFS_ERROR("Invalid version %d.%d", major_version, minor_version);
-        return LFS_ERR_INVAL;
+        return 0;
     }
 
-    return 0;
+cleanup:
+
+    lfs_deinit(lfs);
+    return err;
 }
 
 int lfs_unmount(lfs_t *lfs) {
-    return lfs_deinit(lfs);
+    lfs_deinit(lfs);
+    return 0;
 }
 
 
@@ -2415,7 +2442,8 @@ static int lfs_relocate(lfs_t *lfs,
 
         // update internal root
         if (lfs_paircmp(oldpair, lfs->root) == 0) {
-            LFS_DEBUG("Relocating root %d %d", newpair[0], newpair[1]);
+            LFS_DEBUG("Relocating root %" PRIu32 " %" PRIu32,
+                    newpair[0], newpair[1]);
             lfs->root[0] = newpair[0];
             lfs->root[1] = newpair[1];
         }
@@ -2453,7 +2481,11 @@ int lfs_deorphan(lfs_t *lfs) {
     lfs_dir_t cwd = {.d.tail[0] = 0, .d.tail[1] = 1};
 
     // iterate over all directory directory entries
-    while (!lfs_pairisnull(cwd.d.tail)) {
+    for (int i = 0; i < lfs->cfg->block_count; i++) {
+        if (lfs_pairisnull(cwd.d.tail)) {
+            return 0;
+        }
+
         int err = lfs_dir_fetch(lfs, &cwd, cwd.d.tail);
         if (err) {
             return err;
@@ -2471,7 +2503,7 @@ int lfs_deorphan(lfs_t *lfs) {
 
             if (!res) {
                 // we are an orphan
-                LFS_DEBUG("Found orphan %d %d",
+                LFS_DEBUG("Found orphan %" PRIu32 " %" PRIu32,
                         pdir.d.tail[0], pdir.d.tail[1]);
 
                 pdir.d.tail[0] = cwd.d.tail[0];
@@ -2482,12 +2514,12 @@ int lfs_deorphan(lfs_t *lfs) {
                     return err;
                 }
 
-                break;
+                return 0;
             }
 
             if (!lfs_pairsync(entry.d.u.dir, pdir.d.tail)) {
                 // we have desynced
-                LFS_DEBUG("Found desync %d %d",
+                LFS_DEBUG("Found desync %" PRIu32 " %" PRIu32,
                         entry.d.u.dir[0], entry.d.u.dir[1]);
 
                 pdir.d.tail[0] = entry.d.u.dir[0];
@@ -2498,7 +2530,7 @@ int lfs_deorphan(lfs_t *lfs) {
                     return err;
                 }
 
-                break;
+                return 0;
             }
         }
 
@@ -2522,14 +2554,14 @@ int lfs_deorphan(lfs_t *lfs) {
                 }
 
                 if (moved) {
-                    LFS_DEBUG("Found move %d %d",
+                    LFS_DEBUG("Found move %" PRIu32 " %" PRIu32,
                             entry.d.u.dir[0], entry.d.u.dir[1]);
                     err = lfs_dir_remove(lfs, &cwd, &entry);
                     if (err) {
                         return err;
                     }
                 } else {
-                    LFS_DEBUG("Found partial move %d %d",
+                    LFS_DEBUG("Found partial move %" PRIu32 " %" PRIu32,
                             entry.d.u.dir[0], entry.d.u.dir[1]);
                     entry.d.type &= ~0x80;
                     err = lfs_dir_update(lfs, &cwd, &entry, NULL);
@@ -2543,6 +2575,7 @@ int lfs_deorphan(lfs_t *lfs) {
         memcpy(&pdir, &cwd, sizeof(pdir));
     }
 
-    return 0;
+    // If we reached here, we have more directory pairs than blocks in the
+    // filesystem... So something must be horribly wrong
+    return LFS_ERR_CORRUPT;
 }
-

+ 41 - 25
lfs.h

@@ -1,19 +1,8 @@
 /*
  * The little filesystem
  *
- * Copyright (c) 2017 ARM Limited
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Copyright (c) 2017, Arm Limited. All rights reserved.
+ * SPDX-License-Identifier: BSD-3-Clause
  */
 #ifndef LFS_H
 #define LFS_H
@@ -21,13 +10,18 @@
 #include <stdint.h>
 #include <stdbool.h>
 
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
 
 /// Version info ///
 
 // Software library version
 // Major (top-nibble), incremented on backwards incompatible changes
 // Minor (bottom-nibble), incremented on feature additions
-#define LFS_VERSION 0x00010003
+#define LFS_VERSION 0x00010006
 #define LFS_VERSION_MAJOR (0xffff & (LFS_VERSION >> 16))
 #define LFS_VERSION_MINOR (0xffff & (LFS_VERSION >>  0))
 
@@ -173,6 +167,12 @@ struct lfs_config {
     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;
+};
 
 // File info structure
 struct lfs_info {
@@ -220,6 +220,7 @@ typedef struct lfs_file {
     lfs_block_t head;
     lfs_size_t size;
 
+    const struct lfs_file_config *cfg;
     uint32_t flags;
     lfs_off_t pos;
     lfs_block_t block;
@@ -259,9 +260,9 @@ typedef struct lfs_superblock {
 } lfs_superblock_t;
 
 typedef struct lfs_free {
-    lfs_block_t begin;
-    lfs_block_t size;
     lfs_block_t off;
+    lfs_block_t size;
+    lfs_block_t i;
     lfs_block_t ack;
     uint32_t *buffer;
 } lfs_free_t;
@@ -287,7 +288,8 @@ typedef struct lfs {
 // Format a block device with the littlefs
 //
 // Requires a littlefs object and config struct. This clobbers the littlefs
-// object, and does not leave the filesystem mounted.
+// 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.
 int lfs_format(lfs_t *lfs, const struct lfs_config *config);
@@ -296,7 +298,8 @@ int lfs_format(lfs_t *lfs, const struct lfs_config *config);
 //
 // Requires a littlefs object and config struct. Multiple filesystems
 // may be mounted simultaneously with multiple littlefs objects. Both
-// lfs and config must be allocated while mounted.
+// lfs and config must be allocated while mounted. The config struct must
+// be zeroed for defaults and backwards compatibility.
 //
 // Returns a negative error code on failure.
 int lfs_mount(lfs_t *lfs, const struct lfs_config *config);
@@ -320,10 +323,6 @@ int lfs_remove(lfs_t *lfs, const char *path);
 // If the destination exists, it must match the source in type.
 // If the destination is a directory, the directory must be empty.
 //
-// Note: If power loss occurs, it is possible that the file or directory
-// will exist in both the oldpath and newpath simultaneously after the
-// next mount.
-//
 // Returns a negative error code on failure.
 int lfs_rename(lfs_t *lfs, const char *oldpath, const char *newpath);
 
@@ -338,14 +337,27 @@ int lfs_stat(lfs_t *lfs, const char *path, struct lfs_info *info);
 
 // Open a file
 //
-// The mode that the file is opened in is determined
-// by the flags, which are values from the enum lfs_open_flags
-// that are bitwise-ored together.
+// The mode that the file is opened in is determined by the flags, which
+// are values from the enum lfs_open_flags that are bitwise-ored together.
 //
 // Returns a negative error code on failure.
 int lfs_file_open(lfs_t *lfs, lfs_file_t *file,
         const char *path, int flags);
 
+// Open a file with extra configuration
+//
+// The mode that the file is opened in is determined by the flags, which
+// are values from the enum lfs_open_flags that are bitwise-ored together.
+//
+// The config struct provides additional config options per file as described
+// above. The config struct must be allocated while the file is open, and the
+// config struct must be zeroed for defaults and backwards compatibility.
+//
+// Returns a negative error code on failure.
+int lfs_file_opencfg(lfs_t *lfs, lfs_file_t *file,
+        const char *path, int flags,
+        const struct lfs_file_config *config);
+
 // Close a file
 //
 // Any pending writes are written out to storage as though
@@ -475,4 +487,8 @@ int lfs_traverse(lfs_t *lfs, int (*cb)(void*, lfs_block_t), void *data);
 int lfs_deorphan(lfs_t *lfs);
 
 
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
 #endif

+ 2 - 13
lfs_util.c

@@ -1,19 +1,8 @@
 /*
  * lfs util functions
  *
- * Copyright (c) 2017 ARM Limited
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Copyright (c) 2017, Arm Limited. All rights reserved.
+ * SPDX-License-Identifier: BSD-3-Clause
  */
 #include "lfs_util.h"
 

+ 14 - 13
lfs_util.h

@@ -1,19 +1,8 @@
 /*
  * lfs utility functions
  *
- * Copyright (c) 2017 ARM Limited
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Copyright (c) 2017, Arm Limited. All rights reserved.
+ * SPDX-License-Identifier: BSD-3-Clause
  */
 #ifndef LFS_UTIL_H
 #define LFS_UTIL_H
@@ -45,6 +34,11 @@
 #include <stdio.h>
 #endif
 
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
 
 // Macros, may be replaced by system specific wrappers. Arguments to these
 // macros must not have side-effects as the macros can be removed for a smaller
@@ -169,6 +163,7 @@ static inline void *lfs_malloc(size_t size) {
 #ifndef LFS_NO_MALLOC
     return malloc(size);
 #else
+    (void)size;
     return NULL;
 #endif
 }
@@ -177,9 +172,15 @@ static inline void *lfs_malloc(size_t size) {
 static inline void lfs_free(void *p) {
 #ifndef LFS_NO_MALLOC
     free(p);
+#else
+    (void)p;
 #endif
 }
 
 
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
 #endif
 #endif

+ 124 - 1
tests/test_alloc.sh

@@ -289,7 +289,7 @@ tests/test.py << TEST
     }
     lfs_file_close(&lfs, &file[0]) => 0;
 
-    // open whole
+    // open hole
     lfs_remove(&lfs, "bump") => 0;
 
     lfs_mkdir(&lfs, "splitdir") => 0;
@@ -301,5 +301,128 @@ tests/test.py << TEST
     lfs_unmount(&lfs) => 0;
 TEST
 
+echo "--- Outdated lookahead test ---"
+rm -rf blocks
+tests/test.py << TEST
+    lfs_format(&lfs, &cfg) => 0;
+
+    lfs_mount(&lfs, &cfg) => 0;
+
+    // fill completely with two files
+    lfs_file_open(&lfs, &file[0], "exhaustion1",
+            LFS_O_WRONLY | LFS_O_CREAT) => 0;
+    size = strlen("blahblahblahblah");
+    memcpy(buffer, "blahblahblahblah", size);
+    for (lfs_size_t i = 0;
+            i < ((cfg.block_count-4)/2)*(cfg.block_size-8);
+            i += size) {
+        lfs_file_write(&lfs, &file[0], buffer, size) => size;
+    }
+    lfs_file_close(&lfs, &file[0]) => 0;
+
+    lfs_file_open(&lfs, &file[0], "exhaustion2",
+            LFS_O_WRONLY | LFS_O_CREAT) => 0;
+    size = strlen("blahblahblahblah");
+    memcpy(buffer, "blahblahblahblah", size);
+    for (lfs_size_t i = 0;
+            i < ((cfg.block_count-4+1)/2)*(cfg.block_size-8);
+            i += size) {
+        lfs_file_write(&lfs, &file[0], buffer, size) => size;
+    }
+    lfs_file_close(&lfs, &file[0]) => 0;
+
+    // remount to force reset of lookahead
+    lfs_unmount(&lfs) => 0;
+
+    lfs_mount(&lfs, &cfg) => 0;
+
+    // rewrite one file
+    lfs_file_open(&lfs, &file[0], "exhaustion1",
+            LFS_O_WRONLY | LFS_O_TRUNC) => 0;
+    lfs_file_sync(&lfs, &file[0]) => 0;
+    size = strlen("blahblahblahblah");
+    memcpy(buffer, "blahblahblahblah", size);
+    for (lfs_size_t i = 0;
+            i < ((cfg.block_count-4)/2)*(cfg.block_size-8);
+            i += size) {
+        lfs_file_write(&lfs, &file[0], buffer, size) => size;
+    }
+    lfs_file_close(&lfs, &file[0]) => 0;
+
+    // rewrite second file, this requires lookahead does not
+    // use old population
+    lfs_file_open(&lfs, &file[0], "exhaustion2",
+            LFS_O_WRONLY | LFS_O_TRUNC) => 0;
+    lfs_file_sync(&lfs, &file[0]) => 0;
+    size = strlen("blahblahblahblah");
+    memcpy(buffer, "blahblahblahblah", size);
+    for (lfs_size_t i = 0;
+            i < ((cfg.block_count-4+1)/2)*(cfg.block_size-8);
+            i += size) {
+        lfs_file_write(&lfs, &file[0], buffer, size) => size;
+    }
+    lfs_file_close(&lfs, &file[0]) => 0;
+TEST
+
+echo "--- Outdated lookahead and split dir test ---"
+rm -rf blocks
+tests/test.py << TEST
+    lfs_format(&lfs, &cfg) => 0;
+
+    lfs_mount(&lfs, &cfg) => 0;
+
+    // fill completely with two files
+    lfs_file_open(&lfs, &file[0], "exhaustion1",
+            LFS_O_WRONLY | LFS_O_CREAT) => 0;
+    size = strlen("blahblahblahblah");
+    memcpy(buffer, "blahblahblahblah", size);
+    for (lfs_size_t i = 0;
+            i < ((cfg.block_count-4)/2)*(cfg.block_size-8);
+            i += size) {
+        lfs_file_write(&lfs, &file[0], buffer, size) => size;
+    }
+    lfs_file_close(&lfs, &file[0]) => 0;
+
+    lfs_file_open(&lfs, &file[0], "exhaustion2",
+            LFS_O_WRONLY | LFS_O_CREAT) => 0;
+    size = strlen("blahblahblahblah");
+    memcpy(buffer, "blahblahblahblah", size);
+    for (lfs_size_t i = 0;
+            i < ((cfg.block_count-4+1)/2)*(cfg.block_size-8);
+            i += size) {
+        lfs_file_write(&lfs, &file[0], buffer, size) => size;
+    }
+    lfs_file_close(&lfs, &file[0]) => 0;
+
+    // remount to force reset of lookahead
+    lfs_unmount(&lfs) => 0;
+
+    lfs_mount(&lfs, &cfg) => 0;
+
+    // rewrite one file with a hole of one block
+    lfs_file_open(&lfs, &file[0], "exhaustion1",
+            LFS_O_WRONLY | LFS_O_TRUNC) => 0;
+    lfs_file_sync(&lfs, &file[0]) => 0;
+    size = strlen("blahblahblahblah");
+    memcpy(buffer, "blahblahblahblah", size);
+    for (lfs_size_t i = 0;
+            i < ((cfg.block_count-4)/2 - 1)*(cfg.block_size-8);
+            i += size) {
+        lfs_file_write(&lfs, &file[0], buffer, size) => size;
+    }
+    lfs_file_close(&lfs, &file[0]) => 0;
+
+    // try to allocate a directory, should fail!
+    lfs_mkdir(&lfs, "split") => LFS_ERR_NOSPC;
+
+    // file should not fail
+    lfs_file_open(&lfs, &file[0], "notasplit",
+            LFS_O_WRONLY | LFS_O_CREAT) => 0;
+    lfs_file_write(&lfs, &file[0], "hi", 2) => 2;
+    lfs_file_close(&lfs, &file[0]) => 0;
+
+    lfs_unmount(&lfs) => 0;
+TEST
+
 echo "--- Results ---"
 tests/stats.py

+ 2 - 2
tests/test_files.sh

@@ -30,7 +30,7 @@ TEST
 
 w_test() {
 tests/test.py << TEST
-    lfs_size_t size = $1;
+    size = $1;
     lfs_size_t chunk = 31;
     srand(0);
     lfs_mount(&lfs, &cfg) => 0;
@@ -50,7 +50,7 @@ TEST
 
 r_test() {
 tests/test.py << TEST
-    lfs_size_t size = $1;
+    size = $1;
     lfs_size_t chunk = 29;
     srand(0);
     lfs_mount(&lfs, &cfg) => 0;

+ 3 - 3
tests/test_parallel.sh → tests/test_interspersed.sh

@@ -1,13 +1,13 @@
 #!/bin/bash
 set -eu
 
-echo "=== Parallel tests ==="
+echo "=== Interspersed tests ==="
 rm -rf blocks
 tests/test.py << TEST
     lfs_format(&lfs, &cfg) => 0;
 TEST
 
-echo "--- Parallel file test ---"
+echo "--- Interspersed file test ---"
 tests/test.py << TEST
     lfs_mount(&lfs, &cfg) => 0;
     lfs_file_open(&lfs, &file[0], "a", LFS_O_WRONLY | LFS_O_CREAT) => 0;
@@ -77,7 +77,7 @@ tests/test.py << TEST
     lfs_unmount(&lfs) => 0;
 TEST
 
-echo "--- Parallel remove file test ---"
+echo "--- Interspersed remove file test ---"
 tests/test.py << TEST
     lfs_mount(&lfs, &cfg) => 0;
     lfs_file_open(&lfs, &file[0], "e", LFS_O_WRONLY | LFS_O_CREAT) => 0;

+ 16 - 0
tests/test_paths.sh

@@ -90,6 +90,22 @@ tests/test.py << TEST
     lfs_unmount(&lfs) => 0;
 TEST
 
+echo "--- Trailing dot path tests ---"
+tests/test.py << TEST
+    lfs_mount(&lfs, &cfg) => 0;
+    lfs_stat(&lfs, "tea/hottea/", &info) => 0;
+    strcmp(info.name, "hottea") => 0;
+    lfs_stat(&lfs, "tea/hottea/.", &info) => 0;
+    strcmp(info.name, "hottea") => 0;
+    lfs_stat(&lfs, "tea/hottea/./.", &info) => 0;
+    strcmp(info.name, "hottea") => 0;
+    lfs_stat(&lfs, "tea/hottea/..", &info) => 0;
+    strcmp(info.name, "tea") => 0;
+    lfs_stat(&lfs, "tea/hottea/../.", &info) => 0;
+    strcmp(info.name, "tea") => 0;
+    lfs_unmount(&lfs) => 0;
+TEST
+
 echo "--- Root dot dot path tests ---"
 tests/test.py << TEST
     lfs_mount(&lfs, &cfg) => 0;

+ 4 - 4
tests/test_seek.sh

@@ -153,7 +153,7 @@ tests/test.py << TEST
     lfs_file_read(&lfs, &file[0], buffer, size) => size;
     memcmp(buffer, "kittycatcat", size) => 0;
 
-    lfs_size_t size = lfs_file_size(&lfs, &file[0]);
+    size = lfs_file_size(&lfs, &file[0]);
     lfs_file_seek(&lfs, &file[0], 0, LFS_SEEK_CUR) => size;
 
     lfs_file_close(&lfs, &file[0]) => 0;
@@ -202,7 +202,7 @@ tests/test.py << TEST
     lfs_file_read(&lfs, &file[0], buffer, size) => size;
     memcmp(buffer, "kittycatcat", size) => 0;
 
-    lfs_size_t size = lfs_file_size(&lfs, &file[0]);
+    size = lfs_file_size(&lfs, &file[0]);
     lfs_file_seek(&lfs, &file[0], 0, LFS_SEEK_CUR) => size;
 
     lfs_file_close(&lfs, &file[0]) => 0;
@@ -243,7 +243,7 @@ tests/test.py << TEST
     lfs_file_read(&lfs, &file[0], buffer, size) => size;
     memcmp(buffer, "kittycatcat", size) => 0;
 
-    lfs_size_t size = lfs_file_size(&lfs, &file[0]);
+    size = lfs_file_size(&lfs, &file[0]);
     lfs_file_seek(&lfs, &file[0], 0, LFS_SEEK_CUR) => size;
 
     lfs_file_close(&lfs, &file[0]) => 0;
@@ -286,7 +286,7 @@ tests/test.py << TEST
     lfs_file_read(&lfs, &file[0], buffer, size) => size;
     memcmp(buffer, "kittycatcat", size) => 0;
 
-    lfs_size_t size = lfs_file_size(&lfs, &file[0]);
+    size = lfs_file_size(&lfs, &file[0]);
     lfs_file_seek(&lfs, &file[0], 0, LFS_SEEK_CUR) => size;
 
     lfs_file_close(&lfs, &file[0]) => 0;