Browse Source

Merge tag 'v2.11.2' into upstream

wdfk-prog 1 month ago
parent
commit
e54ca1383c

+ 16 - 13
.github/workflows/release.yml

@@ -20,7 +20,7 @@ jobs:
       github.event.workflow_run.head_sha == github.sha}}
 
     steps:
-      - uses: actions/checkout@v2
+      - uses: actions/checkout@v4
         with:
           ref: ${{github.event.workflow_run.head_sha}}
           # need workflow access since we push branches
@@ -30,26 +30,29 @@ jobs:
           fetch-depth: 0
 
       # try to get results from tests
-      - uses: dawidd6/action-download-artifact@v2
+      - uses: actions/download-artifact@v4
         continue-on-error: true
         with:
-          workflow: ${{github.event.workflow_run.name}}
-          run_id: ${{github.event.workflow_run.id}}
-          name: sizes
+          github-token: ${{secrets.GITHUB_TOKEN}}
+          run-id: ${{github.event.workflow_run.id}}
+          pattern: '{sizes,sizes-*}'
+          merge-multiple: true
           path: sizes
-      - uses: dawidd6/action-download-artifact@v2
+      - uses: actions/download-artifact@v4
         continue-on-error: true
         with:
-          workflow: ${{github.event.workflow_run.name}}
-          run_id: ${{github.event.workflow_run.id}}
-          name: cov
+          github-token: ${{secrets.GITHUB_TOKEN}}
+          run-id: ${{github.event.workflow_run.id}}
+          pattern: '{cov,cov-*}'
+          merge-multiple: true
           path: cov
-      - uses: dawidd6/action-download-artifact@v2
+      - uses: actions/download-artifact@v4
         continue-on-error: true
         with:
-          workflow: ${{github.event.workflow_run.name}}
-          run_id: ${{github.event.workflow_run.id}}
-          name: bench
+          github-token: ${{secrets.GITHUB_TOKEN}}
+          run-id: ${{github.event.workflow_run.id}}
+          pattern: '{bench,bench-*}'
+          merge-multiple: true
           path: bench
 
       - name: find-version

+ 10 - 8
.github/workflows/status.yml

@@ -13,12 +13,13 @@ jobs:
   status:
     runs-on: ubuntu-latest
     steps:
-      - uses: dawidd6/action-download-artifact@v2
+      - uses: actions/download-artifact@v4
         continue-on-error: true
         with:
-          workflow: ${{github.event.workflow_run.name}}
-          run_id: ${{github.event.workflow_run.id}}
-          name: status
+          github-token: ${{secrets.GITHUB_TOKEN}}
+          run-id: ${{github.event.workflow_run.id}}
+          pattern: '{status,status-*}'
+          merge-multiple: true
           path: status
       - name: update-status
         continue-on-error: true
@@ -67,12 +68,13 @@ jobs:
 
     steps:
       # generated comment?
-      - uses: dawidd6/action-download-artifact@v2
+      - uses: actions/download-artifact@v4
         continue-on-error: true
         with:
-          workflow: ${{github.event.workflow_run.name}}
-          run_id: ${{github.event.workflow_run.id}}
-          name: comment
+          github-token: ${{secrets.GITHUB_TOKEN}}
+          run-id: ${{github.event.workflow_run.id}}
+          pattern: '{comment,comment-*}'
+          merge-multiple: true
           path: comment
       - name: update-comment
         continue-on-error: true

+ 79 - 42
.github/workflows/test.yml

@@ -21,7 +21,7 @@ jobs:
         arch: [x86_64, thumb, mips, powerpc]
 
     steps:
-      - uses: actions/checkout@v2
+      - uses: actions/checkout@v4
       - name: install
         run: |
           # need a few things
@@ -235,9 +235,9 @@ jobs:
 
       # create size statuses
       - name: upload-sizes
-        uses: actions/upload-artifact@v2
+        uses: actions/upload-artifact@v4
         with:
-          name: sizes
+          name: sizes-${{matrix.arch}}
           path: sizes
       - name: status-sizes
         run: |
@@ -273,16 +273,16 @@ jobs:
             }' | tee status/$(basename $f .csv).json
           done
       - name: upload-status-sizes
-        uses: actions/upload-artifact@v2
+        uses: actions/upload-artifact@v4
         with:
-          name: status
+          name: status-sizes-${{matrix.arch}}
           path: status
           retention-days: 1
 
       # create cov statuses
       - name: upload-cov
         if: ${{matrix.arch == 'x86_64'}}
-        uses: actions/upload-artifact@v2
+        uses: actions/upload-artifact@v4
         with:
           name: cov
           path: cov
@@ -317,11 +317,11 @@ jobs:
               target_step: env.STEP,
             }' | tee status/$(basename $f .csv)-$s.json
           done
-      - name: upload-status-sizes
+      - name: upload-status-cov
         if: ${{matrix.arch == 'x86_64'}}
-        uses: actions/upload-artifact@v2
+        uses: actions/upload-artifact@v4
         with:
-          name: status
+          name: status-cov
           path: status
           retention-days: 1
 
@@ -336,7 +336,7 @@ jobs:
         pls: [1, 2]
 
     steps:
-      - uses: actions/checkout@v2
+      - uses: actions/checkout@v4
       - name: install
         run: |
           # need a few things
@@ -361,7 +361,7 @@ jobs:
   test-no-intrinsics:
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/checkout@v2
+      - uses: actions/checkout@v4
       - name: install
         run: |
           # need a few things
@@ -374,11 +374,50 @@ jobs:
         run: |
           CFLAGS="$CFLAGS -DLFS_NO_INTRINSICS" make test
 
+  test-shrink:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v4
+      - name: install
+        run: |
+          # need a few things
+          sudo apt-get update -qq
+          sudo apt-get install -qq gcc python3 python3-pip
+          pip3 install toml
+          gcc --version
+          python3 --version
+      - name: test-no-intrinsics
+        run: |
+          CFLAGS="$CFLAGS -DLFS_SHRINKNONRELOCATING" make test
+
+  # run with all trace options enabled to at least make sure these
+  # all compile
+  test-yes-trace:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v4
+      - name: install
+        run: |
+          # need a few things
+          sudo apt-get update -qq
+          sudo apt-get install -qq gcc python3 python3-pip
+          pip3 install toml
+          gcc --version
+          python3 --version
+      - name: test-yes-trace
+        run: |
+          CFLAGS="$CFLAGS \
+            -DLFS_YES_TRACE \
+            -DLFS_RAMBD_YES_TRACE \
+            -DLFS_FILEBD_YES_TRACE \
+            -DLFS_RAMBD_YES_TRACE" \
+            make test
+
   # run LFS_MULTIVERSION tests
   test-multiversion:
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/checkout@v2
+      - uses: actions/checkout@v4
       - name: install
         run: |
           # need a few things
@@ -395,7 +434,7 @@ jobs:
   test-lfs2_0:
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/checkout@v2
+      - uses: actions/checkout@v4
       - name: install
         run: |
           # need a few things
@@ -414,7 +453,7 @@ jobs:
   test-valgrind:
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/checkout@v2
+      - uses: actions/checkout@v4
       - name: install
         run: |
           # need a few things
@@ -431,12 +470,11 @@ jobs:
           TESTFLAGS="$TESTFLAGS --valgrind --context=1024 -Gdefault -Pnone" \
             make test
 
-  # test that compilation is warning free under clang
-  # run with Clang, mostly to check for Clang-specific warnings
+  # compile/run with Clang, mostly to check for Clang-specific warnings
   test-clang:
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/checkout@v2
+      - uses: actions/checkout@v4
       - name: install
         run: |
           # need a few things
@@ -446,12 +484,8 @@ jobs:
           python3 --version
       - name: test-clang
         run: |
-          # override CFLAGS since Clang does not support -fcallgraph-info
-          # and -ftrack-macro-expansions
-          make \
-            CC=clang \
-            CFLAGS="$CFLAGS -MMD -g3 -I. -std=c99 -Wall -Wextra -pedantic" \
-            test
+          CC=clang \
+            make test
 
   # run benchmarks
   #
@@ -459,7 +493,7 @@ jobs:
   bench:
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/checkout@v2
+      - uses: actions/checkout@v4
       - name: install
         run: |
           # need a few things
@@ -491,7 +525,7 @@ jobs:
 
       # create bench statuses
       - name: upload-bench
-        uses: actions/upload-artifact@v2
+        uses: actions/upload-artifact@v4
         with:
           name: bench
           path: bench
@@ -525,9 +559,9 @@ jobs:
             }' | tee status/$(basename $f .csv)-$s.json
           done
       - name: upload-status-bench
-        uses: actions/upload-artifact@v2
+        uses: actions/upload-artifact@v4
         with:
-          name: status
+          name: status-bench
           path: status
           retention-days: 1
 
@@ -535,10 +569,10 @@ jobs:
   test-compat:
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/checkout@v2
+      - uses: actions/checkout@v4
         if: ${{github.event_name == 'pull_request'}}
       # checkout the current pr target into lfsp
-      - uses: actions/checkout@v2
+      - uses: actions/checkout@v4
         if: ${{github.event_name == 'pull_request'}}
         with:
           ref: ${{github.event.pull_request.base.ref}}
@@ -572,7 +606,7 @@ jobs:
     runs-on: ubuntu-latest
     if: ${{!endsWith(github.ref, '-prefix')}}
     steps:
-      - uses: actions/checkout@v2
+      - uses: actions/checkout@v4
       - name: install
         run: |
           # need a few things
@@ -582,7 +616,7 @@ jobs:
           gcc --version
           python3 --version
           fusermount -V
-      - uses: actions/checkout@v2
+      - uses: actions/checkout@v4
         with:
           repository: littlefs-project/littlefs-fuse
           ref: v2
@@ -622,7 +656,7 @@ jobs:
     runs-on: ubuntu-latest
     if: ${{!endsWith(github.ref, '-prefix')}}
     steps:
-      - uses: actions/checkout@v2
+      - uses: actions/checkout@v4
       - name: install
         run: |
           # need a few things
@@ -632,12 +666,12 @@ jobs:
           gcc --version
           python3 --version
           fusermount -V
-      - uses: actions/checkout@v2
+      - uses: actions/checkout@v4
         with:
           repository: littlefs-project/littlefs-fuse
           ref: v2
           path: v2
-      - uses: actions/checkout@v2
+      - uses: actions/checkout@v4
         with:
           repository: littlefs-project/littlefs-fuse
           ref: v1
@@ -694,7 +728,7 @@ jobs:
     runs-on: ubuntu-latest
     needs: [test, bench]
     steps:
-      - uses: actions/checkout@v2
+      - uses: actions/checkout@v4
         if: ${{github.event_name == 'pull_request'}}
       - name: install
         if: ${{github.event_name == 'pull_request'}}
@@ -704,23 +738,26 @@ jobs:
           pip3 install toml
           gcc --version
           python3 --version
-      - uses: actions/download-artifact@v2
+      - uses: actions/download-artifact@v4
         if: ${{github.event_name == 'pull_request'}}
         continue-on-error: true
         with:
-          name: sizes
+          pattern: '{sizes,sizes-*}'
+          merge-multiple: true
           path: sizes
-      - uses: actions/download-artifact@v2
+      - uses: actions/download-artifact@v4
         if: ${{github.event_name == 'pull_request'}}
         continue-on-error: true
         with:
-          name: cov
+          pattern: '{cov,cov-*}'
+          merge-multiple: true
           path: cov
-      - uses: actions/download-artifact@v2
+      - uses: actions/download-artifact@v4
         if: ${{github.event_name == 'pull_request'}}
         continue-on-error: true
         with:
-          name: bench
+          pattern: '{bench,bench-*}'
+          merge-multiple: true
           path: bench
 
       # try to find results from tests
@@ -862,7 +899,7 @@ jobs:
             body: $comment,
           }' | tee comment/comment.json
       - name: upload-comment
-        uses: actions/upload-artifact@v2
+        uses: actions/upload-artifact@v4
         with:
           name: comment
           path: comment

+ 11 - 1
Makefile

@@ -18,6 +18,12 @@ VALGRIND ?= valgrind
 GDB		 ?= gdb
 PERF	 ?= perf
 
+# guess clang or gcc (clang sometimes masquerades as gcc because of
+# course it does)
+ifneq ($(shell $(CC) --version | grep clang),)
+NO_GCC = 1
+endif
+
 SRC  ?= $(filter-out $(wildcard *.t.* *.b.*),$(wildcard *.c))
 OBJ  := $(SRC:%.c=$(BUILDDIR)/%.o)
 DEP  := $(SRC:%.c=$(BUILDDIR)/%.d)
@@ -59,12 +65,15 @@ BENCH_PERF  := $(BENCH_RUNNER:%=%.perf)
 BENCH_TRACE := $(BENCH_RUNNER:%=%.trace)
 BENCH_CSV   := $(BENCH_RUNNER:%=%.csv)
 
-CFLAGS += -fcallgraph-info=su
 CFLAGS += -g3
 CFLAGS += -I.
 CFLAGS += -std=c99 -Wall -Wextra -pedantic
 CFLAGS += -Wmissing-prototypes
+ifndef NO_GCC
+CFLAGS += -fcallgraph-info=su
 CFLAGS += -ftrack-macro-expansion=0
+endif
+
 ifdef DEBUG
 CFLAGS += -O0
 else
@@ -466,6 +475,7 @@ benchmarks-diff: $(BENCH_CSV)
 # rules
 -include $(DEP)
 -include $(TEST_DEP)
+-include $(BENCH_DEP)
 .SUFFIXES:
 .SECONDARY:
 

+ 55 - 1
README.md

@@ -199,6 +199,47 @@ The tests assume a Linux environment and can be started with make:
 make test
 ```
 
+Tests are implemented in C in the .toml files found in the `tests` directory.
+When developing a feature or fixing a bug, it is frequently useful to run a
+single test case or suite of tests:
+
+``` bash
+./scripts/test.py -l runners/test_runner  # list available test suites
+./scripts/test.py -L runners/test_runner test_dirs  # list available test cases
+./scripts/test.py runners/test_runner test_dirs  # run a specific test suite
+```
+
+If an assert fails in a test, test.py will try to print information about the
+failure:
+
+``` bash
+tests/test_dirs.toml:1:failure: test_dirs_root:1g12gg2 (PROG_SIZE=16, ERASE_SIZE=512) failed
+tests/test_dirs.toml:5:assert: assert failed with 0, expected eq 42
+    lfs_mount(&lfs, cfg) => 42;
+```
+
+This includes the test id, which can be passed to test.py to run only that
+specific test permutation:
+
+``` bash
+./scripts/test.py runners/test_runner test_dirs_root:1g12gg2  # run a specific test permutation
+./scripts/test.py runners/test_runner test_dirs_root:1g12gg2 --gdb  # drop into gdb on failure
+```
+
+Some other flags that may be useful:
+
+```bash
+./scripts/test.py runners/test_runner -b -j  # run tests in parallel
+./scripts/test.py runners/test_runner -v -O-  # redirect stdout to stdout
+./scripts/test.py runners/test_runner -ddisk  # capture resulting disk image
+```
+
+See `-h/--help` for a full list of available flags:
+
+``` bash
+./scripts/test.py --help
+```
+
 ## License
 
 The littlefs is provided under the [BSD-3-Clause] license. See
@@ -226,7 +267,11 @@ License Identifiers that are here available: http://spdx.org/licenses/
   to create images of the filesystem on your PC. Check if littlefs will fit
   your needs, create images for a later download to the target memory or
   inspect the content of a binary image of the target memory.
-  
+
+- [littlefs-toy] - A command-line tool for creating and working with littlefs
+  images. Uses syntax similar to tar command for ease of use. Supports working
+  on littlefs images embedded inside another file (firmware image, etc).
+
 - [littlefs2-rust] - A Rust wrapper for littlefs. This project allows you
   to use littlefs in a Rust-friendly API, reaping the benefits of Rust's memory
   safety and other guarantees.
@@ -251,6 +296,12 @@ License Identifiers that are here available: http://spdx.org/licenses/
   filesystem over USB. Allows mounting littlefs on a host PC without additional
   drivers.
 
+- [ramcrc32bd] - An example block device using littlefs's 32-bit CRC for
+  error-correction.
+
+- [ramrsbd] - An example block device using Reed-Solomon codes for
+  error-correction.
+
 - [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.
@@ -274,6 +325,7 @@ License Identifiers that are here available: http://spdx.org/licenses/
 [littlefs-js]: https://github.com/geky/littlefs-js
 [littlefs-js-demo]:http://littlefs.geky.net/demo.html
 [littlefs-python]: https://pypi.org/project/littlefs-python/
+[littlefs-toy]: https://github.com/tjko/littlefs-toy
 [littlefs2-rust]: https://crates.io/crates/littlefs2
 [nim-littlefs]: https://github.com/Graveflo/nim-littlefs
 [chamelon]: https://github.com/yomimono/chamelon
@@ -281,6 +333,8 @@ License Identifiers that are here available: http://spdx.org/licenses/
 [mklfs]: https://github.com/whitecatboard/Lua-RTOS-ESP32/tree/master/components/mklfs/src
 [mklittlefs]: https://github.com/earlephilhower/mklittlefs
 [pico-littlefs-usb]: https://github.com/oyama/pico-littlefs-usb
+[ramcrc32bd]: https://github.com/geky/ramcrc32bd
+[ramrsbd]: https://github.com/geky/ramrsbd
 [Mbed OS]: https://github.com/armmbed/mbed-os
 [LittleFileSystem]: https://os.mbed.com/docs/mbed-os/latest/apis/littlefilesystem.html
 [SPIFFS]: https://github.com/pellepl/spiffs

+ 1 - 1
bd/lfs_filebd.c

@@ -133,7 +133,7 @@ int lfs_filebd_prog(const struct lfs_config *cfg, lfs_block_t block,
 
 int lfs_filebd_erase(const struct lfs_config *cfg, lfs_block_t block) {
     LFS_FILEBD_TRACE("lfs_filebd_erase(%p, 0x%"PRIx32" (%"PRIu32"))",
-            (void*)cfg, block, ((lfs_file_t*)cfg->context)->cfg->erase_size);
+            (void*)cfg, block, ((lfs_filebd_t*)cfg->context)->cfg->erase_size);
     lfs_filebd_t *bd = cfg->context;
 
     // check if erase is valid

+ 231 - 144
lfs.c

@@ -93,6 +93,7 @@ static int lfs_bd_read(lfs_t *lfs,
             // bypass cache?
             diff = lfs_aligndown(diff, lfs->cfg->read_size);
             int err = lfs->cfg->read(lfs->cfg, block, off, data, diff);
+            LFS_ASSERT(err <= 0);
             if (err) {
                 return err;
             }
@@ -282,6 +283,21 @@ static int lfs_bd_erase(lfs_t *lfs, lfs_block_t block) {
 
 
 /// Small type-level utilities ///
+
+// some operations on paths
+static inline lfs_size_t lfs_path_namelen(const char *path) {
+    return strcspn(path, "/");
+}
+
+static inline bool lfs_path_islast(const char *path) {
+    lfs_size_t namelen = lfs_path_namelen(path);
+    return path[namelen + strspn(path + namelen, "/")] == '\0';
+}
+
+static inline bool lfs_path_isdir(const char *path) {
+    return path[lfs_path_namelen(path)] != '\0';
+}
+
 // operations on block pairs
 static inline void lfs_pair_swap(lfs_block_t pair[2]) {
     lfs_block_t t = pair[0];
@@ -389,18 +405,15 @@ struct lfs_diskoff {
 
 // operations on global state
 static inline void lfs_gstate_xor(lfs_gstate_t *a, const lfs_gstate_t *b) {
-    for (int i = 0; i < 3; i++) {
-        ((uint32_t*)a)[i] ^= ((const uint32_t*)b)[i];
-    }
+    a->tag ^= b->tag;
+    a->pair[0] ^= b->pair[0];
+    a->pair[1] ^= b->pair[1];
 }
 
 static inline bool lfs_gstate_iszero(const lfs_gstate_t *a) {
-    for (int i = 0; i < 3; i++) {
-        if (((uint32_t*)a)[i] != 0) {
-            return false;
-        }
-    }
-    return true;
+    return a->tag == 0
+            && a->pair[0] == 0
+            && a->pair[1] == 0;
 }
 
 #ifndef LFS_READONLY
@@ -727,6 +740,7 @@ static lfs_stag_t lfs_dir_getslice(lfs_t *lfs, const lfs_mdir_t *dir,
         int err = lfs_bd_read(lfs,
                 NULL, &lfs->rcache, sizeof(ntag),
                 dir->pair[0], off, &ntag, sizeof(ntag));
+        LFS_ASSERT(err <= 0);
         if (err) {
             return err;
         }
@@ -755,6 +769,7 @@ static lfs_stag_t lfs_dir_getslice(lfs_t *lfs, const lfs_mdir_t *dir,
             err = lfs_bd_read(lfs,
                     NULL, &lfs->rcache, diff,
                     dir->pair[0], off+sizeof(tag)+goff, gbuffer, diff);
+            LFS_ASSERT(err <= 0);
             if (err) {
                 return err;
             }
@@ -816,9 +831,6 @@ static int lfs_dir_getread(lfs_t *lfs, const lfs_mdir_t *dir,
                 size -= diff;
                 continue;
             }
-
-            // rcache takes priority
-            diff = lfs_min(diff, rcache->off-off);
         }
 
         // load to cache, first condition can no longer fail
@@ -1270,6 +1282,7 @@ static lfs_stag_t lfs_dir_fetchmatch(lfs_t *lfs,
                     if (err == LFS_ERR_CORRUPT) {
                         break;
                     }
+                    return err;
                 }
 
                 lfs_fcrc_fromle32(&fcrc);
@@ -1461,32 +1474,46 @@ static int lfs_dir_find_match(void *data,
     return LFS_CMP_EQ;
 }
 
+// lfs_dir_find tries to set path and id even if file is not found
+//
+// returns:
+// - 0                  if file is found
+// - LFS_ERR_NOENT      if file or parent is not found
+// - LFS_ERR_NOTDIR     if parent is not a dir
 static lfs_stag_t lfs_dir_find(lfs_t *lfs, lfs_mdir_t *dir,
         const char **path, uint16_t *id) {
     // we reduce path to a single name if we can find it
     const char *name = *path;
-    if (id) {
-        *id = 0x3ff;
-    }
 
     // default to root dir
     lfs_stag_t tag = LFS_MKTAG(LFS_TYPE_DIR, 0x3ff, 0);
     dir->tail[0] = lfs->root[0];
     dir->tail[1] = lfs->root[1];
 
+    // empty paths are not allowed
+    if (*name == '\0') {
+        return LFS_ERR_INVAL;
+    }
+
     while (true) {
 nextname:
-        // skip slashes
-        name += strspn(name, "/");
+        // skip slashes if we're a directory
+        if (lfs_tag_type3(tag) == LFS_TYPE_DIR) {
+            name += strspn(name, "/");
+        }
         lfs_size_t namelen = strcspn(name, "/");
 
-        // skip '.' and root '..'
-        if ((namelen == 1 && memcmp(name, ".", 1) == 0) ||
-            (namelen == 2 && memcmp(name, "..", 2) == 0)) {
+        // skip '.'
+        if (namelen == 1 && memcmp(name, ".", 1) == 0) {
             name += namelen;
             goto nextname;
         }
 
+        // error on unmatched '..', trying to go above root?
+        if (namelen == 2 && memcmp(name, "..", 2) == 0) {
+            return LFS_ERR_INVAL;
+        }
+
         // skip if matched by '..' in name
         const char *suffix = name + namelen;
         lfs_size_t sufflen;
@@ -1498,7 +1525,9 @@ nextname:
                 break;
             }
 
-            if (sufflen == 2 && memcmp(suffix, "..", 2) == 0) {
+            if (sufflen == 1 && memcmp(suffix, ".", 1) == 0) {
+                // noop
+            } else if (sufflen == 2 && memcmp(suffix, "..", 2) == 0) {
                 depth -= 1;
                 if (depth == 0) {
                     name = suffix + sufflen;
@@ -1512,14 +1541,14 @@ nextname:
         }
 
         // found path
-        if (name[0] == '\0') {
+        if (*name == '\0') {
             return tag;
         }
 
         // update what we've found so far
         *path = name;
 
-        // only continue if we hit a directory
+        // only continue if we're a directory
         if (lfs_tag_type3(tag) != LFS_TYPE_DIR) {
             return LFS_ERR_NOTDIR;
         }
@@ -1539,8 +1568,7 @@ nextname:
             tag = lfs_dir_fetchmatch(lfs, dir, dir->tail,
                     LFS_MKTAG(0x780, 0, 0),
                     LFS_MKTAG(LFS_TYPE_NAME, 0, namelen),
-                     // are we last name?
-                    (strchr(name, '/') == NULL) ? id : NULL,
+                    id,
                     lfs_dir_find_match, &(struct lfs_dir_find_match){
                         lfs, name, namelen});
             if (tag < 0) {
@@ -2128,13 +2156,14 @@ static int lfs_dir_splittingcompact(lfs_t *lfs, lfs_mdir_t *dir,
             // And we cap at half a block to avoid degenerate cases with
             // nearly-full metadata blocks.
             //
+            lfs_size_t metadata_max = (lfs->cfg->metadata_max)
+                    ? lfs->cfg->metadata_max
+                    : lfs->cfg->block_size;
             if (end - split < 0xff
                     && size <= lfs_min(
-                        lfs->cfg->block_size - 40,
+                        metadata_max - 40,
                         lfs_alignup(
-                            (lfs->cfg->metadata_max
-                                ? lfs->cfg->metadata_max
-                                : lfs->cfg->block_size)/2,
+                            metadata_max/2,
                             lfs->cfg->prog_size))) {
                 break;
             }
@@ -2239,7 +2268,7 @@ static int lfs_dir_relocatingcommit(lfs_t *lfs, lfs_mdir_t *dir,
         }
     }
 
-    if (dir->erased) {
+    if (dir->erased && dir->count < 0xff) {
         // try to commit
         struct lfs_commit commit = {
             .block = dir->pair[0],
@@ -2338,7 +2367,8 @@ fixmlist:;
             if (d->m.pair != pair) {
                 for (int i = 0; i < attrcount; i++) {
                     if (lfs_tag_type3(attrs[i].tag) == LFS_TYPE_DELETE &&
-                            d->id == lfs_tag_id(attrs[i].tag)) {
+                            d->id == lfs_tag_id(attrs[i].tag) &&
+                            d->type != LFS_TYPE_DIR) {
                         d->m.pair[0] = LFS_BLOCK_NULL;
                         d->m.pair[1] = LFS_BLOCK_NULL;
                     } else if (lfs_tag_type3(attrs[i].tag) == LFS_TYPE_DELETE &&
@@ -2527,7 +2557,7 @@ static int lfs_dir_orphaningcommit(lfs_t *lfs, lfs_mdir_t *dir,
         if (err != LFS_ERR_NOENT) {
             if (lfs_gstate_hasorphans(&lfs->gstate)) {
                 // next step, clean up orphans
-                err = lfs_fs_preporphans(lfs, -hasparent);
+                err = lfs_fs_preporphans(lfs, -(int8_t)hasparent);
                 if (err) {
                     return err;
                 }
@@ -2603,12 +2633,12 @@ static int lfs_mkdir_(lfs_t *lfs, const char *path) {
     cwd.next = lfs->mlist;
     uint16_t id;
     err = lfs_dir_find(lfs, &cwd.m, &path, &id);
-    if (!(err == LFS_ERR_NOENT && id != 0x3ff)) {
+    if (!(err == LFS_ERR_NOENT && lfs_path_islast(path))) {
         return (err < 0) ? err : LFS_ERR_EXIST;
     }
 
     // check that name fits
-    lfs_size_t nlen = strlen(path);
+    lfs_size_t nlen = lfs_path_namelen(path);
     if (nlen > lfs->name_max) {
         return LFS_ERR_NAMETOOLONG;
     }
@@ -3057,7 +3087,7 @@ static int lfs_file_opencfg_(lfs_t *lfs, lfs_file_t *file,
 
     // allocate entry for file if it doesn't exist
     lfs_stag_t tag = lfs_dir_find(lfs, &file->m, &path, &file->id);
-    if (tag < 0 && !(tag == LFS_ERR_NOENT && file->id != 0x3ff)) {
+    if (tag < 0 && !(tag == LFS_ERR_NOENT && lfs_path_islast(path))) {
         err = tag;
         goto cleanup;
     }
@@ -3077,8 +3107,14 @@ static int lfs_file_opencfg_(lfs_t *lfs, lfs_file_t *file,
             goto cleanup;
         }
 
+        // don't allow trailing slashes
+        if (lfs_path_isdir(path)) {
+            err = LFS_ERR_NOTDIR;
+            goto cleanup;
+        }
+
         // check that name fits
-        lfs_size_t nlen = strlen(path);
+        lfs_size_t nlen = lfs_path_namelen(path);
         if (nlen > lfs->name_max) {
             err = LFS_ERR_NAMETOOLONG;
             goto cleanup;
@@ -3664,22 +3700,16 @@ static lfs_ssize_t lfs_file_write_(lfs_t *lfs, lfs_file_t *file,
 static lfs_soff_t lfs_file_seek_(lfs_t *lfs, lfs_file_t *file,
         lfs_soff_t off, int whence) {
     // find new pos
+    //
+    // fortunately for us, littlefs is limited to 31-bit file sizes, so we
+    // don't have to worry too much about integer overflow
     lfs_off_t npos = file->pos;
     if (whence == LFS_SEEK_SET) {
         npos = off;
     } else if (whence == LFS_SEEK_CUR) {
-        if ((lfs_soff_t)file->pos + off < 0) {
-            return LFS_ERR_INVAL;
-        } else {
-            npos = file->pos + off;
-        }
+        npos = file->pos + (lfs_off_t)off;
     } else if (whence == LFS_SEEK_END) {
-        lfs_soff_t res = lfs_file_size_(lfs, file) + off;
-        if (res < 0) {
-            return LFS_ERR_INVAL;
-        } else {
-            npos = res;
-        }
+        npos = (lfs_off_t)lfs_file_size_(lfs, file) + (lfs_off_t)off;
     }
 
     if (npos > lfs->file_max) {
@@ -3694,13 +3724,8 @@ static lfs_soff_t lfs_file_seek_(lfs_t *lfs, lfs_file_t *file,
 
     // if we're only reading and our new offset is still in the file's cache
     // we can avoid flushing and needing to reread the data
-    if (
-#ifndef LFS_READONLY
-        !(file->flags & LFS_F_WRITING)
-#else
-        true
-#endif
-            ) {
+    if ((file->flags & LFS_F_READING)
+            && file->off != lfs->cfg->block_size) {
         int oindex = lfs_ctz_index(lfs, &(lfs_off_t){file->pos});
         lfs_off_t noff = npos;
         int nindex = lfs_ctz_index(lfs, &noff);
@@ -3842,6 +3867,12 @@ static int lfs_stat_(lfs_t *lfs, const char *path, struct lfs_info *info) {
         return (int)tag;
     }
 
+    // only allow trailing slashes on dirs
+    if (strchr(path, '/') != NULL
+            && lfs_tag_type3(tag) != LFS_TYPE_DIR) {
+        return LFS_ERR_NOTDIR;
+    }
+
     return lfs_dir_getinfo(lfs, &cwd, lfs_tag_id(tag), info);
 }
 
@@ -3902,7 +3933,9 @@ static int lfs_remove_(lfs_t *lfs, const char *path) {
     }
 
     lfs->mlist = dir.next;
-    if (lfs_tag_type3(tag) == LFS_TYPE_DIR) {
+    if (lfs_gstate_hasorphans(&lfs->gstate)) {
+        LFS_ASSERT(lfs_tag_type3(tag) == LFS_TYPE_DIR);
+
         // fix orphan
         err = lfs_fs_preporphans(lfs, -1);
         if (err) {
@@ -3944,7 +3977,7 @@ static int lfs_rename_(lfs_t *lfs, const char *oldpath, const char *newpath) {
     uint16_t newid;
     lfs_stag_t prevtag = lfs_dir_find(lfs, &newcwd, &newpath, &newid);
     if ((prevtag < 0 || lfs_tag_id(prevtag) == 0x3ff) &&
-            !(prevtag == LFS_ERR_NOENT && newid != 0x3ff)) {
+            !(prevtag == LFS_ERR_NOENT && lfs_path_islast(newpath))) {
         return (prevtag < 0) ? (int)prevtag : LFS_ERR_INVAL;
     }
 
@@ -3955,8 +3988,14 @@ static int lfs_rename_(lfs_t *lfs, const char *oldpath, const char *newpath) {
     struct lfs_mlist prevdir;
     prevdir.next = lfs->mlist;
     if (prevtag == LFS_ERR_NOENT) {
+        // if we're a file, don't allow trailing slashes
+        if (lfs_path_isdir(newpath)
+                && lfs_tag_type3(oldtag) != LFS_TYPE_DIR) {
+            return LFS_ERR_NOTDIR;
+        }
+
         // check that name fits
-        lfs_size_t nlen = strlen(newpath);
+        lfs_size_t nlen = lfs_path_namelen(newpath);
         if (nlen > lfs->name_max) {
             return LFS_ERR_NAMETOOLONG;
         }
@@ -4016,7 +4055,8 @@ static int lfs_rename_(lfs_t *lfs, const char *oldpath, const char *newpath) {
             {LFS_MKTAG_IF(prevtag != LFS_ERR_NOENT,
                 LFS_TYPE_DELETE, newid, 0), NULL},
             {LFS_MKTAG(LFS_TYPE_CREATE, newid, 0), NULL},
-            {LFS_MKTAG(lfs_tag_type3(oldtag), newid, strlen(newpath)), newpath},
+            {LFS_MKTAG(lfs_tag_type3(oldtag),
+                newid, lfs_path_namelen(newpath)), newpath},
             {LFS_MKTAG(LFS_FROM_MOVE, newid, lfs_tag_id(oldtag)), &oldcwd},
             {LFS_MKTAG_IF(samepair,
                 LFS_TYPE_DELETE, newoldid, 0), NULL}));
@@ -4039,8 +4079,10 @@ static int lfs_rename_(lfs_t *lfs, const char *oldpath, const char *newpath) {
     }
 
     lfs->mlist = prevdir.next;
-    if (prevtag != LFS_ERR_NOENT
-            && lfs_tag_type3(prevtag) == LFS_TYPE_DIR) {
+    if (lfs_gstate_hasorphans(&lfs->gstate)) {
+        LFS_ASSERT(prevtag != LFS_ERR_NOENT
+                && lfs_tag_type3(prevtag) == LFS_TYPE_DIR);
+
         // fix orphan
         err = lfs_fs_preporphans(lfs, -1);
         if (err) {
@@ -4173,6 +4215,14 @@ static int lfs_init(lfs_t *lfs, const struct lfs_config *cfg) {
     // which littlefs currently does not support
     LFS_ASSERT((bool)0x80000000);
 
+    // check that the required io functions are provided
+    LFS_ASSERT(lfs->cfg->read != NULL);
+#ifndef LFS_READONLY
+    LFS_ASSERT(lfs->cfg->prog != NULL);
+    LFS_ASSERT(lfs->cfg->erase != NULL);
+    LFS_ASSERT(lfs->cfg->sync != NULL);
+#endif
+
     // validate that the lfs-cfg sizes were initiated properly before
     // performing any arithmetic logics with them
     LFS_ASSERT(lfs->cfg->read_size != 0);
@@ -4209,6 +4259,15 @@ static int lfs_init(lfs_t *lfs, const struct lfs_config *cfg) {
     LFS_ASSERT(lfs->cfg->compact_thresh == (lfs_size_t)-1
             || lfs->cfg->compact_thresh <= lfs->cfg->block_size);
 
+    // check that metadata_max is a multiple of read_size and prog_size,
+    // and a factor of the block_size
+    LFS_ASSERT(!lfs->cfg->metadata_max
+            || lfs->cfg->metadata_max % lfs->cfg->read_size == 0);
+    LFS_ASSERT(!lfs->cfg->metadata_max
+            || lfs->cfg->metadata_max % lfs->cfg->prog_size == 0);
+    LFS_ASSERT(!lfs->cfg->metadata_max
+            || lfs->cfg->block_size % lfs->cfg->metadata_max == 0);
+
     // setup read cache
     if (lfs->cfg->read_buffer) {
         lfs->rcache.buffer = lfs->cfg->read_buffer;
@@ -4396,6 +4455,30 @@ cleanup:
 }
 #endif
 
+struct lfs_tortoise_t {
+    lfs_block_t pair[2];
+    lfs_size_t i;
+    lfs_size_t period;
+};
+
+static int lfs_tortoise_detectcycles(
+    const lfs_mdir_t *dir, struct lfs_tortoise_t *tortoise) {
+    // detect cycles with Brent's algorithm
+    if (lfs_pair_issync(dir->tail, tortoise->pair)) {
+        LFS_WARN("Cycle detected in tail list");
+        return LFS_ERR_CORRUPT;
+    }
+    if (tortoise->i == tortoise->period) {
+        tortoise->pair[0] = dir->tail[0];
+        tortoise->pair[1] = dir->tail[1];
+        tortoise->i = 0;
+        tortoise->period *= 2;
+    }
+    tortoise->i += 1;
+
+    return LFS_ERR_OK;
+}
+
 static int lfs_mount_(lfs_t *lfs, const struct lfs_config *cfg) {
     int err = lfs_init(lfs, cfg);
     if (err) {
@@ -4404,23 +4487,16 @@ static int lfs_mount_(lfs_t *lfs, const struct lfs_config *cfg) {
 
     // scan directory blocks for superblock and any global updates
     lfs_mdir_t dir = {.tail = {0, 1}};
-    lfs_block_t tortoise[2] = {LFS_BLOCK_NULL, LFS_BLOCK_NULL};
-    lfs_size_t tortoise_i = 1;
-    lfs_size_t tortoise_period = 1;
+    struct lfs_tortoise_t tortoise = {
+        .pair = {LFS_BLOCK_NULL, LFS_BLOCK_NULL},
+        .i = 1,
+        .period = 1,
+    };
     while (!lfs_pair_isnull(dir.tail)) {
-        // detect cycles with Brent's algorithm
-        if (lfs_pair_issync(dir.tail, tortoise)) {
-            LFS_WARN("Cycle detected in tail list");
-            err = LFS_ERR_CORRUPT;
+        err = lfs_tortoise_detectcycles(&dir, &tortoise);
+        if (err < 0) {
             goto cleanup;
         }
-        if (tortoise_i == tortoise_period) {
-            tortoise[0] = dir.tail[0];
-            tortoise[1] = dir.tail[1];
-            tortoise_i = 0;
-            tortoise_period *= 2;
-        }
-        tortoise_i += 1;
 
         // fetch next block in tail list
         lfs_stag_t tag = lfs_dir_fetchmatch(lfs, &dir, dir.tail,
@@ -4633,22 +4709,17 @@ int lfs_fs_traverse_(lfs_t *lfs,
     }
 #endif
 
-    lfs_block_t tortoise[2] = {LFS_BLOCK_NULL, LFS_BLOCK_NULL};
-    lfs_size_t tortoise_i = 1;
-    lfs_size_t tortoise_period = 1;
+    struct lfs_tortoise_t tortoise = {
+        .pair = {LFS_BLOCK_NULL, LFS_BLOCK_NULL},
+        .i = 1,
+        .period = 1,
+    };
+    int err = LFS_ERR_OK;
     while (!lfs_pair_isnull(dir.tail)) {
-        // detect cycles with Brent's algorithm
-        if (lfs_pair_issync(dir.tail, tortoise)) {
-            LFS_WARN("Cycle detected in tail list");
+        err = lfs_tortoise_detectcycles(&dir, &tortoise);
+        if (err < 0) {
             return LFS_ERR_CORRUPT;
         }
-        if (tortoise_i == tortoise_period) {
-            tortoise[0] = dir.tail[0];
-            tortoise[1] = dir.tail[1];
-            tortoise_i = 0;
-            tortoise_period *= 2;
-        }
-        tortoise_i += 1;
 
         for (int i = 0; i < 2; i++) {
             int err = cb(data, dir.tail[i]);
@@ -4727,22 +4798,17 @@ static int lfs_fs_pred(lfs_t *lfs,
     // iterate over all directory directory entries
     pdir->tail[0] = 0;
     pdir->tail[1] = 1;
-    lfs_block_t tortoise[2] = {LFS_BLOCK_NULL, LFS_BLOCK_NULL};
-    lfs_size_t tortoise_i = 1;
-    lfs_size_t tortoise_period = 1;
+    struct lfs_tortoise_t tortoise = {
+        .pair = {LFS_BLOCK_NULL, LFS_BLOCK_NULL},
+        .i = 1,
+        .period = 1,
+    };
+    int err = LFS_ERR_OK;
     while (!lfs_pair_isnull(pdir->tail)) {
-        // detect cycles with Brent's algorithm
-        if (lfs_pair_issync(pdir->tail, tortoise)) {
-            LFS_WARN("Cycle detected in tail list");
+        err = lfs_tortoise_detectcycles(pdir, &tortoise);
+        if (err < 0) {
             return LFS_ERR_CORRUPT;
         }
-        if (tortoise_i == tortoise_period) {
-            tortoise[0] = pdir->tail[0];
-            tortoise[1] = pdir->tail[1];
-            tortoise_i = 0;
-            tortoise_period *= 2;
-        }
-        tortoise_i += 1;
 
         if (lfs_pair_cmp(pdir->tail, pair) == 0) {
             return 0;
@@ -4792,22 +4858,17 @@ static lfs_stag_t lfs_fs_parent(lfs_t *lfs, const lfs_block_t pair[2],
     // use fetchmatch with callback to find pairs
     parent->tail[0] = 0;
     parent->tail[1] = 1;
-    lfs_block_t tortoise[2] = {LFS_BLOCK_NULL, LFS_BLOCK_NULL};
-    lfs_size_t tortoise_i = 1;
-    lfs_size_t tortoise_period = 1;
+    struct lfs_tortoise_t tortoise = {
+        .pair = {LFS_BLOCK_NULL, LFS_BLOCK_NULL},
+        .i = 1,
+        .period = 1,
+    };
+    int err = LFS_ERR_OK;
     while (!lfs_pair_isnull(parent->tail)) {
-        // detect cycles with Brent's algorithm
-        if (lfs_pair_issync(parent->tail, tortoise)) {
-            LFS_WARN("Cycle detected in tail list");
-            return LFS_ERR_CORRUPT;
-        }
-        if (tortoise_i == tortoise_period) {
-            tortoise[0] = parent->tail[0];
-            tortoise[1] = parent->tail[1];
-            tortoise_i = 0;
-            tortoise_period *= 2;
+        err = lfs_tortoise_detectcycles(parent, &tortoise);
+        if (err < 0) {
+            return err;
         }
-        tortoise_i += 1;
 
         lfs_stag_t tag = lfs_dir_fetchmatch(lfs, parent, parent->tail,
                 LFS_MKTAG(0x7ff, 0, 0x3ff),
@@ -5165,7 +5226,9 @@ static int lfs_fs_gc_(lfs_t *lfs) {
     }
 
     // try to populate the lookahead buffer, unless it's already full
-    if (lfs->lookahead.size < 8*lfs->cfg->lookahead_size) {
+    if (lfs->lookahead.size < lfs_min(
+            8 * lfs->cfg->lookahead_size,
+            lfs->block_count)) {
         err = lfs_alloc_scan(lfs);
         if (err) {
             return err;
@@ -5177,40 +5240,64 @@ static int lfs_fs_gc_(lfs_t *lfs) {
 #endif
 
 #ifndef LFS_READONLY
+#ifdef LFS_SHRINKNONRELOCATING
+static int lfs_shrink_checkblock(void *data, lfs_block_t block) {
+    lfs_size_t threshold = *((lfs_size_t*)data);
+    if (block >= threshold) {
+        return LFS_ERR_NOTEMPTY;
+    }
+    return 0;
+}
+#endif
+
 static int lfs_fs_grow_(lfs_t *lfs, lfs_size_t block_count) {
-    // shrinking is not supported
-    LFS_ASSERT(block_count >= lfs->block_count);
+    int err;
 
-    if (block_count > lfs->block_count) {
-        lfs->block_count = block_count;
+    if (block_count == lfs->block_count) {
+        return 0;
+    }
 
-        // fetch the root
-        lfs_mdir_t root;
-        int err = lfs_dir_fetch(lfs, &root, lfs->root);
+    
+#ifndef LFS_SHRINKNONRELOCATING
+    // shrinking is not supported
+    LFS_ASSERT(block_count >= lfs->block_count);
+#endif
+#ifdef LFS_SHRINKNONRELOCATING
+    if (block_count < lfs->block_count) {
+        err = lfs_fs_traverse_(lfs, lfs_shrink_checkblock, &block_count, true);
         if (err) {
             return err;
         }
+    }
+#endif
 
-        // update the superblock
-        lfs_superblock_t superblock;
-        lfs_stag_t tag = lfs_dir_get(lfs, &root, LFS_MKTAG(0x7ff, 0x3ff, 0),
-                LFS_MKTAG(LFS_TYPE_INLINESTRUCT, 0, sizeof(superblock)),
-                &superblock);
-        if (tag < 0) {
-            return tag;
-        }
-        lfs_superblock_fromle32(&superblock);
+    lfs->block_count = block_count;
 
-        superblock.block_count = lfs->block_count;
+    // fetch the root
+    lfs_mdir_t root;
+    err = lfs_dir_fetch(lfs, &root, lfs->root);
+    if (err) {
+        return err;
+    }
 
-        lfs_superblock_tole32(&superblock);
-        err = lfs_dir_commit(lfs, &root, LFS_MKATTRS(
-                {tag, &superblock}));
-        if (err) {
-            return err;
-        }
+    // update the superblock
+    lfs_superblock_t superblock;
+    lfs_stag_t tag = lfs_dir_get(lfs, &root, LFS_MKTAG(0x7ff, 0x3ff, 0),
+            LFS_MKTAG(LFS_TYPE_INLINESTRUCT, 0, sizeof(superblock)),
+            &superblock);
+    if (tag < 0) {
+        return tag;
     }
+    lfs_superblock_fromle32(&superblock);
+
+    superblock.block_count = lfs->block_count;
 
+    lfs_superblock_tole32(&superblock);
+    err = lfs_dir_commit(lfs, &root, LFS_MKATTRS(
+            {tag, &superblock}));
+    if (err) {
+        return err;
+    }
     return 0;
 }
 #endif
@@ -5890,7 +5977,7 @@ int lfs_format(lfs_t *lfs, const struct lfs_config *cfg) {
                 ".read=%p, .prog=%p, .erase=%p, .sync=%p, "
                 ".read_size=%"PRIu32", .prog_size=%"PRIu32", "
                 ".block_size=%"PRIu32", .block_count=%"PRIu32", "
-                ".block_cycles=%"PRIu32", .cache_size=%"PRIu32", "
+                ".block_cycles=%"PRId32", .cache_size=%"PRIu32", "
                 ".lookahead_size=%"PRIu32", .read_buffer=%p, "
                 ".prog_buffer=%p, .lookahead_buffer=%p, "
                 ".name_max=%"PRIu32", .file_max=%"PRIu32", "
@@ -5920,7 +6007,7 @@ int lfs_mount(lfs_t *lfs, const struct lfs_config *cfg) {
                 ".read=%p, .prog=%p, .erase=%p, .sync=%p, "
                 ".read_size=%"PRIu32", .prog_size=%"PRIu32", "
                 ".block_size=%"PRIu32", .block_count=%"PRIu32", "
-                ".block_cycles=%"PRIu32", .cache_size=%"PRIu32", "
+                ".block_cycles=%"PRId32", .cache_size=%"PRIu32", "
                 ".lookahead_size=%"PRIu32", .read_buffer=%p, "
                 ".prog_buffer=%p, .lookahead_buffer=%p, "
                 ".name_max=%"PRIu32", .file_max=%"PRIu32", "
@@ -6057,7 +6144,7 @@ int lfs_file_open(lfs_t *lfs, lfs_file_t *file, const char *path, int flags) {
         return err;
     }
     LFS_TRACE("lfs_file_open(%p, %p, \"%s\", %x)",
-            (void*)lfs, (void*)file, path, flags);
+            (void*)lfs, (void*)file, path, (unsigned)flags);
     LFS_ASSERT(!lfs_mlist_isopen(lfs->mlist, (struct lfs_mlist*)file));
 
     err = lfs_file_open_(lfs, file, path, flags);
@@ -6077,7 +6164,7 @@ int lfs_file_opencfg(lfs_t *lfs, lfs_file_t *file,
     }
     LFS_TRACE("lfs_file_opencfg(%p, %p, \"%s\", %x, %p {"
                  ".buffer=%p, .attrs=%p, .attr_count=%"PRIu32"})",
-            (void*)lfs, (void*)file, path, flags,
+            (void*)lfs, (void*)file, path, (unsigned)flags,
             (void*)cfg, cfg->buffer, (void*)cfg->attrs, cfg->attr_count);
     LFS_ASSERT(!lfs_mlist_isopen(lfs->mlist, (struct lfs_mlist*)file));
 
@@ -6230,7 +6317,7 @@ lfs_soff_t lfs_file_size(lfs_t *lfs, lfs_file_t *file) {
 
     lfs_soff_t res = lfs_file_size_(lfs, file);
 
-    LFS_TRACE("lfs_file_size -> %"PRId32, res);
+    LFS_TRACE("lfs_file_size -> %"PRIu32, res);
     LFS_UNLOCK(lfs->cfg);
     return res;
 }
@@ -6439,7 +6526,7 @@ int lfs_migrate(lfs_t *lfs, const struct lfs_config *cfg) {
                 ".read=%p, .prog=%p, .erase=%p, .sync=%p, "
                 ".read_size=%"PRIu32", .prog_size=%"PRIu32", "
                 ".block_size=%"PRIu32", .block_count=%"PRIu32", "
-                ".block_cycles=%"PRIu32", .cache_size=%"PRIu32", "
+                ".block_cycles=%"PRId32", .cache_size=%"PRIu32", "
                 ".lookahead_size=%"PRIu32", .read_buffer=%p, "
                 ".prog_buffer=%p, .lookahead_buffer=%p, "
                 ".name_max=%"PRIu32", .file_max=%"PRIu32", "

+ 6 - 2
lfs.h

@@ -21,7 +21,7 @@ extern "C"
 // Software library version
 // Major (top-nibble), incremented on backwards incompatible changes
 // Minor (bottom-nibble), incremented on feature additions
-#define LFS_VERSION 0x00020009
+#define LFS_VERSION 0x0002000b
 #define LFS_VERSION_MAJOR (0xffff & (LFS_VERSION >> 16))
 #define LFS_VERSION_MINOR (0xffff & (LFS_VERSION >>  0))
 
@@ -766,7 +766,11 @@ int lfs_fs_gc(lfs_t *lfs);
 // Grows the filesystem to a new size, updating the superblock with the new
 // block count.
 //
-// Note: This is irreversible.
+// If LFS_SHRINKNONRELOCATING is defined, this function will also accept
+// block_counts smaller than the current configuration, after checking
+// that none of the blocks that are being removed are in use.
+// Note that littlefs's pseudorandom block allocation means that
+// this is very unlikely to work in the general case.
 //
 // Returns a negative error code on failure.
 int lfs_fs_grow(lfs_t *lfs, lfs_size_t block_count);

+ 30 - 12
lfs_util.h

@@ -8,6 +8,9 @@
 #ifndef LFS_UTIL_H
 #define LFS_UTIL_H
 
+#define LFS_STRINGIZE(x) LFS_STRINGIZE2(x)
+#define LFS_STRINGIZE2(x) #x
+
 // Users can override lfs_util.h with their own configuration by defining
 // LFS_CONFIG as a header file to include (-DLFS_CONFIG=lfs_config.h).
 //
@@ -15,11 +18,26 @@
 // provided by the config file. To start, I would suggest copying lfs_util.h
 // and modifying as needed.
 #ifdef LFS_CONFIG
-#define LFS_STRINGIZE(x) LFS_STRINGIZE2(x)
-#define LFS_STRINGIZE2(x) #x
 #include LFS_STRINGIZE(LFS_CONFIG)
 #else
 
+// Alternatively, users can provide a header file which defines
+// macros and other things consumed by littlefs.
+//
+// For example, provide my_defines.h, which contains
+// something like:
+//
+// #include <stddef.h>
+// extern void *my_malloc(size_t sz);
+// #define LFS_MALLOC(sz) my_malloc(sz)
+//
+// And build littlefs with the header by defining LFS_DEFINES.
+// (-DLFS_DEFINES=my_defines.h)
+
+#ifdef LFS_DEFINES
+#include LFS_STRINGIZE(LFS_DEFINES)
+#endif
+
 // System includes
 #include <stdint.h>
 #include <stdbool.h>
@@ -177,10 +195,10 @@ static inline uint32_t lfs_fromle32(uint32_t a) {
     (defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__))
     return __builtin_bswap32(a);
 #else
-    return (((uint8_t*)&a)[0] <<  0) |
-           (((uint8_t*)&a)[1] <<  8) |
-           (((uint8_t*)&a)[2] << 16) |
-           (((uint8_t*)&a)[3] << 24);
+    return ((uint32_t)((uint8_t*)&a)[0] <<  0) |
+           ((uint32_t)((uint8_t*)&a)[1] <<  8) |
+           ((uint32_t)((uint8_t*)&a)[2] << 16) |
+           ((uint32_t)((uint8_t*)&a)[3] << 24);
 #endif
 }
 
@@ -200,10 +218,10 @@ static inline uint32_t lfs_frombe32(uint32_t a) {
     (defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __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);
+    return ((uint32_t)((uint8_t*)&a)[0] << 24) |
+           ((uint32_t)((uint8_t*)&a)[1] << 16) |
+           ((uint32_t)((uint8_t*)&a)[2] <<  8) |
+           ((uint32_t)((uint8_t*)&a)[3] <<  0);
 #endif
 }
 
@@ -213,8 +231,8 @@ static inline uint32_t lfs_tobe32(uint32_t a) {
 
 // Calculate CRC-32 with polynomial = 0x04c11db7
 #ifdef LFS_CRC
-uint32_t lfs_crc(uint32_t crc, const void *buffer, size_t size) {
-    return LFS_CRC(crc, buffer, size)
+static inline uint32_t lfs_crc(uint32_t crc, const void *buffer, size_t size) {
+    return LFS_CRC(crc, buffer, size);
 }
 #else
 uint32_t lfs_crc(uint32_t crc, const void *buffer, size_t size);

+ 6 - 0
runners/bench_runner.c

@@ -123,8 +123,13 @@ typedef struct bench_id {
 
 
 // bench suites are linked into a custom ld section
+#if defined(__APPLE__)
+extern struct bench_suite __start__bench_suites __asm("section$start$__DATA$_bench_suites");
+extern struct bench_suite __stop__bench_suites __asm("section$end$__DATA$_bench_suites");
+#else
 extern struct bench_suite __start__bench_suites;
 extern struct bench_suite __stop__bench_suites;
+#endif
 
 const struct bench_suite *bench_suites = &__start__bench_suites;
 #define BENCH_SUITE_COUNT \
@@ -1322,6 +1327,7 @@ void perm_run(
         .cache_size         = CACHE_SIZE,
         .lookahead_size     = LOOKAHEAD_SIZE,
         .compact_thresh     = COMPACT_THRESH,
+        .metadata_max       = METADATA_MAX,
         .inline_max         = INLINE_MAX,
     };
 

+ 10 - 7
runners/bench_runner.h

@@ -96,12 +96,13 @@ intmax_t bench_define(size_t define);
 #define CACHE_SIZE_i         6
 #define LOOKAHEAD_SIZE_i     7
 #define COMPACT_THRESH_i     8
-#define INLINE_MAX_i         9
-#define BLOCK_CYCLES_i       10
-#define ERASE_VALUE_i        11
-#define ERASE_CYCLES_i       12
-#define BADBLOCK_BEHAVIOR_i  13
-#define POWERLOSS_BEHAVIOR_i 14
+#define METADATA_MAX_i       9
+#define INLINE_MAX_i         10
+#define BLOCK_CYCLES_i       11
+#define ERASE_VALUE_i        12
+#define ERASE_CYCLES_i       13
+#define BADBLOCK_BEHAVIOR_i  14
+#define POWERLOSS_BEHAVIOR_i 15
 
 #define READ_SIZE           bench_define(READ_SIZE_i)
 #define PROG_SIZE           bench_define(PROG_SIZE_i)
@@ -112,6 +113,7 @@ intmax_t bench_define(size_t define);
 #define CACHE_SIZE          bench_define(CACHE_SIZE_i)
 #define LOOKAHEAD_SIZE      bench_define(LOOKAHEAD_SIZE_i)
 #define COMPACT_THRESH      bench_define(COMPACT_THRESH_i)
+#define METADATA_MAX        bench_define(METADATA_MAX_i)
 #define INLINE_MAX          bench_define(INLINE_MAX_i)
 #define BLOCK_CYCLES        bench_define(BLOCK_CYCLES_i)
 #define ERASE_VALUE         bench_define(ERASE_VALUE_i)
@@ -129,6 +131,7 @@ intmax_t bench_define(size_t define);
     BENCH_DEF(CACHE_SIZE,         lfs_max(64,lfs_max(READ_SIZE,PROG_SIZE))) \
     BENCH_DEF(LOOKAHEAD_SIZE,     16) \
     BENCH_DEF(COMPACT_THRESH,     0) \
+    BENCH_DEF(METADATA_MAX,       0) \
     BENCH_DEF(INLINE_MAX,         0) \
     BENCH_DEF(BLOCK_CYCLES,       -1) \
     BENCH_DEF(ERASE_VALUE,        0xff) \
@@ -137,7 +140,7 @@ intmax_t bench_define(size_t define);
     BENCH_DEF(POWERLOSS_BEHAVIOR, LFS_EMUBD_POWERLOSS_NOOP)
 
 #define BENCH_GEOMETRY_DEFINE_COUNT 4
-#define BENCH_IMPLICIT_DEFINE_COUNT 15
+#define BENCH_IMPLICIT_DEFINE_COUNT 16
 
 
 #endif

+ 10 - 0
runners/test_runner.c

@@ -136,8 +136,13 @@ typedef struct test_id {
 
 
 // test suites are linked into a custom ld section
+#if defined(__APPLE__)
+extern struct test_suite __start__test_suites __asm("section$start$__DATA$_test_suites");
+extern struct test_suite __stop__test_suites __asm("section$end$__DATA$_test_suites");
+#else
 extern struct test_suite __start__test_suites;
 extern struct test_suite __stop__test_suites;
+#endif
 
 const struct test_suite *test_suites = &__start__test_suites;
 #define TEST_SUITE_COUNT \
@@ -1347,6 +1352,7 @@ static void run_powerloss_none(
         .cache_size         = CACHE_SIZE,
         .lookahead_size     = LOOKAHEAD_SIZE,
         .compact_thresh     = COMPACT_THRESH,
+        .metadata_max       = METADATA_MAX,
         .inline_max         = INLINE_MAX,
     #ifdef LFS_MULTIVERSION
         .disk_version       = DISK_VERSION,
@@ -1425,6 +1431,7 @@ static void run_powerloss_linear(
         .cache_size         = CACHE_SIZE,
         .lookahead_size     = LOOKAHEAD_SIZE,
         .compact_thresh     = COMPACT_THRESH,
+        .metadata_max       = METADATA_MAX,
         .inline_max         = INLINE_MAX,
     #ifdef LFS_MULTIVERSION
         .disk_version       = DISK_VERSION,
@@ -1520,6 +1527,7 @@ static void run_powerloss_log(
         .cache_size         = CACHE_SIZE,
         .lookahead_size     = LOOKAHEAD_SIZE,
         .compact_thresh     = COMPACT_THRESH,
+        .metadata_max       = METADATA_MAX,
         .inline_max         = INLINE_MAX,
     #ifdef LFS_MULTIVERSION
         .disk_version       = DISK_VERSION,
@@ -1613,6 +1621,7 @@ static void run_powerloss_cycles(
         .cache_size         = CACHE_SIZE,
         .lookahead_size     = LOOKAHEAD_SIZE,
         .compact_thresh     = COMPACT_THRESH,
+        .metadata_max       = METADATA_MAX,
         .inline_max         = INLINE_MAX,
     #ifdef LFS_MULTIVERSION
         .disk_version       = DISK_VERSION,
@@ -1804,6 +1813,7 @@ static void run_powerloss_exhaustive(
         .cache_size         = CACHE_SIZE,
         .lookahead_size     = LOOKAHEAD_SIZE,
         .compact_thresh     = COMPACT_THRESH,
+        .metadata_max       = METADATA_MAX,
         .inline_max         = INLINE_MAX,
     #ifdef LFS_MULTIVERSION
         .disk_version       = DISK_VERSION,

+ 11 - 8
runners/test_runner.h

@@ -89,13 +89,14 @@ intmax_t test_define(size_t define);
 #define CACHE_SIZE_i         6
 #define LOOKAHEAD_SIZE_i     7
 #define COMPACT_THRESH_i     8
-#define INLINE_MAX_i         9
-#define BLOCK_CYCLES_i       10
-#define ERASE_VALUE_i        11
-#define ERASE_CYCLES_i       12
-#define BADBLOCK_BEHAVIOR_i  13
-#define POWERLOSS_BEHAVIOR_i 14
-#define DISK_VERSION_i       15
+#define METADATA_MAX_i       9
+#define INLINE_MAX_i         10
+#define BLOCK_CYCLES_i       11
+#define ERASE_VALUE_i        12
+#define ERASE_CYCLES_i       13
+#define BADBLOCK_BEHAVIOR_i  14
+#define POWERLOSS_BEHAVIOR_i 15
+#define DISK_VERSION_i       16
 
 #define READ_SIZE           TEST_DEFINE(READ_SIZE_i)
 #define PROG_SIZE           TEST_DEFINE(PROG_SIZE_i)
@@ -106,6 +107,7 @@ intmax_t test_define(size_t define);
 #define CACHE_SIZE          TEST_DEFINE(CACHE_SIZE_i)
 #define LOOKAHEAD_SIZE      TEST_DEFINE(LOOKAHEAD_SIZE_i)
 #define COMPACT_THRESH      TEST_DEFINE(COMPACT_THRESH_i)
+#define METADATA_MAX        TEST_DEFINE(METADATA_MAX_i)
 #define INLINE_MAX          TEST_DEFINE(INLINE_MAX_i)
 #define BLOCK_CYCLES        TEST_DEFINE(BLOCK_CYCLES_i)
 #define ERASE_VALUE         TEST_DEFINE(ERASE_VALUE_i)
@@ -124,6 +126,7 @@ intmax_t test_define(size_t define);
     TEST_DEF(CACHE_SIZE,         lfs_max(64,lfs_max(READ_SIZE,PROG_SIZE))) \
     TEST_DEF(LOOKAHEAD_SIZE,     16) \
     TEST_DEF(COMPACT_THRESH,     0) \
+    TEST_DEF(METADATA_MAX,       0) \
     TEST_DEF(INLINE_MAX,         0) \
     TEST_DEF(BLOCK_CYCLES,       -1) \
     TEST_DEF(ERASE_VALUE,        0xff) \
@@ -133,7 +136,7 @@ intmax_t test_define(size_t define);
     TEST_DEF(DISK_VERSION,       0)
 
 #define TEST_GEOMETRY_DEFINE_COUNT 4
-#define TEST_IMPLICIT_DEFINE_COUNT 16
+#define TEST_IMPLICIT_DEFINE_COUNT 17
 
 
 #endif

+ 4 - 1
scripts/bench.py

@@ -404,12 +404,15 @@ def compile(bench_paths, **args):
                         f.writeln()
 
                 # create suite struct
-                #
+                f.writeln('#if defined(__APPLE__)')
+                f.writeln('__attribute__((section("__DATA,_bench_suites")))')
+                f.writeln('#else')
                 # note we place this in the custom bench_suites section with
                 # minimum alignment, otherwise GCC ups the alignment to
                 # 32-bytes for some reason
                 f.writeln('__attribute__((section("_bench_suites"), '
                     'aligned(1)))')
+                f.writeln('#endif')
                 f.writeln('const struct bench_suite __bench__%s__suite = {'
                     % suite.name)
                 f.writeln(4*' '+'.name = "%s",' % suite.name)

+ 1 - 1
scripts/changeprefix.py

@@ -73,7 +73,7 @@ def changefile(from_prefix, to_prefix, from_path, to_path, *,
         shutil.copystat(from_path, to_path)
 
     if to_path_temp:
-        os.rename(to_path, from_path)
+        shutil.move(to_path, from_path)
     elif from_path != '-':
         os.remove(from_path)
 

+ 29 - 3
scripts/prettyasserts.py

@@ -35,10 +35,10 @@ LEXEMES = {
     'assert':   ['assert'],
     'arrow':    ['=>'],
     'string':   [r'"(?:\\.|[^"])*"', r"'(?:\\.|[^'])\'"],
-    'paren':    ['\(', '\)'],
+    'paren':    [r'\(', r'\)'],
     'cmp':      CMP.keys(),
-    'logic':    ['\&\&', '\|\|'],
-    'sep':      [':', ';', '\{', '\}', ','],
+    'logic':    [r'\&\&', r'\|\|'],
+    'sep':      [':', ';', r'\{', r'\}', ','],
     'op':       ['->'], # specifically ops that conflict with cmp
 }
 
@@ -86,6 +86,13 @@ def write_header(f, limit=LIMIT):
     f.writeln("}")
     f.writeln()
     f.writeln("__attribute__((unused))")
+    f.writeln("static void __pretty_assert_print_ptr(")
+    f.writeln("        const void *v, size_t size) {")
+    f.writeln("    (void)size;")
+    f.writeln("    printf(\"%p\", v);")
+    f.writeln("}")
+    f.writeln()
+    f.writeln("__attribute__((unused))")
     f.writeln("static void __pretty_assert_print_mem(")
     f.writeln("        const void *v, size_t size) {")
     f.writeln("    const uint8_t *v_ = v;")
@@ -183,6 +190,23 @@ def write_header(f, limit=LIMIT):
         f.writeln("                _rh, strlen(_rh)); \\")
         f.writeln("    } \\")
         f.writeln("} while (0)")
+    for op, cmp in sorted(CMP.items()):
+        # Only EQ and NE are supported when compared to NULL.
+        if cmp not in ['eq', 'ne']:
+            continue
+        f.writeln("#define __PRETTY_ASSERT_PTR_%s(lh, rh) do { \\"
+            % cmp.upper())
+        f.writeln("    const void *_lh = (const void*)(uintptr_t)lh; \\")
+        f.writeln("    const void *_rh = (const void*)(uintptr_t)rh; \\")
+        f.writeln("    if (!(_lh %s _rh)) { \\" % op)
+        f.writeln("        __pretty_assert_fail( \\")
+        f.writeln("                __FILE__, __LINE__, \\")
+        f.writeln("                __pretty_assert_print_ptr, \"%s\", \\"
+            % cmp)
+        f.writeln("                (const void*){_lh}, 0, \\")
+        f.writeln("                (const void*){_rh}, 0); \\")
+        f.writeln("    } \\")
+        f.writeln("} while (0)")
     f.writeln()
     f.writeln()
 
@@ -301,6 +325,8 @@ def p_assert(p):
         cmp = p.expect('cmp') ; p.accept('ws')
         rh = p_expr(p) ; p.accept('ws')
         p.expect(')')
+        if rh == 'NULL' or lh == 'NULL':
+            return mkassert('ptr', CMP[cmp], lh, rh)
         return mkassert('int', CMP[cmp], lh, rh)
     except ParseFailure:
         p.pop(state)

+ 23 - 20
scripts/test.py

@@ -102,9 +102,9 @@ class TestCase:
                 # the runner itself.
                 for v_ in csplit(v):
                     m = re.search(r'\brange\b\s*\('
-                        '(?P<start>[^,\s]*)'
-                        '\s*(?:,\s*(?P<stop>[^,\s]*)'
-                        '\s*(?:,\s*(?P<step>[^,\s]*)\s*)?)?\)',
+                        r'(?P<start>[^,\s]*)'
+                        r'\s*(?:,\s*(?P<stop>[^,\s]*)'
+                        r'\s*(?:,\s*(?P<step>[^,\s]*)\s*)?)?\)',
                         v_)
                     if m:
                         start = (int(m.group('start'), 0)
@@ -163,8 +163,8 @@ class TestSuite:
             code_linenos = []
             for i, line in enumerate(f):
                 match = re.match(
-                    '(?P<case>\[\s*cases\s*\.\s*(?P<name>\w+)\s*\])'
-                        '|' '(?P<code>code\s*=)',
+                    r'(?P<case>\[\s*cases\s*\.\s*(?P<name>\w+)\s*\])'
+                        r'|' r'(?P<code>code\s*=)',
                     line)
                 if match and match.group('case'):
                     case_linenos.append((i+1, match.group('name')))
@@ -412,12 +412,15 @@ def compile(test_paths, **args):
                         f.writeln()
 
                 # create suite struct
-                #
+                f.writeln('#if defined(__APPLE__)')
+                f.writeln('__attribute__((section("__DATA,_test_suites")))')
+                f.writeln('#else')
                 # note we place this in the custom test_suites section with
                 # minimum alignment, otherwise GCC ups the alignment to
                 # 32-bytes for some reason
                 f.writeln('__attribute__((section("_test_suites"), '
                     'aligned(1)))')
+                f.writeln('#endif')
                 f.writeln('const struct test_suite __test__%s__suite = {'
                     % suite.name)
                 f.writeln(4*' '+'.name = "%s",' % suite.name)
@@ -602,9 +605,9 @@ def find_perms(runner_, ids=[], **args):
         errors='replace',
         close_fds=False)
     pattern = re.compile(
-        '^(?P<case>[^\s]+)'
-            '\s+(?P<flags>[^\s]+)'
-            '\s+(?P<filtered>\d+)/(?P<perms>\d+)')
+        r'^(?P<case>[^\s]+)'
+            r'\s+(?P<flags>[^\s]+)'
+            r'\s+(?P<filtered>\d+)/(?P<perms>\d+)')
     # skip the first line
     for line in it.islice(proc.stdout, 1, None):
         m = pattern.match(line)
@@ -632,8 +635,8 @@ def find_perms(runner_, ids=[], **args):
         errors='replace',
         close_fds=False)
     pattern = re.compile(
-        '^(?P<case>[^\s]+)'
-            '\s+(?P<path>[^:]+):(?P<lineno>\d+)')
+        r'^(?P<case>[^\s]+)'
+            r'\s+(?P<path>[^:]+):(?P<lineno>\d+)')
     # skip the first line
     for line in it.islice(proc.stdout, 1, None):
         m = pattern.match(line)
@@ -676,8 +679,8 @@ def find_path(runner_, id, **args):
         errors='replace',
         close_fds=False)
     pattern = re.compile(
-        '^(?P<case>[^\s]+)'
-            '\s+(?P<path>[^:]+):(?P<lineno>\d+)')
+        r'^(?P<case>[^\s]+)'
+            r'\s+(?P<path>[^:]+):(?P<lineno>\d+)')
     # skip the first line
     for line in it.islice(proc.stdout, 1, None):
         m = pattern.match(line)
@@ -706,7 +709,7 @@ def find_defines(runner_, id, **args):
         errors='replace',
         close_fds=False)
     defines = co.OrderedDict()
-    pattern = re.compile('^(?P<define>\w+)=(?P<value>.+)')
+    pattern = re.compile(r'^(?P<define>\w+)=(?P<value>.+)')
     for line in proc.stdout:
         m = pattern.match(line)
         if m:
@@ -781,12 +784,12 @@ def run_stage(name, runner_, ids, stdout_, trace_, output_, **args):
     failures = []
     killed = False
 
-    pattern = re.compile('^(?:'
-            '(?P<op>running|finished|skipped|powerloss) '
-                '(?P<id>(?P<case>[^:]+)[^\s]*)'
-            '|' '(?P<path>[^:]+):(?P<lineno>\d+):(?P<op_>assert):'
-                ' *(?P<message>.*)'
-        ')$')
+    pattern = re.compile(r'^(?:'
+            r'(?P<op>running|finished|skipped|powerloss) '
+                r'(?P<id>(?P<case>[^:]+)[^\s]*)'
+            r'|' r'(?P<path>[^:]+):(?P<lineno>\d+):(?P<op_>assert):'
+                r' *(?P<message>.*)'
+        r')$')
     locals = th.local()
     children = set()
 

+ 76 - 0
tests/test_dirs.toml

@@ -725,6 +725,82 @@ code = '''
     lfs_unmount(&lfs) => 0;
 '''
 
+[cases.test_dirs_remove_read]
+defines.N = 10
+if = 'N < BLOCK_COUNT/2'
+code = '''
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
+    lfs_mkdir(&lfs, "prickly-pear") => 0;
+    for (int i = 0; i < N; i++) {
+        char path[1024];
+        sprintf(path, "prickly-pear/cactus%03d", i);
+        lfs_mkdir(&lfs, path) => 0;
+    }
+    lfs_dir_t dir;
+    lfs_dir_open(&lfs, &dir, "prickly-pear") => 0;
+    struct lfs_info info;
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(info.type == LFS_TYPE_DIR);
+    assert(strcmp(info.name, ".") == 0);
+    lfs_dir_read(&lfs, &dir, &info) => 1;
+    assert(info.type == LFS_TYPE_DIR);
+    assert(strcmp(info.name, "..") == 0);
+    for (int i = 0; i < N; i++) {
+        char path[1024];
+        sprintf(path, "cactus%03d", i);
+        lfs_dir_read(&lfs, &dir, &info) => 1;
+        assert(info.type == LFS_TYPE_DIR);
+        assert(strcmp(info.name, path) == 0);
+    }
+    lfs_dir_read(&lfs, &dir, &info) => 0;
+    lfs_dir_close(&lfs, &dir) => 0;
+    lfs_unmount(&lfs);
+
+    for (lfs_size_t k = 0; k < N; k++) {
+        for (lfs_size_t j = 0; j < N; j++) {
+            lfs_mount(&lfs, cfg) => 0;
+            lfs_dir_open(&lfs, &dir, "prickly-pear") => 0;
+            lfs_dir_read(&lfs, &dir, &info) => 1;
+            assert(info.type == LFS_TYPE_DIR);
+            assert(strcmp(info.name, ".") == 0);
+            lfs_dir_read(&lfs, &dir, &info) => 1;
+            assert(info.type == LFS_TYPE_DIR);
+            assert(strcmp(info.name, "..") == 0);
+            // iterate over dirs < j
+            for (unsigned i = 0; i < j; i++) {
+                char path[1024];
+                sprintf(path, "cactus%03d", i);
+                lfs_dir_read(&lfs, &dir, &info) => 1;
+                assert(info.type == LFS_TYPE_DIR);
+                assert(strcmp(info.name, path) == 0);
+            }
+
+            // remove k while iterating
+            char path[1024];
+            sprintf(path, "prickly-pear/cactus%03d", k);
+            lfs_remove(&lfs, path) => 0;
+
+            // iterate over dirs >= j
+            for (unsigned i = j; i < ((k >= j) ? N-1 : N); i++) {
+                char path[1024];
+                sprintf(path, "cactus%03d", (k >= j && i >= k) ? i+1 : i);
+                lfs_dir_read(&lfs, &dir, &info) => 1;
+                assert(info.type == LFS_TYPE_DIR);
+                assert(strcmp(info.name, path) == 0);
+            }
+            lfs_dir_read(&lfs, &dir, &info) => 0;
+            lfs_dir_close(&lfs, &dir) => 0;
+
+            // recreate k
+            sprintf(path, "prickly-pear/cactus%03d", k);
+            lfs_mkdir(&lfs, path) => 0;
+            lfs_unmount(&lfs) => 0;
+        }
+    }
+'''
+
 [cases.test_dirs_other_errors]
 code = '''
     lfs_t lfs;

+ 68 - 1
tests/test_orphans.toml

@@ -207,7 +207,8 @@ code = '''
 [cases.test_orphans_reentrant]
 reentrant = true
 # TODO fix this case, caused by non-DAG trees
-if = '!(DEPTH == 3 && CACHE_SIZE != 64)'
+# NOTE the second condition is required
+if = '!(DEPTH == 3 && CACHE_SIZE != 64) && 2*FILES < BLOCK_COUNT'
 defines = [
     {FILES=6,  DEPTH=1, CYCLES=20},
     {FILES=26, DEPTH=1, CYCLES=20},
@@ -271,3 +272,69 @@ code = '''
     lfs_unmount(&lfs) => 0;
 '''
 
+# non-reentrant testing for orphans, this is the same as reentrant
+# testing, but we test way more states than we could under powerloss
+[cases.test_orphans_nonreentrant]
+# TODO fix this case, caused by non-DAG trees
+# NOTE the second condition is required
+if = '!(DEPTH == 3 && CACHE_SIZE != 64) && 2*FILES < BLOCK_COUNT'
+defines = [
+    {FILES=6,   DEPTH=1, CYCLES=2000},
+    {FILES=26,  DEPTH=1, CYCLES=2000},
+    {FILES=3,   DEPTH=3, CYCLES=2000},
+]
+code = '''
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
+
+    uint32_t prng = 1;
+    const char alpha[] = "abcdefghijklmnopqrstuvwxyz";
+    for (unsigned i = 0; i < CYCLES; i++) {
+        // create random path
+        char full_path[256];
+        for (unsigned d = 0; d < DEPTH; d++) {
+            sprintf(&full_path[2*d], "/%c", alpha[TEST_PRNG(&prng) % FILES]);
+        }
+
+        // if it does not exist, we create it, else we destroy
+        struct lfs_info info;
+        int res = lfs_stat(&lfs, full_path, &info);
+        if (res == LFS_ERR_NOENT) {
+            // create each directory in turn, ignore if dir already exists
+            for (unsigned d = 0; d < DEPTH; d++) {
+                char path[1024];
+                strcpy(path, full_path);
+                path[2*d+2] = '\0';
+                int err = lfs_mkdir(&lfs, path);
+                assert(!err || err == LFS_ERR_EXIST);
+            }
+
+            for (unsigned d = 0; d < DEPTH; d++) {
+                char path[1024];
+                strcpy(path, full_path);
+                path[2*d+2] = '\0';
+                lfs_stat(&lfs, path, &info) => 0;
+                assert(strcmp(info.name, &path[2*d+1]) == 0);
+                assert(info.type == LFS_TYPE_DIR);
+            }
+        } else {
+            // is valid dir?
+            assert(strcmp(info.name, &full_path[2*(DEPTH-1)+1]) == 0);
+            assert(info.type == LFS_TYPE_DIR);
+
+            // try to delete path in reverse order, ignore if dir is not empty
+            for (int d = DEPTH-1; d >= 0; d--) {
+                char path[1024];
+                strcpy(path, full_path);
+                path[2*d+2] = '\0';
+                int err = lfs_remove(&lfs, path);
+                assert(!err || err == LFS_ERR_NOTEMPTY);
+            }
+
+            lfs_stat(&lfs, full_path, &info) => LFS_ERR_NOENT;
+        }
+    }
+    lfs_unmount(&lfs) => 0;
+'''
+

+ 7269 - 207
tests/test_paths.toml

@@ -1,336 +1,7398 @@
 
 # simple path test
-[cases.test_paths_normal]
+[cases.test_paths_simple]
+defines.DIR = [false, true]
 code = '''
     lfs_t lfs;
     lfs_format(&lfs, cfg) => 0;
     lfs_mount(&lfs, cfg) => 0;
-    lfs_mkdir(&lfs, "tea") => 0;
-    lfs_mkdir(&lfs, "tea/hottea") => 0;
-    lfs_mkdir(&lfs, "tea/warmtea") => 0;
-    lfs_mkdir(&lfs, "tea/coldtea") => 0;
 
+    // create paths
+    lfs_mkdir(&lfs, "coffee") => 0;
+    if (DIR) {
+        lfs_mkdir(&lfs, "coffee/drip") => 0;
+        lfs_mkdir(&lfs, "coffee/coldbrew") => 0;
+        lfs_mkdir(&lfs, "coffee/turkish") => 0;
+        lfs_mkdir(&lfs, "coffee/tubruk") => 0;
+        lfs_mkdir(&lfs, "coffee/vietnamese") => 0;
+        lfs_mkdir(&lfs, "coffee/thai") => 0;
+    } else {
+        lfs_file_t file;
+        lfs_file_open(&lfs, &file, "coffee/drip",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "coffee/coldbrew",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "coffee/turkish",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "coffee/tubruk",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "coffee/vietnamese",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "coffee/thai",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+    }
+
+    // stat paths
     struct lfs_info info;
-    lfs_stat(&lfs, "tea/hottea", &info) => 0;
-    assert(strcmp(info.name, "hottea") == 0);
-    lfs_stat(&lfs, "/tea/hottea", &info) => 0;
-    assert(strcmp(info.name, "hottea") == 0);
+    lfs_stat(&lfs, "coffee/drip", &info) => 0;
+    assert(strcmp(info.name, "drip") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "coffee/coldbrew", &info) => 0;
+    assert(strcmp(info.name, "coldbrew") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "coffee/turkish", &info) => 0;
+    assert(strcmp(info.name, "turkish") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "coffee/tubruk", &info) => 0;
+    assert(strcmp(info.name, "tubruk") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "coffee/vietnamese", &info) => 0;
+    assert(strcmp(info.name, "vietnamese") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "coffee/thai", &info) => 0;
+    assert(strcmp(info.name, "thai") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+
+    // file open paths, only works on files!
+    if (DIR) {
+        lfs_file_t file;
+        lfs_file_open(&lfs, &file, "coffee/drip",
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "coffee/coldbrew",
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "coffee/turkish",
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "coffee/tubruk",
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "coffee/vietnamese",
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "coffee/thai",
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+
+        lfs_file_open(&lfs, &file, "coffee/drip",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "coffee/coldbrew",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "coffee/turkish",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "coffee/tubruk",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "coffee/vietnamese",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "coffee/thai",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+
+        lfs_file_open(&lfs, &file, "coffee/drip",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "coffee/coldbrew",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "coffee/turkish",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "coffee/tubruk",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "coffee/vietnamese",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "coffee/thai",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+    } else {
+        lfs_file_t file;
+        lfs_file_open(&lfs, &file, "coffee/drip",
+                LFS_O_RDONLY) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "coffee/coldbrew",
+                LFS_O_RDONLY) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "coffee/turkish",
+                LFS_O_RDONLY) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "coffee/tubruk",
+                LFS_O_RDONLY) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "coffee/vietnamese",
+                LFS_O_RDONLY) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "coffee/thai",
+                LFS_O_RDONLY) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+
+        lfs_file_open(&lfs, &file, "coffee/drip",
+                LFS_O_WRONLY | LFS_O_CREAT) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "coffee/coldbrew",
+                LFS_O_WRONLY | LFS_O_CREAT) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "coffee/turkish",
+                LFS_O_WRONLY | LFS_O_CREAT) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "coffee/tubruk",
+                LFS_O_WRONLY | LFS_O_CREAT) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "coffee/vietnamese",
+                LFS_O_WRONLY | LFS_O_CREAT) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "coffee/thai",
+                LFS_O_WRONLY | LFS_O_CREAT) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+
+        lfs_file_open(&lfs, &file, "coffee/drip",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "coffee/coldbrew",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "coffee/turkish",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "coffee/tubruk",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "coffee/vietnamese",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "coffee/thai",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+    }
+
+    // dir open paths, only works on dirs!
+    if (DIR) {
+        lfs_dir_t dir;
+        lfs_dir_open(&lfs, &dir, "coffee/drip") => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+        lfs_dir_open(&lfs, &dir, "coffee/coldbrew") => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+        lfs_dir_open(&lfs, &dir, "coffee/turkish") => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+        lfs_dir_open(&lfs, &dir, "coffee/tubruk") => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+        lfs_dir_open(&lfs, &dir, "coffee/vietnamese") => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+        lfs_dir_open(&lfs, &dir, "coffee/thai") => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+    } else {
+        lfs_dir_t dir;
+        lfs_dir_open(&lfs, &dir, "coffee/drip") => LFS_ERR_NOTDIR;
+        lfs_dir_open(&lfs, &dir, "coffee/coldbrew") => LFS_ERR_NOTDIR;
+        lfs_dir_open(&lfs, &dir, "coffee/turkish") => LFS_ERR_NOTDIR;
+        lfs_dir_open(&lfs, &dir, "coffee/tubruk") => LFS_ERR_NOTDIR;
+        lfs_dir_open(&lfs, &dir, "coffee/vietnamese") => LFS_ERR_NOTDIR;
+        lfs_dir_open(&lfs, &dir, "coffee/thai") => LFS_ERR_NOTDIR;
+    }
+
+    // rename paths
+    lfs_mkdir(&lfs, "espresso") => 0;
+    lfs_rename(&lfs,
+            "coffee/drip",
+            "espresso/espresso") => 0;
+    lfs_rename(&lfs,
+            "coffee/coldbrew",
+            "espresso/americano") => 0;
+    lfs_rename(&lfs,
+            "coffee/turkish",
+            "espresso/macchiato") => 0;
+    lfs_rename(&lfs,
+            "coffee/tubruk",
+            "espresso/latte") => 0;
+    lfs_rename(&lfs,
+            "coffee/vietnamese",
+            "espresso/cappuccino") => 0;
+    lfs_rename(&lfs,
+            "coffee/thai",
+            "espresso/mocha") => 0;
+
+    // stat paths
+    lfs_stat(&lfs, "espresso/espresso", &info) => 0;
+    assert(strcmp(info.name, "espresso") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "espresso/americano", &info) => 0;
+    assert(strcmp(info.name, "americano") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "espresso/macchiato", &info) => 0;
+    assert(strcmp(info.name, "macchiato") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "espresso/latte", &info) => 0;
+    assert(strcmp(info.name, "latte") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "espresso/cappuccino", &info) => 0;
+    assert(strcmp(info.name, "cappuccino") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "espresso/mocha", &info) => 0;
+    assert(strcmp(info.name, "mocha") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+
+    lfs_stat(&lfs, "coffee/drip", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "coffee/coldbrew", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "coffee/turkish", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "coffee/tubruk", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "coffee/vietnamese", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "coffee/thai", &info) => LFS_ERR_NOENT;
+
+    // remove paths
+    lfs_remove(&lfs, "espresso/espresso") => 0;
+    lfs_remove(&lfs, "espresso/americano") => 0;
+    lfs_remove(&lfs, "espresso/macchiato") => 0;
+    lfs_remove(&lfs, "espresso/latte") => 0;
+    lfs_remove(&lfs, "espresso/cappuccino") => 0;
+    lfs_remove(&lfs, "espresso/mocha") => 0;
+
+    // stat paths
+    lfs_stat(&lfs, "espresso/espresso", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "espresso/americano", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "espresso/macchiato", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "espresso/latte", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "espresso/cappuccino", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "espresso/mocha", &info) => LFS_ERR_NOENT;
 
-    lfs_mkdir(&lfs, "/milk") => 0;
-    lfs_stat(&lfs, "/milk", &info) => 0;
-    assert(strcmp(info.name, "milk") == 0);
-    lfs_stat(&lfs, "milk", &info) => 0;
-    assert(strcmp(info.name, "milk") == 0);
     lfs_unmount(&lfs) => 0;
 '''
 
-# redundant slashes
-[cases.test_paths_redundant_slashes]
+# absolute path test
+#
+# littlefs does not provide cd, so these are the same as relative paths
+[cases.test_paths_absolute]
+defines.DIR = [false, true]
 code = '''
     lfs_t lfs;
     lfs_format(&lfs, cfg) => 0;
     lfs_mount(&lfs, cfg) => 0;
-    lfs_mkdir(&lfs, "tea") => 0;
-    lfs_mkdir(&lfs, "tea/hottea") => 0;
-    lfs_mkdir(&lfs, "tea/warmtea") => 0;
-    lfs_mkdir(&lfs, "tea/coldtea") => 0;
 
+    // create paths
+    lfs_mkdir(&lfs, "coffee") => 0;
+    if (DIR) {
+        lfs_mkdir(&lfs, "/coffee/drip") => 0;
+        lfs_mkdir(&lfs, "/coffee/coldbrew") => 0;
+        lfs_mkdir(&lfs, "/coffee/turkish") => 0;
+        lfs_mkdir(&lfs, "/coffee/tubruk") => 0;
+        lfs_mkdir(&lfs, "/coffee/vietnamese") => 0;
+        lfs_mkdir(&lfs, "/coffee/thai") => 0;
+    } else {
+        lfs_file_t file;
+        lfs_file_open(&lfs, &file, "/coffee/drip",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "/coffee/coldbrew",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "/coffee/turkish",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "/coffee/tubruk",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "/coffee/vietnamese",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "/coffee/thai",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+    }
+
+    // stat paths
     struct lfs_info info;
-    lfs_stat(&lfs, "/tea/hottea", &info) => 0;
-    assert(strcmp(info.name, "hottea") == 0);
-    lfs_stat(&lfs, "//tea//hottea", &info) => 0;
-    assert(strcmp(info.name, "hottea") == 0);
-    lfs_stat(&lfs, "///tea///hottea", &info) => 0;
-    assert(strcmp(info.name, "hottea") == 0);
+    lfs_stat(&lfs, "/coffee/drip", &info) => 0;
+    assert(strcmp(info.name, "drip") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "coffee/coldbrew", &info) => 0;
+    assert(strcmp(info.name, "coldbrew") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "/coffee/turkish", &info) => 0;
+    assert(strcmp(info.name, "turkish") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "coffee/tubruk", &info) => 0;
+    assert(strcmp(info.name, "tubruk") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "/coffee/vietnamese", &info) => 0;
+    assert(strcmp(info.name, "vietnamese") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "coffee/thai", &info) => 0;
+    assert(strcmp(info.name, "thai") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+
+    // file open paths, only works on files!
+    if (DIR) {
+        lfs_file_t file;
+        lfs_file_open(&lfs, &file, "/coffee/drip",
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "/coffee/coldbrew",
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "/coffee/turkish",
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "/coffee/tubruk",
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "/coffee/vietnamese",
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "/coffee/thai",
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+
+        lfs_file_open(&lfs, &file, "/coffee/drip",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "/coffee/coldbrew",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "/coffee/turkish",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "/coffee/tubruk",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "/coffee/vietnamese",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "/coffee/thai",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+
+        lfs_file_open(&lfs, &file, "/coffee/drip",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "/coffee/coldbrew",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "/coffee/turkish",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "/coffee/tubruk",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "/coffee/vietnamese",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "/coffee/thai",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+    } else {
+        lfs_file_t file;
+        lfs_file_open(&lfs, &file, "/coffee/drip",
+                LFS_O_RDONLY) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "/coffee/coldbrew",
+                LFS_O_RDONLY) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "/coffee/turkish",
+                LFS_O_RDONLY) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "/coffee/tubruk",
+                LFS_O_RDONLY) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "/coffee/vietnamese",
+                LFS_O_RDONLY) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "/coffee/thai",
+                LFS_O_RDONLY) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+
+        lfs_file_open(&lfs, &file, "/coffee/drip",
+                LFS_O_WRONLY | LFS_O_CREAT) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "/coffee/coldbrew",
+                LFS_O_WRONLY | LFS_O_CREAT) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "/coffee/turkish",
+                LFS_O_WRONLY | LFS_O_CREAT) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "/coffee/tubruk",
+                LFS_O_WRONLY | LFS_O_CREAT) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "/coffee/vietnamese",
+                LFS_O_WRONLY | LFS_O_CREAT) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "/coffee/thai",
+                LFS_O_WRONLY | LFS_O_CREAT) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+
+        lfs_file_open(&lfs, &file, "/coffee/drip",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "/coffee/coldbrew",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "/coffee/turkish",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "/coffee/tubruk",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "/coffee/vietnamese",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "/coffee/thai",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+    }
+
+    // dir open paths, only works on dirs!
+    if (DIR) {
+        lfs_dir_t dir;
+        lfs_dir_open(&lfs, &dir, "/coffee/drip") => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+        lfs_dir_open(&lfs, &dir, "/coffee/coldbrew") => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+        lfs_dir_open(&lfs, &dir, "/coffee/turkish") => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+        lfs_dir_open(&lfs, &dir, "/coffee/tubruk") => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+        lfs_dir_open(&lfs, &dir, "/coffee/vietnamese") => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+        lfs_dir_open(&lfs, &dir, "/coffee/thai") => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+    } else {
+        lfs_dir_t dir;
+        lfs_dir_open(&lfs, &dir, "/coffee/drip") => LFS_ERR_NOTDIR;
+        lfs_dir_open(&lfs, &dir, "/coffee/coldbrew") => LFS_ERR_NOTDIR;
+        lfs_dir_open(&lfs, &dir, "/coffee/turkish") => LFS_ERR_NOTDIR;
+        lfs_dir_open(&lfs, &dir, "/coffee/tubruk") => LFS_ERR_NOTDIR;
+        lfs_dir_open(&lfs, &dir, "/coffee/vietnamese") => LFS_ERR_NOTDIR;
+        lfs_dir_open(&lfs, &dir, "/coffee/thai") => LFS_ERR_NOTDIR;
+    }
+
+    // rename paths
+    lfs_mkdir(&lfs, "espresso") => 0;
+    lfs_rename(&lfs,
+            "coffee/drip",
+            "/espresso/espresso") => 0;
+    lfs_rename(&lfs,
+            "coffee/coldbrew",
+            "/espresso/americano") => 0;
+    lfs_rename(&lfs,
+            "/coffee/turkish",
+            "espresso/macchiato") => 0;
+    lfs_rename(&lfs,
+            "/coffee/tubruk",
+            "espresso/latte") => 0;
+    lfs_rename(&lfs,
+            "/coffee/vietnamese",
+            "/espresso/cappuccino") => 0;
+    lfs_rename(&lfs,
+            "/coffee/thai",
+            "/espresso/mocha") => 0;
+
+    // stat paths
+    lfs_stat(&lfs, "/espresso/espresso", &info) => 0;
+    assert(strcmp(info.name, "espresso") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "espresso/americano", &info) => 0;
+    assert(strcmp(info.name, "americano") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "/espresso/macchiato", &info) => 0;
+    assert(strcmp(info.name, "macchiato") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "espresso/latte", &info) => 0;
+    assert(strcmp(info.name, "latte") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "/espresso/cappuccino", &info) => 0;
+    assert(strcmp(info.name, "cappuccino") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "espresso/mocha", &info) => 0;
+    assert(strcmp(info.name, "mocha") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+
+    lfs_stat(&lfs, "/coffee/drip", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "/coffee/coldbrew", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "/coffee/turkish", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "/coffee/tubruk", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "/coffee/vietnamese", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "/coffee/thai", &info) => LFS_ERR_NOENT;
+
+    // remove paths
+    lfs_remove(&lfs, "/espresso/espresso") => 0;
+    lfs_remove(&lfs, "/espresso/americano") => 0;
+    lfs_remove(&lfs, "/espresso/macchiato") => 0;
+    lfs_remove(&lfs, "/espresso/latte") => 0;
+    lfs_remove(&lfs, "/espresso/cappuccino") => 0;
+    lfs_remove(&lfs, "/espresso/mocha") => 0;
+
+    // stat paths
+    lfs_stat(&lfs, "/espresso/espresso", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "espresso/americano", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "/espresso/macchiato", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "espresso/latte", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "/espresso/cappuccino", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "espresso/mocha", &info) => LFS_ERR_NOENT;
 
-    lfs_mkdir(&lfs, "////milk") => 0;
-    lfs_stat(&lfs, "////milk", &info) => 0;
-    assert(strcmp(info.name, "milk") == 0);
-    lfs_stat(&lfs, "milk", &info) => 0;
-    assert(strcmp(info.name, "milk") == 0);
     lfs_unmount(&lfs) => 0;
 '''
 
-# dot path test
-[cases.test_paths_dot]
+# redundant slashes
+[cases.test_paths_redundant_slashes]
+defines.DIR = [false, true]
 code = '''
     lfs_t lfs;
     lfs_format(&lfs, cfg) => 0;
     lfs_mount(&lfs, cfg) => 0;
-    lfs_mkdir(&lfs, "tea") => 0;
-    lfs_mkdir(&lfs, "tea/hottea") => 0;
-    lfs_mkdir(&lfs, "tea/warmtea") => 0;
-    lfs_mkdir(&lfs, "tea/coldtea") => 0;
 
+    // create paths
+    lfs_mkdir(&lfs, "coffee") => 0;
+    if (DIR) {
+        lfs_mkdir(&lfs, "/coffee/drip") => 0;
+        lfs_mkdir(&lfs, "//coffee//coldbrew") => 0;
+        lfs_mkdir(&lfs, "///coffee///turkish") => 0;
+        lfs_mkdir(&lfs, "////coffee////tubruk") => 0;
+        lfs_mkdir(&lfs, "/////coffee/////vietnamese") => 0;
+        lfs_mkdir(&lfs, "//////coffee//////thai") => 0;
+    } else {
+        lfs_file_t file;
+        lfs_file_open(&lfs, &file, "/coffee/drip",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "//coffee//coldbrew",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "///coffee///turkish",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "////coffee////tubruk",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "/////coffee/////vietnamese",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "//////coffee//////thai",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+    }
+
+    // stat paths
     struct lfs_info info;
-    lfs_stat(&lfs, "./tea/hottea", &info) => 0;
-    assert(strcmp(info.name, "hottea") == 0);
-    lfs_stat(&lfs, "/./tea/hottea", &info) => 0;
-    assert(strcmp(info.name, "hottea") == 0);
-    lfs_stat(&lfs, "/././tea/hottea", &info) => 0;
-    assert(strcmp(info.name, "hottea") == 0);
-    lfs_stat(&lfs, "/./tea/./hottea", &info) => 0;
-    assert(strcmp(info.name, "hottea") == 0);
+    lfs_stat(&lfs, "//////coffee//////drip", &info) => 0;
+    assert(strcmp(info.name, "drip") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "/////coffee/////coldbrew", &info) => 0;
+    assert(strcmp(info.name, "coldbrew") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "////coffee////turkish", &info) => 0;
+    assert(strcmp(info.name, "turkish") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "///coffee///tubruk", &info) => 0;
+    assert(strcmp(info.name, "tubruk") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "//coffee//vietnamese", &info) => 0;
+    assert(strcmp(info.name, "vietnamese") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "/coffee/thai", &info) => 0;
+    assert(strcmp(info.name, "thai") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+
+    // file open paths, only works on files!
+    if (DIR) {
+        lfs_file_t file;
+        lfs_file_open(&lfs, &file, "/coffee/drip",
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "//coffee//coldbrew",
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "///coffee///turkish",
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "////coffee////tubruk",
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "/////coffee/////vietnamese",
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "//////coffee//////thai",
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+
+        lfs_file_open(&lfs, &file, "/coffee/drip",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "//coffee//coldbrew",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "///coffee///turkish",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "////coffee////tubruk",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "/////coffee/////vietnamese",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "//////coffee//////thai",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+
+        lfs_file_open(&lfs, &file, "/coffee/drip",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "//coffee//coldbrew",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "///coffee///turkish",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "////coffee////tubruk",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "/////coffee/////vietnamese",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "//////coffee//////thai",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+    } else {
+        lfs_file_t file;
+        lfs_file_open(&lfs, &file, "/coffee/drip",
+                LFS_O_RDONLY) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "//coffee//coldbrew",
+                LFS_O_RDONLY) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "///coffee///turkish",
+                LFS_O_RDONLY) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "////coffee////tubruk",
+                LFS_O_RDONLY) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "/////coffee/////vietnamese",
+                LFS_O_RDONLY) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "//////coffee//////thai",
+                LFS_O_RDONLY) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+
+        lfs_file_open(&lfs, &file, "/coffee/drip",
+                LFS_O_WRONLY | LFS_O_CREAT) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "//coffee//coldbrew",
+                LFS_O_WRONLY | LFS_O_CREAT) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "///coffee///turkish",
+                LFS_O_WRONLY | LFS_O_CREAT) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "////coffee////tubruk",
+                LFS_O_WRONLY | LFS_O_CREAT) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "/////coffee/////vietnamese",
+                LFS_O_WRONLY | LFS_O_CREAT) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "//////coffee//////thai",
+                LFS_O_WRONLY | LFS_O_CREAT) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+
+        lfs_file_open(&lfs, &file, "/coffee/drip",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "//coffee//coldbrew",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "///coffee///turkish",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "////coffee////tubruk",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "/////coffee/////vietnamese",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "//////coffee//////thai",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+    }
+
+    // dir open paths, only works on dirs!
+    if (DIR) {
+        lfs_dir_t dir;
+        lfs_dir_open(&lfs, &dir, "/coffee/drip") => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+        lfs_dir_open(&lfs, &dir, "//coffee//coldbrew") => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+        lfs_dir_open(&lfs, &dir, "///coffee///turkish") => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+        lfs_dir_open(&lfs, &dir, "////coffee////tubruk") => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+        lfs_dir_open(&lfs, &dir, "/////coffee/////vietnamese") => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+        lfs_dir_open(&lfs, &dir, "//////coffee//////thai") => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+    } else {
+        lfs_dir_t dir;
+        lfs_dir_open(&lfs, &dir, "/coffee/drip") => LFS_ERR_NOTDIR;
+        lfs_dir_open(&lfs, &dir, "//coffee//coldbrew") => LFS_ERR_NOTDIR;
+        lfs_dir_open(&lfs, &dir, "///coffee///turkish") => LFS_ERR_NOTDIR;
+        lfs_dir_open(&lfs, &dir, "////coffee////tubruk") => LFS_ERR_NOTDIR;
+        lfs_dir_open(&lfs, &dir, "/////coffee/////vietnamese") => LFS_ERR_NOTDIR;
+        lfs_dir_open(&lfs, &dir, "//////coffee//////thai") => LFS_ERR_NOTDIR;
+    }
+
+    // rename paths
+    lfs_mkdir(&lfs, "espresso") => 0;
+    lfs_rename(&lfs,
+            "//////coffee//////drip",
+            "/espresso/espresso") => 0;
+    lfs_rename(&lfs,
+            "/////coffee/////coldbrew",
+            "//espresso//americano") => 0;
+    lfs_rename(&lfs,
+            "////coffee////turkish",
+            "///espresso///macchiato") => 0;
+    lfs_rename(&lfs,
+            "///coffee///tubruk",
+            "////espresso////latte") => 0;
+    lfs_rename(&lfs,
+            "//coffee//vietnamese",
+            "/////espresso/////cappuccino") => 0;
+    lfs_rename(&lfs,
+            "/coffee/thai",
+            "//////espresso//////mocha") => 0;
+
+    // stat paths
+    lfs_stat(&lfs, "//////espresso//////espresso", &info) => 0;
+    assert(strcmp(info.name, "espresso") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "/////espresso/////americano", &info) => 0;
+    assert(strcmp(info.name, "americano") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "////espresso////macchiato", &info) => 0;
+    assert(strcmp(info.name, "macchiato") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "///espresso///latte", &info) => 0;
+    assert(strcmp(info.name, "latte") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "//espresso//cappuccino", &info) => 0;
+    assert(strcmp(info.name, "cappuccino") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "/espresso/mocha", &info) => 0;
+    assert(strcmp(info.name, "mocha") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+
+    lfs_stat(&lfs, "//////coffee//////drip", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "/////coffee/////coldbrew", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "////coffee////turkish", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "///coffee///tubruk", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "//coffee//vietnamese", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "/coffee/thai", &info) => LFS_ERR_NOENT;
+
+    // remove paths
+    lfs_remove(&lfs, "/espresso/espresso") => 0;
+    lfs_remove(&lfs, "//espresso//americano") => 0;
+    lfs_remove(&lfs, "///espresso///macchiato") => 0;
+    lfs_remove(&lfs, "////espresso////latte") => 0;
+    lfs_remove(&lfs, "/////espresso/////cappuccino") => 0;
+    lfs_remove(&lfs, "//////espresso//////mocha") => 0;
+
+    // stat paths
+    lfs_stat(&lfs, "//////espresso//////espresso", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "/////espresso/////americano", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "////espresso////macchiato", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "///espresso///latte", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "//espresso//cappuccino", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "/espresso/mocha", &info) => LFS_ERR_NOENT;
 
-    lfs_mkdir(&lfs, "/./milk") => 0;
-    lfs_stat(&lfs, "/./milk", &info) => 0;
-    assert(strcmp(info.name, "milk") == 0);
-    lfs_stat(&lfs, "milk", &info) => 0;
-    assert(strcmp(info.name, "milk") == 0);
     lfs_unmount(&lfs) => 0;
 '''
 
-# dot dot path test
-[cases.test_paths_dot_dot]
+# test trailing slashes
+#
+# trailing slashes are only allowed on directories
+[cases.test_paths_trailing_slashes]
+defines.DIR = [false, true]
 code = '''
     lfs_t lfs;
     lfs_format(&lfs, cfg) => 0;
     lfs_mount(&lfs, cfg) => 0;
-    lfs_mkdir(&lfs, "tea") => 0;
-    lfs_mkdir(&lfs, "tea/hottea") => 0;
-    lfs_mkdir(&lfs, "tea/warmtea") => 0;
-    lfs_mkdir(&lfs, "tea/coldtea") => 0;
+
+    // create paths
     lfs_mkdir(&lfs, "coffee") => 0;
-    lfs_mkdir(&lfs, "coffee/hotcoffee") => 0;
-    lfs_mkdir(&lfs, "coffee/warmcoffee") => 0;
-    lfs_mkdir(&lfs, "coffee/coldcoffee") => 0;
+    if (DIR) {
+        lfs_mkdir(&lfs, "coffee/drip/") => 0;
+        lfs_mkdir(&lfs, "coffee/coldbrew//") => 0;
+        lfs_mkdir(&lfs, "coffee/turkish///") => 0;
+        lfs_mkdir(&lfs, "coffee/tubruk////") => 0;
+        lfs_mkdir(&lfs, "coffee/vietnamese/////") => 0;
+        lfs_mkdir(&lfs, "coffee/thai//////") => 0;
+    } else {
+        lfs_file_t file;
+        lfs_file_open(&lfs, &file, "coffee/drip/",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_NOTDIR;
+        lfs_file_open(&lfs, &file, "coffee/coldbrew//",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_NOTDIR;
+        lfs_file_open(&lfs, &file, "coffee/turkish///",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_NOTDIR;
+        lfs_file_open(&lfs, &file, "coffee/tubruk////",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_NOTDIR;
+        lfs_file_open(&lfs, &file, "coffee/vietnamese/////",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_NOTDIR;
+        lfs_file_open(&lfs, &file, "coffee/thai//////",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_NOTDIR;
 
+        // still create so we have something to test
+        lfs_file_open(&lfs, &file, "coffee/drip",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "coffee/coldbrew",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "coffee/turkish",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "coffee/tubruk",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "coffee/vietnamese",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "coffee/thai",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+    }
+
+    // stat paths
     struct lfs_info info;
-    lfs_stat(&lfs, "coffee/../tea/hottea", &info) => 0;
-    assert(strcmp(info.name, "hottea") == 0);
-    lfs_stat(&lfs, "tea/coldtea/../hottea", &info) => 0;
-    assert(strcmp(info.name, "hottea") == 0);
-    lfs_stat(&lfs, "coffee/coldcoffee/../../tea/hottea", &info) => 0;
-    assert(strcmp(info.name, "hottea") == 0);
-    lfs_stat(&lfs, "coffee/../coffee/../tea/hottea", &info) => 0;
-    assert(strcmp(info.name, "hottea") == 0);
+    if (DIR) {
+        lfs_stat(&lfs, "coffee/drip//////", &info) => 0;
+        assert(strcmp(info.name, "drip") == 0);
+        assert(info.type == LFS_TYPE_DIR);
+        lfs_stat(&lfs, "coffee/coldbrew/////", &info) => 0;
+        assert(strcmp(info.name, "coldbrew") == 0);
+        assert(info.type == LFS_TYPE_DIR);
+        lfs_stat(&lfs, "coffee/turkish////", &info) => 0;
+        assert(strcmp(info.name, "turkish") == 0);
+        assert(info.type == LFS_TYPE_DIR);
+        lfs_stat(&lfs, "coffee/tubruk///", &info) => 0;
+        assert(strcmp(info.name, "tubruk") == 0);
+        assert(info.type == LFS_TYPE_DIR);
+        lfs_stat(&lfs, "coffee/vietnamese//", &info) => 0;
+        assert(strcmp(info.name, "vietnamese") == 0);
+        assert(info.type == LFS_TYPE_DIR);
+        lfs_stat(&lfs, "coffee/thai/", &info) => 0;
+        assert(strcmp(info.name, "thai") == 0);
+        assert(info.type == LFS_TYPE_DIR);
+    } else {
+        lfs_stat(&lfs, "coffee/drip//////", &info) => LFS_ERR_NOTDIR;
+        lfs_stat(&lfs, "coffee/coldbrew/////", &info) => LFS_ERR_NOTDIR;
+        lfs_stat(&lfs, "coffee/turkish////", &info) => LFS_ERR_NOTDIR;
+        lfs_stat(&lfs, "coffee/tubruk///", &info) => LFS_ERR_NOTDIR;
+        lfs_stat(&lfs, "coffee/vietnamese//", &info) => LFS_ERR_NOTDIR;
+        lfs_stat(&lfs, "coffee/thai/", &info) => LFS_ERR_NOTDIR;
+    }
+
+    // file open paths, only works on files!
+    if (DIR) {
+        lfs_file_t file;
+        lfs_file_open(&lfs, &file, "coffee/drip/",
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "coffee/coldbrew//",
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "coffee/turkish///",
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "coffee/tubruk////",
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "coffee/vietnamese/////",
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "coffee/thai//////",
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+
+        lfs_file_open(&lfs, &file, "coffee/drip/",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "coffee/coldbrew//",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "coffee/turkish///",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "coffee/tubruk////",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "coffee/vietnamese/////",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "coffee/thai//////",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+
+        lfs_file_open(&lfs, &file, "coffee/drip/",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "coffee/coldbrew//",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "coffee/turkish///",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "coffee/tubruk////",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "coffee/vietnamese/////",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "coffee/thai//////",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+    } else {
+        lfs_file_t file;
+        lfs_file_open(&lfs, &file, "coffee/drip/",
+                LFS_O_RDONLY) => LFS_ERR_NOTDIR;
+        lfs_file_open(&lfs, &file, "coffee/coldbrew//",
+                LFS_O_RDONLY) => LFS_ERR_NOTDIR;
+        lfs_file_open(&lfs, &file, "coffee/turkish///",
+                LFS_O_RDONLY) => LFS_ERR_NOTDIR;
+        lfs_file_open(&lfs, &file, "coffee/tubruk////",
+                LFS_O_RDONLY) => LFS_ERR_NOTDIR;
+        lfs_file_open(&lfs, &file, "coffee/vietnamese/////",
+                LFS_O_RDONLY) => LFS_ERR_NOTDIR;
+        lfs_file_open(&lfs, &file, "coffee/thai//////",
+                LFS_O_RDONLY) => LFS_ERR_NOTDIR;
+
+        lfs_file_open(&lfs, &file, "coffee/drip/",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_NOTDIR;
+        lfs_file_open(&lfs, &file, "coffee/coldbrew//",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_NOTDIR;
+        lfs_file_open(&lfs, &file, "coffee/turkish///",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_NOTDIR;
+        lfs_file_open(&lfs, &file, "coffee/tubruk////",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_NOTDIR;
+        lfs_file_open(&lfs, &file, "coffee/vietnamese/////",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_NOTDIR;
+        lfs_file_open(&lfs, &file, "coffee/thai//////",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_NOTDIR;
+
+        lfs_file_open(&lfs, &file, "coffee/drip/",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_NOTDIR;
+        lfs_file_open(&lfs, &file, "coffee/coldbrew//",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_NOTDIR;
+        lfs_file_open(&lfs, &file, "coffee/turkish///",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_NOTDIR;
+        lfs_file_open(&lfs, &file, "coffee/tubruk////",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_NOTDIR;
+        lfs_file_open(&lfs, &file, "coffee/vietnamese/////",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_NOTDIR;
+        lfs_file_open(&lfs, &file, "coffee/thai//////",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_NOTDIR;
+    }
+
+    // dir open paths, only works on dirs!
+    if (DIR) {
+        lfs_dir_t dir;
+        lfs_dir_open(&lfs, &dir, "coffee/drip/") => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+        lfs_dir_open(&lfs, &dir, "coffee/coldbrew//") => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+        lfs_dir_open(&lfs, &dir, "coffee/turkish///") => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+        lfs_dir_open(&lfs, &dir, "coffee/tubruk////") => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+        lfs_dir_open(&lfs, &dir, "coffee/vietnamese/////") => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+        lfs_dir_open(&lfs, &dir, "coffee/thai//////") => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+    } else {
+        lfs_dir_t dir;
+        lfs_dir_open(&lfs, &dir, "coffee/drip/") => LFS_ERR_NOTDIR;
+        lfs_dir_open(&lfs, &dir, "coffee/coldbrew//") => LFS_ERR_NOTDIR;
+        lfs_dir_open(&lfs, &dir, "coffee/turkish///") => LFS_ERR_NOTDIR;
+        lfs_dir_open(&lfs, &dir, "coffee/tubruk////") => LFS_ERR_NOTDIR;
+        lfs_dir_open(&lfs, &dir, "coffee/vietnamese/////") => LFS_ERR_NOTDIR;
+        lfs_dir_open(&lfs, &dir, "coffee/thai//////") => LFS_ERR_NOTDIR;
+    }
+
+    // rename paths
+    lfs_mkdir(&lfs, "espresso") => 0;
+    if (DIR) {
+        lfs_rename(&lfs,
+                "coffee/drip//////",
+                "espresso/espresso/") => 0;
+        lfs_rename(&lfs,
+                "coffee/coldbrew/////",
+                "espresso/americano//") => 0;
+        lfs_rename(&lfs,
+                "coffee/turkish////",
+                "espresso/macchiato///") => 0;
+        lfs_rename(&lfs,
+                "coffee/tubruk///",
+                "espresso/latte////") => 0;
+        lfs_rename(&lfs,
+                "coffee/vietnamese//",
+                "espresso/cappuccino/////") => 0;
+        lfs_rename(&lfs,
+                "coffee/thai/",
+                "espresso/mocha//////") => 0;
+
+        // stat paths
+        lfs_stat(&lfs, "espresso/espresso//////", &info) => 0;
+        assert(strcmp(info.name, "espresso") == 0);
+        assert(info.type == LFS_TYPE_DIR);
+        lfs_stat(&lfs, "espresso/americano/////", &info) => 0;
+        assert(strcmp(info.name, "americano") == 0);
+        assert(info.type == LFS_TYPE_DIR);
+        lfs_stat(&lfs, "espresso/macchiato////", &info) => 0;
+        assert(strcmp(info.name, "macchiato") == 0);
+        assert(info.type == LFS_TYPE_DIR);
+        lfs_stat(&lfs, "espresso/latte///", &info) => 0;
+        assert(strcmp(info.name, "latte") == 0);
+        assert(info.type == LFS_TYPE_DIR);
+        lfs_stat(&lfs, "espresso/cappuccino//", &info) => 0;
+        assert(strcmp(info.name, "cappuccino") == 0);
+        assert(info.type == LFS_TYPE_DIR);
+        lfs_stat(&lfs, "espresso/mocha/", &info) => 0;
+        assert(strcmp(info.name, "mocha") == 0);
+        assert(info.type == LFS_TYPE_DIR);
+
+        lfs_stat(&lfs, "coffee/drip//////", &info) => LFS_ERR_NOENT;
+        lfs_stat(&lfs, "coffee/coldbrew/////", &info) => LFS_ERR_NOENT;
+        lfs_stat(&lfs, "coffee/turkish////", &info) => LFS_ERR_NOENT;
+        lfs_stat(&lfs, "coffee/tubruk///", &info) => LFS_ERR_NOENT;
+        lfs_stat(&lfs, "coffee/vietnamese//", &info) => LFS_ERR_NOENT;
+        lfs_stat(&lfs, "coffee/thai/", &info) => LFS_ERR_NOENT;
+
+        // remove paths
+        lfs_remove(&lfs, "espresso/espresso/") => 0;
+        lfs_remove(&lfs, "espresso/americano//") => 0;
+        lfs_remove(&lfs, "espresso/macchiato///") => 0;
+        lfs_remove(&lfs, "espresso/latte////") => 0;
+        lfs_remove(&lfs, "espresso/cappuccino/////") => 0;
+        lfs_remove(&lfs, "espresso/mocha//////") => 0;
+
+        // stat paths
+        lfs_stat(&lfs, "espresso/espresso//////", &info) => LFS_ERR_NOENT;
+        lfs_stat(&lfs, "espresso/americano/////", &info) => LFS_ERR_NOENT;
+        lfs_stat(&lfs, "espresso/macchiato////", &info) => LFS_ERR_NOENT;
+        lfs_stat(&lfs, "espresso/latte///", &info) => LFS_ERR_NOENT;
+        lfs_stat(&lfs, "espresso/cappuccino//", &info) => LFS_ERR_NOENT;
+        lfs_stat(&lfs, "espresso/mocha/", &info) => LFS_ERR_NOENT;
+
+    } else {
+        // bad source
+        lfs_rename(&lfs,
+                "coffee/drip//////",
+                "espresso/espresso") => LFS_ERR_NOTDIR;
+        lfs_rename(&lfs,
+                "coffee/coldbrew/////",
+                "espresso/americano") => LFS_ERR_NOTDIR;
+        lfs_rename(&lfs,
+                "coffee/turkish////",
+                "espresso/macchiato") => LFS_ERR_NOTDIR;
+        lfs_rename(&lfs,
+                "coffee/tubruk///",
+                "espresso/latte") => LFS_ERR_NOTDIR;
+        lfs_rename(&lfs,
+                "coffee/vietnamese//",
+                "espresso/cappuccino") => LFS_ERR_NOTDIR;
+        lfs_rename(&lfs,
+                "coffee/thai/",
+                "espresso/mocha") => LFS_ERR_NOTDIR;
+
+        // bad destination
+        lfs_rename(&lfs,
+                "coffee/drip",
+                "espresso/espresso/") => LFS_ERR_NOTDIR;
+        lfs_rename(&lfs,
+                "coffee/coldbrew",
+                "espresso/americano//") => LFS_ERR_NOTDIR;
+        lfs_rename(&lfs,
+                "coffee/turkish",
+                "espresso/macchiato///") => LFS_ERR_NOTDIR;
+        lfs_rename(&lfs,
+                "coffee/tubruk",
+                "espresso/latte////") => LFS_ERR_NOTDIR;
+        lfs_rename(&lfs,
+                "coffee/vietnamese",
+                "espresso/cappuccino/////") => LFS_ERR_NOTDIR;
+        lfs_rename(&lfs,
+                "coffee/thai",
+                "espresso/mocha//////") => LFS_ERR_NOTDIR;
+
+        // bad source and bad destination
+        lfs_rename(&lfs,
+                "coffee/drip//////",
+                "espresso/espresso/") => LFS_ERR_NOTDIR;
+        lfs_rename(&lfs,
+                "coffee/coldbrew/////",
+                "espresso/americano//") => LFS_ERR_NOTDIR;
+        lfs_rename(&lfs,
+                "coffee/turkish////",
+                "espresso/macchiato///") => LFS_ERR_NOTDIR;
+        lfs_rename(&lfs,
+                "coffee/tubruk///",
+                "espresso/latte////") => LFS_ERR_NOTDIR;
+        lfs_rename(&lfs,
+                "coffee/vietnamese//",
+                "espresso/cappuccino/////") => LFS_ERR_NOTDIR;
+        lfs_rename(&lfs,
+                "coffee/thai/",
+                "espresso/mocha//////") => LFS_ERR_NOTDIR;
+
+        // remove paths
+        lfs_remove(&lfs, "coffee/drip/") => LFS_ERR_NOTDIR;
+        lfs_remove(&lfs, "coffee/coldbrew//") => LFS_ERR_NOTDIR;
+        lfs_remove(&lfs, "coffee/turkish///") => LFS_ERR_NOTDIR;
+        lfs_remove(&lfs, "coffee/tubruk////") => LFS_ERR_NOTDIR;
+        lfs_remove(&lfs, "coffee/vietnamese/////") => LFS_ERR_NOTDIR;
+        lfs_remove(&lfs, "coffee/thai//////") => LFS_ERR_NOTDIR;
+
+        // stat paths
+        lfs_stat(&lfs, "espresso/espresso", &info) => LFS_ERR_NOENT;
+        lfs_stat(&lfs, "espresso/americano", &info) => LFS_ERR_NOENT;
+        lfs_stat(&lfs, "espresso/macchiato", &info) => LFS_ERR_NOENT;
+        lfs_stat(&lfs, "espresso/latte", &info) => LFS_ERR_NOENT;
+        lfs_stat(&lfs, "espresso/cappuccino", &info) => LFS_ERR_NOENT;
+        lfs_stat(&lfs, "espresso/mocha", &info) => LFS_ERR_NOENT;
+
+        lfs_stat(&lfs, "coffee/drip", &info) => 0;
+        assert(strcmp(info.name, "drip") == 0);
+        assert(info.type == LFS_TYPE_REG);
+        lfs_stat(&lfs, "coffee/coldbrew", &info) => 0;
+        assert(strcmp(info.name, "coldbrew") == 0);
+        assert(info.type == LFS_TYPE_REG);
+        lfs_stat(&lfs, "coffee/turkish", &info) => 0;
+        assert(strcmp(info.name, "turkish") == 0);
+        assert(info.type == LFS_TYPE_REG);
+        lfs_stat(&lfs, "coffee/tubruk", &info) => 0;
+        assert(strcmp(info.name, "tubruk") == 0);
+        assert(info.type == LFS_TYPE_REG);
+        lfs_stat(&lfs, "coffee/vietnamese", &info) => 0;
+        assert(strcmp(info.name, "vietnamese") == 0);
+        assert(info.type == LFS_TYPE_REG);
+        lfs_stat(&lfs, "coffee/thai", &info) => 0;
+        assert(strcmp(info.name, "thai") == 0);
+        assert(info.type == LFS_TYPE_REG);
+    }
 
-    lfs_mkdir(&lfs, "coffee/../milk") => 0;
-    lfs_stat(&lfs, "coffee/../milk", &info) => 0;
-    strcmp(info.name, "milk") => 0;
-    lfs_stat(&lfs, "milk", &info) => 0;
-    strcmp(info.name, "milk") => 0;
     lfs_unmount(&lfs) => 0;
 '''
 
-# trailing dot path test
-[cases.test_paths_trailing_dot]
+# dot path tests
+[cases.test_paths_dots]
+defines.DIR = [false, true]
 code = '''
     lfs_t lfs;
     lfs_format(&lfs, cfg) => 0;
     lfs_mount(&lfs, cfg) => 0;
-    lfs_mkdir(&lfs, "tea") => 0;
-    lfs_mkdir(&lfs, "tea/hottea") => 0;
-    lfs_mkdir(&lfs, "tea/warmtea") => 0;
-    lfs_mkdir(&lfs, "tea/coldtea") => 0;
 
+    // create paths
+    lfs_mkdir(&lfs, "coffee") => 0;
+    if (DIR) {
+        lfs_mkdir(&lfs, "/coffee/drip") => 0;
+        lfs_mkdir(&lfs, "/./coffee/./coldbrew") => 0;
+        lfs_mkdir(&lfs, "/././coffee/././turkish") => 0;
+        lfs_mkdir(&lfs, "/./././coffee/./././tubruk") => 0;
+        lfs_mkdir(&lfs, "/././././coffee/././././vietnamese") => 0;
+        lfs_mkdir(&lfs, "/./././././coffee/./././././thai") => 0;
+    } else {
+        lfs_file_t file;
+        lfs_file_open(&lfs, &file, "/coffee/drip",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "/./coffee/./coldbrew",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "/././coffee/././turkish",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "/./././coffee/./././tubruk",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "/././././coffee/././././vietnamese",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "/./././././coffee/./././././thai",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+    }
+
+    // stat paths
     struct lfs_info info;
-    lfs_stat(&lfs, "tea/hottea/", &info) => 0;
-    assert(strcmp(info.name, "hottea") == 0);
-    lfs_stat(&lfs, "tea/hottea/.", &info) => 0;
-    assert(strcmp(info.name, "hottea") == 0);
-    lfs_stat(&lfs, "tea/hottea/./.", &info) => 0;
-    assert(strcmp(info.name, "hottea") == 0);
-    lfs_stat(&lfs, "tea/hottea/..", &info) => 0;
-    assert(strcmp(info.name, "tea") == 0);
-    lfs_stat(&lfs, "tea/hottea/../.", &info) => 0;
-    assert(strcmp(info.name, "tea") == 0);
+    lfs_stat(&lfs, "/./././././coffee/./././././drip", &info) => 0;
+    assert(strcmp(info.name, "drip") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "/././././coffee/././././coldbrew", &info) => 0;
+    assert(strcmp(info.name, "coldbrew") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "/./././coffee/./././turkish", &info) => 0;
+    assert(strcmp(info.name, "turkish") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "/././coffee/././tubruk", &info) => 0;
+    assert(strcmp(info.name, "tubruk") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "/./coffee/./vietnamese", &info) => 0;
+    assert(strcmp(info.name, "vietnamese") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "/coffee/thai", &info) => 0;
+    assert(strcmp(info.name, "thai") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+
+    // file open paths, only works on files!
+    if (DIR) {
+        lfs_file_t file;
+        lfs_file_open(&lfs, &file, "/coffee/drip",
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "/./coffee/./coldbrew",
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "/././coffee/././turkish",
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "/./././coffee/./././tubruk",
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "/././././coffee/././././vietnamese",
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "/./././././coffee/./././././thai",
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+
+        lfs_file_open(&lfs, &file, "/coffee/drip",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "/./coffee/./coldbrew",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "/././coffee/././turkish",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "/./././coffee/./././tubruk",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "/././././coffee/././././vietnamese",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "/./././././coffee/./././././thai",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+
+        lfs_file_open(&lfs, &file, "/coffee/drip",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "/./coffee/./coldbrew",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "/././coffee/././turkish",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "/./././coffee/./././tubruk",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "/././././coffee/././././vietnamese",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "/./././././coffee/./././././thai",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+    } else {
+        lfs_file_t file;
+        lfs_file_open(&lfs, &file, "/coffee/drip",
+                LFS_O_RDONLY) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "/./coffee/./coldbrew",
+                LFS_O_RDONLY) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "/././coffee/././turkish",
+                LFS_O_RDONLY) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "/./././coffee/./././tubruk",
+                LFS_O_RDONLY) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "/././././coffee/././././vietnamese",
+                LFS_O_RDONLY) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "/./././././coffee/./././././thai",
+                LFS_O_RDONLY) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+
+        lfs_file_open(&lfs, &file, "/coffee/drip",
+                LFS_O_WRONLY | LFS_O_CREAT) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "/./coffee/./coldbrew",
+                LFS_O_WRONLY | LFS_O_CREAT) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "/././coffee/././turkish",
+                LFS_O_WRONLY | LFS_O_CREAT) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "/./././coffee/./././tubruk",
+                LFS_O_WRONLY | LFS_O_CREAT) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "/././././coffee/././././vietnamese",
+                LFS_O_WRONLY | LFS_O_CREAT) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "/./././././coffee/./././././thai",
+                LFS_O_WRONLY | LFS_O_CREAT) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+
+        lfs_file_open(&lfs, &file, "/coffee/drip",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "/./coffee/./coldbrew",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "/././coffee/././turkish",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "/./././coffee/./././tubruk",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "/././././coffee/././././vietnamese",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "/./././././coffee/./././././thai",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+    }
+
+    // dir open paths, only works on dirs!
+    if (DIR) {
+        lfs_dir_t dir;
+        lfs_dir_open(&lfs, &dir, "/coffee/drip") => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+        lfs_dir_open(&lfs, &dir, "/./coffee/./coldbrew") => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+        lfs_dir_open(&lfs, &dir, "/././coffee/././turkish") => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+        lfs_dir_open(&lfs, &dir, "/./././coffee/./././tubruk") => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+        lfs_dir_open(&lfs, &dir, "/././././coffee/././././vietnamese") => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+        lfs_dir_open(&lfs, &dir, "/./././././coffee/./././././thai") => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+    } else {
+        lfs_dir_t dir;
+        lfs_dir_open(&lfs, &dir, "/coffee/drip") => LFS_ERR_NOTDIR;
+        lfs_dir_open(&lfs, &dir, "/./coffee/./coldbrew") => LFS_ERR_NOTDIR;
+        lfs_dir_open(&lfs, &dir, "/././coffee/././turkish") => LFS_ERR_NOTDIR;
+        lfs_dir_open(&lfs, &dir, "/./././coffee/./././tubruk") => LFS_ERR_NOTDIR;
+        lfs_dir_open(&lfs, &dir, "/././././coffee/././././vietnamese") => LFS_ERR_NOTDIR;
+        lfs_dir_open(&lfs, &dir, "/./././././coffee/./././././thai") => LFS_ERR_NOTDIR;
+    }
+
+    // rename paths
+    lfs_mkdir(&lfs, "espresso") => 0;
+    lfs_rename(&lfs,
+            "/./././././coffee/./././././drip",
+            "/espresso/espresso") => 0;
+    lfs_rename(&lfs,
+            "/././././coffee/././././coldbrew",
+            "/./espresso/./americano") => 0;
+    lfs_rename(&lfs,
+            "/./././coffee/./././turkish",
+            "/././espresso/././macchiato") => 0;
+    lfs_rename(&lfs,
+            "/././coffee/././tubruk",
+            "/./././espresso/./././latte") => 0;
+    lfs_rename(&lfs,
+            "/./coffee/./vietnamese",
+            "/././././espresso/././././cappuccino") => 0;
+    lfs_rename(&lfs,
+            "/coffee/thai",
+            "/./././././espresso/./././././mocha") => 0;
+
+    // stat paths
+    lfs_stat(&lfs, "/./././././espresso/./././././espresso", &info) => 0;
+    assert(strcmp(info.name, "espresso") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "/././././espresso/././././americano", &info) => 0;
+    assert(strcmp(info.name, "americano") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "/./././espresso/./././macchiato", &info) => 0;
+    assert(strcmp(info.name, "macchiato") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "/././espresso/././latte", &info) => 0;
+    assert(strcmp(info.name, "latte") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "/./espresso/./cappuccino", &info) => 0;
+    assert(strcmp(info.name, "cappuccino") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "/espresso/mocha", &info) => 0;
+    assert(strcmp(info.name, "mocha") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+
+    lfs_stat(&lfs, "/./././././coffee/./././././drip", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "/././././coffee/././././coldbrew", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "/./././coffee/./././turkish", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "/././coffee/././tubruk", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "/./coffee/./vietnamese", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "/coffee/thai", &info) => LFS_ERR_NOENT;
+
+    // remove paths
+    lfs_remove(&lfs, "/espresso/espresso") => 0;
+    lfs_remove(&lfs, "/./espresso/./americano") => 0;
+    lfs_remove(&lfs, "/././espresso/././macchiato") => 0;
+    lfs_remove(&lfs, "/./././espresso/./././latte") => 0;
+    lfs_remove(&lfs, "/././././espresso/././././cappuccino") => 0;
+    lfs_remove(&lfs, "/./././././espresso/./././././mocha") => 0;
+
+    // stat paths
+    lfs_stat(&lfs, "/./././././espresso/./././././espresso", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "/././././espresso/././././americano", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "/./././espresso/./././macchiato", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "/././espresso/././latte", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "/./espresso/./cappuccino", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "/espresso/mocha", &info) => LFS_ERR_NOENT;
+
     lfs_unmount(&lfs) => 0;
 '''
 
-# leading dot path test
-[cases.test_paths_leading_dot]
+# test trailing dots, these get a bit weird
+#
+# POSIX deviations:
+#
+# - We accept modifications of directories with trailing dots:
+#   - littlefs: remove("a/.") => 0
+#   - POSIX:    remove("a/.") => EBUSY
+#   Reason: Not worth implementing.
+#
+[cases.test_paths_trailing_dots]
+defines.DIR = [false, true]
 code = '''
     lfs_t lfs;
     lfs_format(&lfs, cfg) => 0;
     lfs_mount(&lfs, cfg) => 0;
-    lfs_mkdir(&lfs, ".milk") => 0;
+
+    // create paths
+    lfs_mkdir(&lfs, "coffee") => 0;
+    if (DIR) {
+        lfs_mkdir(&lfs, "coffee/drip/.") => LFS_ERR_NOENT;
+        lfs_mkdir(&lfs, "coffee/coldbrew/./.") => LFS_ERR_NOENT;
+        lfs_mkdir(&lfs, "coffee/turkish/././.") => LFS_ERR_NOENT;
+        lfs_mkdir(&lfs, "coffee/tubruk/./././.") => LFS_ERR_NOENT;
+        lfs_mkdir(&lfs, "coffee/vietnamese/././././.") => LFS_ERR_NOENT;
+        lfs_mkdir(&lfs, "coffee/thai/./././././.") => LFS_ERR_NOENT;
+
+        // still create so we have something to test
+        lfs_mkdir(&lfs, "coffee/drip") => 0;
+        lfs_mkdir(&lfs, "coffee/coldbrew") => 0;
+        lfs_mkdir(&lfs, "coffee/turkish") => 0;
+        lfs_mkdir(&lfs, "coffee/tubruk") => 0;
+        lfs_mkdir(&lfs, "coffee/vietnamese") => 0;
+        lfs_mkdir(&lfs, "coffee/thai") => 0;
+
+    } else {
+        lfs_file_t file;
+        lfs_file_open(&lfs, &file, "coffee/drip/.",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_NOENT;
+        lfs_file_open(&lfs, &file, "coffee/coldbrew/./.",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_NOENT;
+        lfs_file_open(&lfs, &file, "coffee/turkish/././.",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_NOENT;
+        lfs_file_open(&lfs, &file, "coffee/tubruk/./././.",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_NOENT;
+        lfs_file_open(&lfs, &file, "coffee/vietnamese/././././.",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_NOENT;
+        lfs_file_open(&lfs, &file, "coffee/thai/./././././.",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_NOENT;
+
+        // still create so we have something to test
+        lfs_file_open(&lfs, &file, "coffee/drip",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "coffee/coldbrew",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "coffee/turkish",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "coffee/tubruk",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "coffee/vietnamese",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "coffee/thai",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+    }
+
+    // stat paths
     struct lfs_info info;
-    lfs_stat(&lfs, ".milk", &info) => 0;
-    strcmp(info.name, ".milk") => 0;
-    lfs_stat(&lfs, "tea/.././.milk", &info) => 0;
-    strcmp(info.name, ".milk") => 0;
+    if (DIR) {
+        lfs_stat(&lfs, "coffee/drip/./././././.", &info) => 0;
+        assert(strcmp(info.name, "drip") == 0);
+        assert(info.type == LFS_TYPE_DIR);
+        lfs_stat(&lfs, "coffee/coldbrew/././././.", &info) => 0;
+        assert(strcmp(info.name, "coldbrew") == 0);
+        assert(info.type == LFS_TYPE_DIR);
+        lfs_stat(&lfs, "coffee/turkish/./././.", &info) => 0;
+        assert(strcmp(info.name, "turkish") == 0);
+        assert(info.type == LFS_TYPE_DIR);
+        lfs_stat(&lfs, "coffee/tubruk/././.", &info) => 0;
+        assert(strcmp(info.name, "tubruk") == 0);
+        assert(info.type == LFS_TYPE_DIR);
+        lfs_stat(&lfs, "coffee/vietnamese/./.", &info) => 0;
+        assert(strcmp(info.name, "vietnamese") == 0);
+        assert(info.type == LFS_TYPE_DIR);
+        lfs_stat(&lfs, "coffee/thai/.", &info) => 0;
+        assert(strcmp(info.name, "thai") == 0);
+        assert(info.type == LFS_TYPE_DIR);
+    } else {
+        lfs_stat(&lfs, "coffee/drip/./././././.", &info) => LFS_ERR_NOTDIR;
+        lfs_stat(&lfs, "coffee/coldbrew/././././.", &info) => LFS_ERR_NOTDIR;
+        lfs_stat(&lfs, "coffee/turkish/./././.", &info) => LFS_ERR_NOTDIR;
+        lfs_stat(&lfs, "coffee/tubruk/././.", &info) => LFS_ERR_NOTDIR;
+        lfs_stat(&lfs, "coffee/vietnamese/./.", &info) => LFS_ERR_NOTDIR;
+        lfs_stat(&lfs, "coffee/thai/.", &info) => LFS_ERR_NOTDIR;
+    }
+
+    // file open paths, only works on files!
+    if (DIR) {
+        lfs_file_t file;
+        lfs_file_open(&lfs, &file, "coffee/drip/.",
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "coffee/coldbrew/./.",
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "coffee/turkish/././.",
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "coffee/tubruk/./././.",
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "coffee/vietnamese/././././.",
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "coffee/thai/./././././.",
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+
+        lfs_file_open(&lfs, &file, "coffee/drip/.",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "coffee/coldbrew/./.",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "coffee/turkish/././.",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "coffee/tubruk/./././.",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "coffee/vietnamese/././././.",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "coffee/thai/./././././.",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+
+        lfs_file_open(&lfs, &file, "coffee/drip/.",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "coffee/coldbrew/./.",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "coffee/turkish/././.",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "coffee/tubruk/./././.",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "coffee/vietnamese/././././.",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "coffee/thai/./././././.",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+    } else {
+        lfs_file_t file;
+        lfs_file_open(&lfs, &file, "coffee/drip/.",
+                LFS_O_RDONLY) => LFS_ERR_NOTDIR;
+        lfs_file_open(&lfs, &file, "coffee/coldbrew/./.",
+                LFS_O_RDONLY) => LFS_ERR_NOTDIR;
+        lfs_file_open(&lfs, &file, "coffee/turkish/././.",
+                LFS_O_RDONLY) => LFS_ERR_NOTDIR;
+        lfs_file_open(&lfs, &file, "coffee/tubruk/./././.",
+                LFS_O_RDONLY) => LFS_ERR_NOTDIR;
+        lfs_file_open(&lfs, &file, "coffee/vietnamese/././././.",
+                LFS_O_RDONLY) => LFS_ERR_NOTDIR;
+        lfs_file_open(&lfs, &file, "coffee/thai/./././././.",
+                LFS_O_RDONLY) => LFS_ERR_NOTDIR;
+
+        lfs_file_open(&lfs, &file, "coffee/drip/.",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_NOTDIR;
+        lfs_file_open(&lfs, &file, "coffee/coldbrew/./.",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_NOTDIR;
+        lfs_file_open(&lfs, &file, "coffee/turkish/././.",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_NOTDIR;
+        lfs_file_open(&lfs, &file, "coffee/tubruk/./././.",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_NOTDIR;
+        lfs_file_open(&lfs, &file, "coffee/vietnamese/././././.",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_NOTDIR;
+        lfs_file_open(&lfs, &file, "coffee/thai/./././././.",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_NOTDIR;
+
+        lfs_file_open(&lfs, &file, "coffee/drip/.",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_NOTDIR;
+        lfs_file_open(&lfs, &file, "coffee/coldbrew/./.",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_NOTDIR;
+        lfs_file_open(&lfs, &file, "coffee/turkish/././.",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_NOTDIR;
+        lfs_file_open(&lfs, &file, "coffee/tubruk/./././.",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_NOTDIR;
+        lfs_file_open(&lfs, &file, "coffee/vietnamese/././././.",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_NOTDIR;
+        lfs_file_open(&lfs, &file, "coffee/thai/./././././.",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_NOTDIR;
+    }
+
+    // dir open paths, only works on dirs!
+    if (DIR) {
+        lfs_dir_t dir;
+        lfs_dir_open(&lfs, &dir, "coffee/drip/.") => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+        lfs_dir_open(&lfs, &dir, "coffee/coldbrew/./.") => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+        lfs_dir_open(&lfs, &dir, "coffee/turkish/././.") => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+        lfs_dir_open(&lfs, &dir, "coffee/tubruk/./././.") => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+        lfs_dir_open(&lfs, &dir, "coffee/vietnamese/././././.") => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+        lfs_dir_open(&lfs, &dir, "coffee/thai/./././././.") => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+    } else {
+        lfs_dir_t dir;
+        lfs_dir_open(&lfs, &dir, "coffee/drip/.") => LFS_ERR_NOTDIR;
+        lfs_dir_open(&lfs, &dir, "coffee/coldbrew/./.") => LFS_ERR_NOTDIR;
+        lfs_dir_open(&lfs, &dir, "coffee/turkish/././.") => LFS_ERR_NOTDIR;
+        lfs_dir_open(&lfs, &dir, "coffee/tubruk/./././.") => LFS_ERR_NOTDIR;
+        lfs_dir_open(&lfs, &dir, "coffee/vietnamese/././././.") => LFS_ERR_NOTDIR;
+        lfs_dir_open(&lfs, &dir, "coffee/thai/./././././.") => LFS_ERR_NOTDIR;
+    }
+
+    // rename paths
+    lfs_mkdir(&lfs, "espresso") => 0;
+    if (DIR) {
+        // bad destination
+        lfs_rename(&lfs,
+                "coffee/drip",
+                "espresso/espresso/.") => LFS_ERR_NOENT;
+        lfs_rename(&lfs,
+                "coffee/coldbrew",
+                "espresso/americano/./.") => LFS_ERR_NOENT;
+        lfs_rename(&lfs,
+                "coffee/turkish",
+                "espresso/macchiato/././.") => LFS_ERR_NOENT;
+        lfs_rename(&lfs,
+                "coffee/tubruk",
+                "espresso/latte/./././.") => LFS_ERR_NOENT;
+        lfs_rename(&lfs,
+                "coffee/vietnamese",
+                "espresso/cappuccino/././././.") => LFS_ERR_NOENT;
+        lfs_rename(&lfs,
+                "coffee/thai",
+                "espresso/mocha/./././././.") => LFS_ERR_NOENT;
+
+        // bad source and bad destination
+        lfs_rename(&lfs,
+                "coffee/drip/./././././.",
+                "espresso/espresso/.") => LFS_ERR_NOENT;
+        lfs_rename(&lfs,
+                "coffee/coldbrew/././././.",
+                "espresso/americano/./.") => LFS_ERR_NOENT;
+        lfs_rename(&lfs,
+                "coffee/turkish/./././.",
+                "espresso/macchiato/././.") => LFS_ERR_NOENT;
+        lfs_rename(&lfs,
+                "coffee/tubruk/././.",
+                "espresso/latte/./././.") => LFS_ERR_NOENT;
+        lfs_rename(&lfs,
+                "coffee/vietnamese/./.",
+                "espresso/cappuccino/././././.") => LFS_ERR_NOENT;
+        lfs_rename(&lfs,
+                "coffee/thai/.",
+                "espresso/mocha/./././././.") => LFS_ERR_NOENT;
+
+        // this one works
+        lfs_rename(&lfs,
+                "coffee/drip/./././././.",
+                "espresso/espresso") => 0;
+        lfs_rename(&lfs,
+                "coffee/coldbrew/././././.",
+                "espresso/americano") => 0;
+        lfs_rename(&lfs,
+                "coffee/turkish/./././.",
+                "espresso/macchiato") => 0;
+        lfs_rename(&lfs,
+                "coffee/tubruk/././.",
+                "espresso/latte") => 0;
+        lfs_rename(&lfs,
+                "coffee/vietnamese/./.",
+                "espresso/cappuccino") => 0;
+        lfs_rename(&lfs,
+                "coffee/thai/.",
+                "espresso/mocha") => 0;
+
+        // stat paths
+        lfs_stat(&lfs, "espresso/espresso/./././././.", &info) => 0;
+        assert(strcmp(info.name, "espresso") == 0);
+        assert(info.type == LFS_TYPE_DIR);
+        lfs_stat(&lfs, "espresso/americano/././././.", &info) => 0;
+        assert(strcmp(info.name, "americano") == 0);
+        assert(info.type == LFS_TYPE_DIR);
+        lfs_stat(&lfs, "espresso/macchiato/./././.", &info) => 0;
+        assert(strcmp(info.name, "macchiato") == 0);
+        assert(info.type == LFS_TYPE_DIR);
+        lfs_stat(&lfs, "espresso/latte/././.", &info) => 0;
+        assert(strcmp(info.name, "latte") == 0);
+        assert(info.type == LFS_TYPE_DIR);
+        lfs_stat(&lfs, "espresso/cappuccino/./.", &info) => 0;
+        assert(strcmp(info.name, "cappuccino") == 0);
+        assert(info.type == LFS_TYPE_DIR);
+        lfs_stat(&lfs, "espresso/mocha/.", &info) => 0;
+        assert(strcmp(info.name, "mocha") == 0);
+        assert(info.type == LFS_TYPE_DIR);
+
+        lfs_stat(&lfs, "coffee/drip/./././././.", &info) => LFS_ERR_NOENT;
+        lfs_stat(&lfs, "coffee/coldbrew/././././.", &info) => LFS_ERR_NOENT;
+        lfs_stat(&lfs, "coffee/turkish/./././.", &info) => LFS_ERR_NOENT;
+        lfs_stat(&lfs, "coffee/tubruk/././.", &info) => LFS_ERR_NOENT;
+        lfs_stat(&lfs, "coffee/vietnamese/./.", &info) => LFS_ERR_NOENT;
+        lfs_stat(&lfs, "coffee/thai/.", &info) => LFS_ERR_NOENT;
+
+        // remove paths
+        lfs_remove(&lfs, "espresso/espresso/.") => 0;
+        lfs_remove(&lfs, "espresso/americano/./.") => 0;
+        lfs_remove(&lfs, "espresso/macchiato/././.") => 0;
+        lfs_remove(&lfs, "espresso/latte/./././.") => 0;
+        lfs_remove(&lfs, "espresso/cappuccino/././././.") => 0;
+        lfs_remove(&lfs, "espresso/mocha/./././././.") => 0;
+
+        // stat paths
+        lfs_stat(&lfs, "espresso/espresso/./././././.", &info) => LFS_ERR_NOENT;
+        lfs_stat(&lfs, "espresso/americano/././././.", &info) => LFS_ERR_NOENT;
+        lfs_stat(&lfs, "espresso/macchiato/./././.", &info) => LFS_ERR_NOENT;
+        lfs_stat(&lfs, "espresso/latte/././.", &info) => LFS_ERR_NOENT;
+        lfs_stat(&lfs, "espresso/cappuccino/./.", &info) => LFS_ERR_NOENT;
+        lfs_stat(&lfs, "espresso/mocha/.", &info) => LFS_ERR_NOENT;
+
+    } else {
+        // bad source
+        lfs_rename(&lfs,
+                "coffee/drip/./././././.",
+                "espresso/espresso") => LFS_ERR_NOTDIR;
+        lfs_rename(&lfs,
+                "coffee/coldbrew/././././.",
+                "espresso/americano") => LFS_ERR_NOTDIR;
+        lfs_rename(&lfs,
+                "coffee/turkish/./././.",
+                "espresso/macchiato") => LFS_ERR_NOTDIR;
+        lfs_rename(&lfs,
+                "coffee/tubruk/././.",
+                "espresso/latte") => LFS_ERR_NOTDIR;
+        lfs_rename(&lfs,
+                "coffee/vietnamese/./.",
+                "espresso/cappuccino") => LFS_ERR_NOTDIR;
+        lfs_rename(&lfs,
+                "coffee/thai/.",
+                "espresso/mocha") => LFS_ERR_NOTDIR;
+
+        // bad destination
+        lfs_rename(&lfs,
+                "coffee/drip",
+                "espresso/espresso/.") => LFS_ERR_NOENT;
+        lfs_rename(&lfs,
+                "coffee/coldbrew",
+                "espresso/americano/./.") => LFS_ERR_NOENT;
+        lfs_rename(&lfs,
+                "coffee/turkish",
+                "espresso/macchiato/././.") => LFS_ERR_NOENT;
+        lfs_rename(&lfs,
+                "coffee/tubruk",
+                "espresso/latte/./././.") => LFS_ERR_NOENT;
+        lfs_rename(&lfs,
+                "coffee/vietnamese",
+                "espresso/cappuccino/././././.") => LFS_ERR_NOENT;
+        lfs_rename(&lfs,
+                "coffee/thai",
+                "espresso/mocha/./././././.") => LFS_ERR_NOENT;
+
+        // bad source and bad destination
+        lfs_rename(&lfs,
+                "coffee/drip/./././././.",
+                "espresso/espresso/.") => LFS_ERR_NOTDIR;
+        lfs_rename(&lfs,
+                "coffee/coldbrew/././././.",
+                "espresso/americano/./.") => LFS_ERR_NOTDIR;
+        lfs_rename(&lfs,
+                "coffee/turkish/./././.",
+                "espresso/macchiato/././.") => LFS_ERR_NOTDIR;
+        lfs_rename(&lfs,
+                "coffee/tubruk/././.",
+                "espresso/latte/./././.") => LFS_ERR_NOTDIR;
+        lfs_rename(&lfs,
+                "coffee/vietnamese/./.",
+                "espresso/cappuccino/././././.") => LFS_ERR_NOTDIR;
+        lfs_rename(&lfs,
+                "coffee/thai/.",
+                "espresso/mocha/./././././.") => LFS_ERR_NOTDIR;
+
+        // remove paths
+        lfs_remove(&lfs, "coffee/drip/.") => LFS_ERR_NOTDIR;
+        lfs_remove(&lfs, "coffee/coldbrew/./.") => LFS_ERR_NOTDIR;
+        lfs_remove(&lfs, "coffee/turkish/././.") => LFS_ERR_NOTDIR;
+        lfs_remove(&lfs, "coffee/tubruk/./././.") => LFS_ERR_NOTDIR;
+        lfs_remove(&lfs, "coffee/vietnamese/././././.") => LFS_ERR_NOTDIR;
+        lfs_remove(&lfs, "coffee/thai/./././././.") => LFS_ERR_NOTDIR;
+
+        // stat paths
+        lfs_stat(&lfs, "espresso/espresso", &info) => LFS_ERR_NOENT;
+        lfs_stat(&lfs, "espresso/americano", &info) => LFS_ERR_NOENT;
+        lfs_stat(&lfs, "espresso/macchiato", &info) => LFS_ERR_NOENT;
+        lfs_stat(&lfs, "espresso/latte", &info) => LFS_ERR_NOENT;
+        lfs_stat(&lfs, "espresso/cappuccino", &info) => LFS_ERR_NOENT;
+        lfs_stat(&lfs, "espresso/mocha", &info) => LFS_ERR_NOENT;
+
+        lfs_stat(&lfs, "coffee/drip", &info) => 0;
+        assert(strcmp(info.name, "drip") == 0);
+        assert(info.type == LFS_TYPE_REG);
+        lfs_stat(&lfs, "coffee/coldbrew", &info) => 0;
+        assert(strcmp(info.name, "coldbrew") == 0);
+        assert(info.type == LFS_TYPE_REG);
+        lfs_stat(&lfs, "coffee/turkish", &info) => 0;
+        assert(strcmp(info.name, "turkish") == 0);
+        assert(info.type == LFS_TYPE_REG);
+        lfs_stat(&lfs, "coffee/tubruk", &info) => 0;
+        assert(strcmp(info.name, "tubruk") == 0);
+        assert(info.type == LFS_TYPE_REG);
+        lfs_stat(&lfs, "coffee/vietnamese", &info) => 0;
+        assert(strcmp(info.name, "vietnamese") == 0);
+        assert(info.type == LFS_TYPE_REG);
+        lfs_stat(&lfs, "coffee/thai", &info) => 0;
+        assert(strcmp(info.name, "thai") == 0);
+        assert(info.type == LFS_TYPE_REG);
+    }
+
     lfs_unmount(&lfs) => 0;
 '''
 
-# root dot dot path test
-[cases.test_paths_root_dot_dot]
+# dot dot path tests
+[cases.test_paths_dotdots]
+defines.DIR = [false, true]
 code = '''
     lfs_t lfs;
     lfs_format(&lfs, cfg) => 0;
     lfs_mount(&lfs, cfg) => 0;
-    lfs_mkdir(&lfs, "tea") => 0;
-    lfs_mkdir(&lfs, "tea/hottea") => 0;
-    lfs_mkdir(&lfs, "tea/warmtea") => 0;
-    lfs_mkdir(&lfs, "tea/coldtea") => 0;
+
+    // create paths
+    lfs_mkdir(&lfs, "no") => 0;
+    lfs_mkdir(&lfs, "no/no") => 0;
     lfs_mkdir(&lfs, "coffee") => 0;
-    lfs_mkdir(&lfs, "coffee/hotcoffee") => 0;
-    lfs_mkdir(&lfs, "coffee/warmcoffee") => 0;
-    lfs_mkdir(&lfs, "coffee/coldcoffee") => 0;
+    lfs_mkdir(&lfs, "coffee/no") => 0;
+    if (DIR) {
+        lfs_mkdir(&lfs, "/coffee/drip") => 0;
+        lfs_mkdir(&lfs, "/no/../coffee/coldbrew") => 0;
+        lfs_mkdir(&lfs, "/coffee/no/../turkish") => 0;
+        lfs_mkdir(&lfs, "/no/no/../../coffee/tubruk") => 0;
+        lfs_mkdir(&lfs, "/no/no/../../coffee/no/../vietnamese") => 0;
+        lfs_mkdir(&lfs, "/no/no/../../no/no/../../coffee/thai") => 0;
+    } else {
+        lfs_file_t file;
+        lfs_file_open(&lfs, &file, "/coffee/drip",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "/no/../coffee/coldbrew",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "/coffee/no/../turkish",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "/no/no/../../coffee/tubruk",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "/no/no/../../coffee/no/../vietnamese",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "/no/no/../../no/no/../../coffee/thai",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+    }
 
+    // stat paths
     struct lfs_info info;
-    lfs_stat(&lfs, "coffee/../../../../../../tea/hottea", &info) => 0;
-    strcmp(info.name, "hottea") => 0;
+    lfs_stat(&lfs, "/no/no/../../no/no/../../coffee/drip", &info) => 0;
+    assert(strcmp(info.name, "drip") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "/no/no/../../coffee/no/../coldbrew", &info) => 0;
+    assert(strcmp(info.name, "coldbrew") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "/no/no/../../coffee/turkish", &info) => 0;
+    assert(strcmp(info.name, "turkish") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "/coffee/no/../tubruk", &info) => 0;
+    assert(strcmp(info.name, "tubruk") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "/no/../coffee/vietnamese", &info) => 0;
+    assert(strcmp(info.name, "vietnamese") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "/coffee/thai", &info) => 0;
+    assert(strcmp(info.name, "thai") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+
+    // file open paths, only works on files!
+    if (DIR) {
+        lfs_file_t file;
+        lfs_file_open(&lfs, &file, "/coffee/drip",
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "/no/../coffee/coldbrew",
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "/coffee/no/../turkish",
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "/no/no/../../coffee/tubruk",
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "/no/no/../../coffee/no/../vietnamese",
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "/no/no/../../no/no/../../coffee/thai",
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+
+        lfs_file_open(&lfs, &file, "/coffee/drip",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "/no/../coffee/coldbrew",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "/coffee/no/../turkish",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "/no/no/../../coffee/tubruk",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "/no/no/../../coffee/no/../vietnamese",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "/no/no/../../no/no/../../coffee/thai",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+
+        lfs_file_open(&lfs, &file, "/coffee/drip",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "/no/../coffee/coldbrew",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "/coffee/no/../turkish",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "/no/no/../../coffee/tubruk",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "/no/no/../../coffee/no/../vietnamese",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "/no/no/../../no/no/../../coffee/thai",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+    } else {
+        lfs_file_t file;
+        lfs_file_open(&lfs, &file, "/coffee/drip",
+                LFS_O_RDONLY) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "/no/../coffee/coldbrew",
+                LFS_O_RDONLY) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "/coffee/no/../turkish",
+                LFS_O_RDONLY) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "/no/no/../../coffee/tubruk",
+                LFS_O_RDONLY) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "/no/no/../../coffee/no/../vietnamese",
+                LFS_O_RDONLY) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "/no/no/../../no/no/../../coffee/thai",
+                LFS_O_RDONLY) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+
+        lfs_file_open(&lfs, &file, "/coffee/drip",
+                LFS_O_WRONLY | LFS_O_CREAT) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "/no/../coffee/coldbrew",
+                LFS_O_WRONLY | LFS_O_CREAT) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "/coffee/no/../turkish",
+                LFS_O_WRONLY | LFS_O_CREAT) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "/no/no/../../coffee/tubruk",
+                LFS_O_WRONLY | LFS_O_CREAT) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "/no/no/../../coffee/no/../vietnamese",
+                LFS_O_WRONLY | LFS_O_CREAT) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "/no/no/../../no/no/../../coffee/thai",
+                LFS_O_WRONLY | LFS_O_CREAT) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+
+        lfs_file_open(&lfs, &file, "/coffee/drip",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "/no/../coffee/coldbrew",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "/coffee/no/../turkish",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "/no/no/../../coffee/tubruk",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "/no/no/../../coffee/no/../vietnamese",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "/no/no/../../no/no/../../coffee/thai",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+    }
+
+    // dir open paths, only works on dirs!
+    if (DIR) {
+        lfs_dir_t dir;
+        lfs_dir_open(&lfs, &dir, "/coffee/drip") => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+        lfs_dir_open(&lfs, &dir, "/no/../coffee/coldbrew") => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+        lfs_dir_open(&lfs, &dir, "/coffee/no/../turkish") => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+        lfs_dir_open(&lfs, &dir, "/no/no/../../coffee/tubruk") => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+        lfs_dir_open(&lfs, &dir, "/no/no/../../coffee/no/../vietnamese") => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+        lfs_dir_open(&lfs, &dir, "/no/no/../../no/no/../../coffee/thai") => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+    } else {
+        lfs_dir_t dir;
+        lfs_dir_open(&lfs, &dir, "/coffee/drip") => LFS_ERR_NOTDIR;
+        lfs_dir_open(&lfs, &dir, "/no/../coffee/coldbrew") => LFS_ERR_NOTDIR;
+        lfs_dir_open(&lfs, &dir, "/coffee/no/../turkish") => LFS_ERR_NOTDIR;
+        lfs_dir_open(&lfs, &dir, "/no/no/../../coffee/tubruk") => LFS_ERR_NOTDIR;
+        lfs_dir_open(&lfs, &dir, "/no/no/../../coffee/no/../vietnamese") => LFS_ERR_NOTDIR;
+        lfs_dir_open(&lfs, &dir, "/no/no/../../no/no/../../coffee/thai") => LFS_ERR_NOTDIR;
+    }
+
+    // rename paths
+    lfs_mkdir(&lfs, "espresso") => 0;
+    lfs_rename(&lfs,
+            "/no/no/../../no/no/../../coffee/drip",
+            "/espresso/espresso") => 0;
+    lfs_rename(&lfs,
+            "/no/no/../../coffee/no/../coldbrew",
+            "/no/../espresso/americano") => 0;
+    lfs_rename(&lfs,
+            "/no/no/../../coffee/turkish",
+            "/espresso/no/../macchiato") => 0;
+    lfs_rename(&lfs,
+            "/coffee/no/../tubruk",
+            "/no/no/../../espresso/latte") => 0;
+    lfs_rename(&lfs,
+            "/no/../coffee/vietnamese",
+            "/no/no/../../espresso/no/../cappuccino") => 0;
+    lfs_rename(&lfs,
+            "/coffee/thai",
+            "/no/no/../../no/no/../../espresso/mocha") => 0;
+
+    // stat paths
+    lfs_stat(&lfs, "/no/no/../../no/no/../../espresso/espresso", &info) => 0;
+    assert(strcmp(info.name, "espresso") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "/no/no/../../espresso/no/../americano", &info) => 0;
+    assert(strcmp(info.name, "americano") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "/no/no/../../espresso/macchiato", &info) => 0;
+    assert(strcmp(info.name, "macchiato") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "/espresso/no/../latte", &info) => 0;
+    assert(strcmp(info.name, "latte") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "/no/../espresso/cappuccino", &info) => 0;
+    assert(strcmp(info.name, "cappuccino") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "/espresso/mocha", &info) => 0;
+    assert(strcmp(info.name, "mocha") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+
+    lfs_stat(&lfs, "/no/no/../../no/no/../../coffee/drip", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "/no/no/../../coffee/no/../coldbrew", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "/no/no/../../coffee/turkish", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "/coffee/no/../tubruk", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "/no/../coffee/vietnamese", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "/coffee/thai", &info) => LFS_ERR_NOENT;
+
+    // remove paths
+    lfs_remove(&lfs, "/espresso/espresso") => 0;
+    lfs_remove(&lfs, "/no/../espresso/americano") => 0;
+    lfs_remove(&lfs, "/espresso/no/../macchiato") => 0;
+    lfs_remove(&lfs, "/no/no/../../espresso/latte") => 0;
+    lfs_remove(&lfs, "/no/no/../../espresso/no/../cappuccino") => 0;
+    lfs_remove(&lfs, "/no/no/../../no/no/../../espresso/mocha") => 0;
+
+    // stat paths
+    lfs_stat(&lfs, "/no/no/../../no/no/../../espresso/espresso", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "/no/no/../../espresso/no/../americano", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "/no/no/../../espresso/macchiato", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "/espresso/no/../latte", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "/no/../espresso/cappuccino", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "/espresso/mocha", &info) => LFS_ERR_NOENT;
 
-    lfs_mkdir(&lfs, "coffee/../../../../../../milk") => 0;
-    lfs_stat(&lfs, "coffee/../../../../../../milk", &info) => 0;
-    strcmp(info.name, "milk") => 0;
-    lfs_stat(&lfs, "milk", &info) => 0;
-    strcmp(info.name, "milk") => 0;
     lfs_unmount(&lfs) => 0;
 '''
 
-# invalid path tests
-[cases.test_paths_invalid]
+# test trailing dot dots, these get really weird
+#
+# POSIX deviations:
+#
+# - We do not check for existance of directories followed by dotdots:
+#   - littlefs: stat("a/missing/..") => 0
+#   - POSIX:    stat("a/missing/..") => ENOENT
+#   Reason: Difficult to implement non-recursively.
+#
+# - We accept modifications of directories with trailing dotdots:
+#   - littlefs: rename("a/b/..", "c") => 0
+#   - POSIX:    rename("a/b/..", "c") => EBUSY
+#   Reason: Not worth implementing.
+#
+[cases.test_paths_trailing_dotdots]
+defines.DIR = [false, true]
 code = '''
     lfs_t lfs;
-    lfs_format(&lfs, cfg);
+    lfs_format(&lfs, cfg) => 0;
     lfs_mount(&lfs, cfg) => 0;
-    struct lfs_info info;
-    lfs_stat(&lfs, "dirt", &info) => LFS_ERR_NOENT;
-    lfs_stat(&lfs, "dirt/ground", &info) => LFS_ERR_NOENT;
-    lfs_stat(&lfs, "dirt/ground/earth", &info) => LFS_ERR_NOENT;
 
-    lfs_remove(&lfs, "dirt") => LFS_ERR_NOENT;
-    lfs_remove(&lfs, "dirt/ground") => LFS_ERR_NOENT;
-    lfs_remove(&lfs, "dirt/ground/earth") => LFS_ERR_NOENT;
+    // create paths
+    lfs_mkdir(&lfs, "coffee") => 0;
+    if (DIR) {
+        lfs_mkdir(&lfs, "coffee/drip/..") => LFS_ERR_EXIST;
+        lfs_mkdir(&lfs, "coffee/coldbrew/../..") => LFS_ERR_EXIST;
+        lfs_mkdir(&lfs, "coffee/turkish/../../..") => LFS_ERR_INVAL;
+        lfs_mkdir(&lfs, "coffee/tubruk/../../../..") => LFS_ERR_INVAL;
+        lfs_mkdir(&lfs, "coffee/vietnamese/../../../../..") => LFS_ERR_INVAL;
+        lfs_mkdir(&lfs, "coffee/thai/../../../../../..") => LFS_ERR_INVAL;
+
+        // still create so we have something to test
+        lfs_mkdir(&lfs, "coffee/drip") => 0;
+        lfs_mkdir(&lfs, "coffee/coldbrew") => 0;
+        lfs_mkdir(&lfs, "coffee/turkish") => 0;
+        lfs_mkdir(&lfs, "coffee/tubruk") => 0;
+        lfs_mkdir(&lfs, "coffee/vietnamese") => 0;
+        lfs_mkdir(&lfs, "coffee/thai") => 0;
 
-    lfs_mkdir(&lfs, "dirt/ground") => LFS_ERR_NOENT;
+    } else {
+        lfs_file_t file;
+        lfs_file_open(&lfs, &file, "coffee/drip/..",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "coffee/coldbrew/../..",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "coffee/turkish/../../..",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_INVAL;
+        lfs_file_open(&lfs, &file, "coffee/tubruk/../../../..",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_INVAL;
+        lfs_file_open(&lfs, &file, "coffee/vietnamese/../../../../..",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_INVAL;
+        lfs_file_open(&lfs, &file, "coffee/thai/../../../../../..",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_INVAL;
+
+        // still create so we have something to test
+        lfs_file_open(&lfs, &file, "coffee/drip",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "coffee/coldbrew",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "coffee/turkish",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "coffee/tubruk",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "coffee/vietnamese",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "coffee/thai",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+    }
+
+    // stat paths
+    struct lfs_info info;
+    lfs_stat(&lfs, "coffee/drip/../../../../../..", &info) => LFS_ERR_INVAL;
+    lfs_stat(&lfs, "coffee/coldbrew/../../../../..", &info) => LFS_ERR_INVAL;
+    lfs_stat(&lfs, "coffee/turkish/../../../..", &info) => LFS_ERR_INVAL;
+    lfs_stat(&lfs, "coffee/tubruk/../../..", &info) => LFS_ERR_INVAL;
+    lfs_stat(&lfs, "coffee/vietnamese/../..", &info) => 0;
+    assert(strcmp(info.name, "/") == 0);
+    assert(info.type == LFS_TYPE_DIR);
+    lfs_stat(&lfs, "coffee/thai/..", &info) => 0;
+    assert(strcmp(info.name, "coffee") == 0);
+    assert(info.type == LFS_TYPE_DIR);
+
+    // file open paths, only works on files!
     lfs_file_t file;
-    lfs_file_open(&lfs, &file, "dirt/ground", LFS_O_WRONLY | LFS_O_CREAT)
-            => LFS_ERR_NOENT;
-    lfs_mkdir(&lfs, "dirt/ground/earth") => LFS_ERR_NOENT;
-    lfs_file_open(&lfs, &file, "dirt/ground/earth", LFS_O_WRONLY | LFS_O_CREAT)
-            => LFS_ERR_NOENT;
+    lfs_file_open(&lfs, &file, "coffee/drip/..",
+            LFS_O_RDONLY) => LFS_ERR_ISDIR;
+    lfs_file_open(&lfs, &file, "coffee/coldbrew/../..",
+            LFS_O_RDONLY) => LFS_ERR_ISDIR;
+    lfs_file_open(&lfs, &file, "coffee/turkish/../../..",
+            LFS_O_RDONLY) => LFS_ERR_INVAL;
+    lfs_file_open(&lfs, &file, "coffee/tubruk/../../../..",
+            LFS_O_RDONLY) => LFS_ERR_INVAL;
+    lfs_file_open(&lfs, &file, "coffee/vietnamese/../../../../..",
+            LFS_O_RDONLY) => LFS_ERR_INVAL;
+    lfs_file_open(&lfs, &file, "coffee/thai/../../../../../..",
+            LFS_O_RDONLY) => LFS_ERR_INVAL;
+
+    lfs_file_open(&lfs, &file, "coffee/drip/..",
+            LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+    lfs_file_open(&lfs, &file, "coffee/coldbrew/../..",
+            LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+    lfs_file_open(&lfs, &file, "coffee/turkish/../../..",
+            LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_INVAL;
+    lfs_file_open(&lfs, &file, "coffee/tubruk/../../../..",
+            LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_INVAL;
+    lfs_file_open(&lfs, &file, "coffee/vietnamese/../../../../..",
+            LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_INVAL;
+    lfs_file_open(&lfs, &file, "coffee/thai/../../../../../..",
+            LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_INVAL;
+
+    lfs_file_open(&lfs, &file, "coffee/drip/..",
+            LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+    lfs_file_open(&lfs, &file, "coffee/coldbrew/../..",
+            LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+    lfs_file_open(&lfs, &file, "coffee/turkish/../../..",
+            LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_INVAL;
+    lfs_file_open(&lfs, &file, "coffee/tubruk/../../../..",
+            LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_INVAL;
+    lfs_file_open(&lfs, &file, "coffee/vietnamese/../../../../..",
+            LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_INVAL;
+    lfs_file_open(&lfs, &file, "coffee/thai/../../../../../..",
+            LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_INVAL;
+
+    // dir open paths, only works on dirs!
+    lfs_dir_t dir;
+    lfs_dir_open(&lfs, &dir, "coffee/drip/..") => 0;
+    lfs_dir_close(&lfs, &dir) => 0;
+    lfs_dir_open(&lfs, &dir, "coffee/coldbrew/../..") => 0;
+    lfs_dir_close(&lfs, &dir) => 0;
+    lfs_dir_open(&lfs, &dir, "coffee/turkish/../../..") => LFS_ERR_INVAL;
+    lfs_dir_open(&lfs, &dir, "coffee/tubruk/../../../..") => LFS_ERR_INVAL;
+    lfs_dir_open(&lfs, &dir, "coffee/vietnamese/../../../../..") => LFS_ERR_INVAL;
+    lfs_dir_open(&lfs, &dir, "coffee/thai/../../../../../..") => LFS_ERR_INVAL;
+
+    // rename paths
+    lfs_mkdir(&lfs, "espresso") => 0;
+    // bad source
+    lfs_rename(&lfs,
+            "coffee/drip/../../../../../..",
+            "espresso/espresso") => LFS_ERR_INVAL;
+    lfs_rename(&lfs,
+            "coffee/coldbrew/../../../../..",
+            "espresso/americano") => LFS_ERR_INVAL;
+    lfs_rename(&lfs,
+            "coffee/turkish/../../../..",
+            "espresso/macchiato") => LFS_ERR_INVAL;
+    lfs_rename(&lfs,
+            "coffee/tubruk/../../..",
+            "espresso/latte") => LFS_ERR_INVAL;
+    lfs_rename(&lfs,
+            "coffee/vietnamese/../..",
+            "espresso/cappuccino") => LFS_ERR_INVAL;
+    // this one works
+    lfs_rename(&lfs,
+            "coffee/thai/..",
+            "espresso/mocha") => 0;
+    lfs_rename(&lfs,
+            "espresso/mocha",
+            "coffee") => 0;
+
+    // bad destination
+    if (DIR) {
+        // this one works
+        lfs_rename(&lfs,
+                "coffee/drip",
+                "espresso/espresso/..") => 0;
+        lfs_rename(&lfs,
+                "espresso",
+                "coffee/drip") => 0;
+    } else {
+        lfs_rename(&lfs,
+                "coffee/drip",
+                "espresso/espresso/..") => LFS_ERR_ISDIR;
+    }
+    lfs_rename(&lfs,
+            "coffee/coldbrew",
+            "espresso/americano/../..") => LFS_ERR_INVAL;
+    lfs_rename(&lfs,
+            "coffee/turkish",
+            "espresso/macchiato/../../..") => LFS_ERR_INVAL;
+    lfs_rename(&lfs,
+            "coffee/tubruk",
+            "espresso/latte/../../../..") => LFS_ERR_INVAL;
+    lfs_rename(&lfs,
+            "coffee/vietnamese",
+            "espresso/cappuccino/../../../../..") => LFS_ERR_INVAL;
+    lfs_rename(&lfs,
+            "coffee/thai",
+            "espresso/mocha/../../../../../..") => LFS_ERR_INVAL;
+
+    // bad source and bad destination
+    lfs_rename(&lfs,
+            "coffee/drip/../../../../../..",
+            "espresso/espresso/..") => LFS_ERR_INVAL;
+    lfs_rename(&lfs,
+            "coffee/coldbrew/../../../../..",
+            "espresso/americano/../..") => LFS_ERR_INVAL;
+    lfs_rename(&lfs,
+            "coffee/turkish/../../../..",
+            "espresso/macchiato/../../..") => LFS_ERR_INVAL;
+    lfs_rename(&lfs,
+            "coffee/tubruk/../../..",
+            "espresso/latte/../../../..") => LFS_ERR_INVAL;
+    lfs_rename(&lfs,
+            "coffee/vietnamese/../..",
+            "espresso/cappuccino/../../../../..") => LFS_ERR_INVAL;
+    lfs_rename(&lfs,
+            "coffee/thai/..",
+            "espresso/mocha/../../../../../..") => LFS_ERR_INVAL;
+
+    // remove paths
+    lfs_remove(&lfs, "coffee/drip/..") => LFS_ERR_NOTEMPTY;
+    lfs_remove(&lfs, "coffee/coldbrew/../..") => LFS_ERR_INVAL;
+    lfs_remove(&lfs, "coffee/turkish/../../..") => LFS_ERR_INVAL;
+    lfs_remove(&lfs, "coffee/tubruk/../../../..") => LFS_ERR_INVAL;
+    lfs_remove(&lfs, "coffee/vietnamese/../../../../..") => LFS_ERR_INVAL;
+    lfs_remove(&lfs, "coffee/thai/../../../../../..") => LFS_ERR_INVAL;
+
+    // stat paths
+    lfs_stat(&lfs, "espresso/espresso", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "espresso/americano", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "espresso/macchiato", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "espresso/latte", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "espresso/cappuccino", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "espresso/mocha", &info) => LFS_ERR_NOENT;
+
+    lfs_stat(&lfs, "coffee/drip", &info) => 0;
+    assert(strcmp(info.name, "drip") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "coffee/coldbrew", &info) => 0;
+    assert(strcmp(info.name, "coldbrew") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "coffee/turkish", &info) => 0;
+    assert(strcmp(info.name, "turkish") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "coffee/tubruk", &info) => 0;
+    assert(strcmp(info.name, "tubruk") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "coffee/vietnamese", &info) => 0;
+    assert(strcmp(info.name, "vietnamese") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "coffee/thai", &info) => 0;
+    assert(strcmp(info.name, "thai") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+
     lfs_unmount(&lfs) => 0;
 '''
 
-# root operations
-[cases.test_paths_root]
+# dot dot dot path tests
+[cases.test_paths_dot_dotdots]
+defines.DIR = [false, true]
 code = '''
     lfs_t lfs;
     lfs_format(&lfs, cfg) => 0;
     lfs_mount(&lfs, cfg) => 0;
+
+    // create paths
+    lfs_mkdir(&lfs, "no") => 0;
+    lfs_mkdir(&lfs, "no/no") => 0;
+    lfs_mkdir(&lfs, "coffee") => 0;
+    lfs_mkdir(&lfs, "coffee/no") => 0;
+    if (DIR) {
+        lfs_mkdir(&lfs, "/coffee/drip") => 0;
+        lfs_mkdir(&lfs, "/no/./../coffee/coldbrew") => 0;
+        lfs_mkdir(&lfs, "/coffee/no/./../turkish") => 0;
+        lfs_mkdir(&lfs, "/no/no/./.././../coffee/tubruk") => 0;
+        lfs_mkdir(&lfs, "/no/no/./.././../coffee/no/./../vietnamese") => 0;
+        lfs_mkdir(&lfs, "/no/no/./.././../no/no/./.././../coffee/thai") => 0;
+    } else {
+        lfs_file_t file;
+        lfs_file_open(&lfs, &file, "/coffee/drip",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "/no/./../coffee/coldbrew",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "/coffee/no/./../turkish",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "/no/no/./.././../coffee/tubruk",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "/no/no/./.././../coffee/no/./../vietnamese",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "/no/no/./.././../no/no/./.././../coffee/thai",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+    }
+
+    // stat paths
     struct lfs_info info;
-    lfs_stat(&lfs, "/", &info) => 0;
-    assert(strcmp(info.name, "/") == 0);
-    assert(info.type == LFS_TYPE_DIR);
+    lfs_stat(&lfs, "/no/no/./.././../no/no/./.././../coffee/drip", &info) => 0;
+    assert(strcmp(info.name, "drip") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "/no/no/./.././../coffee/no/./../coldbrew", &info) => 0;
+    assert(strcmp(info.name, "coldbrew") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "/no/no/./.././../coffee/turkish", &info) => 0;
+    assert(strcmp(info.name, "turkish") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "/coffee/no/./../tubruk", &info) => 0;
+    assert(strcmp(info.name, "tubruk") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "/no/./../coffee/vietnamese", &info) => 0;
+    assert(strcmp(info.name, "vietnamese") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "/coffee/thai", &info) => 0;
+    assert(strcmp(info.name, "thai") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
 
-    lfs_mkdir(&lfs, "/") => LFS_ERR_EXIST;
-    lfs_file_t file;
-    lfs_file_open(&lfs, &file, "/", LFS_O_WRONLY | LFS_O_CREAT)
-            => LFS_ERR_ISDIR;
+    // file open paths, only works on files!
+    if (DIR) {
+        lfs_file_t file;
+        lfs_file_open(&lfs, &file, "/coffee/drip",
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "/no/./../coffee/coldbrew",
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "/coffee/no/./../turkish",
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "/no/no/./.././../coffee/tubruk",
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "/no/no/./.././../coffee/no/./../vietnamese",
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "/no/no/./.././../no/no/./.././../coffee/thai",
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+
+        lfs_file_open(&lfs, &file, "/coffee/drip",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "/no/./../coffee/coldbrew",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "/coffee/no/./../turkish",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "/no/no/./.././../coffee/tubruk",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "/no/no/./.././../coffee/no/./../vietnamese",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "/no/no/./.././../no/no/./.././../coffee/thai",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+
+        lfs_file_open(&lfs, &file, "/coffee/drip",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "/no/./../coffee/coldbrew",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "/coffee/no/./../turkish",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "/no/no/./.././../coffee/tubruk",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "/no/no/./.././../coffee/no/./../vietnamese",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "/no/no/./.././../no/no/./.././../coffee/thai",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+    } else {
+        lfs_file_t file;
+        lfs_file_open(&lfs, &file, "/coffee/drip",
+                LFS_O_RDONLY) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "/no/./../coffee/coldbrew",
+                LFS_O_RDONLY) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "/coffee/no/./../turkish",
+                LFS_O_RDONLY) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "/no/no/./.././../coffee/tubruk",
+                LFS_O_RDONLY) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "/no/no/./.././../coffee/no/./../vietnamese",
+                LFS_O_RDONLY) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "/no/no/./.././../no/no/./.././../coffee/thai",
+                LFS_O_RDONLY) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+
+        lfs_file_open(&lfs, &file, "/coffee/drip",
+                LFS_O_WRONLY | LFS_O_CREAT) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "/no/./../coffee/coldbrew",
+                LFS_O_WRONLY | LFS_O_CREAT) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "/coffee/no/./../turkish",
+                LFS_O_WRONLY | LFS_O_CREAT) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "/no/no/./.././../coffee/tubruk",
+                LFS_O_WRONLY | LFS_O_CREAT) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "/no/no/./.././../coffee/no/./../vietnamese",
+                LFS_O_WRONLY | LFS_O_CREAT) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "/no/no/./.././../no/no/./.././../coffee/thai",
+                LFS_O_WRONLY | LFS_O_CREAT) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+
+        lfs_file_open(&lfs, &file, "/coffee/drip",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "/no/./../coffee/coldbrew",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "/coffee/no/./../turkish",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "/no/no/./.././../coffee/tubruk",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "/no/no/./.././../coffee/no/./../vietnamese",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "/no/no/./.././../no/no/./.././../coffee/thai",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+    }
+
+    // dir open paths, only works on dirs!
+    if (DIR) {
+        lfs_dir_t dir;
+        lfs_dir_open(&lfs, &dir, "/coffee/drip") => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+        lfs_dir_open(&lfs, &dir, "/no/./../coffee/coldbrew") => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+        lfs_dir_open(&lfs, &dir, "/coffee/no/./../turkish") => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+        lfs_dir_open(&lfs, &dir, "/no/no/./.././../coffee/tubruk") => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+        lfs_dir_open(&lfs, &dir, "/no/no/./.././../coffee/no/./../vietnamese") => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+        lfs_dir_open(&lfs, &dir, "/no/no/./.././../no/no/./.././../coffee/thai") => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+    } else {
+        lfs_dir_t dir;
+        lfs_dir_open(&lfs, &dir, "/coffee/drip") => LFS_ERR_NOTDIR;
+        lfs_dir_open(&lfs, &dir, "/no/./../coffee/coldbrew") => LFS_ERR_NOTDIR;
+        lfs_dir_open(&lfs, &dir, "/coffee/no/./../turkish") => LFS_ERR_NOTDIR;
+        lfs_dir_open(&lfs, &dir, "/no/no/./.././../coffee/tubruk") => LFS_ERR_NOTDIR;
+        lfs_dir_open(&lfs, &dir, "/no/no/./.././../coffee/no/./../vietnamese") => LFS_ERR_NOTDIR;
+        lfs_dir_open(&lfs, &dir, "/no/no/./.././../no/no/./.././../coffee/thai") => LFS_ERR_NOTDIR;
+    }
+
+    // rename paths
+    lfs_mkdir(&lfs, "espresso") => 0;
+    lfs_rename(&lfs,
+            "/no/no/./.././../no/no/./.././../coffee/drip",
+            "/espresso/espresso") => 0;
+    lfs_rename(&lfs,
+            "/no/no/./.././../coffee/no/./../coldbrew",
+            "/no/./../espresso/americano") => 0;
+    lfs_rename(&lfs,
+            "/no/no/./.././../coffee/turkish",
+            "/espresso/no/./../macchiato") => 0;
+    lfs_rename(&lfs,
+            "/coffee/no/./../tubruk",
+            "/no/no/./.././../espresso/latte") => 0;
+    lfs_rename(&lfs,
+            "/no/./../coffee/vietnamese",
+            "/no/no/./.././../espresso/no/./../cappuccino") => 0;
+    lfs_rename(&lfs,
+            "/coffee/thai",
+            "/no/no/./.././../no/no/./.././../espresso/mocha") => 0;
+
+    // stat paths
+    lfs_stat(&lfs, "/no/no/./.././../no/no/./.././../espresso/espresso", &info) => 0;
+    assert(strcmp(info.name, "espresso") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "/no/no/./.././../espresso/no/./../americano", &info) => 0;
+    assert(strcmp(info.name, "americano") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "/no/no/./.././../espresso/macchiato", &info) => 0;
+    assert(strcmp(info.name, "macchiato") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "/espresso/no/./../latte", &info) => 0;
+    assert(strcmp(info.name, "latte") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "/no/./../espresso/cappuccino", &info) => 0;
+    assert(strcmp(info.name, "cappuccino") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "/espresso/mocha", &info) => 0;
+    assert(strcmp(info.name, "mocha") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+
+    lfs_stat(&lfs, "/no/no/./.././../no/no/./.././../coffee/drip", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "/no/no/./.././../coffee/no/./../coldbrew", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "/no/no/./.././../coffee/turkish", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "/coffee/no/./../tubruk", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "/no/./../coffee/vietnamese", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "/coffee/thai", &info) => LFS_ERR_NOENT;
+
+    // remove paths
+    lfs_remove(&lfs, "/espresso/espresso") => 0;
+    lfs_remove(&lfs, "/no/./../espresso/americano") => 0;
+    lfs_remove(&lfs, "/espresso/no/./../macchiato") => 0;
+    lfs_remove(&lfs, "/no/no/./.././../espresso/latte") => 0;
+    lfs_remove(&lfs, "/no/no/./.././../espresso/no/./../cappuccino") => 0;
+    lfs_remove(&lfs, "/no/no/./.././../no/no/./.././../espresso/mocha") => 0;
+
+    // stat paths
+    lfs_stat(&lfs, "/no/no/./.././../no/no/./.././../espresso/espresso", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "/no/no/./.././../espresso/no/./../americano", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "/no/no/./.././../espresso/macchiato", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "/espresso/no/./../latte", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "/no/./../espresso/cappuccino", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "/espresso/mocha", &info) => LFS_ERR_NOENT;
 
-    lfs_remove(&lfs, "/") => LFS_ERR_INVAL;
     lfs_unmount(&lfs) => 0;
 '''
 
-# root representations
-[cases.test_paths_root_reprs]
+# dot dot dot path tests
+[cases.test_paths_dotdotdots]
+defines.DIR = [false, true]
 code = '''
     lfs_t lfs;
     lfs_format(&lfs, cfg) => 0;
     lfs_mount(&lfs, cfg) => 0;
+
+    // create paths
+    lfs_mkdir(&lfs, "coffee") => 0;
+    lfs_mkdir(&lfs, "coffee/...") => 0;
+    if (DIR) {
+        lfs_mkdir(&lfs, "/coffee/.../drip") => 0;
+        lfs_mkdir(&lfs, "/coffee/.../coldbrew") => 0;
+        lfs_mkdir(&lfs, "/coffee/.../turkish") => 0;
+        lfs_mkdir(&lfs, "/coffee/.../tubruk") => 0;
+        lfs_mkdir(&lfs, "/coffee/.../vietnamese") => 0;
+        lfs_mkdir(&lfs, "/coffee/.../thai") => 0;
+    } else {
+        lfs_file_t file;
+        lfs_file_open(&lfs, &file, "/coffee/.../drip",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "/coffee/.../coldbrew",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "/coffee/.../turkish",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "/coffee/.../tubruk",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "/coffee/.../vietnamese",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "/coffee/.../thai",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+    }
+
+    // stat paths
     struct lfs_info info;
-    lfs_stat(&lfs, "/", &info) => 0;
-    assert(strcmp(info.name, "/") == 0);
-    assert(info.type == LFS_TYPE_DIR);
-    lfs_stat(&lfs, "", &info) => 0;
-    assert(strcmp(info.name, "/") == 0);
-    assert(info.type == LFS_TYPE_DIR);
-    lfs_stat(&lfs, ".", &info) => 0;
-    assert(strcmp(info.name, "/") == 0);
-    assert(info.type == LFS_TYPE_DIR);
-    lfs_stat(&lfs, "..", &info) => 0;
-    assert(strcmp(info.name, "/") == 0);
-    assert(info.type == LFS_TYPE_DIR);
-    lfs_stat(&lfs, "//", &info) => 0;
-    assert(strcmp(info.name, "/") == 0);
-    assert(info.type == LFS_TYPE_DIR);
-    lfs_stat(&lfs, "./", &info) => 0;
-    assert(strcmp(info.name, "/") == 0);
-    assert(info.type == LFS_TYPE_DIR);
+    lfs_stat(&lfs, "/coffee/.../drip", &info) => 0;
+    assert(strcmp(info.name, "drip") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "/coffee/.../coldbrew", &info) => 0;
+    assert(strcmp(info.name, "coldbrew") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "/coffee/.../turkish", &info) => 0;
+    assert(strcmp(info.name, "turkish") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "/coffee/.../tubruk", &info) => 0;
+    assert(strcmp(info.name, "tubruk") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "/coffee/.../vietnamese", &info) => 0;
+    assert(strcmp(info.name, "vietnamese") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "/coffee/.../thai", &info) => 0;
+    assert(strcmp(info.name, "thai") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+
+    // file open paths, only works on files!
+    if (DIR) {
+        lfs_file_t file;
+        lfs_file_open(&lfs, &file, "/coffee/.../drip",
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "/coffee/.../coldbrew",
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "/coffee/.../turkish",
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "/coffee/.../tubruk",
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "/coffee/.../vietnamese",
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "/coffee/.../thai",
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+
+        lfs_file_open(&lfs, &file, "/coffee/.../drip",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "/coffee/.../coldbrew",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "/coffee/.../turkish",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "/coffee/.../tubruk",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "/coffee/.../vietnamese",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "/coffee/.../thai",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+
+        lfs_file_open(&lfs, &file, "/coffee/.../drip",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "/coffee/.../coldbrew",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "/coffee/.../turkish",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "/coffee/.../tubruk",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "/coffee/.../vietnamese",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "/coffee/.../thai",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+    } else {
+        lfs_file_t file;
+        lfs_file_open(&lfs, &file, "/coffee/.../drip",
+                LFS_O_RDONLY) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "/coffee/.../coldbrew",
+                LFS_O_RDONLY) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "/coffee/.../turkish",
+                LFS_O_RDONLY) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "/coffee/.../tubruk",
+                LFS_O_RDONLY) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "/coffee/.../vietnamese",
+                LFS_O_RDONLY) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "/coffee/.../thai",
+                LFS_O_RDONLY) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+
+        lfs_file_open(&lfs, &file, "/coffee/.../drip",
+                LFS_O_WRONLY | LFS_O_CREAT) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "/coffee/.../coldbrew",
+                LFS_O_WRONLY | LFS_O_CREAT) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "/coffee/.../turkish",
+                LFS_O_WRONLY | LFS_O_CREAT) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "/coffee/.../tubruk",
+                LFS_O_WRONLY | LFS_O_CREAT) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "/coffee/.../vietnamese",
+                LFS_O_WRONLY | LFS_O_CREAT) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "/coffee/.../thai",
+                LFS_O_WRONLY | LFS_O_CREAT) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+
+        lfs_file_open(&lfs, &file, "/coffee/.../drip",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "/coffee/.../coldbrew",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "/coffee/.../turkish",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "/coffee/.../tubruk",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "/coffee/.../vietnamese",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "/coffee/.../thai",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+    }
+
+    // dir open paths, only works on dirs!
+    if (DIR) {
+        lfs_dir_t dir;
+        lfs_dir_open(&lfs, &dir, "/coffee/.../drip") => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+        lfs_dir_open(&lfs, &dir, "/coffee/.../coldbrew") => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+        lfs_dir_open(&lfs, &dir, "/coffee/.../turkish") => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+        lfs_dir_open(&lfs, &dir, "/coffee/.../tubruk") => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+        lfs_dir_open(&lfs, &dir, "/coffee/.../vietnamese") => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+        lfs_dir_open(&lfs, &dir, "/coffee/.../thai") => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+    } else {
+        lfs_dir_t dir;
+        lfs_dir_open(&lfs, &dir, "/coffee/.../drip") => LFS_ERR_NOTDIR;
+        lfs_dir_open(&lfs, &dir, "/coffee/.../coldbrew") => LFS_ERR_NOTDIR;
+        lfs_dir_open(&lfs, &dir, "/coffee/.../turkish") => LFS_ERR_NOTDIR;
+        lfs_dir_open(&lfs, &dir, "/coffee/.../tubruk") => LFS_ERR_NOTDIR;
+        lfs_dir_open(&lfs, &dir, "/coffee/.../vietnamese") => LFS_ERR_NOTDIR;
+        lfs_dir_open(&lfs, &dir, "/coffee/.../thai") => LFS_ERR_NOTDIR;
+    }
+
+    // rename paths
+    lfs_mkdir(&lfs, "espresso") => 0;
+    lfs_mkdir(&lfs, "espresso/...") => 0;
+    lfs_rename(&lfs,
+            "/coffee/.../drip",
+            "/espresso/.../espresso") => 0;
+    lfs_rename(&lfs,
+            "/coffee/.../coldbrew",
+            "/espresso/.../americano") => 0;
+    lfs_rename(&lfs,
+            "/coffee/.../turkish",
+            "/espresso/.../macchiato") => 0;
+    lfs_rename(&lfs,
+            "/coffee/.../tubruk",
+            "/espresso/.../latte") => 0;
+    lfs_rename(&lfs,
+            "/coffee/.../vietnamese",
+            "/espresso/.../cappuccino") => 0;
+    lfs_rename(&lfs,
+            "/coffee/.../thai",
+            "/espresso/.../mocha") => 0;
+
+    // stat paths
+    lfs_stat(&lfs, "/espresso/.../espresso", &info) => 0;
+    assert(strcmp(info.name, "espresso") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "/espresso/.../americano", &info) => 0;
+    assert(strcmp(info.name, "americano") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "/espresso/.../macchiato", &info) => 0;
+    assert(strcmp(info.name, "macchiato") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "/espresso/.../latte", &info) => 0;
+    assert(strcmp(info.name, "latte") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "/espresso/.../cappuccino", &info) => 0;
+    assert(strcmp(info.name, "cappuccino") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "/espresso/.../mocha", &info) => 0;
+    assert(strcmp(info.name, "mocha") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+
+    lfs_stat(&lfs, "coffee/.../drip", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "coffee/.../coldbrew", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "coffee/.../turkish", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "coffee/.../tubruk", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "coffee/.../vietnamese", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "coffee/.../thai", &info) => LFS_ERR_NOENT;
+
+    // remove paths
+    lfs_remove(&lfs, "/espresso/.../espresso") => 0;
+    lfs_remove(&lfs, "/espresso/.../americano") => 0;
+    lfs_remove(&lfs, "/espresso/.../macchiato") => 0;
+    lfs_remove(&lfs, "/espresso/.../latte") => 0;
+    lfs_remove(&lfs, "/espresso/.../cappuccino") => 0;
+    lfs_remove(&lfs, "/espresso/.../mocha") => 0;
+
+    // stat paths
+    lfs_stat(&lfs, "/espresso/.../espresso", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "/espresso/.../americano", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "/espresso/.../macchiato", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "/espresso/.../latte", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "/espresso/.../cappuccino", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "/espresso/.../mocha", &info) => LFS_ERR_NOENT;
+
     lfs_unmount(&lfs) => 0;
 '''
 
-# superblock conflict test
-[cases.test_paths_superblock_conflict]
+# leading dot path test
+[cases.test_paths_leading_dots]
+defines.DIR = [false, true]
 code = '''
     lfs_t lfs;
     lfs_format(&lfs, cfg) => 0;
     lfs_mount(&lfs, cfg) => 0;
+
+    // create paths
+    lfs_mkdir(&lfs, "coffee") => 0;
+    if (DIR) {
+        lfs_mkdir(&lfs, "/coffee/.drip") => 0;
+        lfs_mkdir(&lfs, "/coffee/..coldbrew") => 0;
+        lfs_mkdir(&lfs, "/coffee/...turkish") => 0;
+        lfs_mkdir(&lfs, "/coffee/....tubruk") => 0;
+        lfs_mkdir(&lfs, "/coffee/.....vietnamese") => 0;
+        lfs_mkdir(&lfs, "/coffee/......thai") => 0;
+    } else {
+        lfs_file_t file;
+        lfs_file_open(&lfs, &file, "/coffee/.drip",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "/coffee/..coldbrew",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "/coffee/...turkish",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "/coffee/....tubruk",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "/coffee/.....vietnamese",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "/coffee/......thai",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+    }
+
+    // stat paths
     struct lfs_info info;
-    lfs_stat(&lfs, "littlefs", &info) => LFS_ERR_NOENT;
-    lfs_remove(&lfs, "littlefs") => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "/coffee/.drip", &info) => 0;
+    assert(strcmp(info.name, ".drip") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "/coffee/..coldbrew", &info) => 0;
+    assert(strcmp(info.name, "..coldbrew") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "/coffee/...turkish", &info) => 0;
+    assert(strcmp(info.name, "...turkish") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "/coffee/....tubruk", &info) => 0;
+    assert(strcmp(info.name, "....tubruk") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "/coffee/.....vietnamese", &info) => 0;
+    assert(strcmp(info.name, ".....vietnamese") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "/coffee/......thai", &info) => 0;
+    assert(strcmp(info.name, "......thai") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+
+    // file open paths, only works on files!
+    if (DIR) {
+        lfs_file_t file;
+        lfs_file_open(&lfs, &file, "/coffee/.drip",
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "/coffee/..coldbrew",
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "/coffee/...turkish",
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "/coffee/....tubruk",
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "/coffee/.....vietnamese",
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "/coffee/......thai",
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+
+        lfs_file_open(&lfs, &file, "/coffee/.drip",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "/coffee/..coldbrew",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "/coffee/...turkish",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "/coffee/....tubruk",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "/coffee/.....vietnamese",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "/coffee/......thai",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+
+        lfs_file_open(&lfs, &file, "/coffee/.drip",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "/coffee/..coldbrew",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "/coffee/...turkish",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "/coffee/....tubruk",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "/coffee/.....vietnamese",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "/coffee/......thai",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+    } else {
+        lfs_file_t file;
+        lfs_file_open(&lfs, &file, "/coffee/.drip",
+                LFS_O_RDONLY) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "/coffee/..coldbrew",
+                LFS_O_RDONLY) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "/coffee/...turkish",
+                LFS_O_RDONLY) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "/coffee/....tubruk",
+                LFS_O_RDONLY) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "/coffee/.....vietnamese",
+                LFS_O_RDONLY) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "/coffee/......thai",
+                LFS_O_RDONLY) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+
+        lfs_file_open(&lfs, &file, "/coffee/.drip",
+                LFS_O_WRONLY | LFS_O_CREAT) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "/coffee/..coldbrew",
+                LFS_O_WRONLY | LFS_O_CREAT) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "/coffee/...turkish",
+                LFS_O_WRONLY | LFS_O_CREAT) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "/coffee/....tubruk",
+                LFS_O_WRONLY | LFS_O_CREAT) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "/coffee/.....vietnamese",
+                LFS_O_WRONLY | LFS_O_CREAT) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "/coffee/......thai",
+                LFS_O_WRONLY | LFS_O_CREAT) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+
+        lfs_file_open(&lfs, &file, "/coffee/.drip",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "/coffee/..coldbrew",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "/coffee/...turkish",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "/coffee/....tubruk",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "/coffee/.....vietnamese",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "/coffee/......thai",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+    }
+
+    // dir open paths, only works on dirs!
+    if (DIR) {
+        lfs_dir_t dir;
+        lfs_dir_open(&lfs, &dir, "/coffee/.drip") => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+        lfs_dir_open(&lfs, &dir, "/coffee/..coldbrew") => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+        lfs_dir_open(&lfs, &dir, "/coffee/...turkish") => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+        lfs_dir_open(&lfs, &dir, "/coffee/....tubruk") => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+        lfs_dir_open(&lfs, &dir, "/coffee/.....vietnamese") => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+        lfs_dir_open(&lfs, &dir, "/coffee/......thai") => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+    } else {
+        lfs_dir_t dir;
+        lfs_dir_open(&lfs, &dir, "/coffee/.drip") => LFS_ERR_NOTDIR;
+        lfs_dir_open(&lfs, &dir, "/coffee/..coldbrew") => LFS_ERR_NOTDIR;
+        lfs_dir_open(&lfs, &dir, "/coffee/...turkish") => LFS_ERR_NOTDIR;
+        lfs_dir_open(&lfs, &dir, "/coffee/....tubruk") => LFS_ERR_NOTDIR;
+        lfs_dir_open(&lfs, &dir, "/coffee/.....vietnamese") => LFS_ERR_NOTDIR;
+        lfs_dir_open(&lfs, &dir, "/coffee/......thai") => LFS_ERR_NOTDIR;
+    }
+
+    // rename paths
+    lfs_mkdir(&lfs, "espresso") => 0;
+    lfs_rename(&lfs,
+            "/coffee/.drip",
+            "/espresso/.espresso") => 0;
+    lfs_rename(&lfs,
+            "/coffee/..coldbrew",
+            "/espresso/..americano") => 0;
+    lfs_rename(&lfs,
+            "/coffee/...turkish",
+            "/espresso/...macchiato") => 0;
+    lfs_rename(&lfs,
+            "/coffee/....tubruk",
+            "/espresso/....latte") => 0;
+    lfs_rename(&lfs,
+            "/coffee/.....vietnamese",
+            "/espresso/.....cappuccino") => 0;
+    lfs_rename(&lfs,
+            "/coffee/......thai",
+            "/espresso/......mocha") => 0;
+
+    // stat paths
+    lfs_stat(&lfs, "/espresso/.espresso", &info) => 0;
+    assert(strcmp(info.name, ".espresso") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "/espresso/..americano", &info) => 0;
+    assert(strcmp(info.name, "..americano") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "/espresso/...macchiato", &info) => 0;
+    assert(strcmp(info.name, "...macchiato") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "/espresso/....latte", &info) => 0;
+    assert(strcmp(info.name, "....latte") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "/espresso/.....cappuccino", &info) => 0;
+    assert(strcmp(info.name, ".....cappuccino") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "/espresso/......mocha", &info) => 0;
+    assert(strcmp(info.name, "......mocha") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+
+    lfs_stat(&lfs, "coffee/.drip", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "coffee/..coldbrew", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "coffee/...turkish", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "coffee/....tubruk", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "coffee/.....vietnamese", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "coffee/......thai", &info) => LFS_ERR_NOENT;
+
+    // remove paths
+    lfs_remove(&lfs, "/espresso/.espresso") => 0;
+    lfs_remove(&lfs, "/espresso/..americano") => 0;
+    lfs_remove(&lfs, "/espresso/...macchiato") => 0;
+    lfs_remove(&lfs, "/espresso/....latte") => 0;
+    lfs_remove(&lfs, "/espresso/.....cappuccino") => 0;
+    lfs_remove(&lfs, "/espresso/......mocha") => 0;
+
+    // stat paths
+    lfs_stat(&lfs, "/espresso/.espresso", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "/espresso/..americano", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "/espresso/...macchiato", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "/espresso/....latte", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "/espresso/.....cappuccino", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "/espresso/......mocha", &info) => LFS_ERR_NOENT;
 
-    lfs_mkdir(&lfs, "littlefs") => 0;
-    lfs_stat(&lfs, "littlefs", &info) => 0;
-    assert(strcmp(info.name, "littlefs") == 0);
-    assert(info.type == LFS_TYPE_DIR);
-    lfs_remove(&lfs, "littlefs") => 0;
-    lfs_stat(&lfs, "littlefs", &info) => LFS_ERR_NOENT;
     lfs_unmount(&lfs) => 0;
 '''
 
-# max path test
-[cases.test_paths_max]
+# root dot dot path test
+[cases.test_paths_root_dotdots]
+defines.DIR = [false, true]
 code = '''
     lfs_t lfs;
     lfs_format(&lfs, cfg) => 0;
     lfs_mount(&lfs, cfg) => 0;
+
+    // create paths
+    lfs_mkdir(&lfs, "no") => 0;
     lfs_mkdir(&lfs, "coffee") => 0;
-    lfs_mkdir(&lfs, "coffee/hotcoffee") => 0;
-    lfs_mkdir(&lfs, "coffee/warmcoffee") => 0;
-    lfs_mkdir(&lfs, "coffee/coldcoffee") => 0;
+    if (DIR) {
+        lfs_mkdir(&lfs, "/../coffee/drip") => LFS_ERR_INVAL;
+        lfs_mkdir(&lfs, "/../../coffee/coldbrew") => LFS_ERR_INVAL;
+        lfs_mkdir(&lfs, "/../../../coffee/turkish") => LFS_ERR_INVAL;
+        lfs_mkdir(&lfs, "/no/../../coffee/tubruk") => LFS_ERR_INVAL;
+        lfs_mkdir(&lfs, "/no/../../../coffee/vietnamese") => LFS_ERR_INVAL;
+        lfs_mkdir(&lfs, "/no/../../../../coffee/thai") => LFS_ERR_INVAL;
+    } else {
+        lfs_file_t file;
+        lfs_file_open(&lfs, &file, "/../coffee/drip",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_INVAL;
+        lfs_file_open(&lfs, &file, "/../../coffee/coldbrew",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_INVAL;
+        lfs_file_open(&lfs, &file, "/../../../coffee/turkish",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_INVAL;
+        lfs_file_open(&lfs, &file, "/no/../../coffee/tubruk",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_INVAL;
+        lfs_file_open(&lfs, &file, "/no/../../../coffee/vietnamese",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_INVAL;
+        lfs_file_open(&lfs, &file, "/no/../../../../coffee/thai",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_INVAL;
+    }
 
-    char path[1024];
-    memset(path, 'w', LFS_NAME_MAX+1);
-    path[LFS_NAME_MAX+1] = '\0';
-    lfs_mkdir(&lfs, path) => LFS_ERR_NAMETOOLONG;
+    // ok, actually create paths
+    if (DIR) {
+        lfs_mkdir(&lfs, "coffee/drip") => 0;
+        lfs_mkdir(&lfs, "coffee/coldbrew") => 0;
+        lfs_mkdir(&lfs, "coffee/turkish") => 0;
+        lfs_mkdir(&lfs, "coffee/tubruk") => 0;
+        lfs_mkdir(&lfs, "coffee/vietnamese") => 0;
+        lfs_mkdir(&lfs, "coffee/thai") => 0;
+    } else {
+        lfs_file_t file;
+        lfs_file_open(&lfs, &file, "coffee/drip",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "coffee/coldbrew",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "coffee/turkish",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "coffee/tubruk",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "coffee/vietnamese",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "coffee/thai",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+    }
+
+    // stat paths
+    struct lfs_info info;
+    lfs_stat(&lfs, "/no/../../../../coffee/drip", &info) => LFS_ERR_INVAL;
+    lfs_stat(&lfs, "/no/../../../coffee/coldbrew", &info) => LFS_ERR_INVAL;
+    lfs_stat(&lfs, "/no/../../coffee/turkish", &info) => LFS_ERR_INVAL;
+    lfs_stat(&lfs, "/../../../coffee/tubruk", &info) => LFS_ERR_INVAL;
+    lfs_stat(&lfs, "/../../coffee/vietnamese", &info) => LFS_ERR_INVAL;
+    lfs_stat(&lfs, "/../coffee/thai", &info) => LFS_ERR_INVAL;
+
+    // file open paths, only works on files!
     lfs_file_t file;
-    lfs_file_open(&lfs, &file, path, LFS_O_WRONLY | LFS_O_CREAT)
-            => LFS_ERR_NAMETOOLONG;
+    lfs_file_open(&lfs, &file, "/../coffee/drip",
+            LFS_O_RDONLY) => LFS_ERR_INVAL;
+    lfs_file_open(&lfs, &file, "/../../coffee/coldbrew",
+            LFS_O_RDONLY) => LFS_ERR_INVAL;
+    lfs_file_open(&lfs, &file, "/../../../coffee/turkish",
+            LFS_O_RDONLY) => LFS_ERR_INVAL;
+    lfs_file_open(&lfs, &file, "/no/../../coffee/tubruk",
+            LFS_O_RDONLY) => LFS_ERR_INVAL;
+    lfs_file_open(&lfs, &file, "/no/../../../coffee/vietnamese",
+            LFS_O_RDONLY) => LFS_ERR_INVAL;
+    lfs_file_open(&lfs, &file, "/no/../../../../coffee/thai",
+            LFS_O_RDONLY) => LFS_ERR_INVAL;
+
+    lfs_file_open(&lfs, &file, "/../coffee/drip",
+            LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_INVAL;
+    lfs_file_open(&lfs, &file, "/../../coffee/coldbrew",
+            LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_INVAL;
+    lfs_file_open(&lfs, &file, "/../../../coffee/turkish",
+            LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_INVAL;
+    lfs_file_open(&lfs, &file, "/no/../../coffee/tubruk",
+            LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_INVAL;
+    lfs_file_open(&lfs, &file, "/no/../../../coffee/vietnamese",
+            LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_INVAL;
+    lfs_file_open(&lfs, &file, "/no/../../../../coffee/thai",
+            LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_INVAL;
+
+    lfs_file_open(&lfs, &file, "/../coffee/drip",
+            LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_INVAL;
+    lfs_file_open(&lfs, &file, "/../../coffee/coldbrew",
+            LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_INVAL;
+    lfs_file_open(&lfs, &file, "/../../../coffee/turkish",
+            LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_INVAL;
+    lfs_file_open(&lfs, &file, "/no/../../coffee/tubruk",
+            LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_INVAL;
+    lfs_file_open(&lfs, &file, "/no/../../../coffee/vietnamese",
+            LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_INVAL;
+    lfs_file_open(&lfs, &file, "/no/../../../../coffee/thai",
+            LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_INVAL;
+
+    // dir open paths, only works on dirs!
+    lfs_dir_t dir;
+    lfs_dir_open(&lfs, &dir, "/../coffee/drip") => LFS_ERR_INVAL;
+    lfs_dir_open(&lfs, &dir, "/../../coffee/coldbrew") => LFS_ERR_INVAL;
+    lfs_dir_open(&lfs, &dir, "/../../../coffee/turkish") => LFS_ERR_INVAL;
+    lfs_dir_open(&lfs, &dir, "/no/../../coffee/tubruk") => LFS_ERR_INVAL;
+    lfs_dir_open(&lfs, &dir, "/no/../../../coffee/vietnamese") => LFS_ERR_INVAL;
+    lfs_dir_open(&lfs, &dir, "/no/../../../../coffee/thai") => LFS_ERR_INVAL;
+
+    // rename paths
+    lfs_mkdir(&lfs, "espresso") => 0;
+    // bad source
+    lfs_rename(&lfs,
+            "/no/../../../../coffee/drip",
+            "espresso/espresso") => LFS_ERR_INVAL;
+    lfs_rename(&lfs,
+            "/no/../../../coffee/coldbrew",
+            "espresso/americano") => LFS_ERR_INVAL;
+    lfs_rename(&lfs,
+            "/no/../../coffee/turkish",
+            "espresso/macchiato") => LFS_ERR_INVAL;
+    lfs_rename(&lfs,
+            "/../../../coffee/tubruk",
+            "espresso/latte") => LFS_ERR_INVAL;
+    lfs_rename(&lfs,
+            "/../../coffee/vietnamese",
+            "espresso/cappuccino") => LFS_ERR_INVAL;
+    lfs_rename(&lfs,
+            "/../coffee/thai",
+            "espresso/mocha") => LFS_ERR_INVAL;
+
+    // bad destination
+    lfs_rename(&lfs,
+            "coffee/drip",
+            "/../espresso/espresso") => LFS_ERR_INVAL;
+    lfs_rename(&lfs,
+            "coffee/coldbrew",
+            "/../../espresso/americano") => LFS_ERR_INVAL;
+    lfs_rename(&lfs,
+            "coffee/turkish",
+            "/../../../espresso/macchiato") => LFS_ERR_INVAL;
+    lfs_rename(&lfs,
+            "coffee/tubruk",
+            "/no/../../espresso/latte") => LFS_ERR_INVAL;
+    lfs_rename(&lfs,
+            "coffee/vietnamese",
+            "/no/../../../espresso/cappuccino") => LFS_ERR_INVAL;
+    lfs_rename(&lfs,
+            "coffee/thai",
+            "/no/../../../../espresso/mocha") => LFS_ERR_INVAL;
+
+    // bad source and bad destination
+    lfs_rename(&lfs,
+            "/no/../../../../coffee/drip",
+            "/../espresso/espresso") => LFS_ERR_INVAL;
+    lfs_rename(&lfs,
+            "/no/../../../coffee/coldbrew",
+            "/../../espresso/americano") => LFS_ERR_INVAL;
+    lfs_rename(&lfs,
+            "/no/../../coffee/turkish",
+            "/../../../espresso/macchiato") => LFS_ERR_INVAL;
+    lfs_rename(&lfs,
+            "/../../../coffee/tubruk",
+            "/no/../../espresso/latte") => LFS_ERR_INVAL;
+    lfs_rename(&lfs,
+            "/../../coffee/vietnamese",
+            "/no/../../../espresso/cappuccino") => LFS_ERR_INVAL;
+    lfs_rename(&lfs,
+            "/../coffee/thai",
+            "/no/../../../../espresso/mocha") => LFS_ERR_INVAL;
+
+    // here's a weird one, what happens if our rename is also a noop?
+    lfs_rename(&lfs,
+            "/no/../../../../coffee/drip",
+            "/no/../../../../coffee/drip") => LFS_ERR_INVAL;
+    lfs_rename(&lfs,
+            "/no/../../../coffee/coldbrew",
+            "/no/../../../coffee/coldbrew") => LFS_ERR_INVAL;
+    lfs_rename(&lfs,
+            "/no/../../coffee/turkish",
+            "/no/../../coffee/turkish") => LFS_ERR_INVAL;
+    lfs_rename(&lfs,
+            "/../../../coffee/tubruk",
+            "/../../../coffee/tubruk") => LFS_ERR_INVAL;
+    lfs_rename(&lfs,
+            "/../../coffee/vietnamese",
+            "/../../coffee/vietnamese") => LFS_ERR_INVAL;
+    lfs_rename(&lfs,
+            "/../coffee/thai",
+            "/../coffee/thai") => LFS_ERR_INVAL;
+
+    // remove paths
+    lfs_remove(&lfs, "/../espresso/espresso") => LFS_ERR_INVAL;
+    lfs_remove(&lfs, "/../../espresso/americano") => LFS_ERR_INVAL;
+    lfs_remove(&lfs, "/../../../espresso/macchiato") => LFS_ERR_INVAL;
+    lfs_remove(&lfs, "/no/../../espresso/latte") => LFS_ERR_INVAL;
+    lfs_remove(&lfs, "/no/../../../espresso/cappuccino") => LFS_ERR_INVAL;
+    lfs_remove(&lfs, "/no/../../../../espresso/mocha") => LFS_ERR_INVAL;
+
+    // stat paths
+    lfs_stat(&lfs, "espresso/espresso", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "espresso/americano", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "espresso/macchiato", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "espresso/latte", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "espresso/cappuccino", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "espresso/mocha", &info) => LFS_ERR_NOENT;
+
+    lfs_stat(&lfs, "coffee/drip", &info) => 0;
+    assert(strcmp(info.name, "drip") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "coffee/coldbrew", &info) => 0;
+    assert(strcmp(info.name, "coldbrew") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "coffee/turkish", &info) => 0;
+    assert(strcmp(info.name, "turkish") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "coffee/tubruk", &info) => 0;
+    assert(strcmp(info.name, "tubruk") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "coffee/vietnamese", &info) => 0;
+    assert(strcmp(info.name, "vietnamese") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "coffee/thai", &info) => 0;
+    assert(strcmp(info.name, "thai") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
 
-    memcpy(path, "coffee/", strlen("coffee/"));
-    memset(path+strlen("coffee/"), 'w', LFS_NAME_MAX+1);
-    path[strlen("coffee/")+LFS_NAME_MAX+1] = '\0';
-    lfs_mkdir(&lfs, path) => LFS_ERR_NAMETOOLONG;
-    lfs_file_open(&lfs, &file, path, LFS_O_WRONLY | LFS_O_CREAT)
-            => LFS_ERR_NAMETOOLONG;
     lfs_unmount(&lfs) => 0;
 '''
 
-# really big path test
-[cases.test_paths_really_big]
+# trailing noent tests
+[cases.test_paths_noent]
+defines.DIR = [false, true]
 code = '''
     lfs_t lfs;
     lfs_format(&lfs, cfg) => 0;
     lfs_mount(&lfs, cfg) => 0;
+
+    // create paths
     lfs_mkdir(&lfs, "coffee") => 0;
-    lfs_mkdir(&lfs, "coffee/hotcoffee") => 0;
-    lfs_mkdir(&lfs, "coffee/warmcoffee") => 0;
-    lfs_mkdir(&lfs, "coffee/coldcoffee") => 0;
+    if (DIR) {
+        lfs_mkdir(&lfs, "coffee/drip") => 0;
+        lfs_mkdir(&lfs, "coffee/coldbrew") => 0;
+        lfs_mkdir(&lfs, "coffee/turkish") => 0;
+        lfs_mkdir(&lfs, "coffee/tubruk") => 0;
+        lfs_mkdir(&lfs, "coffee/vietnamese") => 0;
+        lfs_mkdir(&lfs, "coffee/thai") => 0;
+    } else {
+        lfs_file_t file;
+        lfs_file_open(&lfs, &file, "coffee/drip",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "coffee/coldbrew",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "coffee/turkish",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "coffee/tubruk",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "coffee/vietnamese",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "coffee/thai",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+    }
 
-    char path[1024];
-    memset(path, 'w', LFS_NAME_MAX);
-    path[LFS_NAME_MAX] = '\0';
-    lfs_mkdir(&lfs, path) => 0;
-    lfs_remove(&lfs, path) => 0;
+    // stat paths
+    struct lfs_info info;
+    lfs_stat(&lfs, "coffee/_rip", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "coffee/c_ldbrew", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "coffee/tu_kish", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "coffee/tub_uk", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "coffee/_vietnamese", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "coffee/thai_", &info) => LFS_ERR_NOENT;
+
+    // file open paths, only works on files!
     lfs_file_t file;
-    lfs_file_open(&lfs, &file, path,
-            LFS_O_WRONLY | LFS_O_CREAT) => 0;
-    lfs_file_close(&lfs, &file) => 0;
-    lfs_remove(&lfs, path) => 0;
+    lfs_file_open(&lfs, &file, "coffee/_rip",
+            LFS_O_RDONLY) => LFS_ERR_NOENT;
+    lfs_file_open(&lfs, &file, "coffee/c_ldbrew",
+            LFS_O_RDONLY) => LFS_ERR_NOENT;
+    lfs_file_open(&lfs, &file, "coffee/tu_kish",
+            LFS_O_RDONLY) => LFS_ERR_NOENT;
+    lfs_file_open(&lfs, &file, "coffee/tub_uk",
+            LFS_O_RDONLY) => LFS_ERR_NOENT;
+    lfs_file_open(&lfs, &file, "coffee/_vietnamese",
+            LFS_O_RDONLY) => LFS_ERR_NOENT;
+    lfs_file_open(&lfs, &file, "coffee/thai_",
+            LFS_O_RDONLY) => LFS_ERR_NOENT;
+
+    // dir open paths, only works on dirs!
+    lfs_dir_t dir;
+    lfs_dir_open(&lfs, &dir, "coffee/_rip") => LFS_ERR_NOENT;
+    lfs_dir_open(&lfs, &dir, "coffee/c_ldbrew") => LFS_ERR_NOENT;
+    lfs_dir_open(&lfs, &dir, "coffee/tu_kish") => LFS_ERR_NOENT;
+    lfs_dir_open(&lfs, &dir, "coffee/tub_uk") => LFS_ERR_NOENT;
+    lfs_dir_open(&lfs, &dir, "coffee/_vietnamese") => LFS_ERR_NOENT;
+    lfs_dir_open(&lfs, &dir, "coffee/thai_") => LFS_ERR_NOENT;
+
+    // rename paths
+    lfs_mkdir(&lfs, "espresso") => 0;
+    lfs_rename(&lfs,
+            "coffee/_rip",
+            "espresso/espresso") => LFS_ERR_NOENT;
+    lfs_rename(&lfs,
+            "coffee/c_ldbrew",
+            "espresso/americano") => LFS_ERR_NOENT;
+    lfs_rename(&lfs,
+            "coffee/tu_kish",
+            "espresso/macchiato") => LFS_ERR_NOENT;
+    lfs_rename(&lfs,
+            "coffee/tub_uk",
+            "espresso/latte") => LFS_ERR_NOENT;
+    lfs_rename(&lfs,
+            "coffee/_vietnamese",
+            "espresso/cappuccino") => LFS_ERR_NOENT;
+    lfs_rename(&lfs,
+            "coffee/thai_",
+            "espresso/mocha") => LFS_ERR_NOENT;
+
+    // here's a weird one, what happens if our rename is also a noop?
+    lfs_rename(&lfs,
+            "coffee/_rip",
+            "coffee/_rip") => LFS_ERR_NOENT;
+    lfs_rename(&lfs,
+            "coffee/c_ldbrew",
+            "coffee/c_ldbrew") => LFS_ERR_NOENT;
+    lfs_rename(&lfs,
+            "coffee/tu_kish",
+            "coffee/tu_kish") => LFS_ERR_NOENT;
+    lfs_rename(&lfs,
+            "coffee/tub_uk",
+            "coffee/tub_uk") => LFS_ERR_NOENT;
+    lfs_rename(&lfs,
+            "coffee/_vietnamese",
+            "coffee/_vietnamese") => LFS_ERR_NOENT;
+    lfs_rename(&lfs,
+            "coffee/thai_",
+            "coffee/thai_") => LFS_ERR_NOENT;
+
+    // remove paths
+    lfs_remove(&lfs, "coffee/_rip") => LFS_ERR_NOENT;
+    lfs_remove(&lfs, "coffee/c_ldbrew") => LFS_ERR_NOENT;
+    lfs_remove(&lfs, "coffee/tu_kish") => LFS_ERR_NOENT;
+    lfs_remove(&lfs, "coffee/tub_uk") => LFS_ERR_NOENT;
+    lfs_remove(&lfs, "coffee/_vietnamese") => LFS_ERR_NOENT;
+    lfs_remove(&lfs, "coffee/thai_") => LFS_ERR_NOENT;
+
+    // stat paths
+    lfs_stat(&lfs, "espresso/espresso", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "espresso/americano", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "espresso/macchiato", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "espresso/latte", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "espresso/cappuccino", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "espresso/mocha", &info) => LFS_ERR_NOENT;
+
+    lfs_stat(&lfs, "coffee/drip", &info) => 0;
+    assert(strcmp(info.name, "drip") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "coffee/coldbrew", &info) => 0;
+    assert(strcmp(info.name, "coldbrew") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "coffee/turkish", &info) => 0;
+    assert(strcmp(info.name, "turkish") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "coffee/tubruk", &info) => 0;
+    assert(strcmp(info.name, "tubruk") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "coffee/vietnamese", &info) => 0;
+    assert(strcmp(info.name, "vietnamese") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "coffee/thai", &info) => 0;
+    assert(strcmp(info.name, "thai") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
 
-    memcpy(path, "coffee/", strlen("coffee/"));
-    memset(path+strlen("coffee/"), 'w', LFS_NAME_MAX);
-    path[strlen("coffee/")+LFS_NAME_MAX] = '\0';
-    lfs_mkdir(&lfs, path) => 0;
-    lfs_remove(&lfs, path) => 0;
-    lfs_file_open(&lfs, &file, path,
-            LFS_O_WRONLY | LFS_O_CREAT) => 0;
-    lfs_file_close(&lfs, &file) => 0;
-    lfs_remove(&lfs, path) => 0;
     lfs_unmount(&lfs) => 0;
 '''
 
+# parent noent tests
+[cases.test_paths_noent_parent]
+defines.DIR = [false, true]
+code = '''
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
+
+    // create paths
+    lfs_mkdir(&lfs, "coffee") => 0;
+    if (DIR) {
+        lfs_mkdir(&lfs, "_offee/drip") => LFS_ERR_NOENT;
+        lfs_mkdir(&lfs, "c_ffee/coldbrew") => LFS_ERR_NOENT;
+        lfs_mkdir(&lfs, "co_fee/turkish") => LFS_ERR_NOENT;
+        lfs_mkdir(&lfs, "cof_ee/tubruk") => LFS_ERR_NOENT;
+        lfs_mkdir(&lfs, "_coffee/vietnamese") => LFS_ERR_NOENT;
+        lfs_mkdir(&lfs, "coffee_/thai") => LFS_ERR_NOENT;
+    } else {
+        lfs_file_t file;
+        lfs_file_open(&lfs, &file, "_offee/drip",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_NOENT;
+        lfs_file_open(&lfs, &file, "c_ffee/coldbrew",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_NOENT;
+        lfs_file_open(&lfs, &file, "co_fee/turkish",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_NOENT;
+        lfs_file_open(&lfs, &file, "cof_ee/tubruk",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_NOENT;
+        lfs_file_open(&lfs, &file, "_coffee/vietnamese",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_NOENT;
+        lfs_file_open(&lfs, &file, "coffee_/thai",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_NOENT;
+    }
+
+    // ok, actually create paths
+    if (DIR) {
+        lfs_mkdir(&lfs, "coffee/drip") => 0;
+        lfs_mkdir(&lfs, "coffee/coldbrew") => 0;
+        lfs_mkdir(&lfs, "coffee/turkish") => 0;
+        lfs_mkdir(&lfs, "coffee/tubruk") => 0;
+        lfs_mkdir(&lfs, "coffee/vietnamese") => 0;
+        lfs_mkdir(&lfs, "coffee/thai") => 0;
+    } else {
+        lfs_file_t file;
+        lfs_file_open(&lfs, &file, "coffee/drip",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "coffee/coldbrew",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "coffee/turkish",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "coffee/tubruk",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "coffee/vietnamese",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "coffee/thai",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+    }
+
+    // stat paths
+    struct lfs_info info;
+    lfs_stat(&lfs, "_offee/drip", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "c_ffee/coldbrew", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "co_fee/turkish", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "cof_ee/tubruk", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "_coffee/vietnamese", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "coffee_/thai", &info) => LFS_ERR_NOENT;
+
+    // file open paths, only works on files!
+    lfs_file_t file;
+    lfs_file_open(&lfs, &file, "_offee/drip",
+            LFS_O_RDONLY) => LFS_ERR_NOENT;
+    lfs_file_open(&lfs, &file, "c_ffee/coldbrew",
+            LFS_O_RDONLY) => LFS_ERR_NOENT;
+    lfs_file_open(&lfs, &file, "co_fee/turkish",
+            LFS_O_RDONLY) => LFS_ERR_NOENT;
+    lfs_file_open(&lfs, &file, "cof_ee/tubruk",
+            LFS_O_RDONLY) => LFS_ERR_NOENT;
+    lfs_file_open(&lfs, &file, "_coffee/vietnamese",
+            LFS_O_RDONLY) => LFS_ERR_NOENT;
+    lfs_file_open(&lfs, &file, "coffee_/thai",
+            LFS_O_RDONLY) => LFS_ERR_NOENT;
+
+    lfs_file_open(&lfs, &file, "_offee/drip",
+            LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_NOENT;
+    lfs_file_open(&lfs, &file, "c_ffee/coldbrew",
+            LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_NOENT;
+    lfs_file_open(&lfs, &file, "co_fee/turkish",
+            LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_NOENT;
+    lfs_file_open(&lfs, &file, "cof_ee/tubruk",
+            LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_NOENT;
+    lfs_file_open(&lfs, &file, "_coffee/vietnamese",
+            LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_NOENT;
+    lfs_file_open(&lfs, &file, "coffee_/thai",
+            LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_NOENT;
+
+    lfs_file_open(&lfs, &file, "_offee/drip",
+            LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_NOENT;
+    lfs_file_open(&lfs, &file, "c_ffee/coldbrew",
+            LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_NOENT;
+    lfs_file_open(&lfs, &file, "co_fee/turkish",
+            LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_NOENT;
+    lfs_file_open(&lfs, &file, "cof_ee/tubruk",
+            LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_NOENT;
+    lfs_file_open(&lfs, &file, "_coffee/vietnamese",
+            LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_NOENT;
+    lfs_file_open(&lfs, &file, "coffee_/thai",
+            LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_NOENT;
+
+    // dir open paths, only works on dirs!
+    lfs_dir_t dir;
+    lfs_dir_open(&lfs, &dir, "_offee/drip") => LFS_ERR_NOENT;
+    lfs_dir_open(&lfs, &dir, "c_ffee/coldbrew") => LFS_ERR_NOENT;
+    lfs_dir_open(&lfs, &dir, "co_fee/turkish") => LFS_ERR_NOENT;
+    lfs_dir_open(&lfs, &dir, "cof_ee/tubruk") => LFS_ERR_NOENT;
+    lfs_dir_open(&lfs, &dir, "_coffee/vietnamese") => LFS_ERR_NOENT;
+    lfs_dir_open(&lfs, &dir, "coffee_/thai") => LFS_ERR_NOENT;
+
+    // rename paths
+    lfs_mkdir(&lfs, "espresso") => 0;
+    // bad source
+    lfs_rename(&lfs,
+            "_offee/drip",
+            "espresso/espresso") => LFS_ERR_NOENT;
+    lfs_rename(&lfs,
+            "c_ffee/coldbrew",
+            "espresso/americano") => LFS_ERR_NOENT;
+    lfs_rename(&lfs,
+            "co_fee/turkish",
+            "espresso/macchiato") => LFS_ERR_NOENT;
+    lfs_rename(&lfs,
+            "cof_ee/tubruk",
+            "espresso/latte") => LFS_ERR_NOENT;
+    lfs_rename(&lfs,
+            "_coffee/vietnamese",
+            "espresso/cappuccino") => LFS_ERR_NOENT;
+    lfs_rename(&lfs,
+            "coffee_/thai",
+            "espresso/mocha") => LFS_ERR_NOENT;
+
+    // bad destination
+    lfs_rename(&lfs,
+            "coffee/drip",
+            "_spresso/espresso") => LFS_ERR_NOENT;
+    lfs_rename(&lfs,
+            "coffee/coldbrew",
+            "e_presso/americano") => LFS_ERR_NOENT;
+    lfs_rename(&lfs,
+            "coffee/turkish",
+            "es_resso/macchiato") => LFS_ERR_NOENT;
+    lfs_rename(&lfs,
+            "coffee/tubruk",
+            "esp_esso/latte") => LFS_ERR_NOENT;
+    lfs_rename(&lfs,
+            "coffee/vietnamese",
+            "_espresso/cappuccino") => LFS_ERR_NOENT;
+    lfs_rename(&lfs,
+            "coffee/thai",
+            "espresso_/mocha") => LFS_ERR_NOENT;
+
+    // bad source and bad destination
+    lfs_rename(&lfs,
+            "_offee/drip",
+            "_spresso/espresso") => LFS_ERR_NOENT;
+    lfs_rename(&lfs,
+            "c_ffee/coldbrew",
+            "e_presso/americano") => LFS_ERR_NOENT;
+    lfs_rename(&lfs,
+            "co_fee/turkish",
+            "es_resso/macchiato") => LFS_ERR_NOENT;
+    lfs_rename(&lfs,
+            "cof_ee/tubruk",
+            "esp_esso/latte") => LFS_ERR_NOENT;
+    lfs_rename(&lfs,
+            "_coffee/vietnamese",
+            "_espresso/cappuccino") => LFS_ERR_NOENT;
+    lfs_rename(&lfs,
+            "coffee_/thai",
+            "espresso_/mocha") => LFS_ERR_NOENT;
+
+    // here's a weird one, what happens if our rename is also a noop?
+    lfs_rename(&lfs,
+            "_offee/drip",
+            "_offee/drip") => LFS_ERR_NOENT;
+    lfs_rename(&lfs,
+            "c_ffee/coldbrew",
+            "c_ffee/coldbrew") => LFS_ERR_NOENT;
+    lfs_rename(&lfs,
+            "co_fee/turkish",
+            "co_fee/turkish") => LFS_ERR_NOENT;
+    lfs_rename(&lfs,
+            "cof_ee/tubruk",
+            "cof_ee/tubruk") => LFS_ERR_NOENT;
+    lfs_rename(&lfs,
+            "_coffee/vietnamese",
+            "_coffee/vietnamese") => LFS_ERR_NOENT;
+    lfs_rename(&lfs,
+            "coffee_/thai",
+            "coffee_/thai") => LFS_ERR_NOENT;
+
+    // remove paths
+    lfs_remove(&lfs, "_offee/drip") => LFS_ERR_NOENT;
+    lfs_remove(&lfs, "c_ffee/coldbrew") => LFS_ERR_NOENT;
+    lfs_remove(&lfs, "co_fee/turkish") => LFS_ERR_NOENT;
+    lfs_remove(&lfs, "cof_ee/tubruk") => LFS_ERR_NOENT;
+    lfs_remove(&lfs, "_coffee/vietnamese") => LFS_ERR_NOENT;
+    lfs_remove(&lfs, "coffee_/thai") => LFS_ERR_NOENT;
+
+    // stat paths
+    lfs_stat(&lfs, "espresso/espresso", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "espresso/americano", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "espresso/macchiato", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "espresso/latte", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "espresso/cappuccino", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "espresso/mocha", &info) => LFS_ERR_NOENT;
+
+    lfs_stat(&lfs, "coffee/drip", &info) => 0;
+    assert(strcmp(info.name, "drip") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "coffee/coldbrew", &info) => 0;
+    assert(strcmp(info.name, "coldbrew") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "coffee/turkish", &info) => 0;
+    assert(strcmp(info.name, "turkish") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "coffee/tubruk", &info) => 0;
+    assert(strcmp(info.name, "tubruk") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "coffee/vietnamese", &info) => 0;
+    assert(strcmp(info.name, "vietnamese") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "coffee/thai", &info) => 0;
+    assert(strcmp(info.name, "thai") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+
+    lfs_unmount(&lfs) => 0;
+'''
+
+# parent notdir tests
+[cases.test_paths_notdir_parent]
+defines.DIR = [false, true]
+code = '''
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
+
+    // create paths
+    lfs_file_t file;
+    lfs_file_open(&lfs, &file, "drip",
+            LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+    lfs_file_close(&lfs, &file) => 0;
+    lfs_file_open(&lfs, &file, "coldbrew",
+            LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+    lfs_file_close(&lfs, &file) => 0;
+    lfs_file_open(&lfs, &file, "turkish",
+            LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+    lfs_file_close(&lfs, &file) => 0;
+    lfs_file_open(&lfs, &file, "tubruk",
+            LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+    lfs_file_close(&lfs, &file) => 0;
+    lfs_file_open(&lfs, &file, "vietnamese",
+            LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+    lfs_file_close(&lfs, &file) => 0;
+    lfs_file_open(&lfs, &file, "thai",
+            LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+    lfs_file_close(&lfs, &file) => 0;
+
+    if (DIR) {
+        lfs_mkdir(&lfs, "drip/coffee") => LFS_ERR_NOTDIR;
+        lfs_mkdir(&lfs, "coldbrew/coffee") => LFS_ERR_NOTDIR;
+        lfs_mkdir(&lfs, "turkish/coffee") => LFS_ERR_NOTDIR;
+        lfs_mkdir(&lfs, "tubruk/coffee") => LFS_ERR_NOTDIR;
+        lfs_mkdir(&lfs, "vietnamese/coffee") => LFS_ERR_NOTDIR;
+        lfs_mkdir(&lfs, "thai/coffee") => LFS_ERR_NOTDIR;
+    } else {
+        lfs_file_t file;
+        lfs_file_open(&lfs, &file, "drip/coffee",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_NOTDIR;
+        lfs_file_open(&lfs, &file, "coldbrew/coffee",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_NOTDIR;
+        lfs_file_open(&lfs, &file, "turkish/coffee",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_NOTDIR;
+        lfs_file_open(&lfs, &file, "tubruk/coffee",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_NOTDIR;
+        lfs_file_open(&lfs, &file, "vietnamese/coffee",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_NOTDIR;
+        lfs_file_open(&lfs, &file, "thai/coffee",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_NOTDIR;
+    }
+
+    // stat paths
+    struct lfs_info info;
+    lfs_stat(&lfs, "drip/coffee", &info) => LFS_ERR_NOTDIR;
+    lfs_stat(&lfs, "coldbrew/coffee", &info) => LFS_ERR_NOTDIR;
+    lfs_stat(&lfs, "turkish/coffee", &info) => LFS_ERR_NOTDIR;
+    lfs_stat(&lfs, "tubruk/coffee", &info) => LFS_ERR_NOTDIR;
+    lfs_stat(&lfs, "vietnamese/coffee", &info) => LFS_ERR_NOTDIR;
+    lfs_stat(&lfs, "thai/coffee", &info) => LFS_ERR_NOTDIR;
+
+    // file open paths, only works on files!
+    lfs_file_open(&lfs, &file, "drip/coffee",
+            LFS_O_RDONLY) => LFS_ERR_NOTDIR;
+    lfs_file_open(&lfs, &file, "coldbrew/coffee",
+            LFS_O_RDONLY) => LFS_ERR_NOTDIR;
+    lfs_file_open(&lfs, &file, "turkish/coffee",
+            LFS_O_RDONLY) => LFS_ERR_NOTDIR;
+    lfs_file_open(&lfs, &file, "tubruk/coffee",
+            LFS_O_RDONLY) => LFS_ERR_NOTDIR;
+    lfs_file_open(&lfs, &file, "vietnamese/coffee",
+            LFS_O_RDONLY) => LFS_ERR_NOTDIR;
+    lfs_file_open(&lfs, &file, "thai/coffee",
+            LFS_O_RDONLY) => LFS_ERR_NOTDIR;
+
+    lfs_file_open(&lfs, &file, "drip/coffee",
+            LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_NOTDIR;
+    lfs_file_open(&lfs, &file, "coldbrew/coffee",
+            LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_NOTDIR;
+    lfs_file_open(&lfs, &file, "turkish/coffee",
+            LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_NOTDIR;
+    lfs_file_open(&lfs, &file, "tubruk/coffee",
+            LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_NOTDIR;
+    lfs_file_open(&lfs, &file, "vietnamese/coffee",
+            LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_NOTDIR;
+    lfs_file_open(&lfs, &file, "thai/coffee",
+            LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_NOTDIR;
+
+    lfs_file_open(&lfs, &file, "drip/coffee",
+            LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_NOTDIR;
+    lfs_file_open(&lfs, &file, "coldbrew/coffee",
+            LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_NOTDIR;
+    lfs_file_open(&lfs, &file, "turkish/coffee",
+            LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_NOTDIR;
+    lfs_file_open(&lfs, &file, "tubruk/coffee",
+            LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_NOTDIR;
+    lfs_file_open(&lfs, &file, "vietnamese/coffee",
+            LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_NOTDIR;
+    lfs_file_open(&lfs, &file, "thai/coffee",
+            LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_NOTDIR;
+
+    // dir open paths, only works on dirs!
+    lfs_dir_t dir;
+    lfs_dir_open(&lfs, &dir, "drip/coffee") => LFS_ERR_NOTDIR;
+    lfs_dir_open(&lfs, &dir, "coldbrew/coffee") => LFS_ERR_NOTDIR;
+    lfs_dir_open(&lfs, &dir, "turkish/coffee") => LFS_ERR_NOTDIR;
+    lfs_dir_open(&lfs, &dir, "tubruk/coffee") => LFS_ERR_NOTDIR;
+    lfs_dir_open(&lfs, &dir, "vietnamese/coffee") => LFS_ERR_NOTDIR;
+    lfs_dir_open(&lfs, &dir, "thai/coffee") => LFS_ERR_NOTDIR;
+
+    // make some normal paths so we have something to rename
+    lfs_mkdir(&lfs, "coffee") => 0;
+    if (DIR) {
+        lfs_mkdir(&lfs, "coffee/drip") => 0;
+        lfs_mkdir(&lfs, "coffee/coldbrew") => 0;
+        lfs_mkdir(&lfs, "coffee/turkish") => 0;
+        lfs_mkdir(&lfs, "coffee/tubruk") => 0;
+        lfs_mkdir(&lfs, "coffee/vietnamese") => 0;
+        lfs_mkdir(&lfs, "coffee/thai") => 0;
+    } else {
+        lfs_file_t file;
+        lfs_file_open(&lfs, &file, "coffee/drip",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "coffee/coldbrew",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "coffee/turkish",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "coffee/tubruk",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "coffee/vietnamese",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "coffee/thai",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+    }
+
+    // rename paths
+    lfs_mkdir(&lfs, "espresso") => 0;
+    // bad source
+    lfs_rename(&lfs,
+            "drip/coffee",
+            "espresso/espresso") => LFS_ERR_NOTDIR;
+    lfs_rename(&lfs,
+            "coldbrew/coffee",
+            "espresso/americano") => LFS_ERR_NOTDIR;
+    lfs_rename(&lfs,
+            "turkish/coffee",
+            "espresso/macchiato") => LFS_ERR_NOTDIR;
+    lfs_rename(&lfs,
+            "tubruk/coffee",
+            "espresso/latte") => LFS_ERR_NOTDIR;
+    lfs_rename(&lfs,
+            "vietnamese/coffee",
+            "espresso/cappuccino") => LFS_ERR_NOTDIR;
+    lfs_rename(&lfs,
+            "thai/coffee",
+            "espresso/mocha") => LFS_ERR_NOTDIR;
+
+    // bad destination
+    lfs_rename(&lfs,
+            "coffee/drip",
+            "drip/espresso") => LFS_ERR_NOTDIR;
+    lfs_rename(&lfs,
+            "coffee/coldbrew",
+            "coldbrew/espresso") => LFS_ERR_NOTDIR;
+    lfs_rename(&lfs,
+            "coffee/turkish",
+            "turkish/espresso") => LFS_ERR_NOTDIR;
+    lfs_rename(&lfs,
+            "coffee/tubruk",
+            "tubruk/espresso") => LFS_ERR_NOTDIR;
+    lfs_rename(&lfs,
+            "coffee/vietnamese",
+            "vietnamese/espresso") => LFS_ERR_NOTDIR;
+    lfs_rename(&lfs,
+            "coffee/thai",
+            "thai/espresso") => LFS_ERR_NOTDIR;
+
+    // bad source and bad destination
+    lfs_rename(&lfs,
+            "drip/coffee",
+            "drip/espresso") => LFS_ERR_NOTDIR;
+    lfs_rename(&lfs,
+            "coldbrew/coffee",
+            "coldbrew/espresso") => LFS_ERR_NOTDIR;
+    lfs_rename(&lfs,
+            "turkish/coffee",
+            "turkish/espresso") => LFS_ERR_NOTDIR;
+    lfs_rename(&lfs,
+            "tubruk/coffee",
+            "tubruk/espresso") => LFS_ERR_NOTDIR;
+    lfs_rename(&lfs,
+            "vietnamese/coffee",
+            "vietnamese/espresso") => LFS_ERR_NOTDIR;
+    lfs_rename(&lfs,
+            "thai/coffee",
+            "thai/espresso") => LFS_ERR_NOTDIR;
+
+    // here's a weird one, what happens if our rename is also a noop?
+    lfs_rename(&lfs,
+            "drip/coffee",
+            "drip/coffee") => LFS_ERR_NOTDIR;
+    lfs_rename(&lfs,
+            "coldbrew/coffee",
+            "coldbrew/coffee") => LFS_ERR_NOTDIR;
+    lfs_rename(&lfs,
+            "turkish/coffee",
+            "turkish/coffee") => LFS_ERR_NOTDIR;
+    lfs_rename(&lfs,
+            "tubruk/coffee",
+            "tubruk/coffee") => LFS_ERR_NOTDIR;
+    lfs_rename(&lfs,
+            "vietnamese/coffee",
+            "vietnamese/coffee") => LFS_ERR_NOTDIR;
+    lfs_rename(&lfs,
+            "thai/coffee",
+            "thai/coffee") => LFS_ERR_NOTDIR;
+
+    // remove paths
+    lfs_stat(&lfs, "drip/espresso", &info) => LFS_ERR_NOTDIR;
+    lfs_stat(&lfs, "coldbrew/espresso", &info) => LFS_ERR_NOTDIR;
+    lfs_stat(&lfs, "turkish/espresso", &info) => LFS_ERR_NOTDIR;
+    lfs_stat(&lfs, "tubruk/espresso", &info) => LFS_ERR_NOTDIR;
+    lfs_stat(&lfs, "vietnamese/espresso", &info) => LFS_ERR_NOTDIR;
+    lfs_stat(&lfs, "thai/espresso", &info) => LFS_ERR_NOTDIR;
+
+    // stat paths
+    lfs_stat(&lfs, "drip/espresso", &info) => LFS_ERR_NOTDIR;
+    lfs_stat(&lfs, "coldbrew/espresso", &info) => LFS_ERR_NOTDIR;
+    lfs_stat(&lfs, "turkish/espresso", &info) => LFS_ERR_NOTDIR;
+    lfs_stat(&lfs, "tubruk/espresso", &info) => LFS_ERR_NOTDIR;
+    lfs_stat(&lfs, "vietnamese/espresso", &info) => LFS_ERR_NOTDIR;
+    lfs_stat(&lfs, "thai/espresso", &info) => LFS_ERR_NOTDIR;
+
+    lfs_stat(&lfs, "coffee/drip", &info) => 0;
+    assert(strcmp(info.name, "drip") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "coffee/coldbrew", &info) => 0;
+    assert(strcmp(info.name, "coldbrew") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "coffee/turkish", &info) => 0;
+    assert(strcmp(info.name, "turkish") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "coffee/tubruk", &info) => 0;
+    assert(strcmp(info.name, "tubruk") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "coffee/vietnamese", &info) => 0;
+    assert(strcmp(info.name, "vietnamese") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "coffee/thai", &info) => 0;
+    assert(strcmp(info.name, "thai") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+
+    lfs_unmount(&lfs) => 0;
+'''
+
+# noent tests with trailing slashes
+[cases.test_paths_noent_trailing_slashes]
+defines.DIR = [false, true]
+code = '''
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
+
+    // create paths
+    lfs_mkdir(&lfs, "coffee") => 0;
+    if (DIR) {
+        lfs_mkdir(&lfs, "coffee/drip") => 0;
+        lfs_mkdir(&lfs, "coffee/coldbrew") => 0;
+        lfs_mkdir(&lfs, "coffee/turkish") => 0;
+        lfs_mkdir(&lfs, "coffee/tubruk") => 0;
+        lfs_mkdir(&lfs, "coffee/vietnamese") => 0;
+        lfs_mkdir(&lfs, "coffee/thai") => 0;
+    } else {
+        lfs_file_t file;
+        lfs_file_open(&lfs, &file, "coffee/drip",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "coffee/coldbrew",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "coffee/turkish",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "coffee/tubruk",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "coffee/vietnamese",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "coffee/thai",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+    }
+
+    // stat paths
+    struct lfs_info info;
+    lfs_stat(&lfs, "coffee/_rip//////", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "coffee/c_ldbrew/////", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "coffee/tu_kish////", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "coffee/tub_uk///", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "coffee/_vietnamese//", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "coffee/thai_/", &info) => LFS_ERR_NOENT;
+
+    // file open paths, only works on files!
+    lfs_file_t file;
+    lfs_file_open(&lfs, &file, "coffee/_rip/",
+            LFS_O_RDONLY) => LFS_ERR_NOENT;
+    lfs_file_open(&lfs, &file, "coffee/c_ldbrew//",
+            LFS_O_RDONLY) => LFS_ERR_NOENT;
+    lfs_file_open(&lfs, &file, "coffee/tu_kish///",
+            LFS_O_RDONLY) => LFS_ERR_NOENT;
+    lfs_file_open(&lfs, &file, "coffee/tub_uk////",
+            LFS_O_RDONLY) => LFS_ERR_NOENT;
+    lfs_file_open(&lfs, &file, "coffee/_vietnamese/////",
+            LFS_O_RDONLY) => LFS_ERR_NOENT;
+    lfs_file_open(&lfs, &file, "coffee/thai_//////",
+            LFS_O_RDONLY) => LFS_ERR_NOENT;
+
+    lfs_file_open(&lfs, &file, "coffee/_rip/",
+            LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_NOTDIR;
+    lfs_file_open(&lfs, &file, "coffee/c_ldbrew//",
+            LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_NOTDIR;
+    lfs_file_open(&lfs, &file, "coffee/tu_kish///",
+            LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_NOTDIR;
+    lfs_file_open(&lfs, &file, "coffee/tub_uk////",
+            LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_NOTDIR;
+    lfs_file_open(&lfs, &file, "coffee/_vietnamese/////",
+            LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_NOTDIR;
+    lfs_file_open(&lfs, &file, "coffee/thai_//////",
+            LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_NOTDIR;
+
+    lfs_file_open(&lfs, &file, "coffee/_rip/",
+            LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_NOTDIR;
+    lfs_file_open(&lfs, &file, "coffee/c_ldbrew//",
+            LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_NOTDIR;
+    lfs_file_open(&lfs, &file, "coffee/tu_kish///",
+            LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_NOTDIR;
+    lfs_file_open(&lfs, &file, "coffee/tub_uk////",
+            LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_NOTDIR;
+    lfs_file_open(&lfs, &file, "coffee/_vietnamese/////",
+            LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_NOTDIR;
+    lfs_file_open(&lfs, &file, "coffee/thai_//////",
+            LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_NOTDIR;
+
+    // dir open paths, only works on dirs!
+    lfs_dir_t dir;
+    lfs_dir_open(&lfs, &dir, "coffee/_rip/") => LFS_ERR_NOENT;
+    lfs_dir_open(&lfs, &dir, "coffee/c_ldbrew//") => LFS_ERR_NOENT;
+    lfs_dir_open(&lfs, &dir, "coffee/tu_kish///") => LFS_ERR_NOENT;
+    lfs_dir_open(&lfs, &dir, "coffee/tub_uk////") => LFS_ERR_NOENT;
+    lfs_dir_open(&lfs, &dir, "coffee/_vietnamese/////") => LFS_ERR_NOENT;
+    lfs_dir_open(&lfs, &dir, "coffee/thai_//////") => LFS_ERR_NOENT;
+
+    // rename paths
+    lfs_mkdir(&lfs, "espresso") => 0;
+    // bad source
+    lfs_rename(&lfs,
+            "coffee/_rip//////",
+            "espresso/espresso") => LFS_ERR_NOENT;
+    lfs_rename(&lfs,
+            "coffee/c_ldbrew/////",
+            "espresso/americano") => LFS_ERR_NOENT;
+    lfs_rename(&lfs,
+            "coffee/tu_kish////",
+            "espresso/macchiato") => LFS_ERR_NOENT;
+    lfs_rename(&lfs,
+            "coffee/tub_uk///",
+            "espresso/latte") => LFS_ERR_NOENT;
+    lfs_rename(&lfs,
+            "coffee/_vietnamese//",
+            "espresso/cappuccino") => LFS_ERR_NOENT;
+    lfs_rename(&lfs,
+            "coffee/thai_/",
+            "espresso/mocha") => LFS_ERR_NOENT;
+
+    // bad destination
+    lfs_rename(&lfs,
+            "coffee/_rip",
+            "espresso/espresso/") => LFS_ERR_NOENT;
+    lfs_rename(&lfs,
+            "coffee/c_ldbrew",
+            "espresso/americano//") => LFS_ERR_NOENT;
+    lfs_rename(&lfs,
+            "coffee/tu_kish",
+            "espresso/macchiato///") => LFS_ERR_NOENT;
+    lfs_rename(&lfs,
+            "coffee/tub_uk",
+            "espresso/latte////") => LFS_ERR_NOENT;
+    lfs_rename(&lfs,
+            "coffee/_vietnamese",
+            "espresso/cappuccino/////") => LFS_ERR_NOENT;
+    lfs_rename(&lfs,
+            "coffee/thai_",
+            "espresso/mocha//////") => LFS_ERR_NOENT;
+
+    // bad source and bad destination
+    lfs_rename(&lfs,
+            "coffee/_rip//////",
+            "espresso/espresso/") => LFS_ERR_NOENT;
+    lfs_rename(&lfs,
+            "coffee/c_ldbrew/////",
+            "espresso/americano//") => LFS_ERR_NOENT;
+    lfs_rename(&lfs,
+            "coffee/tu_kish////",
+            "espresso/macchiato///") => LFS_ERR_NOENT;
+    lfs_rename(&lfs,
+            "coffee/tub_uk///",
+            "espresso/latte////") => LFS_ERR_NOENT;
+    lfs_rename(&lfs,
+            "coffee/_vietnamese//",
+            "espresso/cappuccino/////") => LFS_ERR_NOENT;
+    lfs_rename(&lfs,
+            "coffee/thai_/",
+            "espresso/mocha//////") => LFS_ERR_NOENT;
+
+    // here's a weird one, what happens if our rename is also a noop?
+    lfs_rename(&lfs,
+            "coffee/_rip//////",
+            "coffee/_rip//////") => LFS_ERR_NOENT;
+    lfs_rename(&lfs,
+            "coffee/c_ldbrew/////",
+            "coffee/c_ldbrew/////") => LFS_ERR_NOENT;
+    lfs_rename(&lfs,
+            "coffee/tu_kish////",
+            "coffee/tu_kish////") => LFS_ERR_NOENT;
+    lfs_rename(&lfs,
+            "coffee/tub_uk///",
+            "coffee/tub_uk///") => LFS_ERR_NOENT;
+    lfs_rename(&lfs,
+            "coffee/_vietnamese//",
+            "coffee/_vietnamese//") => LFS_ERR_NOENT;
+    lfs_rename(&lfs,
+            "coffee/thai_/",
+            "coffee/thai_/") => LFS_ERR_NOENT;
+
+    // remove paths
+    lfs_remove(&lfs, "coffee/_rip/") => LFS_ERR_NOENT;
+    lfs_remove(&lfs, "coffee/c_ldbrew//") => LFS_ERR_NOENT;
+    lfs_remove(&lfs, "coffee/tu_kish///") => LFS_ERR_NOENT;
+    lfs_remove(&lfs, "coffee/tub_uk////") => LFS_ERR_NOENT;
+    lfs_remove(&lfs, "coffee/_vietnamese/////") => LFS_ERR_NOENT;
+    lfs_remove(&lfs, "coffee/thai_//////") => LFS_ERR_NOENT;
+
+    // stat paths
+    lfs_stat(&lfs, "espresso/espresso", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "espresso/americano", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "espresso/macchiato", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "espresso/latte", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "espresso/cappuccino", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "espresso/mocha", &info) => LFS_ERR_NOENT;
+
+    lfs_stat(&lfs, "coffee/drip", &info) => 0;
+    assert(strcmp(info.name, "drip") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "coffee/coldbrew", &info) => 0;
+    assert(strcmp(info.name, "coldbrew") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "coffee/turkish", &info) => 0;
+    assert(strcmp(info.name, "turkish") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "coffee/tubruk", &info) => 0;
+    assert(strcmp(info.name, "tubruk") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "coffee/vietnamese", &info) => 0;
+    assert(strcmp(info.name, "vietnamese") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "coffee/thai", &info) => 0;
+    assert(strcmp(info.name, "thai") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+
+    lfs_unmount(&lfs) => 0;
+'''
+
+# noent tests with trailing dots
+[cases.test_paths_noent_trailing_dots]
+defines.DIR = [false, true]
+code = '''
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
+
+    // create paths
+    lfs_mkdir(&lfs, "coffee") => 0;
+    if (DIR) {
+        lfs_mkdir(&lfs, "coffee/drip") => 0;
+        lfs_mkdir(&lfs, "coffee/coldbrew") => 0;
+        lfs_mkdir(&lfs, "coffee/turkish") => 0;
+        lfs_mkdir(&lfs, "coffee/tubruk") => 0;
+        lfs_mkdir(&lfs, "coffee/vietnamese") => 0;
+        lfs_mkdir(&lfs, "coffee/thai") => 0;
+    } else {
+        lfs_file_t file;
+        lfs_file_open(&lfs, &file, "coffee/drip",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "coffee/coldbrew",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "coffee/turkish",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "coffee/tubruk",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "coffee/vietnamese",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "coffee/thai",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+    }
+
+    // stat paths
+    struct lfs_info info;
+    lfs_stat(&lfs, "coffee/_rip/./././././.", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "coffee/c_ldbrew/././././.", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "coffee/tu_kish/./././.", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "coffee/tub_uk/././.", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "coffee/_vietnamese/./.", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "coffee/thai_/.", &info) => LFS_ERR_NOENT;
+
+    // file open paths, only works on files!
+    lfs_file_t file;
+    lfs_file_open(&lfs, &file, "coffee/_rip/.",
+            LFS_O_RDONLY) => LFS_ERR_NOENT;
+    lfs_file_open(&lfs, &file, "coffee/c_ldbrew/./.",
+            LFS_O_RDONLY) => LFS_ERR_NOENT;
+    lfs_file_open(&lfs, &file, "coffee/tu_kish/././.",
+            LFS_O_RDONLY) => LFS_ERR_NOENT;
+    lfs_file_open(&lfs, &file, "coffee/tub_uk/./././.",
+            LFS_O_RDONLY) => LFS_ERR_NOENT;
+    lfs_file_open(&lfs, &file, "coffee/_vietnamese/././././.",
+            LFS_O_RDONLY) => LFS_ERR_NOENT;
+    lfs_file_open(&lfs, &file, "coffee/thai_/./././././.",
+            LFS_O_RDONLY) => LFS_ERR_NOENT;
+
+    lfs_file_open(&lfs, &file, "coffee/_rip/.",
+            LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_NOENT;
+    lfs_file_open(&lfs, &file, "coffee/c_ldbrew/./.",
+            LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_NOENT;
+    lfs_file_open(&lfs, &file, "coffee/tu_kish/././.",
+            LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_NOENT;
+    lfs_file_open(&lfs, &file, "coffee/tub_uk/./././.",
+            LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_NOENT;
+    lfs_file_open(&lfs, &file, "coffee/_vietnamese/././././.",
+            LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_NOENT;
+    lfs_file_open(&lfs, &file, "coffee/thai_/./././././.",
+            LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_NOENT;
+
+    lfs_file_open(&lfs, &file, "coffee/_rip/.",
+            LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_NOENT;
+    lfs_file_open(&lfs, &file, "coffee/c_ldbrew/./.",
+            LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_NOENT;
+    lfs_file_open(&lfs, &file, "coffee/tu_kish/././.",
+            LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_NOENT;
+    lfs_file_open(&lfs, &file, "coffee/tub_uk/./././.",
+            LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_NOENT;
+    lfs_file_open(&lfs, &file, "coffee/_vietnamese/././././.",
+            LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_NOENT;
+    lfs_file_open(&lfs, &file, "coffee/thai_/./././././.",
+            LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_NOENT;
+
+    // dir open paths, only works on dirs!
+    lfs_dir_t dir;
+    lfs_dir_open(&lfs, &dir, "coffee/_rip/.") => LFS_ERR_NOENT;
+    lfs_dir_open(&lfs, &dir, "coffee/c_ldbrew/./.") => LFS_ERR_NOENT;
+    lfs_dir_open(&lfs, &dir, "coffee/tu_kish/././.") => LFS_ERR_NOENT;
+    lfs_dir_open(&lfs, &dir, "coffee/tub_uk/./././.") => LFS_ERR_NOENT;
+    lfs_dir_open(&lfs, &dir, "coffee/_vietnamese/././././.") => LFS_ERR_NOENT;
+    lfs_dir_open(&lfs, &dir, "coffee/thai_/./././././.") => LFS_ERR_NOENT;
+
+    // rename paths
+    lfs_mkdir(&lfs, "espresso") => 0;
+    // bad source
+    lfs_rename(&lfs,
+            "coffee/_rip/./././././.",
+            "espresso/espresso") => LFS_ERR_NOENT;
+    lfs_rename(&lfs,
+            "coffee/c_ldbrew/././././.",
+            "espresso/americano") => LFS_ERR_NOENT;
+    lfs_rename(&lfs,
+            "coffee/tu_kish/./././.",
+            "espresso/macchiato") => LFS_ERR_NOENT;
+    lfs_rename(&lfs,
+            "coffee/tub_uk/././.",
+            "espresso/latte") => LFS_ERR_NOENT;
+    lfs_rename(&lfs,
+            "coffee/_vietnamese/./.",
+            "espresso/cappuccino") => LFS_ERR_NOENT;
+    lfs_rename(&lfs,
+            "coffee/thai_/.",
+            "espresso/mocha") => LFS_ERR_NOENT;
+
+    // bad destination
+    lfs_rename(&lfs,
+            "coffee/_rip",
+            "espresso/espresso/.") => LFS_ERR_NOENT;
+    lfs_rename(&lfs,
+            "coffee/c_ldbrew",
+            "espresso/americano/./.") => LFS_ERR_NOENT;
+    lfs_rename(&lfs,
+            "coffee/tu_kish",
+            "espresso/macchiato/././.") => LFS_ERR_NOENT;
+    lfs_rename(&lfs,
+            "coffee/tub_uk",
+            "espresso/latte/./././.") => LFS_ERR_NOENT;
+    lfs_rename(&lfs,
+            "coffee/_vietnamese",
+            "espresso/cappuccino/././././.") => LFS_ERR_NOENT;
+    lfs_rename(&lfs,
+            "coffee/thai_",
+            "espresso/mocha/./././././.") => LFS_ERR_NOENT;
+
+    // bad source and bad destination
+    lfs_rename(&lfs,
+            "coffee/_rip/./././././.",
+            "espresso/espresso/.") => LFS_ERR_NOENT;
+    lfs_rename(&lfs,
+            "coffee/c_ldbrew/././././.",
+            "espresso/americano/./.") => LFS_ERR_NOENT;
+    lfs_rename(&lfs,
+            "coffee/tu_kish/./././.",
+            "espresso/macchiato/././.") => LFS_ERR_NOENT;
+    lfs_rename(&lfs,
+            "coffee/tub_uk/././.",
+            "espresso/latte/./././.") => LFS_ERR_NOENT;
+    lfs_rename(&lfs,
+            "coffee/_vietnamese/./.",
+            "espresso/cappuccino/././././.") => LFS_ERR_NOENT;
+    lfs_rename(&lfs,
+            "coffee/thai_/.",
+            "espresso/mocha/./././././.") => LFS_ERR_NOENT;
+
+    // here's a weird one, what happens if our rename is also a noop?
+    lfs_rename(&lfs,
+            "coffee/_rip/./././././.",
+            "coffee/_rip/./././././.") => LFS_ERR_NOENT;
+    lfs_rename(&lfs,
+            "coffee/c_ldbrew/././././.",
+            "coffee/c_ldbrew/././././.") => LFS_ERR_NOENT;
+    lfs_rename(&lfs,
+            "coffee/tu_kish/./././.",
+            "coffee/tu_kish/./././.") => LFS_ERR_NOENT;
+    lfs_rename(&lfs,
+            "coffee/tub_uk/././.",
+            "coffee/tub_uk/././.") => LFS_ERR_NOENT;
+    lfs_rename(&lfs,
+            "coffee/_vietnamese/./.",
+            "coffee/_vietnamese/./.") => LFS_ERR_NOENT;
+    lfs_rename(&lfs,
+            "coffee/thai_/.",
+            "coffee/thai_/.") => LFS_ERR_NOENT;
+
+    // remove paths
+    lfs_remove(&lfs, "coffee/_rip/.") => LFS_ERR_NOENT;
+    lfs_remove(&lfs, "coffee/c_ldbrew/./.") => LFS_ERR_NOENT;
+    lfs_remove(&lfs, "coffee/tu_kish/././.") => LFS_ERR_NOENT;
+    lfs_remove(&lfs, "coffee/tub_uk/./././.") => LFS_ERR_NOENT;
+    lfs_remove(&lfs, "coffee/_vietnamese/././././.") => LFS_ERR_NOENT;
+    lfs_remove(&lfs, "coffee/thai_/./././././.") => LFS_ERR_NOENT;
+
+    // stat paths
+    lfs_stat(&lfs, "espresso/espresso", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "espresso/americano", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "espresso/macchiato", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "espresso/latte", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "espresso/cappuccino", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "espresso/mocha", &info) => LFS_ERR_NOENT;
+
+    lfs_stat(&lfs, "coffee/drip", &info) => 0;
+    assert(strcmp(info.name, "drip") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "coffee/coldbrew", &info) => 0;
+    assert(strcmp(info.name, "coldbrew") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "coffee/turkish", &info) => 0;
+    assert(strcmp(info.name, "turkish") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "coffee/tubruk", &info) => 0;
+    assert(strcmp(info.name, "tubruk") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "coffee/vietnamese", &info) => 0;
+    assert(strcmp(info.name, "vietnamese") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "coffee/thai", &info) => 0;
+    assert(strcmp(info.name, "thai") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+
+    lfs_unmount(&lfs) => 0;
+'''
+
+# noent tests with trailing dotdots
+[cases.test_paths_noent_trailing_dotdots]
+defines.DIR = [false, true]
+code = '''
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
+
+    // create paths
+    lfs_mkdir(&lfs, "coffee") => 0;
+    if (DIR) {
+        lfs_mkdir(&lfs, "coffee/drip") => 0;
+        lfs_mkdir(&lfs, "coffee/coldbrew") => 0;
+        lfs_mkdir(&lfs, "coffee/turkish") => 0;
+        lfs_mkdir(&lfs, "coffee/tubruk") => 0;
+        lfs_mkdir(&lfs, "coffee/vietnamese") => 0;
+        lfs_mkdir(&lfs, "coffee/thai") => 0;
+    } else {
+        lfs_file_t file;
+        lfs_file_open(&lfs, &file, "coffee/drip",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "coffee/coldbrew",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "coffee/turkish",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "coffee/tubruk",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "coffee/vietnamese",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "coffee/thai",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+    }
+
+    // stat paths
+    struct lfs_info info;
+    lfs_stat(&lfs, "coffee/_rip/../../../../../..", &info) => LFS_ERR_INVAL;
+    lfs_stat(&lfs, "coffee/c_ldbrew/../../../../..", &info) => LFS_ERR_INVAL;
+    lfs_stat(&lfs, "coffee/tu_kish/../../../..", &info) => LFS_ERR_INVAL;
+    lfs_stat(&lfs, "coffee/tub_uk/../../..", &info) => LFS_ERR_INVAL;
+    lfs_stat(&lfs, "coffee/_vietnamese/../..", &info) => 0;
+    assert(strcmp(info.name, "/") == 0);
+    assert(info.type == LFS_TYPE_DIR);
+    lfs_stat(&lfs, "coffee/thai_/..", &info) => 0;
+    assert(strcmp(info.name, "coffee") == 0);
+    assert(info.type == LFS_TYPE_DIR);
+
+    // file open paths, only works on files!
+    lfs_file_t file;
+    lfs_file_open(&lfs, &file, "coffee/_rip/..",
+            LFS_O_RDONLY) => LFS_ERR_ISDIR;
+    lfs_file_open(&lfs, &file, "coffee/c_ldbrew/../..",
+            LFS_O_RDONLY) => LFS_ERR_ISDIR;
+    lfs_file_open(&lfs, &file, "coffee/tu_kish/../../..",
+            LFS_O_RDONLY) => LFS_ERR_INVAL;
+    lfs_file_open(&lfs, &file, "coffee/tub_uk/../../../..",
+            LFS_O_RDONLY) => LFS_ERR_INVAL;
+    lfs_file_open(&lfs, &file, "coffee/_vietnamese/../../../../..",
+            LFS_O_RDONLY) => LFS_ERR_INVAL;
+    lfs_file_open(&lfs, &file, "coffee/thai_/../../../../../..",
+            LFS_O_RDONLY) => LFS_ERR_INVAL;
+
+    // dir open paths, only works on dirs!
+    lfs_dir_t dir;
+    lfs_dir_open(&lfs, &dir, "coffee/_rip/..") => 0;
+    lfs_dir_close(&lfs, &dir) => 0;
+    lfs_dir_open(&lfs, &dir, "coffee/c_ldbrew/../..") => 0;
+    lfs_dir_close(&lfs, &dir) => 0;
+    lfs_dir_open(&lfs, &dir, "coffee/tu_kish/../../..") => LFS_ERR_INVAL;
+    lfs_dir_open(&lfs, &dir, "coffee/tub_uk/../../../..") => LFS_ERR_INVAL;
+    lfs_dir_open(&lfs, &dir, "coffee/_vietnamese/../../../../..") => LFS_ERR_INVAL;
+    lfs_dir_open(&lfs, &dir, "coffee/thai_/../../../../../..") => LFS_ERR_INVAL;
+
+    // rename paths
+    lfs_mkdir(&lfs, "espresso") => 0;
+    // bad source
+    lfs_rename(&lfs,
+            "coffee/_rip/../../../../../..",
+            "espresso/espresso") => LFS_ERR_INVAL;
+    lfs_rename(&lfs,
+            "coffee/c_ldbrew/../../../../..",
+            "espresso/americano") => LFS_ERR_INVAL;
+    lfs_rename(&lfs,
+            "coffee/tu_kish/../../../..",
+            "espresso/macchiato") => LFS_ERR_INVAL;
+    lfs_rename(&lfs,
+            "coffee/tub_uk/../../..",
+            "espresso/latte") => LFS_ERR_INVAL;
+    lfs_rename(&lfs,
+            "coffee/_vietnamese/../..",
+            "espresso/cappuccino") => LFS_ERR_INVAL;
+    // this one works
+    lfs_rename(&lfs,
+            "coffee/thai_/..",
+            "espresso/mocha") => 0;
+    lfs_rename(&lfs,
+            "espresso/mocha",
+            "coffee") => 0;
+
+    // bad destination
+    lfs_rename(&lfs,
+            "coffee/_rip",
+            "espresso/espresso/..") => LFS_ERR_NOENT;
+    lfs_rename(&lfs,
+            "coffee/c_ldbrew",
+            "espresso/americano/../..") => LFS_ERR_NOENT;
+    lfs_rename(&lfs,
+            "coffee/tu_kish",
+            "espresso/macchiato/../../..") => LFS_ERR_NOENT;
+    lfs_rename(&lfs,
+            "coffee/tub_uk",
+            "espresso/latte/../../../..") => LFS_ERR_NOENT;
+    lfs_rename(&lfs,
+            "coffee/_vietnamese",
+            "espresso/cappuccino/../../../../..") => LFS_ERR_NOENT;
+    lfs_rename(&lfs,
+            "coffee/thai_",
+            "espresso/mocha/../../../../../..") => LFS_ERR_NOENT;
+
+    // bad source and bad destination
+    lfs_rename(&lfs,
+            "coffee/_rip/../../../../../..",
+            "espresso/espresso/..") => LFS_ERR_INVAL;
+    lfs_rename(&lfs,
+            "coffee/c_ldbrew/../../../../..",
+            "espresso/americano/../..") => LFS_ERR_INVAL;
+    lfs_rename(&lfs,
+            "coffee/tu_kish/../../../..",
+            "espresso/macchiato/../../..") => LFS_ERR_INVAL;
+    lfs_rename(&lfs,
+            "coffee/tub_uk/../../..",
+            "espresso/latte/../../../..") => LFS_ERR_INVAL;
+    lfs_rename(&lfs,
+            "coffee/_vietnamese/../..",
+            "espresso/cappuccino/../../../../..") => LFS_ERR_INVAL;
+    lfs_rename(&lfs,
+            "coffee/thai_/..",
+            "espresso/mocha/../../../../../..") => LFS_ERR_INVAL;
+
+    // here's a weird one, what happens if our rename is also a noop?
+    lfs_rename(&lfs,
+            "coffee/_rip/../../../../../..",
+            "coffee/_rip/../../../../../..") => LFS_ERR_INVAL;
+    lfs_rename(&lfs,
+            "coffee/c_ldbrew/../../../../..",
+            "coffee/c_ldbrew/../../../../..") => LFS_ERR_INVAL;
+    lfs_rename(&lfs,
+            "coffee/tu_kish/../../../..",
+            "coffee/tu_kish/../../../..") => LFS_ERR_INVAL;
+    lfs_rename(&lfs,
+            "coffee/tub_uk/../../..",
+            "coffee/tub_uk/../../..") => LFS_ERR_INVAL;
+    lfs_rename(&lfs,
+            "coffee/_vietnamese/../..",
+            "coffee/_vietnamese/../..") => LFS_ERR_INVAL;
+    lfs_rename(&lfs,
+            "coffee/thai_/..",
+            "coffee/thai_/..") => 0;
+
+    // remove paths
+    lfs_remove(&lfs, "coffee/_rip/..") => LFS_ERR_NOTEMPTY;
+    lfs_remove(&lfs, "coffee/c_ldbrew/../..") => LFS_ERR_INVAL;
+    lfs_remove(&lfs, "coffee/tu_kish/../../..") => LFS_ERR_INVAL;
+    lfs_remove(&lfs, "coffee/tub_uk/../../../..") => LFS_ERR_INVAL;
+    lfs_remove(&lfs, "coffee/_vietnamese/../../../../..") => LFS_ERR_INVAL;
+    lfs_remove(&lfs, "coffee/thai_/../../../../../..") => LFS_ERR_INVAL;
+
+    // stat paths
+    lfs_stat(&lfs, "espresso/espresso", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "espresso/americano", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "espresso/macchiato", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "espresso/latte", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "espresso/cappuccino", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "espresso/mocha", &info) => LFS_ERR_NOENT;
+
+    lfs_stat(&lfs, "coffee/drip", &info) => 0;
+    assert(strcmp(info.name, "drip") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "coffee/coldbrew", &info) => 0;
+    assert(strcmp(info.name, "coldbrew") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "coffee/turkish", &info) => 0;
+    assert(strcmp(info.name, "turkish") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "coffee/tubruk", &info) => 0;
+    assert(strcmp(info.name, "tubruk") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "coffee/vietnamese", &info) => 0;
+    assert(strcmp(info.name, "vietnamese") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "coffee/thai", &info) => 0;
+    assert(strcmp(info.name, "thai") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+
+    lfs_unmount(&lfs) => 0;
+'''
+
+# test an empty path, this should error
+[cases.test_paths_empty]
+defines.DIR = [false, true]
+code = '''
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
+    struct lfs_info info;
+
+    // create empty, this should error
+    if (DIR) {
+        lfs_mkdir(&lfs, "") => LFS_ERR_INVAL;
+    } else {
+        lfs_file_t file;
+        lfs_file_open(&lfs, &file, "",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_INVAL;
+    }
+
+    // stat empty
+    lfs_stat(&lfs, "", &info) => LFS_ERR_INVAL;
+
+    // file open empty, only works on files!
+    lfs_file_t file;
+    lfs_file_open(&lfs, &file, "",
+            LFS_O_RDONLY) => LFS_ERR_INVAL;
+
+    lfs_file_open(&lfs, &file, "",
+            LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_INVAL;
+
+    lfs_file_open(&lfs, &file, "",
+            LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_INVAL;
+
+    // dir open empty, only works on dirs!
+    lfs_dir_t dir;
+    lfs_dir_open(&lfs, &dir, "") => LFS_ERR_INVAL;
+    lfs_dir_close(&lfs, &dir) => 0;
+
+    // rename empty, this should error
+    lfs_rename(&lfs, "", "coffee") => LFS_ERR_INVAL;
+
+    lfs_mkdir(&lfs, "coffee") => 0;
+    lfs_rename(&lfs, "coffee", "") => LFS_ERR_INVAL;
+    lfs_remove(&lfs, "coffee") => 0;
+
+    lfs_rename(&lfs, "", "") => LFS_ERR_INVAL;
+
+    // stat empty
+    lfs_stat(&lfs, "", &info) => LFS_ERR_INVAL;
+
+    // remove empty, this should error
+    lfs_remove(&lfs, "") => LFS_ERR_INVAL;
+
+    // stat empty
+    lfs_stat(&lfs, "", &info) => LFS_ERR_INVAL;
+
+    lfs_unmount(&lfs) => 0;
+'''
+
+# root operations
+#
+# POSIX deviations:
+#
+# - Root modifications return EINVAL instead of EBUSY:
+#   - littlefs: remove("/") => EINVAL
+#   - POSIX:    remove("/") => EBUSY
+#   Reason: This would be the only use of EBUSY in the system.
+#
+[cases.test_paths_root]
+defines.DIR = [false, true]
+code = '''
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
+    struct lfs_info info;
+
+    // create root, this should error
+    if (DIR) {
+        lfs_mkdir(&lfs, "/") => LFS_ERR_EXIST;
+    } else {
+        lfs_file_t file;
+        lfs_file_open(&lfs, &file, "/",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+    }
+
+    // stat root
+    lfs_stat(&lfs, "/", &info) => 0;
+    assert(strcmp(info.name, "/") == 0);
+    assert(info.type == LFS_TYPE_DIR);
+
+    // file open root, only works on files!
+    lfs_file_t file;
+    lfs_file_open(&lfs, &file, "/",
+            LFS_O_RDONLY) => LFS_ERR_ISDIR;
+
+    lfs_file_open(&lfs, &file, "/",
+            LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+
+    lfs_file_open(&lfs, &file, "/",
+            LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+
+    // dir open root, only works on dirs!
+    lfs_dir_t dir;
+    lfs_dir_open(&lfs, &dir, "/") => 0;
+    lfs_dir_close(&lfs, &dir) => 0;
+
+    // rename root, this should error
+    lfs_rename(&lfs, "/", "coffee") => LFS_ERR_INVAL;
+
+    lfs_mkdir(&lfs, "coffee") => 0;
+    lfs_rename(&lfs, "coffee", "/") => LFS_ERR_INVAL;
+    lfs_remove(&lfs, "coffee") => 0;
+
+    lfs_rename(&lfs, "/", "/") => LFS_ERR_INVAL;
+
+    // stat root
+    lfs_stat(&lfs, "/", &info) => 0;
+    assert(strcmp(info.name, "/") == 0);
+    assert(info.type == LFS_TYPE_DIR);
+
+    // remove root, this should error
+    lfs_remove(&lfs, "/") => LFS_ERR_INVAL;
+
+    // stat root
+    lfs_stat(&lfs, "/", &info) => 0;
+    assert(strcmp(info.name, "/") == 0);
+    assert(info.type == LFS_TYPE_DIR);
+
+    lfs_unmount(&lfs) => 0;
+'''
+
+# other root representations
+[cases.test_paths_root_aliases]
+defines.DIR = [false, true]
+code = '''
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
+    struct lfs_info info;
+
+    // create root, this should error
+    if (DIR) {
+        lfs_mkdir(&lfs, "/") => LFS_ERR_EXIST;
+        lfs_mkdir(&lfs, ".") => LFS_ERR_EXIST;
+        lfs_mkdir(&lfs, "./") => LFS_ERR_EXIST;
+        lfs_mkdir(&lfs, "/.") => LFS_ERR_EXIST;
+        lfs_mkdir(&lfs, "//") => LFS_ERR_EXIST;
+    } else {
+        lfs_file_t file;
+        lfs_file_open(&lfs, &file, "/",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, ".",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "./",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "/.",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "//",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+    }
+
+    // stat root
+    lfs_stat(&lfs, "/", &info) => 0;
+    assert(strcmp(info.name, "/") == 0);
+    assert(info.type == LFS_TYPE_DIR);
+    lfs_stat(&lfs, ".", &info) => 0;
+    assert(strcmp(info.name, "/") == 0);
+    assert(info.type == LFS_TYPE_DIR);
+    lfs_stat(&lfs, "./", &info) => 0;
+    assert(strcmp(info.name, "/") == 0);
+    assert(info.type == LFS_TYPE_DIR);
+    lfs_stat(&lfs, "/.", &info) => 0;
+    assert(strcmp(info.name, "/") == 0);
+    assert(info.type == LFS_TYPE_DIR);
+    lfs_stat(&lfs, "//", &info) => 0;
+    assert(strcmp(info.name, "/") == 0);
+    assert(info.type == LFS_TYPE_DIR);
+
+    // file open root, only works on files!
+    lfs_file_t file;
+    lfs_file_open(&lfs, &file, "/",
+            LFS_O_RDONLY) => LFS_ERR_ISDIR;
+    lfs_file_open(&lfs, &file, ".",
+            LFS_O_RDONLY) => LFS_ERR_ISDIR;
+    lfs_file_open(&lfs, &file, "./",
+            LFS_O_RDONLY) => LFS_ERR_ISDIR;
+    lfs_file_open(&lfs, &file, "/.",
+            LFS_O_RDONLY) => LFS_ERR_ISDIR;
+    lfs_file_open(&lfs, &file, "//",
+            LFS_O_RDONLY) => LFS_ERR_ISDIR;
+
+    lfs_file_open(&lfs, &file, "/",
+            LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+    lfs_file_open(&lfs, &file, ".",
+            LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+    lfs_file_open(&lfs, &file, "./",
+            LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+    lfs_file_open(&lfs, &file, "/.",
+            LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+    lfs_file_open(&lfs, &file, "//",
+            LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+
+    lfs_file_open(&lfs, &file, "/",
+            LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+    lfs_file_open(&lfs, &file, ".",
+            LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+    lfs_file_open(&lfs, &file, "./",
+            LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+    lfs_file_open(&lfs, &file, "/.",
+            LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+    lfs_file_open(&lfs, &file, "//",
+            LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+
+    // dir open root, only works on dirs!
+    lfs_dir_t dir;
+    lfs_dir_open(&lfs, &dir, "/") => 0;
+    lfs_dir_close(&lfs, &dir) => 0;
+    lfs_dir_open(&lfs, &dir, ".") => 0;
+    lfs_dir_close(&lfs, &dir) => 0;
+    lfs_dir_open(&lfs, &dir, "./") => 0;
+    lfs_dir_close(&lfs, &dir) => 0;
+    lfs_dir_open(&lfs, &dir, "/.") => 0;
+    lfs_dir_close(&lfs, &dir) => 0;
+    lfs_dir_open(&lfs, &dir, "//") => 0;
+    lfs_dir_close(&lfs, &dir) => 0;
+
+    // rename root, this should error
+    lfs_rename(&lfs, "/", "coffee") => LFS_ERR_INVAL;
+    lfs_rename(&lfs, ".", "coffee") => LFS_ERR_INVAL;
+    lfs_rename(&lfs, "./", "coffee") => LFS_ERR_INVAL;
+    lfs_rename(&lfs, "/.", "coffee") => LFS_ERR_INVAL;
+    lfs_rename(&lfs, "//", "coffee") => LFS_ERR_INVAL;
+
+    lfs_mkdir(&lfs, "coffee") => 0;
+    lfs_rename(&lfs, "coffee", "/") => LFS_ERR_INVAL;
+    lfs_rename(&lfs, "coffee", ".") => LFS_ERR_INVAL;
+    lfs_rename(&lfs, "coffee", "./") => LFS_ERR_INVAL;
+    lfs_rename(&lfs, "coffee", "/.") => LFS_ERR_INVAL;
+    lfs_rename(&lfs, "coffee", "//") => LFS_ERR_INVAL;
+    lfs_remove(&lfs, "coffee") => 0;
+
+    lfs_rename(&lfs, "/", "/") => LFS_ERR_INVAL;
+    lfs_rename(&lfs, ".", ".") => LFS_ERR_INVAL;
+    lfs_rename(&lfs, "..", "..") => LFS_ERR_INVAL;
+    lfs_rename(&lfs, "./", "./") => LFS_ERR_INVAL;
+    lfs_rename(&lfs, "/.", "/.") => LFS_ERR_INVAL;
+    lfs_rename(&lfs, "//", "//") => LFS_ERR_INVAL;
+
+    // stat root
+    lfs_stat(&lfs, "/", &info) => 0;
+    assert(strcmp(info.name, "/") == 0);
+    assert(info.type == LFS_TYPE_DIR);
+    lfs_stat(&lfs, ".", &info) => 0;
+    assert(strcmp(info.name, "/") == 0);
+    assert(info.type == LFS_TYPE_DIR);
+    lfs_stat(&lfs, "./", &info) => 0;
+    assert(strcmp(info.name, "/") == 0);
+    assert(info.type == LFS_TYPE_DIR);
+    lfs_stat(&lfs, "/.", &info) => 0;
+    assert(strcmp(info.name, "/") == 0);
+    assert(info.type == LFS_TYPE_DIR);
+    lfs_stat(&lfs, "//", &info) => 0;
+    assert(strcmp(info.name, "/") == 0);
+    assert(info.type == LFS_TYPE_DIR);
+
+    // remove root, this should error
+    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;
+
+    // stat root
+    lfs_stat(&lfs, "/", &info) => 0;
+    assert(strcmp(info.name, "/") == 0);
+    assert(info.type == LFS_TYPE_DIR);
+    lfs_stat(&lfs, ".", &info) => 0;
+    assert(strcmp(info.name, "/") == 0);
+    assert(info.type == LFS_TYPE_DIR);
+    lfs_stat(&lfs, "./", &info) => 0;
+    assert(strcmp(info.name, "/") == 0);
+    assert(info.type == LFS_TYPE_DIR);
+    lfs_stat(&lfs, "/.", &info) => 0;
+    assert(strcmp(info.name, "/") == 0);
+    assert(info.type == LFS_TYPE_DIR);
+    lfs_stat(&lfs, "//", &info) => 0;
+    assert(strcmp(info.name, "/") == 0);
+    assert(info.type == LFS_TYPE_DIR);
+
+    lfs_unmount(&lfs) => 0;
+'''
+
+# superblock magic shouldn't appear as a file
+[cases.test_paths_magic_noent]
+code = '''
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
+
+    // stat littlefs, which shouldn't exist
+    struct lfs_info info;
+    lfs_stat(&lfs, "littlefs", &info) => LFS_ERR_NOENT;
+
+    // file open littlefs, which shouldn't exist
+    lfs_file_t file;
+    lfs_file_open(&lfs, &file, "littlefs",
+            LFS_O_RDONLY) => LFS_ERR_NOENT;
+
+    // dir open littlefs, which shouldn't exist
+    lfs_dir_t dir;
+    lfs_dir_open(&lfs, &dir, "littlefs") => LFS_ERR_NOENT;
+
+    // rename littlefs, which shouldn't exist
+    lfs_rename(&lfs, "littlefs", "coffee") => LFS_ERR_NOENT;
+
+    // remove littlefs, which shouldn't exist
+    lfs_remove(&lfs, "littlefs") => LFS_ERR_NOENT;
+
+    // stat littlefs, which shouldn't exist
+    lfs_stat(&lfs, "coffee", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "littlefs", &info) => LFS_ERR_NOENT;
+
+    lfs_unmount(&lfs) => 0;
+'''
+
+# superblock magic shouldn't conflict with files, that would be silly
+[cases.test_paths_magic_conflict]
+defines.DIR = [false, true]
+code = '''
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
+
+    // create littlefs
+    if (DIR) {
+        lfs_mkdir(&lfs, "littlefs") => 0;
+    } else {
+        lfs_file_t file;
+        lfs_file_open(&lfs, &file, "littlefs",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+    }
+
+    // stat littlefs
+    struct lfs_info info;
+    lfs_stat(&lfs, "littlefs", &info) => 0;
+    assert(strcmp(info.name, "littlefs") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+
+    // file open littlefs, only works on files!
+    if (DIR) {
+        lfs_file_t file;
+        lfs_file_open(&lfs, &file, "littlefs",
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+
+        lfs_file_open(&lfs, &file, "littlefs",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+
+        lfs_file_open(&lfs, &file, "littlefs",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+    } else {
+        lfs_file_t file;
+        lfs_file_open(&lfs, &file, "littlefs",
+                LFS_O_RDONLY) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+
+        lfs_file_open(&lfs, &file, "littlefs",
+                LFS_O_WRONLY | LFS_O_CREAT) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+
+        lfs_file_open(&lfs, &file, "littlefs",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+    }
+
+    // dir open littlefs, only works on dirs!
+    if (DIR) {
+        lfs_dir_t dir;
+        lfs_dir_open(&lfs, &dir, "littlefs") => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+    } else {
+        lfs_dir_t dir;
+        lfs_dir_open(&lfs, &dir, "littlefs") => LFS_ERR_NOTDIR;
+    }
+
+    // rename littlefs
+    lfs_rename(&lfs, "littlefs", "coffee") => 0;
+    lfs_rename(&lfs, "coffee", "littlefs") => 0;
+
+    // stat littlefs
+    lfs_stat(&lfs, "littlefs", &info) => 0;
+    assert(strcmp(info.name, "littlefs") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+
+    lfs_stat(&lfs, "coffee", &info) => LFS_ERR_NOENT;
+
+    // remove littlefs
+    lfs_remove(&lfs, "littlefs") => 0;
+
+    // stat littlefs
+    lfs_stat(&lfs, "littlefs", &info) => LFS_ERR_NOENT;
+
+    lfs_unmount(&lfs) => 0;
+'''
+
+# test name too long
+[cases.test_paths_nametoolong]
+defines.DIR = [false, true]
+code = '''
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
+
+    char a_name[512];
+    memset(a_name, 'a', LFS_NAME_MAX+1);
+    a_name[LFS_NAME_MAX+1] = '\0';
+
+    // create names that are too long, should error
+    char path[1024];
+    lfs_mkdir(&lfs, "coffee") => 0;
+    if (DIR) {
+        sprintf(path, "coffee/%s", a_name);
+        lfs_mkdir(&lfs, path) => LFS_ERR_NAMETOOLONG;
+    } else {
+        lfs_file_t file;
+        sprintf(path, "coffee/%s", a_name);
+        lfs_file_open(&lfs, &file, path,
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_NAMETOOLONG;
+    }
+
+    // stat paths
+    struct lfs_info info;
+    sprintf(path, "coffee/%s", a_name);
+    lfs_stat(&lfs, path, &info) => LFS_ERR_NOENT;
+
+    // file open paths, only works on files!
+    lfs_file_t file;
+    sprintf(path, "coffee/%s", a_name);
+    lfs_file_open(&lfs, &file, path,
+            LFS_O_RDONLY) => LFS_ERR_NOENT;
+
+    // dir open paths, only works on dirs!
+    lfs_dir_t dir;
+    sprintf(path, "coffee/%s", a_name);
+    lfs_dir_open(&lfs, &dir, path) => LFS_ERR_NOENT;
+
+    // rename paths
+    lfs_mkdir(&lfs, "espresso") => 0;
+    sprintf(path, "coffee/%s", a_name);
+    lfs_rename(&lfs, path, "espresso/espresso") => LFS_ERR_NOENT;
+
+    // renaming with too long a destination is tricky!
+    if (DIR) {
+        lfs_mkdir(&lfs, "coffee/drip") => 0;
+    } else {
+        lfs_file_t file;
+        lfs_file_open(&lfs, &file, "coffee/drip",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+    }
+
+    sprintf(path, "espresso/%s", a_name);
+    lfs_rename(&lfs, "coffee/drip", path) => LFS_ERR_NAMETOOLONG;
+
+    // stat paths
+    lfs_stat(&lfs, "coffee/drip", &info) => 0;
+    assert(strcmp(info.name, "drip") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+
+    sprintf(path, "espresso/%s", a_name);
+    lfs_stat(&lfs, path, &info) => LFS_ERR_NOENT;
+
+    // remove paths
+    sprintf(path, "espresso/%s", a_name);
+    lfs_remove(&lfs, path) => LFS_ERR_NOENT;
+
+    // stat paths
+    lfs_stat(&lfs, "coffee/drip", &info) => 0;
+    assert(strcmp(info.name, "drip") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+
+    sprintf(path, "espresso/%s", a_name);
+    lfs_stat(&lfs, path, &info) => LFS_ERR_NOENT;
+
+    lfs_unmount(&lfs) => 0;
+'''
+
+# test name really long but not too long
+[cases.test_paths_namejustlongenough]
+defines.DIR = [false, true]
+code = '''
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
+
+    char a_name[512];
+    memset(a_name, 'a', LFS_NAME_MAX);
+    a_name[LFS_NAME_MAX] = '\0';
+    char b_name[512];
+    memset(b_name, 'b', LFS_NAME_MAX);
+    b_name[LFS_NAME_MAX] = '\0';
+    char c_name[512];
+    memset(c_name, 'c', LFS_NAME_MAX);
+    c_name[LFS_NAME_MAX] = '\0';
+    char d_name[512];
+    memset(d_name, 'd', LFS_NAME_MAX);
+    d_name[LFS_NAME_MAX] = '\0';
+    char e_name[512];
+    memset(e_name, 'e', LFS_NAME_MAX);
+    e_name[LFS_NAME_MAX] = '\0';
+    char f_name[512];
+    memset(f_name, 'f', LFS_NAME_MAX);
+    f_name[LFS_NAME_MAX] = '\0';
+    char g_name[512];
+    memset(g_name, 'g', LFS_NAME_MAX);
+    g_name[LFS_NAME_MAX] = '\0';
+    char h_name[512];
+    memset(h_name, 'h', LFS_NAME_MAX);
+    h_name[LFS_NAME_MAX] = '\0';
+    char i_name[512];
+    memset(i_name, 'i', LFS_NAME_MAX);
+    i_name[LFS_NAME_MAX] = '\0';
+    char j_name[512];
+    memset(j_name, 'j', LFS_NAME_MAX);
+    j_name[LFS_NAME_MAX] = '\0';
+    char k_name[512];
+    memset(k_name, 'k', LFS_NAME_MAX);
+    k_name[LFS_NAME_MAX] = '\0';
+    char l_name[512];
+    memset(l_name, 'l', LFS_NAME_MAX);
+    l_name[LFS_NAME_MAX] = '\0';
+
+    // create names that aren't too long
+    lfs_mkdir(&lfs, c_name) => 0;
+    char path[1024];
+    if (DIR) {
+        sprintf(path, "%s/%s", c_name, a_name);
+        lfs_mkdir(&lfs, path) => 0;
+        sprintf(path, "%s/%s", c_name, b_name);
+        lfs_mkdir(&lfs, path) => 0;
+        sprintf(path, "%s/%s", c_name, c_name);
+        lfs_mkdir(&lfs, path) => 0;
+        sprintf(path, "%s/%s", c_name, d_name);
+        lfs_mkdir(&lfs, path) => 0;
+        sprintf(path, "%s/%s", c_name, e_name);
+        lfs_mkdir(&lfs, path) => 0;
+        sprintf(path, "%s/%s", c_name, f_name);
+        lfs_mkdir(&lfs, path) => 0;
+    } else {
+        lfs_file_t file;
+        sprintf(path, "%s/%s", c_name, a_name);
+        lfs_file_open(&lfs, &file, path,
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        sprintf(path, "%s/%s", c_name, b_name);
+        lfs_file_open(&lfs, &file, path,
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        sprintf(path, "%s/%s", c_name, c_name);
+        lfs_file_open(&lfs, &file, path,
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        sprintf(path, "%s/%s", c_name, d_name);
+        lfs_file_open(&lfs, &file, path,
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        sprintf(path, "%s/%s", c_name, e_name);
+        lfs_file_open(&lfs, &file, path,
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        sprintf(path, "%s/%s", c_name, f_name);
+        lfs_file_open(&lfs, &file, path,
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+    }
+
+    // stat paths
+    struct lfs_info info;
+    sprintf(path, "%s/%s", c_name, a_name);
+    lfs_stat(&lfs, path, &info) => 0;
+    assert(strcmp(info.name, a_name) == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    sprintf(path, "%s/%s", c_name, b_name);
+    lfs_stat(&lfs, path, &info) => 0;
+    assert(strcmp(info.name, b_name) == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    sprintf(path, "%s/%s", c_name, c_name);
+    lfs_stat(&lfs, path, &info) => 0;
+    assert(strcmp(info.name, c_name) == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    sprintf(path, "%s/%s", c_name, d_name);
+    lfs_stat(&lfs, path, &info) => 0;
+    assert(strcmp(info.name, d_name) == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    sprintf(path, "%s/%s", c_name, e_name);
+    lfs_stat(&lfs, path, &info) => 0;
+    assert(strcmp(info.name, e_name) == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    sprintf(path, "%s/%s", c_name, f_name);
+    lfs_stat(&lfs, path, &info) => 0;
+    assert(strcmp(info.name, f_name) == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+
+    // file open paths, only works on files!
+    if (DIR) {
+        lfs_file_t file;
+        sprintf(path, "%s/%s", c_name, a_name);
+        lfs_file_open(&lfs, &file, path,
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+        sprintf(path, "%s/%s", c_name, b_name);
+        lfs_file_open(&lfs, &file, path,
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+        sprintf(path, "%s/%s", c_name, c_name);
+        lfs_file_open(&lfs, &file, path,
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+        sprintf(path, "%s/%s", c_name, d_name);
+        lfs_file_open(&lfs, &file, path,
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+        sprintf(path, "%s/%s", c_name, e_name);
+        lfs_file_open(&lfs, &file, path,
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+        sprintf(path, "%s/%s", c_name, f_name);
+        lfs_file_open(&lfs, &file, path,
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+
+        sprintf(path, "%s/%s", c_name, a_name);
+        lfs_file_open(&lfs, &file, path,
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+        sprintf(path, "%s/%s", c_name, b_name);
+        lfs_file_open(&lfs, &file, path,
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+        sprintf(path, "%s/%s", c_name, c_name);
+        lfs_file_open(&lfs, &file, path,
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+        sprintf(path, "%s/%s", c_name, d_name);
+        lfs_file_open(&lfs, &file, path,
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+        sprintf(path, "%s/%s", c_name, e_name);
+        lfs_file_open(&lfs, &file, path,
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+        sprintf(path, "%s/%s", c_name, f_name);
+        lfs_file_open(&lfs, &file, path,
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+
+        sprintf(path, "%s/%s", c_name, a_name);
+        lfs_file_open(&lfs, &file, path,
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        sprintf(path, "%s/%s", c_name, b_name);
+        lfs_file_open(&lfs, &file, path,
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        sprintf(path, "%s/%s", c_name, c_name);
+        lfs_file_open(&lfs, &file, path,
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        sprintf(path, "%s/%s", c_name, d_name);
+        lfs_file_open(&lfs, &file, path,
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        sprintf(path, "%s/%s", c_name, e_name);
+        lfs_file_open(&lfs, &file, path,
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        sprintf(path, "%s/%s", c_name, f_name);
+        lfs_file_open(&lfs, &file, path,
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+    } else {
+        lfs_file_t file;
+        sprintf(path, "%s/%s", c_name, a_name);
+        lfs_file_open(&lfs, &file, path,
+                LFS_O_RDONLY) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        sprintf(path, "%s/%s", c_name, b_name);
+        lfs_file_open(&lfs, &file, path,
+                LFS_O_RDONLY) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        sprintf(path, "%s/%s", c_name, c_name);
+        lfs_file_open(&lfs, &file, path,
+                LFS_O_RDONLY) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        sprintf(path, "%s/%s", c_name, d_name);
+        lfs_file_open(&lfs, &file, path,
+                LFS_O_RDONLY) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        sprintf(path, "%s/%s", c_name, e_name);
+        lfs_file_open(&lfs, &file, path,
+                LFS_O_RDONLY) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        sprintf(path, "%s/%s", c_name, f_name);
+        lfs_file_open(&lfs, &file, path,
+                LFS_O_RDONLY) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+
+        sprintf(path, "%s/%s", c_name, a_name);
+        lfs_file_open(&lfs, &file, path,
+                LFS_O_WRONLY | LFS_O_CREAT) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        sprintf(path, "%s/%s", c_name, b_name);
+        lfs_file_open(&lfs, &file, path,
+                LFS_O_WRONLY | LFS_O_CREAT) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        sprintf(path, "%s/%s", c_name, c_name);
+        lfs_file_open(&lfs, &file, path,
+                LFS_O_WRONLY | LFS_O_CREAT) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        sprintf(path, "%s/%s", c_name, d_name);
+        lfs_file_open(&lfs, &file, path,
+                LFS_O_WRONLY | LFS_O_CREAT) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        sprintf(path, "%s/%s", c_name, e_name);
+        lfs_file_open(&lfs, &file, path,
+                LFS_O_WRONLY | LFS_O_CREAT) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        sprintf(path, "%s/%s", c_name, f_name);
+        lfs_file_open(&lfs, &file, path,
+                LFS_O_WRONLY | LFS_O_CREAT) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+
+        sprintf(path, "%s/%s", c_name, a_name);
+        lfs_file_open(&lfs, &file, path,
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        sprintf(path, "%s/%s", c_name, b_name);
+        lfs_file_open(&lfs, &file, path,
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        sprintf(path, "%s/%s", c_name, c_name);
+        lfs_file_open(&lfs, &file, path,
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        sprintf(path, "%s/%s", c_name, d_name);
+        lfs_file_open(&lfs, &file, path,
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        sprintf(path, "%s/%s", c_name, e_name);
+        lfs_file_open(&lfs, &file, path,
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        sprintf(path, "%s/%s", c_name, f_name);
+        lfs_file_open(&lfs, &file, path,
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+    }
+
+    // dir open paths, only works on dirs!
+    if (DIR) {
+        lfs_dir_t dir;
+        sprintf(path, "%s/%s", c_name, a_name);
+        lfs_dir_open(&lfs, &dir, path) => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+        sprintf(path, "%s/%s", c_name, b_name);
+        lfs_dir_open(&lfs, &dir, path) => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+        sprintf(path, "%s/%s", c_name, c_name);
+        lfs_dir_open(&lfs, &dir, path) => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+        sprintf(path, "%s/%s", c_name, d_name);
+        lfs_dir_open(&lfs, &dir, path) => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+        sprintf(path, "%s/%s", c_name, e_name);
+        lfs_dir_open(&lfs, &dir, path) => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+        sprintf(path, "%s/%s", c_name, f_name);
+        lfs_dir_open(&lfs, &dir, path) => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+    } else {
+        lfs_dir_t dir;
+        sprintf(path, "%s/%s", c_name, a_name);
+        lfs_dir_open(&lfs, &dir, path) => LFS_ERR_NOTDIR;
+        sprintf(path, "%s/%s", c_name, b_name);
+        lfs_dir_open(&lfs, &dir, path) => LFS_ERR_NOTDIR;
+        sprintf(path, "%s/%s", c_name, c_name);
+        lfs_dir_open(&lfs, &dir, path) => LFS_ERR_NOTDIR;
+        sprintf(path, "%s/%s", c_name, d_name);
+        lfs_dir_open(&lfs, &dir, path) => LFS_ERR_NOTDIR;
+        sprintf(path, "%s/%s", c_name, e_name);
+        lfs_dir_open(&lfs, &dir, path) => LFS_ERR_NOTDIR;
+        sprintf(path, "%s/%s", c_name, f_name);
+        lfs_dir_open(&lfs, &dir, path) => LFS_ERR_NOTDIR;
+    }
+
+    // rename paths
+    lfs_mkdir(&lfs, e_name) => 0;
+    char path_[1024];
+    sprintf(path, "%s/%s", c_name, a_name);
+    sprintf(path_, "%s/%s", e_name, g_name);
+    lfs_rename(&lfs, path, path_) => 0;
+    sprintf(path, "%s/%s", c_name, b_name);
+    sprintf(path_, "%s/%s", e_name, h_name);
+    lfs_rename(&lfs, path, path_) => 0;
+    sprintf(path, "%s/%s", c_name, c_name);
+    sprintf(path_, "%s/%s", e_name, i_name);
+    lfs_rename(&lfs, path, path_) => 0;
+    sprintf(path, "%s/%s", c_name, d_name);
+    sprintf(path_, "%s/%s", e_name, j_name);
+    lfs_rename(&lfs, path, path_) => 0;
+    sprintf(path, "%s/%s", c_name, e_name);
+    sprintf(path_, "%s/%s", e_name, k_name);
+    lfs_rename(&lfs, path, path_) => 0;
+    sprintf(path, "%s/%s", c_name, f_name);
+    sprintf(path_, "%s/%s", e_name, l_name);
+    lfs_rename(&lfs, path, path_) => 0;
+
+    // stat paths
+    sprintf(path, "%s/%s", e_name, g_name);
+    lfs_stat(&lfs, path, &info) => 0;
+    assert(strcmp(info.name, g_name) == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    sprintf(path, "%s/%s", e_name, h_name);
+    lfs_stat(&lfs, path, &info) => 0;
+    assert(strcmp(info.name, h_name) == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    sprintf(path, "%s/%s", e_name, i_name);
+    lfs_stat(&lfs, path, &info) => 0;
+    assert(strcmp(info.name, i_name) == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    sprintf(path, "%s/%s", e_name, j_name);
+    lfs_stat(&lfs, path, &info) => 0;
+    assert(strcmp(info.name, j_name) == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    sprintf(path, "%s/%s", e_name, k_name);
+    lfs_stat(&lfs, path, &info) => 0;
+    assert(strcmp(info.name, k_name) == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    sprintf(path, "%s/%s", e_name, l_name);
+    lfs_stat(&lfs, path, &info) => 0;
+    assert(strcmp(info.name, l_name) == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+
+    sprintf(path, "%s/%s", c_name, a_name);
+    lfs_stat(&lfs, path, &info) => LFS_ERR_NOENT;
+    sprintf(path, "%s/%s", c_name, b_name);
+    lfs_stat(&lfs, path, &info) => LFS_ERR_NOENT;
+    sprintf(path, "%s/%s", c_name, c_name);
+    lfs_stat(&lfs, path, &info) => LFS_ERR_NOENT;
+    sprintf(path, "%s/%s", c_name, d_name);
+    lfs_stat(&lfs, path, &info) => LFS_ERR_NOENT;
+    sprintf(path, "%s/%s", c_name, e_name);
+    lfs_stat(&lfs, path, &info) => LFS_ERR_NOENT;
+    sprintf(path, "%s/%s", c_name, f_name);
+    lfs_stat(&lfs, path, &info) => LFS_ERR_NOENT;
+
+    // remove paths
+    sprintf(path, "%s/%s", e_name, g_name);
+    lfs_remove(&lfs, path) => 0;
+    sprintf(path, "%s/%s", e_name, h_name);
+    lfs_remove(&lfs, path) => 0;
+    sprintf(path, "%s/%s", e_name, i_name);
+    lfs_remove(&lfs, path) => 0;
+    sprintf(path, "%s/%s", e_name, j_name);
+    lfs_remove(&lfs, path) => 0;
+    sprintf(path, "%s/%s", e_name, k_name);
+    lfs_remove(&lfs, path) => 0;
+    sprintf(path, "%s/%s", e_name, l_name);
+    lfs_remove(&lfs, path) => 0;
+
+    // stat paths
+    sprintf(path, "%s/%s", e_name, g_name);
+    lfs_stat(&lfs, path, &info) => LFS_ERR_NOENT;
+    sprintf(path, "%s/%s", e_name, h_name);
+    lfs_stat(&lfs, path, &info) => LFS_ERR_NOENT;
+    sprintf(path, "%s/%s", e_name, i_name);
+    lfs_stat(&lfs, path, &info) => LFS_ERR_NOENT;
+    sprintf(path, "%s/%s", e_name, j_name);
+    lfs_stat(&lfs, path, &info) => LFS_ERR_NOENT;
+    sprintf(path, "%s/%s", e_name, k_name);
+    lfs_stat(&lfs, path, &info) => LFS_ERR_NOENT;
+    sprintf(path, "%s/%s", e_name, l_name);
+    lfs_stat(&lfs, path, &info) => LFS_ERR_NOENT;
+
+    lfs_unmount(&lfs) => 0;
+'''
+
+# a quick utf8 test, utf8 is easy to support
+[cases.test_paths_utf8]
+defines.DIR = [false, true]
+code = '''
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
+
+    // create paths
+    lfs_mkdir(&lfs, "coffee") => 0;
+    if (DIR) {
+        lfs_mkdir(&lfs, "coffee/dripcoffee") => 0;
+        lfs_mkdir(&lfs, "coffee/coldbrew") => 0;
+        lfs_mkdir(&lfs, "coffee/türkkahvesi") => 0;
+        lfs_mkdir(&lfs, "coffee/ꦏꦺꦴꦥꦶꦠꦸꦧꦿꦸꦏ꧀") => 0;
+        lfs_mkdir(&lfs, "coffee/càphêđá") => 0;
+        lfs_mkdir(&lfs, "coffee/โอเลี้ยง") => 0;
+    } else {
+        lfs_file_t file;
+        lfs_file_open(&lfs, &file, "coffee/dripcoffee",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "coffee/coldbrew",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "coffee/türkkahvesi",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "coffee/ꦏꦺꦴꦥꦶꦠꦸꦧꦿꦸꦏ꧀",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "coffee/càphêđá",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "coffee/โอเลี้ยง",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+    }
+
+    // stat paths
+    struct lfs_info info;
+    lfs_stat(&lfs, "coffee/dripcoffee", &info) => 0;
+    assert(strcmp(info.name, "dripcoffee") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "coffee/coldbrew", &info) => 0;
+    assert(strcmp(info.name, "coldbrew") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "coffee/türkkahvesi", &info) => 0;
+    assert(strcmp(info.name, "türkkahvesi") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "coffee/ꦏꦺꦴꦥꦶꦠꦸꦧꦿꦸꦏ꧀", &info) => 0;
+    assert(strcmp(info.name, "ꦏꦺꦴꦥꦶꦠꦸꦧꦿꦸꦏ꧀") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "coffee/càphêđá", &info) => 0;
+    assert(strcmp(info.name, "càphêđá") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "coffee/โอเลี้ยง", &info) => 0;
+    assert(strcmp(info.name, "โอเลี้ยง") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+
+    // file open paths, only works on files!
+    if (DIR) {
+        lfs_file_t file;
+        lfs_file_open(&lfs, &file, "coffee/dripcoffee",
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "coffee/coldbrew",
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "coffee/türkkahvesi",
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "coffee/ꦏꦺꦴꦥꦶꦠꦸꦧꦿꦸꦏ꧀",
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "coffee/càphêđá",
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "coffee/โอเลี้ยง",
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+
+        lfs_file_open(&lfs, &file, "coffee/dripcoffee",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "coffee/coldbrew",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "coffee/türkkahvesi",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "coffee/ꦏꦺꦴꦥꦶꦠꦸꦧꦿꦸꦏ꧀",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "coffee/càphêđá",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "coffee/โอเลี้ยง",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+
+        lfs_file_open(&lfs, &file, "coffee/dripcoffee",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "coffee/coldbrew",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "coffee/türkkahvesi",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "coffee/ꦏꦺꦴꦥꦶꦠꦸꦧꦿꦸꦏ꧀",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "coffee/càphêđá",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "coffee/โอเลี้ยง",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+    } else {
+        lfs_file_t file;
+        lfs_file_open(&lfs, &file, "coffee/dripcoffee",
+                LFS_O_RDONLY) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "coffee/coldbrew",
+                LFS_O_RDONLY) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "coffee/türkkahvesi",
+                LFS_O_RDONLY) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "coffee/ꦏꦺꦴꦥꦶꦠꦸꦧꦿꦸꦏ꧀",
+                LFS_O_RDONLY) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "coffee/càphêđá",
+                LFS_O_RDONLY) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "coffee/โอเลี้ยง",
+                LFS_O_RDONLY) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+
+        lfs_file_open(&lfs, &file, "coffee/dripcoffee",
+                LFS_O_WRONLY | LFS_O_CREAT) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "coffee/coldbrew",
+                LFS_O_WRONLY | LFS_O_CREAT) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "coffee/türkkahvesi",
+                LFS_O_WRONLY | LFS_O_CREAT) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "coffee/ꦏꦺꦴꦥꦶꦠꦸꦧꦿꦸꦏ꧀",
+                LFS_O_WRONLY | LFS_O_CREAT) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "coffee/càphêđá",
+                LFS_O_WRONLY | LFS_O_CREAT) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "coffee/โอเลี้ยง",
+                LFS_O_WRONLY | LFS_O_CREAT) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+
+        lfs_file_open(&lfs, &file, "coffee/dripcoffee",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "coffee/coldbrew",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "coffee/türkkahvesi",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "coffee/ꦏꦺꦴꦥꦶꦠꦸꦧꦿꦸꦏ꧀",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "coffee/càphêđá",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "coffee/โอเลี้ยง",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+    }
+
+    // dir open paths, only works on dirs!
+    if (DIR) {
+        lfs_dir_t dir;
+        lfs_dir_open(&lfs, &dir, "coffee/dripcoffee") => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+        lfs_dir_open(&lfs, &dir, "coffee/coldbrew") => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+        lfs_dir_open(&lfs, &dir, "coffee/türkkahvesi") => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+        lfs_dir_open(&lfs, &dir, "coffee/ꦏꦺꦴꦥꦶꦠꦸꦧꦿꦸꦏ꧀") => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+        lfs_dir_open(&lfs, &dir, "coffee/càphêđá") => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+        lfs_dir_open(&lfs, &dir, "coffee/โอเลี้ยง") => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+    } else {
+        lfs_dir_t dir;
+        lfs_dir_open(&lfs, &dir, "coffee/dripcoffee") => LFS_ERR_NOTDIR;
+        lfs_dir_open(&lfs, &dir, "coffee/coldbrew") => LFS_ERR_NOTDIR;
+        lfs_dir_open(&lfs, &dir, "coffee/türkkahvesi") => LFS_ERR_NOTDIR;
+        lfs_dir_open(&lfs, &dir, "coffee/ꦏꦺꦴꦥꦶꦠꦸꦧꦿꦸꦏ꧀") => LFS_ERR_NOTDIR;
+        lfs_dir_open(&lfs, &dir, "coffee/càphêđá") => LFS_ERR_NOTDIR;
+        lfs_dir_open(&lfs, &dir, "coffee/โอเลี้ยง") => LFS_ERR_NOTDIR;
+    }
+
+    // rename paths
+    lfs_mkdir(&lfs, "caffè") => 0;
+    lfs_rename(&lfs,
+            "coffee/dripcoffee",
+            "caffè/espresso") => 0;
+    lfs_rename(&lfs,
+            "coffee/coldbrew",
+            "caffè/americano") => 0;
+    lfs_rename(&lfs,
+            "coffee/türkkahvesi",
+            "caffè/macchiato") => 0;
+    lfs_rename(&lfs,
+            "coffee/ꦏꦺꦴꦥꦶꦠꦸꦧꦿꦸꦏ꧀",
+            "caffè/latte") => 0;
+    lfs_rename(&lfs,
+            "coffee/càphêđá",
+            "caffè/cappuccino") => 0;
+    lfs_rename(&lfs,
+            "coffee/โอเลี้ยง",
+            "caffè/mocha") => 0;
+
+    // stat paths
+    lfs_stat(&lfs, "caffè/espresso", &info) => 0;
+    assert(strcmp(info.name, "espresso") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "caffè/americano", &info) => 0;
+    assert(strcmp(info.name, "americano") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "caffè/macchiato", &info) => 0;
+    assert(strcmp(info.name, "macchiato") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "caffè/latte", &info) => 0;
+    assert(strcmp(info.name, "latte") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "caffè/cappuccino", &info) => 0;
+    assert(strcmp(info.name, "cappuccino") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "caffè/mocha", &info) => 0;
+    assert(strcmp(info.name, "mocha") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+
+    lfs_stat(&lfs, "coffee/dripcoffee", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "coffee/coldbrew", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "coffee/türkkahvesi", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "coffee/ꦏꦺꦴꦥꦶꦠꦸꦧꦿꦸꦏ꧀", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "coffee/càphêđá", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "coffee/โอเลี้ยง", &info) => LFS_ERR_NOENT;
+
+    // remove paths
+    lfs_remove(&lfs, "caffè/espresso") => 0;
+    lfs_remove(&lfs, "caffè/americano") => 0;
+    lfs_remove(&lfs, "caffè/macchiato") => 0;
+    lfs_remove(&lfs, "caffè/latte") => 0;
+    lfs_remove(&lfs, "caffè/cappuccino") => 0;
+    lfs_remove(&lfs, "caffè/mocha") => 0;
+
+    // stat paths
+    lfs_stat(&lfs, "caffè/espresso", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "caffè/americano", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "caffè/macchiato", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "caffè/latte", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "caffè/cappuccino", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "caffè/mocha", &info) => LFS_ERR_NOENT;
+
+    lfs_unmount(&lfs) => 0;
+'''
+
+# more utf8 tests
+[cases.test_paths_utf8_ipa]
+defines.DIR = [false, true]
+code = '''
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
+
+    // create paths
+    lfs_mkdir(&lfs, "ˈkɔ.fi") => 0;
+    if (DIR) {
+        lfs_mkdir(&lfs, "ˈkɔ.fi/dɹɪpˈkɔ.fi") => 0;
+        lfs_mkdir(&lfs, "ˈkɔ.fi/koʊldbɹuː") => 0;
+        lfs_mkdir(&lfs, "ˈkɔ.fi/tyɾckɑhvɛˈsi") => 0;
+        lfs_mkdir(&lfs, "ˈkɔ.fi/ˈko.piˈt̪up̚.rʊk̚") => 0;
+        lfs_mkdir(&lfs, "ˈkɔ.fi/kaː˨˩fe˧˧ɗaː˧˥") => 0;
+        lfs_mkdir(&lfs, "ˈkɔ.fi/ʔoː˧.lia̯ŋ˦˥") => 0;
+    } else {
+        lfs_file_t file;
+        lfs_file_open(&lfs, &file, "ˈkɔ.fi/dɹɪpˈkɔ.fi",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "ˈkɔ.fi/koʊldbɹuː",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "ˈkɔ.fi/tyɾckɑhvɛˈsi",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "ˈkɔ.fi/ˈko.piˈt̪up̚.rʊk̚",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "ˈkɔ.fi/kaː˨˩fe˧˧ɗaː˧˥",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "ˈkɔ.fi/ʔoː˧.lia̯ŋ˦˥",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+    }
+
+    // stat paths
+    struct lfs_info info;
+    lfs_stat(&lfs, "ˈkɔ.fi/dɹɪpˈkɔ.fi", &info) => 0;
+    assert(strcmp(info.name, "dɹɪpˈkɔ.fi") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "ˈkɔ.fi/koʊldbɹuː", &info) => 0;
+    assert(strcmp(info.name, "koʊldbɹuː") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "ˈkɔ.fi/tyɾckɑhvɛˈsi", &info) => 0;
+    assert(strcmp(info.name, "tyɾckɑhvɛˈsi") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "ˈkɔ.fi/ˈko.piˈt̪up̚.rʊk̚", &info) => 0;
+    assert(strcmp(info.name, "ˈko.piˈt̪up̚.rʊk̚") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "ˈkɔ.fi/kaː˨˩fe˧˧ɗaː˧˥", &info) => 0;
+    assert(strcmp(info.name, "kaː˨˩fe˧˧ɗaː˧˥") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "ˈkɔ.fi/ʔoː˧.lia̯ŋ˦˥", &info) => 0;
+    assert(strcmp(info.name, "ʔoː˧.lia̯ŋ˦˥") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+
+    // file open paths, only works on files!
+    if (DIR) {
+        lfs_file_t file;
+        lfs_file_open(&lfs, &file, "ˈkɔ.fi/dɹɪpˈkɔ.fi",
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "ˈkɔ.fi/koʊldbɹuː",
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "ˈkɔ.fi/tyɾckɑhvɛˈsi",
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "ˈkɔ.fi/ˈko.piˈt̪up̚.rʊk̚",
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "ˈkɔ.fi/kaː˨˩fe˧˧ɗaː˧˥",
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "ˈkɔ.fi/ʔoː˧.lia̯ŋ˦˥",
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+
+        lfs_file_open(&lfs, &file, "ˈkɔ.fi/dɹɪpˈkɔ.fi",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "ˈkɔ.fi/koʊldbɹuː",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "ˈkɔ.fi/tyɾckɑhvɛˈsi",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "ˈkɔ.fi/ˈko.piˈt̪up̚.rʊk̚",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "ˈkɔ.fi/kaː˨˩fe˧˧ɗaː˧˥",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "ˈkɔ.fi/ʔoː˧.lia̯ŋ˦˥",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+
+        lfs_file_open(&lfs, &file, "ˈkɔ.fi/dɹɪpˈkɔ.fi",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "ˈkɔ.fi/koʊldbɹuː",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "ˈkɔ.fi/tyɾckɑhvɛˈsi",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "ˈkɔ.fi/ˈko.piˈt̪up̚.rʊk̚",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "ˈkɔ.fi/kaː˨˩fe˧˧ɗaː˧˥",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "ˈkɔ.fi/ʔoː˧.lia̯ŋ˦˥",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+    } else {
+        lfs_file_t file;
+        lfs_file_open(&lfs, &file, "ˈkɔ.fi/dɹɪpˈkɔ.fi",
+                LFS_O_RDONLY) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "ˈkɔ.fi/koʊldbɹuː",
+                LFS_O_RDONLY) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "ˈkɔ.fi/tyɾckɑhvɛˈsi",
+                LFS_O_RDONLY) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "ˈkɔ.fi/ˈko.piˈt̪up̚.rʊk̚",
+                LFS_O_RDONLY) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "ˈkɔ.fi/kaː˨˩fe˧˧ɗaː˧˥",
+                LFS_O_RDONLY) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "ˈkɔ.fi/ʔoː˧.lia̯ŋ˦˥",
+                LFS_O_RDONLY) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+
+        lfs_file_open(&lfs, &file, "ˈkɔ.fi/dɹɪpˈkɔ.fi",
+                LFS_O_WRONLY | LFS_O_CREAT) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "ˈkɔ.fi/koʊldbɹuː",
+                LFS_O_WRONLY | LFS_O_CREAT) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "ˈkɔ.fi/tyɾckɑhvɛˈsi",
+                LFS_O_WRONLY | LFS_O_CREAT) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "ˈkɔ.fi/ˈko.piˈt̪up̚.rʊk̚",
+                LFS_O_WRONLY | LFS_O_CREAT) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "ˈkɔ.fi/kaː˨˩fe˧˧ɗaː˧˥",
+                LFS_O_WRONLY | LFS_O_CREAT) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "ˈkɔ.fi/ʔoː˧.lia̯ŋ˦˥",
+                LFS_O_WRONLY | LFS_O_CREAT) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+
+        lfs_file_open(&lfs, &file, "ˈkɔ.fi/dɹɪpˈkɔ.fi",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "ˈkɔ.fi/koʊldbɹuː",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "ˈkɔ.fi/tyɾckɑhvɛˈsi",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "ˈkɔ.fi/ˈko.piˈt̪up̚.rʊk̚",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "ˈkɔ.fi/kaː˨˩fe˧˧ɗaː˧˥",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "ˈkɔ.fi/ʔoː˧.lia̯ŋ˦˥",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+    }
+
+    // dir open paths, only works on dirs!
+    if (DIR) {
+        lfs_dir_t dir;
+        lfs_dir_open(&lfs, &dir, "ˈkɔ.fi/dɹɪpˈkɔ.fi") => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+        lfs_dir_open(&lfs, &dir, "ˈkɔ.fi/koʊldbɹuː") => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+        lfs_dir_open(&lfs, &dir, "ˈkɔ.fi/tyɾckɑhvɛˈsi") => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+        lfs_dir_open(&lfs, &dir, "ˈkɔ.fi/ˈko.piˈt̪up̚.rʊk̚") => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+        lfs_dir_open(&lfs, &dir, "ˈkɔ.fi/kaː˨˩fe˧˧ɗaː˧˥") => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+        lfs_dir_open(&lfs, &dir, "ˈkɔ.fi/ʔoː˧.lia̯ŋ˦˥") => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+    } else {
+        lfs_dir_t dir;
+        lfs_dir_open(&lfs, &dir, "ˈkɔ.fi/dɹɪpˈkɔ.fi") => LFS_ERR_NOTDIR;
+        lfs_dir_open(&lfs, &dir, "ˈkɔ.fi/koʊldbɹuː") => LFS_ERR_NOTDIR;
+        lfs_dir_open(&lfs, &dir, "ˈkɔ.fi/tyɾckɑhvɛˈsi") => LFS_ERR_NOTDIR;
+        lfs_dir_open(&lfs, &dir, "ˈkɔ.fi/ˈko.piˈt̪up̚.rʊk̚") => LFS_ERR_NOTDIR;
+        lfs_dir_open(&lfs, &dir, "ˈkɔ.fi/kaː˨˩fe˧˧ɗaː˧˥") => LFS_ERR_NOTDIR;
+        lfs_dir_open(&lfs, &dir, "ˈkɔ.fi/ʔoː˧.lia̯ŋ˦˥") => LFS_ERR_NOTDIR;
+    }
+
+    // rename paths
+    lfs_mkdir(&lfs, "kafˈfɛ") => 0;
+    lfs_rename(&lfs,
+            "ˈkɔ.fi/dɹɪpˈkɔ.fi",
+            "kafˈfɛ/eˈsprɛsso") => 0;
+    lfs_rename(&lfs,
+            "ˈkɔ.fi/koʊldbɹuː",
+            "kafˈfɛ/ameriˈkano") => 0;
+    lfs_rename(&lfs,
+            "ˈkɔ.fi/tyɾckɑhvɛˈsi",
+            "kafˈfɛ/makˈkjato") => 0;
+    lfs_rename(&lfs,
+            "ˈkɔ.fi/ˈko.piˈt̪up̚.rʊk̚",
+            "kafˈfɛ/ˈlat.te") => 0;
+    lfs_rename(&lfs,
+            "ˈkɔ.fi/kaː˨˩fe˧˧ɗaː˧˥",
+            "kafˈfɛ/kapputˈt͡ʃino") => 0;
+    lfs_rename(&lfs,
+            "ˈkɔ.fi/ʔoː˧.lia̯ŋ˦˥",
+            "kafˈfɛ/ˈmoʊkə") => 0;
+
+    // stat paths
+    lfs_stat(&lfs, "kafˈfɛ/eˈsprɛsso", &info) => 0;
+    assert(strcmp(info.name, "eˈsprɛsso") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "kafˈfɛ/ameriˈkano", &info) => 0;
+    assert(strcmp(info.name, "ameriˈkano") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "kafˈfɛ/makˈkjato", &info) => 0;
+    assert(strcmp(info.name, "makˈkjato") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "kafˈfɛ/ˈlat.te", &info) => 0;
+    assert(strcmp(info.name, "ˈlat.te") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "kafˈfɛ/kapputˈt͡ʃino", &info) => 0;
+    assert(strcmp(info.name, "kapputˈt͡ʃino") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "kafˈfɛ/ˈmoʊkə", &info) => 0;
+    assert(strcmp(info.name, "ˈmoʊkə") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+
+    lfs_stat(&lfs, "ˈkɔ.fi/dɹɪpˈkɔ.fi", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "ˈkɔ.fi/koʊldbɹuː", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "ˈkɔ.fi/tyɾckɑhvɛˈsi", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "ˈkɔ.fi/ˈko.piˈt̪up̚.rʊk̚", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "ˈkɔ.fi/kaː˨˩fe˧˧ɗaː˧˥", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "ˈkɔ.fi/ʔoː˧.lia̯ŋ˦˥", &info) => LFS_ERR_NOENT;
+
+    // remove paths
+    lfs_remove(&lfs, "kafˈfɛ/eˈsprɛsso") => 0;
+    lfs_remove(&lfs, "kafˈfɛ/ameriˈkano") => 0;
+    lfs_remove(&lfs, "kafˈfɛ/makˈkjato") => 0;
+    lfs_remove(&lfs, "kafˈfɛ/ˈlat.te") => 0;
+    lfs_remove(&lfs, "kafˈfɛ/kapputˈt͡ʃino") => 0;
+    lfs_remove(&lfs, "kafˈfɛ/ˈmoʊkə") => 0;
+
+    // stat paths
+    lfs_stat(&lfs, "kafˈfɛ/eˈsprɛsso", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "kafˈfɛ/ameriˈkano", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "kafˈfɛ/makˈkjato", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "kafˈfɛ/ˈlat.te", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "kafˈfɛ/kapputˈt͡ʃino", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "kafˈfɛ/ˈmoʊkə", &info) => LFS_ERR_NOENT;
+
+    lfs_unmount(&lfs) => 0;
+'''
+
+# test spaces have no problems
+[cases.test_paths_spaces]
+defines.DIR = [false, true]
+code = '''
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
+
+    // create paths
+    lfs_mkdir(&lfs, "c o f f e e") => 0;
+    if (DIR) {
+        lfs_mkdir(&lfs, "c o f f e e/d r i p") => 0;
+        lfs_mkdir(&lfs, "c o f f e e/c o l d b r e w") => 0;
+        lfs_mkdir(&lfs, "c o f f e e/t u r k i s h") => 0;
+        lfs_mkdir(&lfs, "c o f f e e/t u b r u k") => 0;
+        lfs_mkdir(&lfs, "c o f f e e/v i e t n a m e s e") => 0;
+        lfs_mkdir(&lfs, "c o f f e e/t h a i") => 0;
+    } else {
+        lfs_file_t file;
+        lfs_file_open(&lfs, &file, "c o f f e e/d r i p",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "c o f f e e/c o l d b r e w",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "c o f f e e/t u r k i s h",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "c o f f e e/t u b r u k",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "c o f f e e/v i e t n a m e s e",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "c o f f e e/t h a i",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+    }
+
+    // stat paths
+    struct lfs_info info;
+    lfs_stat(&lfs, "c o f f e e/d r i p", &info) => 0;
+    assert(strcmp(info.name, "d r i p") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "c o f f e e/c o l d b r e w", &info) => 0;
+    assert(strcmp(info.name, "c o l d b r e w") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "c o f f e e/t u r k i s h", &info) => 0;
+    assert(strcmp(info.name, "t u r k i s h") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "c o f f e e/t u b r u k", &info) => 0;
+    assert(strcmp(info.name, "t u b r u k") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "c o f f e e/v i e t n a m e s e", &info) => 0;
+    assert(strcmp(info.name, "v i e t n a m e s e") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "c o f f e e/t h a i", &info) => 0;
+    assert(strcmp(info.name, "t h a i") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+
+    // file open paths, only works on files!
+    if (DIR) {
+        lfs_file_t file;
+        lfs_file_open(&lfs, &file, "c o f f e e/d r i p",
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "c o f f e e/c o l d b r e w",
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "c o f f e e/t u r k i s h",
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "c o f f e e/t u b r u k",
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "c o f f e e/v i e t n a m e s e",
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "c o f f e e/t h a i",
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+
+        lfs_file_open(&lfs, &file, "c o f f e e/d r i p",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "c o f f e e/c o l d b r e w",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "c o f f e e/t u r k i s h",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "c o f f e e/t u b r u k",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "c o f f e e/v i e t n a m e s e",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "c o f f e e/t h a i",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+
+        lfs_file_open(&lfs, &file, "c o f f e e/d r i p",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "c o f f e e/c o l d b r e w",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "c o f f e e/t u r k i s h",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "c o f f e e/t u b r u k",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "c o f f e e/v i e t n a m e s e",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "c o f f e e/t h a i",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+    } else {
+        lfs_file_t file;
+        lfs_file_open(&lfs, &file, "c o f f e e/d r i p",
+                LFS_O_RDONLY) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "c o f f e e/c o l d b r e w",
+                LFS_O_RDONLY) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "c o f f e e/t u r k i s h",
+                LFS_O_RDONLY) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "c o f f e e/t u b r u k",
+                LFS_O_RDONLY) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "c o f f e e/v i e t n a m e s e",
+                LFS_O_RDONLY) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "c o f f e e/t h a i",
+                LFS_O_RDONLY) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+
+        lfs_file_open(&lfs, &file, "c o f f e e/d r i p",
+                LFS_O_WRONLY | LFS_O_CREAT) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "c o f f e e/c o l d b r e w",
+                LFS_O_WRONLY | LFS_O_CREAT) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "c o f f e e/t u r k i s h",
+                LFS_O_WRONLY | LFS_O_CREAT) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "c o f f e e/t u b r u k",
+                LFS_O_WRONLY | LFS_O_CREAT) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "c o f f e e/v i e t n a m e s e",
+                LFS_O_WRONLY | LFS_O_CREAT) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "c o f f e e/t h a i",
+                LFS_O_WRONLY | LFS_O_CREAT) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+
+        lfs_file_open(&lfs, &file, "c o f f e e/d r i p",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "c o f f e e/c o l d b r e w",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "c o f f e e/t u r k i s h",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "c o f f e e/t u b r u k",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "c o f f e e/v i e t n a m e s e",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "c o f f e e/t h a i",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+    }
+
+    // dir open paths, only works on dirs!
+    if (DIR) {
+        lfs_dir_t dir;
+        lfs_dir_open(&lfs, &dir, "c o f f e e/d r i p") => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+        lfs_dir_open(&lfs, &dir, "c o f f e e/c o l d b r e w") => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+        lfs_dir_open(&lfs, &dir, "c o f f e e/t u r k i s h") => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+        lfs_dir_open(&lfs, &dir, "c o f f e e/t u b r u k") => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+        lfs_dir_open(&lfs, &dir, "c o f f e e/v i e t n a m e s e") => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+        lfs_dir_open(&lfs, &dir, "c o f f e e/t h a i") => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+    } else {
+        lfs_dir_t dir;
+        lfs_dir_open(&lfs, &dir, "c o f f e e/d r i p") => LFS_ERR_NOTDIR;
+        lfs_dir_open(&lfs, &dir, "c o f f e e/c o l d b r e w") => LFS_ERR_NOTDIR;
+        lfs_dir_open(&lfs, &dir, "c o f f e e/t u r k i s h") => LFS_ERR_NOTDIR;
+        lfs_dir_open(&lfs, &dir, "c o f f e e/t u b r u k") => LFS_ERR_NOTDIR;
+        lfs_dir_open(&lfs, &dir, "c o f f e e/v i e t n a m e s e") => LFS_ERR_NOTDIR;
+        lfs_dir_open(&lfs, &dir, "c o f f e e/t h a i") => LFS_ERR_NOTDIR;
+    }
+
+    // rename paths
+    lfs_mkdir(&lfs, "e s p r e s s o") => 0;
+    lfs_rename(&lfs,
+            "c o f f e e/d r i p",
+            "e s p r e s s o/e s p r e s s o") => 0;
+    lfs_rename(&lfs,
+            "c o f f e e/c o l d b r e w",
+            "e s p r e s s o/a m e r i c a n o") => 0;
+    lfs_rename(&lfs,
+            "c o f f e e/t u r k i s h",
+            "e s p r e s s o/m a c c h i a t o") => 0;
+    lfs_rename(&lfs,
+            "c o f f e e/t u b r u k",
+            "e s p r e s s o/l a t t e") => 0;
+    lfs_rename(&lfs,
+            "c o f f e e/v i e t n a m e s e",
+            "e s p r e s s o/c a p p u c c i n o") => 0;
+    lfs_rename(&lfs,
+            "c o f f e e/t h a i",
+            "e s p r e s s o/m o c h a") => 0;
+
+    // stat paths
+    lfs_stat(&lfs, "e s p r e s s o/e s p r e s s o", &info) => 0;
+    assert(strcmp(info.name, "e s p r e s s o") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "e s p r e s s o/a m e r i c a n o", &info) => 0;
+    assert(strcmp(info.name, "a m e r i c a n o") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "e s p r e s s o/m a c c h i a t o", &info) => 0;
+    assert(strcmp(info.name, "m a c c h i a t o") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "e s p r e s s o/l a t t e", &info) => 0;
+    assert(strcmp(info.name, "l a t t e") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "e s p r e s s o/c a p p u c c i n o", &info) => 0;
+    assert(strcmp(info.name, "c a p p u c c i n o") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "e s p r e s s o/m o c h a", &info) => 0;
+    assert(strcmp(info.name, "m o c h a") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+
+    lfs_stat(&lfs, "c o f f e e/d r i p", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "c o f f e e/c o l d b r e w", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "c o f f e e/t u r k i s h", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "c o f f e e/t u b r u k", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "c o f f e e/v i e t n a m e s e", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "c o f f e e/t h a i", &info) => LFS_ERR_NOENT;
+
+    // remove paths
+    lfs_remove(&lfs, "e s p r e s s o/e s p r e s s o") => 0;
+    lfs_remove(&lfs, "e s p r e s s o/a m e r i c a n o") => 0;
+    lfs_remove(&lfs, "e s p r e s s o/m a c c h i a t o") => 0;
+    lfs_remove(&lfs, "e s p r e s s o/l a t t e") => 0;
+    lfs_remove(&lfs, "e s p r e s s o/c a p p u c c i n o") => 0;
+    lfs_remove(&lfs, "e s p r e s s o/m o c h a") => 0;
+
+    // stat paths
+    lfs_stat(&lfs, "e s p r e s s o/e s p r e s s o", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "e s p r e s s o/a m e r i c a n o", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "e s p r e s s o/m a c c h i a t o", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "e s p r e s s o/l a t t e", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "e s p r e s s o/c a p p u c c i n o", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "e s p r e s s o/m o c h a", &info) => LFS_ERR_NOENT;
+
+    lfs_unmount(&lfs) => 0;
+'''
+
+# test with only spaces
+#
+# please don't do this
+[cases.test_paths_oopsallspaces]
+defines.DIR = [false, true]
+code = '''
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
+
+    // create paths
+    lfs_mkdir(&lfs, " ") => 0;
+    if (DIR) {
+        lfs_mkdir(&lfs, " / ") => 0;
+        lfs_mkdir(&lfs, " /  ") => 0;
+        lfs_mkdir(&lfs, " /   ") => 0;
+        lfs_mkdir(&lfs, " /    ") => 0;
+        lfs_mkdir(&lfs, " /     ") => 0;
+        lfs_mkdir(&lfs, " /      ") => 0;
+    } else {
+        lfs_file_t file;
+        lfs_file_open(&lfs, &file, " / ",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, " /  ",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, " /   ",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, " /    ",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, " /     ",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, " /      ",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+    }
+
+    // stat paths
+    struct lfs_info info;
+    lfs_stat(&lfs, " / ", &info) => 0;
+    assert(strcmp(info.name, " ") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, " /  ", &info) => 0;
+    assert(strcmp(info.name, "  ") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, " /   ", &info) => 0;
+    assert(strcmp(info.name, "   ") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, " /    ", &info) => 0;
+    assert(strcmp(info.name, "    ") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, " /     ", &info) => 0;
+    assert(strcmp(info.name, "     ") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, " /      ", &info) => 0;
+    assert(strcmp(info.name, "      ") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+
+    // file open paths, only works on files!
+    if (DIR) {
+        lfs_file_t file;
+        lfs_file_open(&lfs, &file, " / ",
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, " /  ",
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, " /   ",
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, " /    ",
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, " /     ",
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, " /      ",
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+
+        lfs_file_open(&lfs, &file, " / ",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, " /  ",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, " /   ",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, " /    ",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, " /     ",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, " /      ",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+
+        lfs_file_open(&lfs, &file, " / ",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, " /  ",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, " /   ",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, " /    ",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, " /     ",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, " /      ",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+    } else {
+        lfs_file_t file;
+        lfs_file_open(&lfs, &file, " / ",
+                LFS_O_RDONLY) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, " /  ",
+                LFS_O_RDONLY) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, " /   ",
+                LFS_O_RDONLY) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, " /    ",
+                LFS_O_RDONLY) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, " /     ",
+                LFS_O_RDONLY) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, " /      ",
+                LFS_O_RDONLY) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+
+        lfs_file_open(&lfs, &file, " / ",
+                LFS_O_WRONLY | LFS_O_CREAT) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, " /  ",
+                LFS_O_WRONLY | LFS_O_CREAT) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, " /   ",
+                LFS_O_WRONLY | LFS_O_CREAT) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, " /    ",
+                LFS_O_WRONLY | LFS_O_CREAT) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, " /     ",
+                LFS_O_WRONLY | LFS_O_CREAT) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, " /      ",
+                LFS_O_WRONLY | LFS_O_CREAT) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+
+        lfs_file_open(&lfs, &file, " / ",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, " /  ",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, " /   ",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, " /    ",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, " /     ",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, " /      ",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+    }
+
+    // dir open paths, only works on dirs!
+    if (DIR) {
+        lfs_dir_t dir;
+        lfs_dir_open(&lfs, &dir, " / ") => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+        lfs_dir_open(&lfs, &dir, " /  ") => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+        lfs_dir_open(&lfs, &dir, " /   ") => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+        lfs_dir_open(&lfs, &dir, " /    ") => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+        lfs_dir_open(&lfs, &dir, " /     ") => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+        lfs_dir_open(&lfs, &dir, " /      ") => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+    } else {
+        lfs_dir_t dir;
+        lfs_dir_open(&lfs, &dir, " / ") => LFS_ERR_NOTDIR;
+        lfs_dir_open(&lfs, &dir, " /  ") => LFS_ERR_NOTDIR;
+        lfs_dir_open(&lfs, &dir, " /   ") => LFS_ERR_NOTDIR;
+        lfs_dir_open(&lfs, &dir, " /    ") => LFS_ERR_NOTDIR;
+        lfs_dir_open(&lfs, &dir, " /     ") => LFS_ERR_NOTDIR;
+        lfs_dir_open(&lfs, &dir, " /      ") => LFS_ERR_NOTDIR;
+    }
+
+    // rename paths
+    lfs_mkdir(&lfs, "  ") => 0;
+    lfs_rename(&lfs,
+            " / ",
+            "  /      ") => 0;
+    lfs_rename(&lfs,
+            " /  ",
+            "  /       ") => 0;
+    lfs_rename(&lfs,
+            " /   ",
+            "  /        ") => 0;
+    lfs_rename(&lfs,
+            " /    ",
+            "  /         ") => 0;
+    lfs_rename(&lfs,
+            " /     ",
+            "  /          ") => 0;
+    lfs_rename(&lfs,
+            " /      ",
+            "  /           ") => 0;
+
+    // stat paths
+    lfs_stat(&lfs, "  /      ", &info) => 0;
+    assert(strcmp(info.name, "      ") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "  /       ", &info) => 0;
+    assert(strcmp(info.name, "       ") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "  /        ", &info) => 0;
+    assert(strcmp(info.name, "        ") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "  /         ", &info) => 0;
+    assert(strcmp(info.name, "         ") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "  /          ", &info) => 0;
+    assert(strcmp(info.name, "          ") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "  /           ", &info) => 0;
+    assert(strcmp(info.name, "           ") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+
+    lfs_stat(&lfs, " / ", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, " /  ", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, " /   ", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, " /    ", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, " /     ", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, " /      ", &info) => LFS_ERR_NOENT;
+
+    // remove paths
+    lfs_remove(&lfs, "  /      ") => 0;
+    lfs_remove(&lfs, "  /       ") => 0;
+    lfs_remove(&lfs, "  /        ") => 0;
+    lfs_remove(&lfs, "  /         ") => 0;
+    lfs_remove(&lfs, "  /          ") => 0;
+    lfs_remove(&lfs, "  /           ") => 0;
+
+    // stat paths
+    lfs_stat(&lfs, "  /      ", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "  /       ", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "  /        ", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "  /         ", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "  /          ", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "  /           ", &info) => LFS_ERR_NOENT;
+
+    lfs_unmount(&lfs) => 0;
+'''
+
+# test with only ascii control characters
+#
+# littlefs only cares about "./" and NULL
+[cases.test_paths_nonprintable]
+defines.DIR = [false, true]
+code = '''
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
+
+    // create paths
+    lfs_mkdir(&lfs, "\x0c") => 0;
+    if (DIR) {
+        lfs_mkdir(&lfs, "\x0c/\x01") => 0;
+        lfs_mkdir(&lfs, "\x0c/\x02") => 0;
+        lfs_mkdir(&lfs, "\x0c/\x03") => 0;
+        lfs_mkdir(&lfs, "\x0c/\x04") => 0;
+        lfs_mkdir(&lfs, "\x0c/\x05") => 0;
+        lfs_mkdir(&lfs, "\x0c/\x06") => 0;
+    } else {
+        lfs_file_t file;
+        lfs_file_open(&lfs, &file, "\x0c/\x01",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "\x0c/\x02",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "\x0c/\x03",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "\x0c/\x04",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "\x0c/\x05",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "\x0c/\x06",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+    }
+
+    // stat paths
+    struct lfs_info info;
+    lfs_stat(&lfs, "\x0c/\x01", &info) => 0;
+    assert(strcmp(info.name, "\x01") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "\x0c/\x02", &info) => 0;
+    assert(strcmp(info.name, "\x02") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "\x0c/\x03", &info) => 0;
+    assert(strcmp(info.name, "\x03") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "\x0c/\x04", &info) => 0;
+    assert(strcmp(info.name, "\x04") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "\x0c/\x05", &info) => 0;
+    assert(strcmp(info.name, "\x05") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "\x0c/\x06", &info) => 0;
+    assert(strcmp(info.name, "\x06") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+
+    // file open paths, only works on files!
+    if (DIR) {
+        lfs_file_t file;
+        lfs_file_open(&lfs, &file, "\x0c/\x01",
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "\x0c/\x02",
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "\x0c/\x03",
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "\x0c/\x04",
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "\x0c/\x05",
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "\x0c/\x06",
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+
+        lfs_file_open(&lfs, &file, "\x0c/\x01",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "\x0c/\x02",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "\x0c/\x03",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "\x0c/\x04",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "\x0c/\x05",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "\x0c/\x06",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+
+        lfs_file_open(&lfs, &file, "\x0c/\x01",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "\x0c/\x02",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "\x0c/\x03",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "\x0c/\x04",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "\x0c/\x05",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "\x0c/\x06",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+    } else {
+        lfs_file_t file;
+        lfs_file_open(&lfs, &file, "\x0c/\x01",
+                LFS_O_RDONLY) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "\x0c/\x02",
+                LFS_O_RDONLY) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "\x0c/\x03",
+                LFS_O_RDONLY) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "\x0c/\x04",
+                LFS_O_RDONLY) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "\x0c/\x05",
+                LFS_O_RDONLY) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "\x0c/\x06",
+                LFS_O_RDONLY) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+
+        lfs_file_open(&lfs, &file, "\x0c/\x01",
+                LFS_O_WRONLY | LFS_O_CREAT) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "\x0c/\x02",
+                LFS_O_WRONLY | LFS_O_CREAT) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "\x0c/\x03",
+                LFS_O_WRONLY | LFS_O_CREAT) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "\x0c/\x04",
+                LFS_O_WRONLY | LFS_O_CREAT) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "\x0c/\x05",
+                LFS_O_WRONLY | LFS_O_CREAT) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "\x0c/\x06",
+                LFS_O_WRONLY | LFS_O_CREAT) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+
+        lfs_file_open(&lfs, &file, "\x0c/\x01",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "\x0c/\x02",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "\x0c/\x03",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "\x0c/\x04",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "\x0c/\x05",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "\x0c/\x06",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+    }
+
+    // dir open paths, only works on dirs!
+    if (DIR) {
+        lfs_dir_t dir;
+        lfs_dir_open(&lfs, &dir, "\x0c/\x01") => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+        lfs_dir_open(&lfs, &dir, "\x0c/\x02") => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+        lfs_dir_open(&lfs, &dir, "\x0c/\x03") => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+        lfs_dir_open(&lfs, &dir, "\x0c/\x04") => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+        lfs_dir_open(&lfs, &dir, "\x0c/\x05") => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+        lfs_dir_open(&lfs, &dir, "\x0c/\x06") => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+    } else {
+        lfs_dir_t dir;
+        lfs_dir_open(&lfs, &dir, "\x0c/\x01") => LFS_ERR_NOTDIR;
+        lfs_dir_open(&lfs, &dir, "\x0c/\x02") => LFS_ERR_NOTDIR;
+        lfs_dir_open(&lfs, &dir, "\x0c/\x03") => LFS_ERR_NOTDIR;
+        lfs_dir_open(&lfs, &dir, "\x0c/\x04") => LFS_ERR_NOTDIR;
+        lfs_dir_open(&lfs, &dir, "\x0c/\x05") => LFS_ERR_NOTDIR;
+        lfs_dir_open(&lfs, &dir, "\x0c/\x06") => LFS_ERR_NOTDIR;
+    }
+
+    // rename paths
+    lfs_mkdir(&lfs, "\x0e") => 0;
+    lfs_rename(&lfs,
+            "\x0c/\x01",
+            "\x0e/\x1a") => 0;
+    lfs_rename(&lfs,
+            "\x0c/\x02",
+            "\x0e/\x1b") => 0;
+    lfs_rename(&lfs,
+            "\x0c/\x03",
+            "\x0e/\x1c") => 0;
+    lfs_rename(&lfs,
+            "\x0c/\x04",
+            "\x0e/\x1d") => 0;
+    lfs_rename(&lfs,
+            "\x0c/\x05",
+            "\x0e/\x1e") => 0;
+    lfs_rename(&lfs,
+            "\x0c/\x06",
+            "\x0e/\x1f") => 0;
+
+    // stat paths
+    lfs_stat(&lfs, "\x0e/\x1a", &info) => 0;
+    assert(strcmp(info.name, "\x1a") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "\x0e/\x1b", &info) => 0;
+    assert(strcmp(info.name, "\x1b") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "\x0e/\x1c", &info) => 0;
+    assert(strcmp(info.name, "\x1c") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "\x0e/\x1d", &info) => 0;
+    assert(strcmp(info.name, "\x1d") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "\x0e/\x1e", &info) => 0;
+    assert(strcmp(info.name, "\x1e") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "\x0e/\x1f", &info) => 0;
+    assert(strcmp(info.name, "\x1f") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+
+    lfs_stat(&lfs, "\x0c/\x01", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "\x0c/\x02", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "\x0c/\x03", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "\x0c/\x04", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "\x0c/\x05", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "\x0c/\x06", &info) => LFS_ERR_NOENT;
+
+    // remove paths
+    lfs_remove(&lfs, "\x0e/\x1a") => 0;
+    lfs_remove(&lfs, "\x0e/\x1b") => 0;
+    lfs_remove(&lfs, "\x0e/\x1c") => 0;
+    lfs_remove(&lfs, "\x0e/\x1d") => 0;
+    lfs_remove(&lfs, "\x0e/\x1e") => 0;
+    lfs_remove(&lfs, "\x0e/\x1f") => 0;
+
+    // stat paths
+    lfs_stat(&lfs, "\x0e/\x1a", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "\x0e/\x1b", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "\x0e/\x1c", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "\x0e/\x1d", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "\x0e/\x1e", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "\x0e/\x1f", &info) => LFS_ERR_NOENT;
+
+    lfs_unmount(&lfs) => 0;
+'''
+
+# test with only ascii DELs
+#
+# I don't know why you'd do this
+[cases.test_paths_oopsalldels]
+defines.DIR = [false, true]
+code = '''
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
+
+    // create paths
+    lfs_mkdir(&lfs, "\x7f") => 0;
+    if (DIR) {
+        lfs_mkdir(&lfs, "\x7f/\x7f") => 0;
+        lfs_mkdir(&lfs, "\x7f/\x7f\x7f") => 0;
+        lfs_mkdir(&lfs, "\x7f/\x7f\x7f\x7f") => 0;
+        lfs_mkdir(&lfs, "\x7f/\x7f\x7f\x7f\x7f") => 0;
+        lfs_mkdir(&lfs, "\x7f/\x7f\x7f\x7f\x7f\x7f") => 0;
+        lfs_mkdir(&lfs, "\x7f/\x7f\x7f\x7f\x7f\x7f\x7f") => 0;
+    } else {
+        lfs_file_t file;
+        lfs_file_open(&lfs, &file, "\x7f/\x7f",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "\x7f/\x7f\x7f",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "\x7f/\x7f\x7f\x7f",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "\x7f/\x7f\x7f\x7f\x7f",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "\x7f/\x7f\x7f\x7f\x7f\x7f",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "\x7f/\x7f\x7f\x7f\x7f\x7f\x7f",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+    }
+
+    // stat paths
+    struct lfs_info info;
+    lfs_stat(&lfs, "\x7f/\x7f", &info) => 0;
+    assert(strcmp(info.name, "\x7f") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "\x7f/\x7f\x7f", &info) => 0;
+    assert(strcmp(info.name, "\x7f\x7f") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "\x7f/\x7f\x7f\x7f", &info) => 0;
+    assert(strcmp(info.name, "\x7f\x7f\x7f") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "\x7f/\x7f\x7f\x7f\x7f", &info) => 0;
+    assert(strcmp(info.name, "\x7f\x7f\x7f\x7f") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "\x7f/\x7f\x7f\x7f\x7f\x7f", &info) => 0;
+    assert(strcmp(info.name, "\x7f\x7f\x7f\x7f\x7f") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "\x7f/\x7f\x7f\x7f\x7f\x7f\x7f", &info) => 0;
+    assert(strcmp(info.name, "\x7f\x7f\x7f\x7f\x7f\x7f") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+
+    // file open paths, only works on files!
+    if (DIR) {
+        lfs_file_t file;
+        lfs_file_open(&lfs, &file, "\x7f/\x7f",
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "\x7f/\x7f\x7f",
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "\x7f/\x7f\x7f\x7f",
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "\x7f/\x7f\x7f\x7f\x7f",
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "\x7f/\x7f\x7f\x7f\x7f\x7f",
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "\x7f/\x7f\x7f\x7f\x7f\x7f\x7f",
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+
+        lfs_file_open(&lfs, &file, "\x7f/\x7f",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "\x7f/\x7f\x7f",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "\x7f/\x7f\x7f\x7f",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "\x7f/\x7f\x7f\x7f\x7f",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "\x7f/\x7f\x7f\x7f\x7f\x7f",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "\x7f/\x7f\x7f\x7f\x7f\x7f\x7f",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+
+        lfs_file_open(&lfs, &file, "\x7f/\x7f",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "\x7f/\x7f\x7f",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "\x7f/\x7f\x7f\x7f",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "\x7f/\x7f\x7f\x7f\x7f",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "\x7f/\x7f\x7f\x7f\x7f\x7f",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "\x7f/\x7f\x7f\x7f\x7f\x7f\x7f",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+    } else {
+        lfs_file_t file;
+        lfs_file_open(&lfs, &file, "\x7f/\x7f",
+                LFS_O_RDONLY) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "\x7f/\x7f\x7f",
+                LFS_O_RDONLY) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "\x7f/\x7f\x7f\x7f",
+                LFS_O_RDONLY) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "\x7f/\x7f\x7f\x7f\x7f",
+                LFS_O_RDONLY) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "\x7f/\x7f\x7f\x7f\x7f\x7f",
+                LFS_O_RDONLY) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "\x7f/\x7f\x7f\x7f\x7f\x7f\x7f",
+                LFS_O_RDONLY) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+
+        lfs_file_open(&lfs, &file, "\x7f/\x7f",
+                LFS_O_WRONLY | LFS_O_CREAT) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "\x7f/\x7f\x7f",
+                LFS_O_WRONLY | LFS_O_CREAT) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "\x7f/\x7f\x7f\x7f",
+                LFS_O_WRONLY | LFS_O_CREAT) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "\x7f/\x7f\x7f\x7f\x7f",
+                LFS_O_WRONLY | LFS_O_CREAT) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "\x7f/\x7f\x7f\x7f\x7f\x7f",
+                LFS_O_WRONLY | LFS_O_CREAT) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "\x7f/\x7f\x7f\x7f\x7f\x7f\x7f",
+                LFS_O_WRONLY | LFS_O_CREAT) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+
+        lfs_file_open(&lfs, &file, "\x7f/\x7f",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "\x7f/\x7f\x7f",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "\x7f/\x7f\x7f\x7f",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "\x7f/\x7f\x7f\x7f\x7f",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "\x7f/\x7f\x7f\x7f\x7f\x7f",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "\x7f/\x7f\x7f\x7f\x7f\x7f\x7f",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+    }
+
+    // dir open paths, only works on dirs!
+    if (DIR) {
+        lfs_dir_t dir;
+        lfs_dir_open(&lfs, &dir, "\x7f/\x7f") => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+        lfs_dir_open(&lfs, &dir, "\x7f/\x7f\x7f") => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+        lfs_dir_open(&lfs, &dir, "\x7f/\x7f\x7f\x7f") => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+        lfs_dir_open(&lfs, &dir, "\x7f/\x7f\x7f\x7f\x7f") => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+        lfs_dir_open(&lfs, &dir, "\x7f/\x7f\x7f\x7f\x7f\x7f") => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+        lfs_dir_open(&lfs, &dir, "\x7f/\x7f\x7f\x7f\x7f\x7f\x7f") => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+    } else {
+        lfs_dir_t dir;
+        lfs_dir_open(&lfs, &dir, "\x7f/\x7f") => LFS_ERR_NOTDIR;
+        lfs_dir_open(&lfs, &dir, "\x7f/\x7f\x7f") => LFS_ERR_NOTDIR;
+        lfs_dir_open(&lfs, &dir, "\x7f/\x7f\x7f\x7f") => LFS_ERR_NOTDIR;
+        lfs_dir_open(&lfs, &dir, "\x7f/\x7f\x7f\x7f\x7f") => LFS_ERR_NOTDIR;
+        lfs_dir_open(&lfs, &dir, "\x7f/\x7f\x7f\x7f\x7f\x7f") => LFS_ERR_NOTDIR;
+        lfs_dir_open(&lfs, &dir, "\x7f/\x7f\x7f\x7f\x7f\x7f\x7f") => LFS_ERR_NOTDIR;
+    }
+
+    // rename paths
+    lfs_mkdir(&lfs, "\x7f\x7f") => 0;
+    lfs_rename(&lfs,
+            "\x7f/\x7f",
+            "\x7f\x7f/\x7f\x7f\x7f\x7f\x7f\x7f") => 0;
+    lfs_rename(&lfs,
+            "\x7f/\x7f\x7f",
+            "\x7f\x7f/\x7f\x7f\x7f\x7f\x7f\x7f\x7f") => 0;
+    lfs_rename(&lfs,
+            "\x7f/\x7f\x7f\x7f",
+            "\x7f\x7f/\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f") => 0;
+    lfs_rename(&lfs,
+            "\x7f/\x7f\x7f\x7f\x7f",
+            "\x7f\x7f/\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f") => 0;
+    lfs_rename(&lfs,
+            "\x7f/\x7f\x7f\x7f\x7f\x7f",
+            "\x7f\x7f/\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f") => 0;
+    lfs_rename(&lfs,
+            "\x7f/\x7f\x7f\x7f\x7f\x7f\x7f",
+            "\x7f\x7f/\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f") => 0;
+
+    // stat paths
+    lfs_stat(&lfs, "\x7f\x7f/\x7f\x7f\x7f\x7f\x7f\x7f", &info) => 0;
+    assert(strcmp(info.name, "\x7f\x7f\x7f\x7f\x7f\x7f") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "\x7f\x7f/\x7f\x7f\x7f\x7f\x7f\x7f\x7f", &info) => 0;
+    assert(strcmp(info.name, "\x7f\x7f\x7f\x7f\x7f\x7f\x7f") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "\x7f\x7f/\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f", &info) => 0;
+    assert(strcmp(info.name, "\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "\x7f\x7f/\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f", &info) => 0;
+    assert(strcmp(info.name, "\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "\x7f\x7f/\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f", &info) => 0;
+    assert(strcmp(info.name, "\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "\x7f\x7f/\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f", &info) => 0;
+    assert(strcmp(info.name, "\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+
+    lfs_stat(&lfs, "\x7f/\x7f", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "\x7f/\x7f\x7f", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "\x7f/\x7f\x7f\x7f", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "\x7f/\x7f\x7f\x7f\x7f", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "\x7f/\x7f\x7f\x7f\x7f\x7f", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "\x7f/\x7f\x7f\x7f\x7f\x7f\x7f", &info) => LFS_ERR_NOENT;
+
+    // remove paths
+    lfs_remove(&lfs, "\x7f\x7f/\x7f\x7f\x7f\x7f\x7f\x7f") => 0;
+    lfs_remove(&lfs, "\x7f\x7f/\x7f\x7f\x7f\x7f\x7f\x7f\x7f") => 0;
+    lfs_remove(&lfs, "\x7f\x7f/\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f") => 0;
+    lfs_remove(&lfs, "\x7f\x7f/\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f") => 0;
+    lfs_remove(&lfs, "\x7f\x7f/\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f") => 0;
+    lfs_remove(&lfs, "\x7f\x7f/\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f") => 0;
+
+    // stat paths
+    lfs_stat(&lfs, "\x7f\x7f/\x7f\x7f\x7f\x7f\x7f\x7f", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "\x7f\x7f/\x7f\x7f\x7f\x7f\x7f\x7f\x7f", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "\x7f\x7f/\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "\x7f\x7f/\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "\x7f\x7f/\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "\x7f\x7f/\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f", &info) => LFS_ERR_NOENT;
+
+    lfs_unmount(&lfs) => 0;
+'''
+
+# test with invalid utf8 sequences
+#
+# Don't do this! These filenames are not utf8 and will probably break
+# external tools.
+#
+[cases.test_paths_nonutf8]
+defines.DIR = [false, true]
+code = '''
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
+
+    // create paths
+    lfs_mkdir(&lfs, "\xc0") => 0;
+    if (DIR) {
+        lfs_mkdir(&lfs, "\xc0/\xa0") => 0;
+        lfs_mkdir(&lfs, "\xc0/\xb0") => 0;
+        lfs_mkdir(&lfs, "\xc0/\xc0") => 0;
+        lfs_mkdir(&lfs, "\xc0/\xd0") => 0;
+        lfs_mkdir(&lfs, "\xc0/\xe0") => 0;
+        lfs_mkdir(&lfs, "\xc0/\xf0") => 0;
+    } else {
+        lfs_file_t file;
+        lfs_file_open(&lfs, &file, "\xc0/\xa0",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "\xc0/\xb0",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "\xc0/\xc0",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "\xc0/\xd0",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "\xc0/\xe0",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "\xc0/\xf0",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+    }
+
+    // stat paths
+    struct lfs_info info;
+    lfs_stat(&lfs, "\xc0/\xa0", &info) => 0;
+    assert(strcmp(info.name, "\xa0") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "\xc0/\xb0", &info) => 0;
+    assert(strcmp(info.name, "\xb0") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "\xc0/\xc0", &info) => 0;
+    assert(strcmp(info.name, "\xc0") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "\xc0/\xd0", &info) => 0;
+    assert(strcmp(info.name, "\xd0") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "\xc0/\xe0", &info) => 0;
+    assert(strcmp(info.name, "\xe0") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "\xc0/\xf0", &info) => 0;
+    assert(strcmp(info.name, "\xf0") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+
+    // file open paths, only works on files!
+    if (DIR) {
+        lfs_file_t file;
+        lfs_file_open(&lfs, &file, "\xc0/\xa0",
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "\xc0/\xb0",
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "\xc0/\xc0",
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "\xc0/\xd0",
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "\xc0/\xe0",
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "\xc0/\xf0",
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+
+        lfs_file_open(&lfs, &file, "\xc0/\xa0",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "\xc0/\xb0",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "\xc0/\xc0",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "\xc0/\xd0",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "\xc0/\xe0",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "\xc0/\xf0",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+
+        lfs_file_open(&lfs, &file, "\xc0/\xa0",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "\xc0/\xb0",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "\xc0/\xc0",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "\xc0/\xd0",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "\xc0/\xe0",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "\xc0/\xf0",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+    } else {
+        lfs_file_t file;
+        lfs_file_open(&lfs, &file, "\xc0/\xa0",
+                LFS_O_RDONLY) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "\xc0/\xb0",
+                LFS_O_RDONLY) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "\xc0/\xc0",
+                LFS_O_RDONLY) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "\xc0/\xd0",
+                LFS_O_RDONLY) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "\xc0/\xe0",
+                LFS_O_RDONLY) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "\xc0/\xf0",
+                LFS_O_RDONLY) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+
+        lfs_file_open(&lfs, &file, "\xc0/\xa0",
+                LFS_O_WRONLY | LFS_O_CREAT) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "\xc0/\xb0",
+                LFS_O_WRONLY | LFS_O_CREAT) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "\xc0/\xc0",
+                LFS_O_WRONLY | LFS_O_CREAT) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "\xc0/\xd0",
+                LFS_O_WRONLY | LFS_O_CREAT) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "\xc0/\xe0",
+                LFS_O_WRONLY | LFS_O_CREAT) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "\xc0/\xf0",
+                LFS_O_WRONLY | LFS_O_CREAT) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+
+        lfs_file_open(&lfs, &file, "\xc0/\xa0",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "\xc0/\xb0",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "\xc0/\xc0",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "\xc0/\xd0",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "\xc0/\xe0",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "\xc0/\xf0",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+    }
+
+    // dir open paths, only works on dirs!
+    if (DIR) {
+        lfs_dir_t dir;
+        lfs_dir_open(&lfs, &dir, "\xc0/\xa0") => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+        lfs_dir_open(&lfs, &dir, "\xc0/\xb0") => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+        lfs_dir_open(&lfs, &dir, "\xc0/\xc0") => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+        lfs_dir_open(&lfs, &dir, "\xc0/\xd0") => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+        lfs_dir_open(&lfs, &dir, "\xc0/\xe0") => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+        lfs_dir_open(&lfs, &dir, "\xc0/\xf0") => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+    } else {
+        lfs_dir_t dir;
+        lfs_dir_open(&lfs, &dir, "\xc0/\xa0") => LFS_ERR_NOTDIR;
+        lfs_dir_open(&lfs, &dir, "\xc0/\xb0") => LFS_ERR_NOTDIR;
+        lfs_dir_open(&lfs, &dir, "\xc0/\xc0") => LFS_ERR_NOTDIR;
+        lfs_dir_open(&lfs, &dir, "\xc0/\xd0") => LFS_ERR_NOTDIR;
+        lfs_dir_open(&lfs, &dir, "\xc0/\xe0") => LFS_ERR_NOTDIR;
+        lfs_dir_open(&lfs, &dir, "\xc0/\xf0") => LFS_ERR_NOTDIR;
+    }
+
+    // rename paths
+    lfs_mkdir(&lfs, "\xe0") => 0;
+    lfs_rename(&lfs,
+            "\xc0/\xa0",
+            "\xe0/\xaf") => 0;
+    lfs_rename(&lfs,
+            "\xc0/\xb0",
+            "\xe0/\xbf") => 0;
+    lfs_rename(&lfs,
+            "\xc0/\xc0",
+            "\xe0/\xcf") => 0;
+    lfs_rename(&lfs,
+            "\xc0/\xd0",
+            "\xe0/\xdf") => 0;
+    lfs_rename(&lfs,
+            "\xc0/\xe0",
+            "\xe0/\xef") => 0;
+    lfs_rename(&lfs,
+            "\xc0/\xf0",
+            "\xe0/\xff") => 0;
+
+    // stat paths
+    lfs_stat(&lfs, "\xe0/\xaf", &info) => 0;
+    assert(strcmp(info.name, "\xaf") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "\xe0/\xbf", &info) => 0;
+    assert(strcmp(info.name, "\xbf") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "\xe0/\xcf", &info) => 0;
+    assert(strcmp(info.name, "\xcf") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "\xe0/\xdf", &info) => 0;
+    assert(strcmp(info.name, "\xdf") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "\xe0/\xef", &info) => 0;
+    assert(strcmp(info.name, "\xef") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "\xe0/\xff", &info) => 0;
+    assert(strcmp(info.name, "\xff") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+
+    lfs_stat(&lfs, "\xc0/\xa0", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "\xc0/\xb0", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "\xc0/\xc0", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "\xc0/\xd0", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "\xc0/\xe0", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "\xc0/\xf0", &info) => LFS_ERR_NOENT;
+
+    // remove paths
+    lfs_remove(&lfs, "\xe0/\xaf") => 0;
+    lfs_remove(&lfs, "\xe0/\xbf") => 0;
+    lfs_remove(&lfs, "\xe0/\xcf") => 0;
+    lfs_remove(&lfs, "\xe0/\xdf") => 0;
+    lfs_remove(&lfs, "\xe0/\xef") => 0;
+    lfs_remove(&lfs, "\xe0/\xff") => 0;
+
+    // stat paths
+    lfs_stat(&lfs, "\xe0/\xaf", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "\xe0/\xbf", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "\xe0/\xcf", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "\xe0/\xdf", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "\xe0/\xef", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "\xe0/\xff", &info) => LFS_ERR_NOENT;
+
+    lfs_unmount(&lfs) => 0;
+'''
+
+# test with only "\xff" characters
+#
+# Don't do this! These filenames are not utf8 and will probably break
+# external tools.
+#
+[cases.test_paths_oopsallffs]
+defines.DIR = [false, true]
+code = '''
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
+
+    // create paths
+    lfs_mkdir(&lfs, "\xff") => 0;
+    if (DIR) {
+        lfs_mkdir(&lfs, "\xff/\xff") => 0;
+        lfs_mkdir(&lfs, "\xff/\xff\xff") => 0;
+        lfs_mkdir(&lfs, "\xff/\xff\xff\xff") => 0;
+        lfs_mkdir(&lfs, "\xff/\xff\xff\xff\xff") => 0;
+        lfs_mkdir(&lfs, "\xff/\xff\xff\xff\xff\xff") => 0;
+        lfs_mkdir(&lfs, "\xff/\xff\xff\xff\xff\xff\xff") => 0;
+    } else {
+        lfs_file_t file;
+        lfs_file_open(&lfs, &file, "\xff/\xff",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "\xff/\xff\xff",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "\xff/\xff\xff\xff",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "\xff/\xff\xff\xff\xff",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "\xff/\xff\xff\xff\xff\xff",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "\xff/\xff\xff\xff\xff\xff\xff",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+    }
+
+    // stat paths
+    struct lfs_info info;
+    lfs_stat(&lfs, "\xff/\xff", &info) => 0;
+    assert(strcmp(info.name, "\xff") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "\xff/\xff\xff", &info) => 0;
+    assert(strcmp(info.name, "\xff\xff") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "\xff/\xff\xff\xff", &info) => 0;
+    assert(strcmp(info.name, "\xff\xff\xff") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "\xff/\xff\xff\xff\xff", &info) => 0;
+    assert(strcmp(info.name, "\xff\xff\xff\xff") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "\xff/\xff\xff\xff\xff\xff", &info) => 0;
+    assert(strcmp(info.name, "\xff\xff\xff\xff\xff") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "\xff/\xff\xff\xff\xff\xff\xff", &info) => 0;
+    assert(strcmp(info.name, "\xff\xff\xff\xff\xff\xff") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+
+    // file open paths, only works on files!
+    if (DIR) {
+        lfs_file_t file;
+        lfs_file_open(&lfs, &file, "\xff/\xff",
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "\xff/\xff\xff",
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "\xff/\xff\xff\xff",
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "\xff/\xff\xff\xff\xff",
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "\xff/\xff\xff\xff\xff\xff",
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "\xff/\xff\xff\xff\xff\xff\xff",
+                LFS_O_RDONLY) => LFS_ERR_ISDIR;
+
+        lfs_file_open(&lfs, &file, "\xff/\xff",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "\xff/\xff\xff",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "\xff/\xff\xff\xff",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "\xff/\xff\xff\xff\xff",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "\xff/\xff\xff\xff\xff\xff",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+        lfs_file_open(&lfs, &file, "\xff/\xff\xff\xff\xff\xff\xff",
+                LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
+
+        lfs_file_open(&lfs, &file, "\xff/\xff",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "\xff/\xff\xff",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "\xff/\xff\xff\xff",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "\xff/\xff\xff\xff\xff",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "\xff/\xff\xff\xff\xff\xff",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "\xff/\xff\xff\xff\xff\xff\xff",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+    } else {
+        lfs_file_t file;
+        lfs_file_open(&lfs, &file, "\xff/\xff",
+                LFS_O_RDONLY) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "\xff/\xff\xff",
+                LFS_O_RDONLY) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "\xff/\xff\xff\xff",
+                LFS_O_RDONLY) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "\xff/\xff\xff\xff\xff",
+                LFS_O_RDONLY) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "\xff/\xff\xff\xff\xff\xff",
+                LFS_O_RDONLY) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "\xff/\xff\xff\xff\xff\xff\xff",
+                LFS_O_RDONLY) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+
+        lfs_file_open(&lfs, &file, "\xff/\xff",
+                LFS_O_WRONLY | LFS_O_CREAT) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "\xff/\xff\xff",
+                LFS_O_WRONLY | LFS_O_CREAT) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "\xff/\xff\xff\xff",
+                LFS_O_WRONLY | LFS_O_CREAT) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "\xff/\xff\xff\xff\xff",
+                LFS_O_WRONLY | LFS_O_CREAT) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "\xff/\xff\xff\xff\xff\xff",
+                LFS_O_WRONLY | LFS_O_CREAT) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        lfs_file_open(&lfs, &file, "\xff/\xff\xff\xff\xff\xff\xff",
+                LFS_O_WRONLY | LFS_O_CREAT) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+
+        lfs_file_open(&lfs, &file, "\xff/\xff",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "\xff/\xff\xff",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "\xff/\xff\xff\xff",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "\xff/\xff\xff\xff\xff",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "\xff/\xff\xff\xff\xff\xff",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+        lfs_file_open(&lfs, &file, "\xff/\xff\xff\xff\xff\xff\xff",
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
+    }
+
+    // dir open paths, only works on dirs!
+    if (DIR) {
+        lfs_dir_t dir;
+        lfs_dir_open(&lfs, &dir, "\xff/\xff") => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+        lfs_dir_open(&lfs, &dir, "\xff/\xff\xff") => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+        lfs_dir_open(&lfs, &dir, "\xff/\xff\xff\xff") => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+        lfs_dir_open(&lfs, &dir, "\xff/\xff\xff\xff\xff") => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+        lfs_dir_open(&lfs, &dir, "\xff/\xff\xff\xff\xff\xff") => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+        lfs_dir_open(&lfs, &dir, "\xff/\xff\xff\xff\xff\xff\xff") => 0;
+        lfs_dir_close(&lfs, &dir) => 0;
+    } else {
+        lfs_dir_t dir;
+        lfs_dir_open(&lfs, &dir, "\xff/\xff") => LFS_ERR_NOTDIR;
+        lfs_dir_open(&lfs, &dir, "\xff/\xff\xff") => LFS_ERR_NOTDIR;
+        lfs_dir_open(&lfs, &dir, "\xff/\xff\xff\xff") => LFS_ERR_NOTDIR;
+        lfs_dir_open(&lfs, &dir, "\xff/\xff\xff\xff\xff") => LFS_ERR_NOTDIR;
+        lfs_dir_open(&lfs, &dir, "\xff/\xff\xff\xff\xff\xff") => LFS_ERR_NOTDIR;
+        lfs_dir_open(&lfs, &dir, "\xff/\xff\xff\xff\xff\xff\xff") => LFS_ERR_NOTDIR;
+    }
+
+    // rename paths
+    lfs_mkdir(&lfs, "\xff\xff") => 0;
+    lfs_rename(&lfs,
+            "\xff/\xff",
+            "\xff\xff/\xff\xff\xff\xff\xff\xff") => 0;
+    lfs_rename(&lfs,
+            "\xff/\xff\xff",
+            "\xff\xff/\xff\xff\xff\xff\xff\xff\xff") => 0;
+    lfs_rename(&lfs,
+            "\xff/\xff\xff\xff",
+            "\xff\xff/\xff\xff\xff\xff\xff\xff\xff\xff") => 0;
+    lfs_rename(&lfs,
+            "\xff/\xff\xff\xff\xff",
+            "\xff\xff/\xff\xff\xff\xff\xff\xff\xff\xff\xff") => 0;
+    lfs_rename(&lfs,
+            "\xff/\xff\xff\xff\xff\xff",
+            "\xff\xff/\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff") => 0;
+    lfs_rename(&lfs,
+            "\xff/\xff\xff\xff\xff\xff\xff",
+            "\xff\xff/\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff") => 0;
+
+    // stat paths
+    lfs_stat(&lfs, "\xff\xff/\xff\xff\xff\xff\xff\xff", &info) => 0;
+    assert(strcmp(info.name, "\xff\xff\xff\xff\xff\xff") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "\xff\xff/\xff\xff\xff\xff\xff\xff\xff", &info) => 0;
+    assert(strcmp(info.name, "\xff\xff\xff\xff\xff\xff\xff") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "\xff\xff/\xff\xff\xff\xff\xff\xff\xff\xff", &info) => 0;
+    assert(strcmp(info.name, "\xff\xff\xff\xff\xff\xff\xff\xff") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "\xff\xff/\xff\xff\xff\xff\xff\xff\xff\xff\xff", &info) => 0;
+    assert(strcmp(info.name, "\xff\xff\xff\xff\xff\xff\xff\xff\xff") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "\xff\xff/\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff", &info) => 0;
+    assert(strcmp(info.name, "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+    lfs_stat(&lfs, "\xff\xff/\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff", &info) => 0;
+    assert(strcmp(info.name, "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff") == 0);
+    assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG));
+
+    lfs_stat(&lfs, "\xff/\xff", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "\xff/\xff\xff", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "\xff/\xff\xff\xff", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "\xff/\xff\xff\xff\xff", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "\xff/\xff\xff\xff\xff\xff", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "\xff/\xff\xff\xff\xff\xff\xff", &info) => LFS_ERR_NOENT;
+
+    // remove paths
+    lfs_remove(&lfs, "\xff\xff/\xff\xff\xff\xff\xff\xff") => 0;
+    lfs_remove(&lfs, "\xff\xff/\xff\xff\xff\xff\xff\xff\xff") => 0;
+    lfs_remove(&lfs, "\xff\xff/\xff\xff\xff\xff\xff\xff\xff\xff") => 0;
+    lfs_remove(&lfs, "\xff\xff/\xff\xff\xff\xff\xff\xff\xff\xff\xff") => 0;
+    lfs_remove(&lfs, "\xff\xff/\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff") => 0;
+    lfs_remove(&lfs, "\xff\xff/\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff") => 0;
+
+    // stat paths
+    lfs_stat(&lfs, "\xff\xff/\xff\xff\xff\xff\xff\xff", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "\xff\xff/\xff\xff\xff\xff\xff\xff\xff", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "\xff\xff/\xff\xff\xff\xff\xff\xff\xff\xff", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "\xff\xff/\xff\xff\xff\xff\xff\xff\xff\xff\xff", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "\xff\xff/\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff", &info) => LFS_ERR_NOENT;
+    lfs_stat(&lfs, "\xff\xff/\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff", &info) => LFS_ERR_NOENT;
+
+    lfs_unmount(&lfs) => 0;
+'''

+ 168 - 0
tests/test_relocations.toml

@@ -341,3 +341,171 @@ code = '''
     }
     lfs_unmount(&lfs) => 0;
 '''
+
+# non-reentrant testing for orphans, this is the same as reentrant
+# testing, but we test way more states than we could under powerloss
+[cases.test_relocations_nonreentrant]
+# TODO fix this case, caused by non-DAG trees
+# NOTE the second condition is required
+if = '!(DEPTH == 3 && CACHE_SIZE != 64) && 2*FILES < BLOCK_COUNT'
+defines = [
+    {FILES=6,  DEPTH=1, CYCLES=2000, BLOCK_CYCLES=1},
+    {FILES=26, DEPTH=1, CYCLES=2000, BLOCK_CYCLES=1},
+    {FILES=3,  DEPTH=3, CYCLES=2000, BLOCK_CYCLES=1},
+]
+code = '''
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
+
+    uint32_t prng = 1;
+    const char alpha[] = "abcdefghijklmnopqrstuvwxyz";
+    for (unsigned i = 0; i < CYCLES; i++) {
+        // create random path
+        char full_path[256];
+        for (unsigned d = 0; d < DEPTH; d++) {
+            sprintf(&full_path[2*d], "/%c", alpha[TEST_PRNG(&prng) % FILES]);
+        }
+
+        // if it does not exist, we create it, else we destroy
+        struct lfs_info info;
+        int res = lfs_stat(&lfs, full_path, &info);
+        if (res == LFS_ERR_NOENT) {
+            // create each directory in turn, ignore if dir already exists
+            for (unsigned d = 0; d < DEPTH; d++) {
+                char path[1024];
+                strcpy(path, full_path);
+                path[2*d+2] = '\0';
+                int err = lfs_mkdir(&lfs, path);
+                assert(!err || err == LFS_ERR_EXIST);
+            }
+
+            for (unsigned d = 0; d < DEPTH; d++) {
+                char path[1024];
+                strcpy(path, full_path);
+                path[2*d+2] = '\0';
+                lfs_stat(&lfs, path, &info) => 0;
+                assert(strcmp(info.name, &path[2*d+1]) == 0);
+                assert(info.type == LFS_TYPE_DIR);
+            }
+        } else {
+            // is valid dir?
+            assert(strcmp(info.name, &full_path[2*(DEPTH-1)+1]) == 0);
+            assert(info.type == LFS_TYPE_DIR);
+
+            // try to delete path in reverse order, ignore if dir is not empty
+            for (unsigned d = DEPTH-1; d+1 > 0; d--) {
+                char path[1024];
+                strcpy(path, full_path);
+                path[2*d+2] = '\0';
+                int err = lfs_remove(&lfs, path);
+                assert(!err || err == LFS_ERR_NOTEMPTY);
+            }
+
+            lfs_stat(&lfs, full_path, &info) => LFS_ERR_NOENT;
+        }
+    }
+    lfs_unmount(&lfs) => 0;
+'''
+
+# non-reentrant testing for relocations, but now with random renames!
+[cases.test_relocations_nonreentrant_renames]
+# TODO fix this case, caused by non-DAG trees
+# NOTE the second condition is required
+if = '!(DEPTH == 3 && CACHE_SIZE != 64) && 2*FILES < BLOCK_COUNT'
+defines = [
+    {FILES=6,  DEPTH=1, CYCLES=2000, BLOCK_CYCLES=1},
+    {FILES=26, DEPTH=1, CYCLES=2000, BLOCK_CYCLES=1},
+    {FILES=3,  DEPTH=3, CYCLES=2000, BLOCK_CYCLES=1},
+]
+code = '''
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
+
+    uint32_t prng = 1;
+    const char alpha[] = "abcdefghijklmnopqrstuvwxyz";
+    for (unsigned i = 0; i < CYCLES; i++) {
+        // create random path
+        char full_path[256];
+        for (unsigned d = 0; d < DEPTH; d++) {
+            sprintf(&full_path[2*d], "/%c", alpha[TEST_PRNG(&prng) % FILES]);
+        }
+
+        // if it does not exist, we create it, else we destroy
+        struct lfs_info info;
+        int res = lfs_stat(&lfs, full_path, &info);
+        assert(!res || res == LFS_ERR_NOENT);
+        if (res == LFS_ERR_NOENT) {
+            // create each directory in turn, ignore if dir already exists
+            for (unsigned d = 0; d < DEPTH; d++) {
+                char path[1024];
+                strcpy(path, full_path);
+                path[2*d+2] = '\0';
+                int err = lfs_mkdir(&lfs, path);
+                assert(!err || err == LFS_ERR_EXIST);
+            }
+
+            for (unsigned d = 0; d < DEPTH; d++) {
+                char path[1024];
+                strcpy(path, full_path);
+                path[2*d+2] = '\0';
+                lfs_stat(&lfs, path, &info) => 0;
+                assert(strcmp(info.name, &path[2*d+1]) == 0);
+                assert(info.type == LFS_TYPE_DIR);
+            }
+        } else {
+            assert(strcmp(info.name, &full_path[2*(DEPTH-1)+1]) == 0);
+            assert(info.type == LFS_TYPE_DIR);
+
+            // create new random path
+            char new_path[256];
+            for (unsigned d = 0; d < DEPTH; d++) {
+                sprintf(&new_path[2*d], "/%c", alpha[TEST_PRNG(&prng) % FILES]);
+            }
+
+            // if new path does not exist, rename, otherwise destroy
+            res = lfs_stat(&lfs, new_path, &info);
+            assert(!res || res == LFS_ERR_NOENT);
+            if (res == LFS_ERR_NOENT) {
+                // stop once some dir is renamed
+                for (unsigned d = 0; d < DEPTH; d++) {
+                    char path[1024];
+                    strcpy(&path[2*d], &full_path[2*d]);
+                    path[2*d+2] = '\0';
+                    strcpy(&path[128+2*d], &new_path[2*d]);
+                    path[128+2*d+2] = '\0';
+                    int err = lfs_rename(&lfs, path, path+128);
+                    assert(!err || err == LFS_ERR_NOTEMPTY);
+                    if (!err) {
+                        strcpy(path, path+128);
+                    }
+                }
+
+                for (unsigned d = 0; d < DEPTH; d++) {
+                    char path[1024];
+                    strcpy(path, new_path);
+                    path[2*d+2] = '\0';
+                    lfs_stat(&lfs, path, &info) => 0;
+                    assert(strcmp(info.name, &path[2*d+1]) == 0);
+                    assert(info.type == LFS_TYPE_DIR);
+                }
+
+                lfs_stat(&lfs, full_path, &info) => LFS_ERR_NOENT;
+            } else {
+                // try to delete path in reverse order,
+                // ignore if dir is not empty
+                for (unsigned d = DEPTH-1; d+1 > 0; d--) {
+                    char path[1024];
+                    strcpy(path, full_path);
+                    path[2*d+2] = '\0';
+                    int err = lfs_remove(&lfs, path);
+                    assert(!err || err == LFS_ERR_NOTEMPTY);
+                }
+
+                lfs_stat(&lfs, full_path, &info) => LFS_ERR_NOENT;
+            }
+        }
+    }
+    lfs_unmount(&lfs) => 0;
+'''

+ 256 - 1
tests/test_seek.toml

@@ -137,6 +137,130 @@ code = '''
     lfs_unmount(&lfs) => 0;
 '''
 
+# boundary seek and reads
+[cases.test_seek_boundary_read]
+defines.COUNT = 132
+code = '''
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
+    lfs_file_t file;
+    lfs_file_open(&lfs, &file, "kitty",
+            LFS_O_WRONLY | LFS_O_CREAT | LFS_O_APPEND) => 0;
+    size_t size = strlen("kittycatcat");
+    uint8_t buffer[1024];
+    memcpy(buffer, "kittycatcat", size);
+    for (int j = 0; j < COUNT; j++) {
+        lfs_file_write(&lfs, &file, buffer, size);
+    }
+    lfs_file_close(&lfs, &file) => 0;
+    lfs_unmount(&lfs) => 0;
+
+    lfs_mount(&lfs, cfg) => 0;
+    lfs_file_open(&lfs, &file, "kitty", LFS_O_RDONLY) => 0;
+
+    size = strlen("kittycatcat");
+    const lfs_soff_t offsets[] = {
+        512,
+        1024-4,
+        512+1,
+        1024-4+1,
+        512-1,
+        1024-4-1,
+
+        512-strlen("kittycatcat"),
+        1024-4-strlen("kittycatcat"),
+        512-strlen("kittycatcat")+1,
+        1024-4-strlen("kittycatcat")+1,
+        512-strlen("kittycatcat")-1,
+        1024-4-strlen("kittycatcat")-1,
+
+        strlen("kittycatcat")*(COUNT-2)-1,
+    };
+
+    for (unsigned i = 0; i < sizeof(offsets) / sizeof(offsets[0]); i++) {
+        lfs_soff_t off = offsets[i];
+        // read @ offset
+        lfs_file_seek(&lfs, &file, off, LFS_SEEK_SET) => off;
+        lfs_file_read(&lfs, &file, buffer, size) => size;
+        memcmp(buffer,
+                &"kittycatcatkittycatcat"[off % strlen("kittycatcat")],
+                size) => 0;
+        // read after
+        lfs_file_seek(&lfs, &file, off+strlen("kittycatcat")+1, LFS_SEEK_SET)
+                => off+strlen("kittycatcat")+1;
+        lfs_file_read(&lfs, &file, buffer, size) => size;
+        memcmp(buffer,
+                &"kittycatcatkittycatcat"[(off+1) % strlen("kittycatcat")],
+                size) => 0;
+        // read before
+        lfs_file_seek(&lfs, &file, off-strlen("kittycatcat")-1, LFS_SEEK_SET)
+                => off-strlen("kittycatcat")-1;
+        lfs_file_read(&lfs, &file, buffer, size) => size;
+        memcmp(buffer,
+                &"kittycatcatkittycatcat"[(off-1) % strlen("kittycatcat")],
+                size) => 0;
+
+        // read @ 0
+        lfs_file_seek(&lfs, &file, 0, LFS_SEEK_SET) => 0;
+        lfs_file_read(&lfs, &file, buffer, size) => size;
+        memcmp(buffer, "kittycatcat", size) => 0;
+
+        // read @ offset
+        lfs_file_seek(&lfs, &file, off, LFS_SEEK_SET) => off;
+        lfs_file_read(&lfs, &file, buffer, size) => size;
+        memcmp(buffer,
+                &"kittycatcatkittycatcat"[off % strlen("kittycatcat")],
+                size) => 0;
+        // read after
+        lfs_file_seek(&lfs, &file, off+strlen("kittycatcat")+1, LFS_SEEK_SET)
+                => off+strlen("kittycatcat")+1;
+        lfs_file_read(&lfs, &file, buffer, size) => size;
+        memcmp(buffer,
+                &"kittycatcatkittycatcat"[(off+1) % strlen("kittycatcat")],
+                size) => 0;
+        // read before
+        lfs_file_seek(&lfs, &file, off-strlen("kittycatcat")-1, LFS_SEEK_SET)
+                => off-strlen("kittycatcat")-1;
+        lfs_file_read(&lfs, &file, buffer, size) => size;
+        memcmp(buffer,
+                &"kittycatcatkittycatcat"[(off-1) % strlen("kittycatcat")],
+                size) => 0;
+
+        // sync
+        lfs_file_sync(&lfs, &file) => 0;
+
+        // read @ 0
+        lfs_file_seek(&lfs, &file, 0, LFS_SEEK_SET) => 0;
+        lfs_file_read(&lfs, &file, buffer, size) => size;
+        memcmp(buffer, "kittycatcat", size) => 0;
+
+        // read @ offset
+        lfs_file_seek(&lfs, &file, off, LFS_SEEK_SET) => off;
+        lfs_file_read(&lfs, &file, buffer, size) => size;
+        memcmp(buffer,
+                &"kittycatcatkittycatcat"[off % strlen("kittycatcat")],
+                size) => 0;
+        // read after
+        lfs_file_seek(&lfs, &file, off+strlen("kittycatcat")+1, LFS_SEEK_SET)
+                => off+strlen("kittycatcat")+1;
+        lfs_file_read(&lfs, &file, buffer, size) => size;
+        memcmp(buffer,
+                &"kittycatcatkittycatcat"[(off+1) % strlen("kittycatcat")],
+                size) => 0;
+        // read before
+        lfs_file_seek(&lfs, &file, off-strlen("kittycatcat")-1, LFS_SEEK_SET)
+                => off-strlen("kittycatcat")-1;
+        lfs_file_read(&lfs, &file, buffer, size) => size;
+        memcmp(buffer,
+                &"kittycatcatkittycatcat"[(off-1) % strlen("kittycatcat")],
+                size) => 0;
+    }
+
+    lfs_file_close(&lfs, &file) => 0;
+    lfs_unmount(&lfs) => 0;
+'''
+
 # boundary seek and writes
 [cases.test_seek_boundary_write]
 defines.COUNT = 132
@@ -160,31 +284,54 @@ code = '''
     lfs_file_open(&lfs, &file, "kitty", LFS_O_RDWR) => 0;
 
     size = strlen("hedgehoghog");
-    const lfs_soff_t offsets[] = {512, 1020, 513, 1021, 511, 1019, 1441};
+    const lfs_soff_t offsets[] = {
+        512,
+        1024-4,
+        512+1,
+        1024-4+1,
+        512-1,
+        1024-4-1,
+
+        512-strlen("kittycatcat"),
+        1024-4-strlen("kittycatcat"),
+        512-strlen("kittycatcat")+1,
+        1024-4-strlen("kittycatcat")+1,
+        512-strlen("kittycatcat")-1,
+        1024-4-strlen("kittycatcat")-1,
+
+        strlen("kittycatcat")*(COUNT-2)-1,
+    };
 
     for (unsigned i = 0; i < sizeof(offsets) / sizeof(offsets[0]); i++) {
         lfs_soff_t off = offsets[i];
+        // write @ offset
         memcpy(buffer, "hedgehoghog", size);
         lfs_file_seek(&lfs, &file, off, LFS_SEEK_SET) => off;
         lfs_file_write(&lfs, &file, buffer, size) => size;
+
+        // read @ offset
         lfs_file_seek(&lfs, &file, off, LFS_SEEK_SET) => off;
         lfs_file_read(&lfs, &file, buffer, size) => size;
         memcmp(buffer, "hedgehoghog", size) => 0;
 
+        // read @ 0
         lfs_file_seek(&lfs, &file, 0, LFS_SEEK_SET) => 0;
         lfs_file_read(&lfs, &file, buffer, size) => size;
         memcmp(buffer, "kittycatcat", size) => 0;
 
+        // read @ offset
         lfs_file_seek(&lfs, &file, off, LFS_SEEK_SET) => off;
         lfs_file_read(&lfs, &file, buffer, size) => size;
         memcmp(buffer, "hedgehoghog", size) => 0;
 
         lfs_file_sync(&lfs, &file) => 0;
 
+        // read @ 0
         lfs_file_seek(&lfs, &file, 0, LFS_SEEK_SET) => 0;
         lfs_file_read(&lfs, &file, buffer, size) => size;
         memcmp(buffer, "kittycatcat", size) => 0;
 
+        // read @ offset
         lfs_file_seek(&lfs, &file, off, LFS_SEEK_SET) => off;
         lfs_file_read(&lfs, &file, buffer, size) => size;
         memcmp(buffer, "hedgehoghog", size) => 0;
@@ -405,3 +552,111 @@ code = '''
     lfs_file_close(&lfs, &file) => 0;
     lfs_unmount(&lfs) => 0;
 '''
+
+
+# test possible overflow/underflow conditions
+#
+# note these need -fsanitize=undefined to consistently detect
+# overflow/underflow conditions
+
+[cases.test_seek_filemax]
+code = '''
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
+    lfs_file_t file;
+    lfs_file_open(&lfs, &file, "kitty",
+            LFS_O_WRONLY | LFS_O_CREAT | LFS_O_APPEND) => 0;
+    uint8_t buffer[1024];
+    strcpy((char*)buffer, "kittycatcat");
+    size_t size = strlen((char*)buffer);
+    lfs_file_write(&lfs, &file, buffer, size) => size;
+
+    // seek with LFS_SEEK_SET
+    lfs_file_seek(&lfs, &file, LFS_FILE_MAX, LFS_SEEK_SET) => LFS_FILE_MAX;
+
+    // seek with LFS_SEEK_CUR
+    lfs_file_seek(&lfs, &file, 0, LFS_SEEK_CUR) => LFS_FILE_MAX;
+
+    // the file hasn't changed size, so seek end takes us back to the offset=0
+    lfs_file_seek(&lfs, &file, +10, LFS_SEEK_END) => size+10;
+
+    lfs_file_close(&lfs, &file) => 0;
+    lfs_unmount(&lfs) => 0;
+'''
+
+[cases.test_seek_underflow]
+code = '''
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
+    lfs_file_t file;
+    lfs_file_open(&lfs, &file, "kitty",
+            LFS_O_WRONLY | LFS_O_CREAT | LFS_O_APPEND) => 0;
+    uint8_t buffer[1024];
+    strcpy((char*)buffer, "kittycatcat");
+    size_t size = strlen((char*)buffer);
+    lfs_file_write(&lfs, &file, buffer, size) => size;
+
+    // underflow with LFS_SEEK_CUR, should error
+    lfs_file_seek(&lfs, &file, -(size+10), LFS_SEEK_CUR) => LFS_ERR_INVAL;
+    lfs_file_seek(&lfs, &file, -LFS_FILE_MAX, LFS_SEEK_CUR) => LFS_ERR_INVAL;
+    lfs_file_seek(&lfs, &file, -(size+LFS_FILE_MAX), LFS_SEEK_CUR)
+            => LFS_ERR_INVAL;
+
+    // underflow with LFS_SEEK_END, should error
+    lfs_file_seek(&lfs, &file, -(size+10), LFS_SEEK_END) => LFS_ERR_INVAL;
+    lfs_file_seek(&lfs, &file, -LFS_FILE_MAX, LFS_SEEK_END) => LFS_ERR_INVAL;
+    lfs_file_seek(&lfs, &file, -(size+LFS_FILE_MAX), LFS_SEEK_END)
+            => LFS_ERR_INVAL;
+
+    // file pointer should not have changed
+    lfs_file_tell(&lfs, &file) => size;
+
+    lfs_file_close(&lfs, &file) => 0;
+    lfs_unmount(&lfs) => 0;
+'''
+
+[cases.test_seek_overflow]
+code = '''
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
+    lfs_file_t file;
+    lfs_file_open(&lfs, &file, "kitty",
+            LFS_O_WRONLY | LFS_O_CREAT | LFS_O_APPEND) => 0;
+    uint8_t buffer[1024];
+    strcpy((char*)buffer, "kittycatcat");
+    size_t size = strlen((char*)buffer);
+    lfs_file_write(&lfs, &file, buffer, size) => size;
+
+    // seek to LFS_FILE_MAX
+    lfs_file_seek(&lfs, &file, LFS_FILE_MAX, LFS_SEEK_SET) => LFS_FILE_MAX;
+
+    // overflow with LFS_SEEK_CUR, should error
+    lfs_file_seek(&lfs, &file, +10, LFS_SEEK_CUR) => LFS_ERR_INVAL;
+    lfs_file_seek(&lfs, &file, +LFS_FILE_MAX, LFS_SEEK_CUR) => LFS_ERR_INVAL;
+
+    // LFS_SEEK_SET/END don't care about the current file position, but we can
+    // still overflow with a large offset
+
+    // overflow with LFS_SEEK_SET, should error
+    lfs_file_seek(&lfs, &file,
+            +((uint32_t)LFS_FILE_MAX+10),
+            LFS_SEEK_SET) => LFS_ERR_INVAL;
+    lfs_file_seek(&lfs, &file,
+            +((uint32_t)LFS_FILE_MAX+(uint32_t)LFS_FILE_MAX),
+            LFS_SEEK_SET) => LFS_ERR_INVAL;
+
+    // overflow with LFS_SEEK_END, should error
+    lfs_file_seek(&lfs, &file, +(LFS_FILE_MAX-size+10), LFS_SEEK_END)
+            => LFS_ERR_INVAL;
+    lfs_file_seek(&lfs, &file, +(LFS_FILE_MAX-size+LFS_FILE_MAX), LFS_SEEK_END)
+            => LFS_ERR_INVAL;
+
+    // file pointer should not have changed
+    lfs_file_tell(&lfs, &file) => LFS_FILE_MAX;
+
+    lfs_file_close(&lfs, &file) => 0;
+    lfs_unmount(&lfs) => 0;
+'''

+ 109 - 0
tests/test_shrink.toml

@@ -0,0 +1,109 @@
+# simple shrink
+[cases.test_shrink_simple]
+defines.BLOCK_COUNT = [10, 15, 20]
+defines.AFTER_BLOCK_COUNT = [5, 10, 15, 19]
+   
+if = "AFTER_BLOCK_COUNT <= BLOCK_COUNT"
+code = '''
+#ifdef LFS_SHRINKNONRELOCATING
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
+    lfs_fs_grow(&lfs, AFTER_BLOCK_COUNT) => 0;
+    lfs_unmount(&lfs);
+    if (BLOCK_COUNT != AFTER_BLOCK_COUNT) {
+        lfs_mount(&lfs, cfg) => LFS_ERR_INVAL;
+    }
+    lfs_t lfs2 = lfs;
+    struct lfs_config cfg2 = *cfg;
+    cfg2.block_count = AFTER_BLOCK_COUNT;
+    lfs2.cfg = &cfg2;
+    lfs_mount(&lfs2, &cfg2) => 0;
+    lfs_unmount(&lfs2) => 0;
+#endif
+'''
+
+# shrinking full
+[cases.test_shrink_full]
+defines.BLOCK_COUNT = [10, 15, 20]
+defines.AFTER_BLOCK_COUNT = [5, 7, 10, 12, 15, 17, 20]
+defines.FILES_COUNT = [7, 8, 9, 10]
+if = "AFTER_BLOCK_COUNT <= BLOCK_COUNT && FILES_COUNT + 2 < BLOCK_COUNT"
+code = '''
+#ifdef LFS_SHRINKNONRELOCATING
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
+    // create FILES_COUNT files of BLOCK_SIZE - 50 bytes (to avoid inlining)
+    lfs_mount(&lfs, cfg) => 0;
+    for (int i = 0; i < FILES_COUNT + 1; i++) {
+        lfs_file_t file;
+        char path[1024];
+        sprintf(path, "file_%03d", i);
+        lfs_file_open(&lfs, &file, path,
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        char wbuffer[BLOCK_SIZE];
+        memset(wbuffer, 'b', BLOCK_SIZE);
+        // Ensure one block is taken per file, but that files are not inlined.
+        lfs_size_t size = BLOCK_SIZE - 0x40;
+        sprintf(wbuffer, "Hi %03d", i);
+        lfs_file_write(&lfs, &file, wbuffer, size) => size;
+        lfs_file_close(&lfs, &file) => 0;
+    }
+
+    int err = lfs_fs_grow(&lfs, AFTER_BLOCK_COUNT);
+    if (err == 0) {
+        for (int i = 0; i < FILES_COUNT + 1; i++) {
+            lfs_file_t file;
+            char path[1024];
+            sprintf(path, "file_%03d", i);
+            lfs_file_open(&lfs, &file, path,
+                    LFS_O_RDONLY ) => 0;
+            lfs_size_t size = BLOCK_SIZE - 0x40;
+            char wbuffer[size];
+            char wbuffer_ref[size];
+            // Ensure one block is taken per file, but that files are not inlined.
+            memset(wbuffer_ref, 'b', size);
+            sprintf(wbuffer_ref, "Hi %03d", i);
+            lfs_file_read(&lfs, &file, wbuffer, BLOCK_SIZE) => size;
+            lfs_file_close(&lfs, &file) => 0;
+            for (lfs_size_t j = 0; j < size; j++) {
+                wbuffer[j] => wbuffer_ref[j];
+            }
+        }
+    } else {
+        assert(err == LFS_ERR_NOTEMPTY);
+    }
+
+    lfs_unmount(&lfs) => 0;
+    if (err == 0 ) {
+        if ( AFTER_BLOCK_COUNT != BLOCK_COUNT ) {
+            lfs_mount(&lfs, cfg) => LFS_ERR_INVAL;
+        }
+
+        lfs_t lfs2 = lfs;
+        struct lfs_config cfg2 = *cfg;
+        cfg2.block_count = AFTER_BLOCK_COUNT;
+        lfs2.cfg = &cfg2;
+        lfs_mount(&lfs2, &cfg2) => 0;
+        for (int i = 0; i < FILES_COUNT + 1; i++) {
+            lfs_file_t file;
+            char path[1024];
+            sprintf(path, "file_%03d", i);
+            lfs_file_open(&lfs2, &file, path,
+                    LFS_O_RDONLY ) => 0;
+            lfs_size_t size = BLOCK_SIZE - 0x40;
+            char wbuffer[size];
+            char wbuffer_ref[size];
+            // Ensure one block is taken per file, but that files are not inlined.
+            memset(wbuffer_ref, 'b', size);
+            sprintf(wbuffer_ref, "Hi %03d", i);
+            lfs_file_read(&lfs2, &file, wbuffer, BLOCK_SIZE) => size;
+            lfs_file_close(&lfs2, &file) => 0;
+            for (lfs_size_t j = 0; j < size; j++) {
+                wbuffer[j] => wbuffer_ref[j];
+            }
+        }
+        lfs_unmount(&lfs2);
+    }
+#endif
+'''

+ 135 - 0
tests/test_superblocks.toml

@@ -523,3 +523,138 @@ code = '''
     assert(memcmp(buffer, "hello!", 6) == 0);
     lfs_unmount(&lfs) => 0;
 '''
+
+
+# mount and grow the filesystem
+[cases.test_superblocks_shrink]
+defines.BLOCK_COUNT = 'ERASE_COUNT'
+defines.BLOCK_COUNT_2 = ['ERASE_COUNT/2', 'ERASE_COUNT/4', '2']
+defines.KNOWN_BLOCK_COUNT = [true, false]
+code = '''
+#ifdef LFS_SHRINKNONRELOCATING
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
+
+    if (KNOWN_BLOCK_COUNT) {
+        cfg->block_count = BLOCK_COUNT;
+    } else {
+        cfg->block_count = 0;
+    }
+
+    // mount with block_size < erase_size
+    lfs_mount(&lfs, cfg) => 0;
+    struct lfs_fsinfo fsinfo;
+    lfs_fs_stat(&lfs, &fsinfo) => 0;
+    assert(fsinfo.block_size == BLOCK_SIZE);
+    assert(fsinfo.block_count == BLOCK_COUNT);
+    lfs_unmount(&lfs) => 0;
+
+    // same size is a noop
+    lfs_mount(&lfs, cfg) => 0;
+    lfs_fs_grow(&lfs, BLOCK_COUNT) => 0;
+    lfs_fs_stat(&lfs, &fsinfo) => 0;
+    assert(fsinfo.block_size == BLOCK_SIZE);
+    assert(fsinfo.block_count == BLOCK_COUNT);
+    lfs_unmount(&lfs) => 0;
+
+    lfs_mount(&lfs, cfg) => 0;
+    lfs_fs_stat(&lfs, &fsinfo) => 0;
+    assert(fsinfo.block_size == BLOCK_SIZE);
+    assert(fsinfo.block_count == BLOCK_COUNT);
+    lfs_unmount(&lfs) => 0;
+
+    // grow to new size
+    lfs_mount(&lfs, cfg) => 0;
+    lfs_fs_grow(&lfs, BLOCK_COUNT_2) => 0;
+    lfs_fs_stat(&lfs, &fsinfo) => 0;
+    assert(fsinfo.block_size == BLOCK_SIZE);
+    assert(fsinfo.block_count == BLOCK_COUNT_2);
+    lfs_unmount(&lfs) => 0;
+
+    if (KNOWN_BLOCK_COUNT) {
+        cfg->block_count = BLOCK_COUNT_2;
+    } else {
+        cfg->block_count = 0;
+    }
+
+    lfs_mount(&lfs, cfg) => 0;
+    lfs_fs_stat(&lfs, &fsinfo) => 0;
+    assert(fsinfo.block_size == BLOCK_SIZE);
+    assert(fsinfo.block_count == BLOCK_COUNT_2);
+    lfs_unmount(&lfs) => 0;
+
+    // mounting with the previous size should fail
+    cfg->block_count = BLOCK_COUNT;
+    lfs_mount(&lfs, cfg) => LFS_ERR_INVAL;
+
+    if (KNOWN_BLOCK_COUNT) {
+        cfg->block_count = BLOCK_COUNT_2;
+    } else {
+        cfg->block_count = 0;
+    }
+
+    // same size is a noop
+    lfs_mount(&lfs, cfg) => 0;
+    lfs_fs_grow(&lfs, BLOCK_COUNT_2) => 0;
+    lfs_fs_stat(&lfs, &fsinfo) => 0;
+    assert(fsinfo.block_size == BLOCK_SIZE);
+    assert(fsinfo.block_count == BLOCK_COUNT_2);
+    lfs_unmount(&lfs) => 0;
+
+    lfs_mount(&lfs, cfg) => 0;
+    lfs_fs_stat(&lfs, &fsinfo) => 0;
+    assert(fsinfo.block_size == BLOCK_SIZE);
+    assert(fsinfo.block_count == BLOCK_COUNT_2);
+    lfs_unmount(&lfs) => 0;
+
+    // do some work
+    lfs_mount(&lfs, cfg) => 0;
+    lfs_fs_stat(&lfs, &fsinfo) => 0;
+    assert(fsinfo.block_size == BLOCK_SIZE);
+    assert(fsinfo.block_count == BLOCK_COUNT_2);
+    lfs_file_t file;
+    lfs_file_open(&lfs, &file, "test",
+            LFS_O_CREAT | LFS_O_EXCL | LFS_O_WRONLY) => 0;
+    lfs_file_write(&lfs, &file, "hello!", 6) => 6;
+    lfs_file_close(&lfs, &file) => 0;
+    lfs_unmount(&lfs) => 0;
+
+    lfs_mount(&lfs, cfg) => 0;
+    lfs_fs_stat(&lfs, &fsinfo) => 0;
+    assert(fsinfo.block_size == BLOCK_SIZE);
+    assert(fsinfo.block_count == BLOCK_COUNT_2);
+    lfs_file_open(&lfs, &file, "test", LFS_O_RDONLY) => 0;
+    uint8_t buffer[256];
+    lfs_file_read(&lfs, &file, buffer, sizeof(buffer)) => 6;
+    lfs_file_close(&lfs, &file) => 0;
+    assert(memcmp(buffer, "hello!", 6) == 0);
+    lfs_unmount(&lfs) => 0;
+#endif
+'''
+
+# test that metadata_max does not cause problems for superblock compaction
+[cases.test_superblocks_metadata_max]
+defines.METADATA_MAX = [
+    'lfs_max(512, PROG_SIZE)',
+    'lfs_max(BLOCK_SIZE/2, PROG_SIZE)',
+    'BLOCK_SIZE'
+]
+defines.N = [10, 100, 1000]
+code = '''
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
+    lfs_mount(&lfs, cfg) => 0;
+    for (int i = 0; i < N; i++) {
+        lfs_file_t file;
+        char name[256];
+        sprintf(name, "hello%03x", i);
+        lfs_file_open(&lfs, &file, name,
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+        lfs_file_close(&lfs, &file) => 0;
+        struct lfs_info info;
+        lfs_stat(&lfs, name, &info) => 0;
+        assert(strcmp(info.name, name) == 0);
+        assert(info.type == LFS_TYPE_REG);
+    }
+    lfs_unmount(&lfs) => 0;
+'''