├── .gitattributes ├── .github └── workflows │ ├── post-release.yml │ ├── release.yml │ ├── status.yml │ └── test.yml ├── .gitignore ├── DESIGN.md ├── LICENSE.md ├── Makefile ├── README.md ├── SConscript ├── SPEC.md ├── bd ├── lfs_emubd.c ├── lfs_emubd.h ├── lfs_filebd.c ├── lfs_filebd.h ├── lfs_rambd.c └── lfs_rambd.h ├── benches ├── bench_dir.toml ├── bench_file.toml └── bench_superblock.toml ├── dfs_lfs.c ├── lfs.c ├── lfs.h ├── lfs_config.h ├── lfs_crc.c ├── lfs_util.c ├── lfs_util.h ├── runners ├── bench_runner.c ├── bench_runner.h ├── test_runner.c └── test_runner.h ├── scripts ├── bench.py ├── changeprefix.py ├── code.py ├── cov.py ├── data.py ├── perf.py ├── perfbd.py ├── plot.py ├── plotmpl.py ├── prettyasserts.py ├── readblock.py ├── readmdir.py ├── readtree.py ├── stack.py ├── structs.py ├── summary.py ├── tailpipe.py ├── teepipe.py ├── test.py ├── tracebd.py └── watch.py └── tests ├── test_alloc.toml ├── test_attrs.toml ├── test_badblocks.toml ├── test_bd.toml ├── test_compat.toml ├── test_dirs.toml ├── test_entries.toml ├── test_evil.toml ├── test_exhaustion.toml ├── test_files.toml ├── test_interspersed.toml ├── test_move.toml ├── test_orphans.toml ├── test_paths.toml ├── test_powerloss.toml ├── test_relocations.toml ├── test_seek.toml ├── test_superblocks.toml └── test_truncate.toml /.gitattributes: -------------------------------------------------------------------------------- 1 | # GitHub really wants to mark littlefs as a python project, telling it to 2 | # reclassify our test .toml files as C code (which they are 95% of anyways) 3 | # remedies this 4 | *.toml linguist-language=c 5 | -------------------------------------------------------------------------------- /.github/workflows/post-release.yml: -------------------------------------------------------------------------------- 1 | name: post-release 2 | on: 3 | release: 4 | branches: [master] 5 | types: [released] 6 | 7 | defaults: 8 | run: 9 | shell: bash -euv -o pipefail {0} 10 | 11 | jobs: 12 | post-release: 13 | runs-on: ubuntu-latest 14 | steps: 15 | # trigger post-release in dependency repo, this indirection allows the 16 | # dependency repo to be updated often without affecting this repo. At 17 | # the time of this comment, the dependency repo is responsible for 18 | # creating PRs for other dependent repos post-release. 19 | - name: trigger-post-release 20 | continue-on-error: true 21 | run: | 22 | curl -sS -X POST -H "authorization: token ${{secrets.BOT_TOKEN}}" \ 23 | "$GITHUB_API_URL/repos/${{secrets.POST_RELEASE_REPO}}/dispatches" \ 24 | -d "$(jq -n '{ 25 | event_type: "post-release", 26 | client_payload: { 27 | repo: env.GITHUB_REPOSITORY, 28 | version: "${{github.event.release.tag_name}}", 29 | }, 30 | }' | tee /dev/stderr)" 31 | 32 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: release 2 | on: 3 | workflow_run: 4 | workflows: [test] 5 | branches: [master] 6 | types: [completed] 7 | 8 | defaults: 9 | run: 10 | shell: bash -euv -o pipefail {0} 11 | 12 | jobs: 13 | release: 14 | runs-on: ubuntu-latest 15 | 16 | # need to manually check for a couple things 17 | # - tests passed? 18 | # - we are the most recent commit on master? 19 | if: ${{github.event.workflow_run.conclusion == 'success' && 20 | github.event.workflow_run.head_sha == github.sha}} 21 | 22 | steps: 23 | - uses: actions/checkout@v2 24 | with: 25 | ref: ${{github.event.workflow_run.head_sha}} 26 | # need workflow access since we push branches 27 | # containing workflows 28 | token: ${{secrets.BOT_TOKEN}} 29 | # need all tags 30 | fetch-depth: 0 31 | 32 | # try to get results from tests 33 | - uses: dawidd6/action-download-artifact@v2 34 | continue-on-error: true 35 | with: 36 | workflow: ${{github.event.workflow_run.name}} 37 | run_id: ${{github.event.workflow_run.id}} 38 | name: sizes 39 | path: sizes 40 | - uses: dawidd6/action-download-artifact@v2 41 | continue-on-error: true 42 | with: 43 | workflow: ${{github.event.workflow_run.name}} 44 | run_id: ${{github.event.workflow_run.id}} 45 | name: cov 46 | path: cov 47 | - uses: dawidd6/action-download-artifact@v2 48 | continue-on-error: true 49 | with: 50 | workflow: ${{github.event.workflow_run.name}} 51 | run_id: ${{github.event.workflow_run.id}} 52 | name: bench 53 | path: bench 54 | 55 | - name: find-version 56 | run: | 57 | # rip version from lfs.h 58 | LFS_VERSION="$(grep -o '^#define LFS_VERSION .*$' lfs.h \ 59 | | awk '{print $3}')" 60 | LFS_VERSION_MAJOR="$((0xffff & ($LFS_VERSION >> 16)))" 61 | LFS_VERSION_MINOR="$((0xffff & ($LFS_VERSION >> 0)))" 62 | 63 | # find a new patch version based on what we find in our tags 64 | LFS_VERSION_PATCH="$( \ 65 | ( git describe --tags --abbrev=0 \ 66 | --match="v$LFS_VERSION_MAJOR.$LFS_VERSION_MINOR.*" \ 67 | || echo 'v0.0.-1' ) \ 68 | | awk -F '.' '{print $3+1}')" 69 | 70 | # found new version 71 | LFS_VERSION="v$LFS_VERSION_MAJOR` 72 | `.$LFS_VERSION_MINOR` 73 | `.$LFS_VERSION_PATCH" 74 | echo "LFS_VERSION=$LFS_VERSION" 75 | echo "LFS_VERSION=$LFS_VERSION" >> $GITHUB_ENV 76 | echo "LFS_VERSION_MAJOR=$LFS_VERSION_MAJOR" >> $GITHUB_ENV 77 | echo "LFS_VERSION_MINOR=$LFS_VERSION_MINOR" >> $GITHUB_ENV 78 | echo "LFS_VERSION_PATCH=$LFS_VERSION_PATCH" >> $GITHUB_ENV 79 | 80 | # try to find previous version? 81 | - name: find-prev-version 82 | continue-on-error: true 83 | run: | 84 | LFS_PREV_VERSION="$( \ 85 | git describe --tags --abbrev=0 --match 'v*' \ 86 | || true)" 87 | echo "LFS_PREV_VERSION=$LFS_PREV_VERSION" 88 | echo "LFS_PREV_VERSION=$LFS_PREV_VERSION" >> $GITHUB_ENV 89 | 90 | # try to find results from tests 91 | - name: create-table 92 | run: | 93 | # previous results to compare against? 94 | [ -n "$LFS_PREV_VERSION" ] && curl -sS \ 95 | "$GITHUB_API_URL/repos/$GITHUB_REPOSITORY/status/$LFS_PREV_VERSION` 96 | `?per_page=100" \ 97 | | jq -re 'select(.sha != env.GITHUB_SHA) | .statuses[]' \ 98 | >> prev-status.json \ 99 | || true 100 | 101 | # build table for GitHub 102 | declare -A table 103 | 104 | # sizes table 105 | i=0 106 | j=0 107 | for c in "" readonly threadsafe multiversion migrate error-asserts 108 | do 109 | # per-config results 110 | c_or_default=${c:-default} 111 | c_camel=${c_or_default^} 112 | table[$i,$j]=$c_camel 113 | ((j+=1)) 114 | 115 | for s in code stack structs 116 | do 117 | f=sizes/thumb${c:+-$c}.$s.csv 118 | [ -e $f ] && table[$i,$j]=$( \ 119 | export PREV="$(jq -re ' 120 | select(.context == "'"sizes (thumb${c:+, $c}) / $s"'").description 121 | | capture("(?[0-9∞]+)").prev' \ 122 | prev-status.json || echo 0)" 123 | ./scripts/summary.py $f --max=stack_limit -Y \ 124 | | awk ' 125 | NR==2 {$1=0; printf "%s B",$NF} 126 | NR==2 && ENVIRON["PREV"]+0 != 0 { 127 | printf " (%+.1f%%)",100*($NF-ENVIRON["PREV"])/ENVIRON["PREV"] 128 | }' \ 129 | | sed -e 's/ /\ /g') 130 | ((j+=1)) 131 | done 132 | ((j=0, i+=1)) 133 | done 134 | 135 | # coverage table 136 | i=0 137 | j=4 138 | for s in lines branches 139 | do 140 | table[$i,$j]=${s^} 141 | ((j+=1)) 142 | 143 | f=cov/cov.csv 144 | [ -e $f ] && table[$i,$j]=$( \ 145 | export PREV="$(jq -re ' 146 | select(.context == "'"cov / $s"'").description 147 | | capture("(?[0-9]+)/(?[0-9]+)") 148 | | 100*((.prev_a|tonumber) / (.prev_b|tonumber))' \ 149 | prev-status.json || echo 0)" 150 | ./scripts/cov.py -u $f -f$s -Y \ 151 | | awk -F '[ /%]+' -v s=$s ' 152 | NR==2 {$1=0; printf "%d/%d %s",$2,$3,s} 153 | NR==2 && ENVIRON["PREV"]+0 != 0 { 154 | printf " (%+.1f%%)",$4-ENVIRON["PREV"] 155 | }' \ 156 | | sed -e 's/ /\ /g') 157 | ((j=4, i+=1)) 158 | done 159 | 160 | # benchmark table 161 | i=3 162 | j=4 163 | for s in readed proged erased 164 | do 165 | table[$i,$j]=${s^} 166 | ((j+=1)) 167 | 168 | f=bench/bench.csv 169 | [ -e $f ] && table[$i,$j]=$( \ 170 | export PREV="$(jq -re ' 171 | select(.context == "'"bench / $s"'").description 172 | | capture("(?[0-9]+)").prev' \ 173 | prev-status.json || echo 0)" 174 | ./scripts/summary.py $f -f$s=bench_$s -Y \ 175 | | awk ' 176 | NR==2 {$1=0; printf "%s B",$NF} 177 | NR==2 && ENVIRON["PREV"]+0 != 0 { 178 | printf " (%+.1f%%)",100*($NF-ENVIRON["PREV"])/ENVIRON["PREV"] 179 | }' \ 180 | | sed -e 's/ /\ /g') 181 | ((j=4, i+=1)) 182 | done 183 | 184 | # build the actual table 185 | echo "| | Code | Stack | Structs | | Coverage |" >> table.txt 186 | echo "|:--|-----:|------:|--------:|:--|---------:|" >> table.txt 187 | for ((i=0; i<6; i++)) 188 | do 189 | echo -n "|" >> table.txt 190 | for ((j=0; j<6; j++)) 191 | do 192 | echo -n " " >> table.txt 193 | [[ i -eq 2 && j -eq 5 ]] && echo -n "**Benchmarks**" >> table.txt 194 | echo -n "${table[$i,$j]:-}" >> table.txt 195 | echo -n " |" >> table.txt 196 | done 197 | echo >> table.txt 198 | done 199 | 200 | cat table.txt 201 | 202 | # find changes from history 203 | - name: create-changes 204 | run: | 205 | [ -n "$LFS_PREV_VERSION" ] || exit 0 206 | # use explicit link to github commit so that release notes can 207 | # be copied elsewhere 208 | git log "$LFS_PREV_VERSION.." \ 209 | --grep='^Merge' --invert-grep \ 210 | --format="format:[\`%h\`](` 211 | `https://github.com/$GITHUB_REPOSITORY/commit/%h) %s" \ 212 | > changes.txt 213 | echo "CHANGES:" 214 | cat changes.txt 215 | 216 | # create and update major branches (vN and vN-prefix) 217 | - name: create-major-branches 218 | run: | 219 | # create major branch 220 | git branch "v$LFS_VERSION_MAJOR" HEAD 221 | 222 | # create major prefix branch 223 | git config user.name ${{secrets.BOT_USER}} 224 | git config user.email ${{secrets.BOT_EMAIL}} 225 | git fetch "https://github.com/$GITHUB_REPOSITORY.git" \ 226 | "v$LFS_VERSION_MAJOR-prefix" || true 227 | ./scripts/changeprefix.py --git "lfs" "lfs$LFS_VERSION_MAJOR" 228 | git branch "v$LFS_VERSION_MAJOR-prefix" $( \ 229 | git commit-tree $(git write-tree) \ 230 | $(git rev-parse --verify -q FETCH_HEAD | sed -e 's/^/-p /') \ 231 | -p HEAD \ 232 | -m "Generated v$LFS_VERSION_MAJOR prefixes") 233 | git reset --hard 234 | 235 | # push! 236 | git push --atomic origin \ 237 | "v$LFS_VERSION_MAJOR" \ 238 | "v$LFS_VERSION_MAJOR-prefix" 239 | 240 | # build release notes 241 | - name: create-release 242 | run: | 243 | # create release and patch version tag (vN.N.N) 244 | # only draft if not a patch release 245 | touch release.txt 246 | [ -e table.txt ] && cat table.txt >> release.txt 247 | echo >> release.txt 248 | [ -e changes.txt ] && cat changes.txt >> release.txt 249 | cat release.txt 250 | 251 | curl -sS -X POST -H "authorization: token ${{secrets.BOT_TOKEN}}" \ 252 | "$GITHUB_API_URL/repos/$GITHUB_REPOSITORY/releases" \ 253 | -d "$(jq -n --rawfile release release.txt '{ 254 | tag_name: env.LFS_VERSION, 255 | name: env.LFS_VERSION | rtrimstr(".0"), 256 | target_commitish: "${{github.event.workflow_run.head_sha}}", 257 | draft: env.LFS_VERSION | endswith(".0"), 258 | body: $release, 259 | }' | tee /dev/stderr)" 260 | 261 | -------------------------------------------------------------------------------- /.github/workflows/status.yml: -------------------------------------------------------------------------------- 1 | name: status 2 | on: 3 | workflow_run: 4 | workflows: [test] 5 | types: [completed] 6 | 7 | defaults: 8 | run: 9 | shell: bash -euv -o pipefail {0} 10 | 11 | jobs: 12 | # forward custom statuses 13 | status: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: dawidd6/action-download-artifact@v2 17 | continue-on-error: true 18 | with: 19 | workflow: ${{github.event.workflow_run.name}} 20 | run_id: ${{github.event.workflow_run.id}} 21 | name: status 22 | path: status 23 | - name: update-status 24 | continue-on-error: true 25 | run: | 26 | ls status 27 | for s in $(shopt -s nullglob ; echo status/*.json) 28 | do 29 | # parse requested status 30 | export STATE="$(jq -er '.state' $s)" 31 | export CONTEXT="$(jq -er '.context' $s)" 32 | export DESCRIPTION="$(jq -er '.description' $s)" 33 | # help lookup URL for job/steps because GitHub makes 34 | # it VERY HARD to link to specific jobs 35 | export TARGET_URL="$( 36 | jq -er '.target_url // empty' $s || ( 37 | export TARGET_JOB="$(jq -er '.target_job' $s)" 38 | export TARGET_STEP="$(jq -er '.target_step // ""' $s)" 39 | curl -sS -H "authorization: token ${{secrets.BOT_TOKEN}}" \ 40 | "$GITHUB_API_URL/repos/$GITHUB_REPOSITORY/actions/runs/` 41 | `${{github.event.workflow_run.id}}/jobs" \ 42 | | jq -er '.jobs[] 43 | | select(.name == env.TARGET_JOB) 44 | | .html_url 45 | + "?check_suite_focus=true" 46 | + ((.steps[] 47 | | select(.name == env.TARGET_STEP) 48 | | "#step:\(.number):0") // "")'))" 49 | # update status 50 | curl -sS -X POST -H "authorization: token ${{secrets.BOT_TOKEN}}" \ 51 | "$GITHUB_API_URL/repos/$GITHUB_REPOSITORY/statuses/` 52 | `${{github.event.workflow_run.head_sha}}" \ 53 | -d "$(jq -n '{ 54 | state: env.STATE, 55 | context: env.CONTEXT, 56 | description: env.DESCRIPTION, 57 | target_url: env.TARGET_URL, 58 | }' | tee /dev/stderr)" 59 | done 60 | 61 | # forward custom pr-comments 62 | comment: 63 | runs-on: ubuntu-latest 64 | 65 | # only run on success (we don't want garbage comments!) 66 | if: ${{github.event.workflow_run.conclusion == 'success'}} 67 | 68 | steps: 69 | # generated comment? 70 | - uses: dawidd6/action-download-artifact@v2 71 | continue-on-error: true 72 | with: 73 | workflow: ${{github.event.workflow_run.name}} 74 | run_id: ${{github.event.workflow_run.id}} 75 | name: comment 76 | path: comment 77 | - name: update-comment 78 | continue-on-error: true 79 | run: | 80 | ls comment 81 | for s in $(shopt -s nullglob ; echo comment/*.json) 82 | do 83 | export NUMBER="$(jq -er '.number' $s)" 84 | export BODY="$(jq -er '.body' $s)" 85 | 86 | # check that the comment was from the most recent commit on the 87 | # pull request 88 | [ "$(curl -sS -H "authorization: token ${{secrets.BOT_TOKEN}}" \ 89 | "$GITHUB_API_URL/repos/$GITHUB_REPOSITORY/pulls/$NUMBER" \ 90 | | jq -er '.head.sha')" \ 91 | == ${{github.event.workflow_run.head_sha}} ] || continue 92 | 93 | # update comment 94 | curl -sS -X POST -H "authorization: token ${{secrets.BOT_TOKEN}}" \ 95 | "$GITHUB_API_URL/repos/$GITHUB_REPOSITORY/issues/` 96 | `$NUMBER/comments" \ 97 | -d "$(jq -n '{ 98 | body: env.BODY, 99 | }' | tee /dev/stderr)" 100 | done 101 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compilation output 2 | *.o 3 | *.d 4 | *.a 5 | *.ci 6 | *.csv 7 | *.t.* 8 | *.b.* 9 | *.gcno 10 | *.gcda 11 | *.perf 12 | lfs 13 | liblfs.a 14 | 15 | # Testing things 16 | runners/test_runner 17 | runners/bench_runner 18 | lfs.code.csv 19 | lfs.data.csv 20 | lfs.stack.csv 21 | lfs.structs.csv 22 | lfs.cov.csv 23 | lfs.perf.csv 24 | lfs.perfbd.csv 25 | lfs.test.csv 26 | lfs.bench.csv 27 | 28 | # Misc 29 | tags 30 | .gdb_history 31 | scripts/__pycache__ 32 | 33 | # Historical, probably should remove at some point 34 | tests/*.toml.* 35 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2022, The littlefs authors. 2 | Copyright (c) 2017, Arm Limited. All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | - Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | - Redistributions in binary form must reproduce the above copyright notice, this 10 | list of conditions and the following disclaimer in the documentation and/or 11 | other materials provided with the distribution. 12 | - Neither the name of ARM nor the names of its contributors may be used to 13 | endorse or promote products derived from this software without specific prior 14 | written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 20 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 23 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## littlefs for RT-Thread 2 | 3 | A little fail-safe filesystem designed for microcontrollers. 4 | 5 | ``` 6 | | | | .---._____ 7 | .-----. | | 8 | --|o |---| littlefs | 9 | --| |---| | 10 | '-----' '----------' 11 | | | | 12 | ``` 13 | 14 | **Power-loss resilience** - littlefs is designed to handle random power 15 | failures. All file operations have strong copy-on-write guarantees and if 16 | power is lost the filesystem will fall back to the last known good state. 17 | 18 | **Dynamic wear leveling** - littlefs is designed with flash in mind, and 19 | provides wear leveling over dynamic blocks. Additionally, littlefs can 20 | detect bad blocks and work around them. 21 | 22 | **Bounded RAM/ROM** - littlefs is designed to work with a small amount of 23 | memory. RAM usage is strictly bounded, which means RAM consumption does not 24 | change as the filesystem grows. The filesystem contains no unbounded 25 | recursion and dynamic memory is limited to configurable buffers that can be 26 | provided statically. 27 | 28 | ## Example 29 | 30 | Here's a simple example that updates a file named `boot_count` every time 31 | main runs. The program can be interrupted at any time without losing track 32 | of how many times it has been booted and without corrupting the filesystem: 33 | 34 | ``` c 35 | #include "lfs.h" 36 | 37 | // variables used by the filesystem 38 | lfs_t lfs; 39 | lfs_file_t file; 40 | 41 | // configuration of the filesystem is provided by this struct 42 | const struct lfs_config cfg = { 43 | // block device operations 44 | .read = user_provided_block_device_read, 45 | .prog = user_provided_block_device_prog, 46 | .erase = user_provided_block_device_erase, 47 | .sync = user_provided_block_device_sync, 48 | 49 | // block device configuration 50 | .read_size = 16, 51 | .prog_size = 16, 52 | .block_size = 4096, 53 | .block_count = 128, 54 | .cache_size = 16, 55 | .lookahead_size = 16, 56 | .block_cycles = 500, 57 | }; 58 | 59 | // entry point 60 | int main(void) { 61 | // mount the filesystem 62 | int err = lfs_mount(&lfs, &cfg); 63 | 64 | // reformat if we can't mount the filesystem 65 | // this should only happen on the first boot 66 | if (err) { 67 | lfs_format(&lfs, &cfg); 68 | lfs_mount(&lfs, &cfg); 69 | } 70 | 71 | // read current count 72 | uint32_t boot_count = 0; 73 | lfs_file_open(&lfs, &file, "boot_count", LFS_O_RDWR | LFS_O_CREAT); 74 | lfs_file_read(&lfs, &file, &boot_count, sizeof(boot_count)); 75 | 76 | // update boot count 77 | boot_count += 1; 78 | lfs_file_rewind(&lfs, &file); 79 | lfs_file_write(&lfs, &file, &boot_count, sizeof(boot_count)); 80 | 81 | // remember the storage is not updated until the file is closed successfully 82 | lfs_file_close(&lfs, &file); 83 | 84 | // release any resources we were using 85 | lfs_unmount(&lfs); 86 | 87 | // print the boot count 88 | printf("boot_count: %d\n", boot_count); 89 | } 90 | ``` 91 | 92 | ## Usage 93 | 94 | Detailed documentation (or at least as much detail as is currently available) 95 | can be found in the comments in [lfs.h](lfs.h). 96 | 97 | littlefs takes in a configuration structure that defines how the filesystem 98 | operates. The configuration struct provides the filesystem with the block 99 | device operations and dimensions, tweakable parameters that tradeoff memory 100 | usage for performance, and optional static buffers if the user wants to avoid 101 | dynamic memory. 102 | 103 | The state of the littlefs is stored in the `lfs_t` type which is left up 104 | to the user to allocate, allowing multiple filesystems to be in use 105 | simultaneously. With the `lfs_t` and configuration struct, a user can 106 | format a block device or mount the filesystem. 107 | 108 | Once mounted, the littlefs provides a full set of POSIX-like file and 109 | directory functions, with the deviation that the allocation of filesystem 110 | structures must be provided by the user. 111 | 112 | All POSIX operations, such as remove and rename, are atomic, even in event 113 | of power-loss. Additionally, file updates are not actually committed to 114 | the filesystem until sync or close is called on the file. 115 | 116 | ## Other notes 117 | 118 | Littlefs is written in C, and specifically should compile with any compiler 119 | that conforms to the `C99` standard. 120 | 121 | All littlefs calls have the potential to return a negative error code. The 122 | errors can be either one of those found in the `enum lfs_error` in 123 | [lfs.h](lfs.h), or an error returned by the user's block device operations. 124 | 125 | In the configuration struct, the `prog` and `erase` function provided by the 126 | user may return a `LFS_ERR_CORRUPT` error if the implementation already can 127 | detect corrupt blocks. However, the wear leveling does not depend on the return 128 | code of these functions, instead all data is read back and checked for 129 | integrity. 130 | 131 | If your storage caches writes, make sure that the provided `sync` function 132 | flushes all the data to memory and ensures that the next read fetches the data 133 | from memory, otherwise data integrity can not be guaranteed. If the `write` 134 | function does not perform caching, and therefore each `read` or `write` call 135 | hits the memory, the `sync` function can simply return 0. 136 | 137 | ## Design 138 | 139 | At a high level, littlefs is a block based filesystem that uses small logs to 140 | store metadata and larger copy-on-write (COW) structures to store file data. 141 | 142 | In littlefs, these ingredients form a sort of two-layered cake, with the small 143 | logs (called metadata pairs) providing fast updates to metadata anywhere on 144 | storage, while the COW structures store file data compactly and without any 145 | wear amplification cost. 146 | 147 | Both of these data structures are built out of blocks, which are fed by a 148 | common block allocator. By limiting the number of erases allowed on a block 149 | per allocation, the allocator provides dynamic wear leveling over the entire 150 | filesystem. 151 | 152 | ``` 153 | root 154 | .--------.--------. 155 | | A'| B'| | 156 | | | |-> | 157 | | | | | 158 | '--------'--------' 159 | .----' '--------------. 160 | A v B v 161 | .--------.--------. .--------.--------. 162 | | C'| D'| | | E'|new| | 163 | | | |-> | | | E'|-> | 164 | | | | | | | | | 165 | '--------'--------' '--------'--------' 166 | .-' '--. | '------------------. 167 | v v .-' v 168 | .--------. .--------. v .--------. 169 | | C | | D | .--------. write | new E | 170 | | | | | | E | ==> | | 171 | | | | | | | | | 172 | '--------' '--------' | | '--------' 173 | '--------' .-' | 174 | .-' '-. .-------------|------' 175 | v v v v 176 | .--------. .--------. .--------. 177 | | F | | G | | new F | 178 | | | | | | | 179 | | | | | | | 180 | '--------' '--------' '--------' 181 | ``` 182 | 183 | More details on how littlefs works can be found in [DESIGN.md](DESIGN.md) and 184 | [SPEC.md](SPEC.md). 185 | 186 | - [DESIGN.md](DESIGN.md) - A fully detailed dive into how littlefs works. 187 | I would suggest reading it as the tradeoffs at work are quite interesting. 188 | 189 | - [SPEC.md](SPEC.md) - The on-disk specification of littlefs with all the 190 | nitty-gritty details. May be useful for tooling development. 191 | 192 | ## Testing 193 | 194 | The littlefs comes with a test suite designed to run on a PC using the 195 | [emulated block device](bd/lfs_testbd.h) found in the `bd` directory. 196 | The tests assume a Linux environment and can be started with make: 197 | 198 | ``` bash 199 | make test 200 | ``` 201 | 202 | ## License 203 | 204 | The littlefs is provided under the [BSD-3-Clause] license. See 205 | [LICENSE.md](LICENSE.md) for more information. Contributions to this project 206 | are accepted under the same license. 207 | 208 | Individual files contain the following tag instead of the full license text. 209 | 210 | SPDX-License-Identifier: BSD-3-Clause 211 | 212 | This enables machine processing of license information based on the SPDX 213 | License Identifiers that are here available: http://spdx.org/licenses/ 214 | 215 | ## Related projects 216 | 217 | - [littlefs-fuse] - A [FUSE] wrapper for littlefs. The project allows you to 218 | mount littlefs directly on a Linux machine. Can be useful for debugging 219 | littlefs if you have an SD card handy. 220 | 221 | - [littlefs-js] - A javascript wrapper for littlefs. I'm not sure why you would 222 | want this, but it is handy for demos. You can see it in action 223 | [here][littlefs-js-demo]. 224 | 225 | - [littlefs-python] - A Python wrapper for littlefs. The project allows you 226 | to create images of the filesystem on your PC. Check if littlefs will fit 227 | your needs, create images for a later download to the target memory or 228 | inspect the content of a binary image of the target memory. 229 | 230 | - [littlefs2-rust] - A Rust wrapper for littlefs. This project allows you 231 | to use littlefs in a Rust-friendly API, reaping the benefits of Rust's memory 232 | safety and other guarantees. 233 | 234 | - [nim-littlefs] - A Nim wrapper and API for littlefs. Includes a fuse 235 | implementation based on [littlefs-fuse] 236 | 237 | - [chamelon] - A pure-OCaml implementation of (most of) littlefs, designed for 238 | use with the MirageOS library operating system project. It is interoperable 239 | with the reference implementation, with some caveats. 240 | 241 | - [littlefs-disk-img-viewer] - A memory-efficient web application for viewing 242 | littlefs disk images in your web browser. 243 | 244 | - [mklfs] - A command line tool for creating littlefs images. Used in the Lua 245 | RTOS ecosystem. 246 | 247 | - [mklittlefs] - A command line tool for creating littlefs images. Used in the 248 | ESP8266 and RP2040 ecosystem. 249 | 250 | - [pico-littlefs-usb] - An interface for littlefs that emulates a FAT12 251 | filesystem over USB. Allows mounting littlefs on a host PC without additional 252 | drivers. 253 | 254 | - [Mbed OS] - The easiest way to get started with littlefs is to jump into Mbed 255 | which already has block device drivers for most forms of embedded storage. 256 | littlefs is available in Mbed OS as the [LittleFileSystem] class. 257 | 258 | - [SPIFFS] - Another excellent embedded filesystem for NOR flash. As a more 259 | traditional logging filesystem with full static wear-leveling, SPIFFS will 260 | likely outperform littlefs on small memories such as the internal flash on 261 | microcontrollers. 262 | 263 | - [Dhara] - An interesting NAND flash translation layer designed for small 264 | MCUs. It offers static wear-leveling and power-resilience with only a fixed 265 | _O(|address|)_ pointer structure stored on each block and in RAM. 266 | 267 | - [ChaN's FatFs] - A lightweight reimplementation of the infamous FAT filesystem 268 | for microcontroller-scale devices. Due to limitations of FAT it can't provide 269 | power-loss resilience, but it does allow easy interop with PCs. 270 | 271 | [BSD-3-Clause]: https://spdx.org/licenses/BSD-3-Clause.html 272 | [littlefs-fuse]: https://github.com/geky/littlefs-fuse 273 | [FUSE]: https://github.com/libfuse/libfuse 274 | [littlefs-js]: https://github.com/geky/littlefs-js 275 | [littlefs-js-demo]:http://littlefs.geky.net/demo.html 276 | [littlefs-python]: https://pypi.org/project/littlefs-python/ 277 | [littlefs2-rust]: https://crates.io/crates/littlefs2 278 | [nim-littlefs]: https://github.com/Graveflo/nim-littlefs 279 | [chamelon]: https://github.com/yomimono/chamelon 280 | [littlefs-disk-img-viewer]: https://github.com/tniessen/littlefs-disk-img-viewer 281 | [mklfs]: https://github.com/whitecatboard/Lua-RTOS-ESP32/tree/master/components/mklfs/src 282 | [mklittlefs]: https://github.com/earlephilhower/mklittlefs 283 | [pico-littlefs-usb]: https://github.com/oyama/pico-littlefs-usb 284 | [Mbed OS]: https://github.com/armmbed/mbed-os 285 | [LittleFileSystem]: https://os.mbed.com/docs/mbed-os/latest/apis/littlefilesystem.html 286 | [SPIFFS]: https://github.com/pellepl/spiffs 287 | [Dhara]: https://github.com/dlbeer/dhara 288 | [ChaN's FatFs]: http://elm-chan.org/fsw/ff/00index_e.html 289 | -------------------------------------------------------------------------------- /SConscript: -------------------------------------------------------------------------------- 1 | # RT-Thread building script for component 2 | 3 | import os 4 | import shutil 5 | 6 | from building import * 7 | 8 | cwd = GetCurrentDir() 9 | src = Glob('*.c') + Glob('*.cpp') 10 | CPPPATH = [cwd] 11 | CPPDEFINES = ['LFS_CONFIG=lfs_config.h'] 12 | 13 | #delate non-used files 14 | try: 15 | shutil.rmtree(os.path.join(cwd,'.github')) 16 | shutil.rmtree(os.path.join(cwd,'bd')) 17 | shutil.rmtree(os.path.join(cwd,'scripts')) 18 | shutil.rmtree(os.path.join(cwd,'tests')) 19 | os.remove(os.path.join(cwd,'Makefile')) 20 | except: 21 | pass 22 | 23 | group = DefineGroup('littlefs', src, depend = ['PKG_USING_LITTLEFS', 'RT_USING_DFS'], CPPPATH = CPPPATH, CPPDEFINES = CPPDEFINES) 24 | 25 | Return('group') 26 | -------------------------------------------------------------------------------- /bd/lfs_emubd.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Emulating block device, wraps filebd and rambd while providing a bunch 3 | * of hooks for testing littlefs in various conditions. 4 | * 5 | * Copyright (c) 2022, The littlefs authors. 6 | * Copyright (c) 2017, Arm Limited. All rights reserved. 7 | * SPDX-License-Identifier: BSD-3-Clause 8 | */ 9 | #ifndef LFS_EMUBD_H 10 | #define LFS_EMUBD_H 11 | 12 | #include "lfs.h" 13 | #include "lfs_util.h" 14 | #include "bd/lfs_rambd.h" 15 | #include "bd/lfs_filebd.h" 16 | 17 | #ifdef __cplusplus 18 | extern "C" 19 | { 20 | #endif 21 | 22 | 23 | // Block device specific tracing 24 | #ifndef LFS_EMUBD_TRACE 25 | #ifdef LFS_EMUBD_YES_TRACE 26 | #define LFS_EMUBD_TRACE(...) LFS_TRACE(__VA_ARGS__) 27 | #else 28 | #define LFS_EMUBD_TRACE(...) 29 | #endif 30 | #endif 31 | 32 | // Mode determining how "bad-blocks" behave during testing. This simulates 33 | // some real-world circumstances such as progs not sticking (prog-noop), 34 | // a readonly disk (erase-noop), and ECC failures (read-error). 35 | // 36 | // Not that read-noop is not allowed. Read _must_ return a consistent (but 37 | // may be arbitrary) value on every read. 38 | typedef enum lfs_emubd_badblock_behavior { 39 | LFS_EMUBD_BADBLOCK_PROGERROR = 0, // Error on prog 40 | LFS_EMUBD_BADBLOCK_ERASEERROR = 1, // Error on erase 41 | LFS_EMUBD_BADBLOCK_READERROR = 2, // Error on read 42 | LFS_EMUBD_BADBLOCK_PROGNOOP = 3, // Prog does nothing silently 43 | LFS_EMUBD_BADBLOCK_ERASENOOP = 4, // Erase does nothing silently 44 | } lfs_emubd_badblock_behavior_t; 45 | 46 | // Mode determining how power-loss behaves during testing. For now this 47 | // only supports a noop behavior, leaving the data on-disk untouched. 48 | typedef enum lfs_emubd_powerloss_behavior { 49 | LFS_EMUBD_POWERLOSS_NOOP = 0, // Progs are atomic 50 | LFS_EMUBD_POWERLOSS_OOO = 1, // Blocks are written out-of-order 51 | } lfs_emubd_powerloss_behavior_t; 52 | 53 | // Type for measuring read/program/erase operations 54 | typedef uint64_t lfs_emubd_io_t; 55 | typedef int64_t lfs_emubd_sio_t; 56 | 57 | // Type for measuring wear 58 | typedef uint32_t lfs_emubd_wear_t; 59 | typedef int32_t lfs_emubd_swear_t; 60 | 61 | // Type for tracking power-cycles 62 | typedef uint32_t lfs_emubd_powercycles_t; 63 | typedef int32_t lfs_emubd_spowercycles_t; 64 | 65 | // Type for delays in nanoseconds 66 | typedef uint64_t lfs_emubd_sleep_t; 67 | typedef int64_t lfs_emubd_ssleep_t; 68 | 69 | // emubd config, this is required for testing 70 | struct lfs_emubd_config { 71 | // Minimum size of a read operation in bytes. 72 | lfs_size_t read_size; 73 | 74 | // Minimum size of a program operation in bytes. 75 | lfs_size_t prog_size; 76 | 77 | // Size of an erase operation in bytes. 78 | lfs_size_t erase_size; 79 | 80 | // Number of erase blocks on the device. 81 | lfs_size_t erase_count; 82 | 83 | // 8-bit erase value to use for simulating erases. -1 does not simulate 84 | // erases, which can speed up testing by avoiding the extra block-device 85 | // operations to store the erase value. 86 | int32_t erase_value; 87 | 88 | // Number of erase cycles before a block becomes "bad". The exact behavior 89 | // of bad blocks is controlled by badblock_behavior. 90 | uint32_t erase_cycles; 91 | 92 | // The mode determining how bad-blocks fail 93 | lfs_emubd_badblock_behavior_t badblock_behavior; 94 | 95 | // Number of write operations (erase/prog) before triggering a power-loss. 96 | // power_cycles=0 disables this. The exact behavior of power-loss is 97 | // controlled by a combination of powerloss_behavior and powerloss_cb. 98 | lfs_emubd_powercycles_t power_cycles; 99 | 100 | // The mode determining how power-loss affects disk 101 | lfs_emubd_powerloss_behavior_t powerloss_behavior; 102 | 103 | // Function to call to emulate power-loss. The exact behavior of power-loss 104 | // is up to the runner to provide. 105 | void (*powerloss_cb)(void*); 106 | 107 | // Data for power-loss callback 108 | void *powerloss_data; 109 | 110 | // True to track when power-loss could have occured. Note this involves 111 | // heavy memory usage! 112 | bool track_branches; 113 | 114 | // Path to file to use as a mirror of the disk. This provides a way to view 115 | // the current state of the block device. 116 | const char *disk_path; 117 | 118 | // Artificial delay in nanoseconds, there is no purpose for this other 119 | // than slowing down the simulation. 120 | lfs_emubd_sleep_t read_sleep; 121 | 122 | // Artificial delay in nanoseconds, there is no purpose for this other 123 | // than slowing down the simulation. 124 | lfs_emubd_sleep_t prog_sleep; 125 | 126 | // Artificial delay in nanoseconds, there is no purpose for this other 127 | // than slowing down the simulation. 128 | lfs_emubd_sleep_t erase_sleep; 129 | }; 130 | 131 | // A reference counted block 132 | typedef struct lfs_emubd_block { 133 | uint32_t rc; 134 | lfs_emubd_wear_t wear; 135 | 136 | uint8_t data[]; 137 | } lfs_emubd_block_t; 138 | 139 | // Disk mirror 140 | typedef struct lfs_emubd_disk { 141 | uint32_t rc; 142 | int fd; 143 | uint8_t *scratch; 144 | } lfs_emubd_disk_t; 145 | 146 | // emubd state 147 | typedef struct lfs_emubd { 148 | // array of copy-on-write blocks 149 | lfs_emubd_block_t **blocks; 150 | 151 | // some other test state 152 | lfs_emubd_io_t readed; 153 | lfs_emubd_io_t proged; 154 | lfs_emubd_io_t erased; 155 | lfs_emubd_powercycles_t power_cycles; 156 | lfs_ssize_t ooo_block; 157 | lfs_emubd_block_t *ooo_data; 158 | lfs_emubd_disk_t *disk; 159 | 160 | const struct lfs_emubd_config *cfg; 161 | } lfs_emubd_t; 162 | 163 | 164 | /// Block device API /// 165 | 166 | // Create an emulating block device using the geometry in lfs_config 167 | int lfs_emubd_create(const struct lfs_config *cfg, 168 | const struct lfs_emubd_config *bdcfg); 169 | 170 | // Clean up memory associated with block device 171 | int lfs_emubd_destroy(const struct lfs_config *cfg); 172 | 173 | // Read a block 174 | int lfs_emubd_read(const struct lfs_config *cfg, lfs_block_t block, 175 | lfs_off_t off, void *buffer, lfs_size_t size); 176 | 177 | // Program a block 178 | // 179 | // The block must have previously been erased. 180 | int lfs_emubd_prog(const struct lfs_config *cfg, lfs_block_t block, 181 | lfs_off_t off, const void *buffer, lfs_size_t size); 182 | 183 | // Erase a block 184 | // 185 | // A block must be erased before being programmed. The 186 | // state of an erased block is undefined. 187 | int lfs_emubd_erase(const struct lfs_config *cfg, lfs_block_t block); 188 | 189 | // Sync the block device 190 | int lfs_emubd_sync(const struct lfs_config *cfg); 191 | 192 | 193 | /// Additional extended API for driving test features /// 194 | 195 | // A CRC of a block for debugging purposes 196 | int lfs_emubd_crc(const struct lfs_config *cfg, 197 | lfs_block_t block, uint32_t *crc); 198 | 199 | // A CRC of the entire block device for debugging purposes 200 | int lfs_emubd_bdcrc(const struct lfs_config *cfg, uint32_t *crc); 201 | 202 | // Get total amount of bytes read 203 | lfs_emubd_sio_t lfs_emubd_readed(const struct lfs_config *cfg); 204 | 205 | // Get total amount of bytes programmed 206 | lfs_emubd_sio_t lfs_emubd_proged(const struct lfs_config *cfg); 207 | 208 | // Get total amount of bytes erased 209 | lfs_emubd_sio_t lfs_emubd_erased(const struct lfs_config *cfg); 210 | 211 | // Manually set amount of bytes read 212 | int lfs_emubd_setreaded(const struct lfs_config *cfg, lfs_emubd_io_t readed); 213 | 214 | // Manually set amount of bytes programmed 215 | int lfs_emubd_setproged(const struct lfs_config *cfg, lfs_emubd_io_t proged); 216 | 217 | // Manually set amount of bytes erased 218 | int lfs_emubd_seterased(const struct lfs_config *cfg, lfs_emubd_io_t erased); 219 | 220 | // Get simulated wear on a given block 221 | lfs_emubd_swear_t lfs_emubd_wear(const struct lfs_config *cfg, 222 | lfs_block_t block); 223 | 224 | // Manually set simulated wear on a given block 225 | int lfs_emubd_setwear(const struct lfs_config *cfg, 226 | lfs_block_t block, lfs_emubd_wear_t wear); 227 | 228 | // Get the remaining power-cycles 229 | lfs_emubd_spowercycles_t lfs_emubd_powercycles( 230 | const struct lfs_config *cfg); 231 | 232 | // Manually set the remaining power-cycles 233 | int lfs_emubd_setpowercycles(const struct lfs_config *cfg, 234 | lfs_emubd_powercycles_t power_cycles); 235 | 236 | // Create a copy-on-write copy of the state of this block device 237 | int lfs_emubd_copy(const struct lfs_config *cfg, lfs_emubd_t *copy); 238 | 239 | 240 | #ifdef __cplusplus 241 | } /* extern "C" */ 242 | #endif 243 | 244 | #endif 245 | -------------------------------------------------------------------------------- /bd/lfs_filebd.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Block device emulated in a file 3 | * 4 | * Copyright (c) 2022, The littlefs authors. 5 | * Copyright (c) 2017, Arm Limited. All rights reserved. 6 | * SPDX-License-Identifier: BSD-3-Clause 7 | */ 8 | #include "bd/lfs_filebd.h" 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | #ifdef _WIN32 15 | #include 16 | #endif 17 | 18 | int lfs_filebd_create(const struct lfs_config *cfg, const char *path, 19 | const struct lfs_filebd_config *bdcfg) { 20 | LFS_FILEBD_TRACE("lfs_filebd_create(%p {.context=%p, " 21 | ".read=%p, .prog=%p, .erase=%p, .sync=%p}, " 22 | "\"%s\", " 23 | "%p {.read_size=%"PRIu32", .prog_size=%"PRIu32", " 24 | ".erase_size=%"PRIu32", .erase_count=%"PRIu32"})", 25 | (void*)cfg, cfg->context, 26 | (void*)(uintptr_t)cfg->read, (void*)(uintptr_t)cfg->prog, 27 | (void*)(uintptr_t)cfg->erase, (void*)(uintptr_t)cfg->sync, 28 | path, 29 | (void*)bdcfg, 30 | bdcfg->read_size, bdcfg->prog_size, bdcfg->erase_size, 31 | bdcfg->erase_count); 32 | lfs_filebd_t *bd = cfg->context; 33 | bd->cfg = bdcfg; 34 | 35 | // open file 36 | #ifdef _WIN32 37 | bd->fd = open(path, O_RDWR | O_CREAT | O_BINARY, 0666); 38 | #else 39 | bd->fd = open(path, O_RDWR | O_CREAT, 0666); 40 | #endif 41 | 42 | if (bd->fd < 0) { 43 | int err = -errno; 44 | LFS_FILEBD_TRACE("lfs_filebd_create -> %d", err); 45 | return err; 46 | } 47 | 48 | LFS_FILEBD_TRACE("lfs_filebd_create -> %d", 0); 49 | return 0; 50 | } 51 | 52 | int lfs_filebd_destroy(const struct lfs_config *cfg) { 53 | LFS_FILEBD_TRACE("lfs_filebd_destroy(%p)", (void*)cfg); 54 | lfs_filebd_t *bd = cfg->context; 55 | int err = close(bd->fd); 56 | if (err < 0) { 57 | err = -errno; 58 | LFS_FILEBD_TRACE("lfs_filebd_destroy -> %d", err); 59 | return err; 60 | } 61 | LFS_FILEBD_TRACE("lfs_filebd_destroy -> %d", 0); 62 | return 0; 63 | } 64 | 65 | int lfs_filebd_read(const struct lfs_config *cfg, lfs_block_t block, 66 | lfs_off_t off, void *buffer, lfs_size_t size) { 67 | LFS_FILEBD_TRACE("lfs_filebd_read(%p, " 68 | "0x%"PRIx32", %"PRIu32", %p, %"PRIu32")", 69 | (void*)cfg, block, off, buffer, size); 70 | lfs_filebd_t *bd = cfg->context; 71 | 72 | // check if read is valid 73 | LFS_ASSERT(block < bd->cfg->erase_count); 74 | LFS_ASSERT(off % bd->cfg->read_size == 0); 75 | LFS_ASSERT(size % bd->cfg->read_size == 0); 76 | LFS_ASSERT(off+size <= bd->cfg->erase_size); 77 | 78 | // zero for reproducibility (in case file is truncated) 79 | memset(buffer, 0, size); 80 | 81 | // read 82 | off_t res1 = lseek(bd->fd, 83 | (off_t)block*bd->cfg->erase_size + (off_t)off, SEEK_SET); 84 | if (res1 < 0) { 85 | int err = -errno; 86 | LFS_FILEBD_TRACE("lfs_filebd_read -> %d", err); 87 | return err; 88 | } 89 | 90 | ssize_t res2 = read(bd->fd, buffer, size); 91 | if (res2 < 0) { 92 | int err = -errno; 93 | LFS_FILEBD_TRACE("lfs_filebd_read -> %d", err); 94 | return err; 95 | } 96 | 97 | LFS_FILEBD_TRACE("lfs_filebd_read -> %d", 0); 98 | return 0; 99 | } 100 | 101 | int lfs_filebd_prog(const struct lfs_config *cfg, lfs_block_t block, 102 | lfs_off_t off, const void *buffer, lfs_size_t size) { 103 | LFS_FILEBD_TRACE("lfs_filebd_prog(%p, " 104 | "0x%"PRIx32", %"PRIu32", %p, %"PRIu32")", 105 | (void*)cfg, block, off, buffer, size); 106 | lfs_filebd_t *bd = cfg->context; 107 | 108 | // check if write is valid 109 | LFS_ASSERT(block < bd->cfg->erase_count); 110 | LFS_ASSERT(off % bd->cfg->prog_size == 0); 111 | LFS_ASSERT(size % bd->cfg->prog_size == 0); 112 | LFS_ASSERT(off+size <= bd->cfg->erase_size); 113 | 114 | // program data 115 | off_t res1 = lseek(bd->fd, 116 | (off_t)block*bd->cfg->erase_size + (off_t)off, SEEK_SET); 117 | if (res1 < 0) { 118 | int err = -errno; 119 | LFS_FILEBD_TRACE("lfs_filebd_prog -> %d", err); 120 | return err; 121 | } 122 | 123 | ssize_t res2 = write(bd->fd, buffer, size); 124 | if (res2 < 0) { 125 | int err = -errno; 126 | LFS_FILEBD_TRACE("lfs_filebd_prog -> %d", err); 127 | return err; 128 | } 129 | 130 | LFS_FILEBD_TRACE("lfs_filebd_prog -> %d", 0); 131 | return 0; 132 | } 133 | 134 | int lfs_filebd_erase(const struct lfs_config *cfg, lfs_block_t block) { 135 | LFS_FILEBD_TRACE("lfs_filebd_erase(%p, 0x%"PRIx32" (%"PRIu32"))", 136 | (void*)cfg, block, ((lfs_file_t*)cfg->context)->cfg->erase_size); 137 | lfs_filebd_t *bd = cfg->context; 138 | 139 | // check if erase is valid 140 | LFS_ASSERT(block < bd->cfg->erase_count); 141 | 142 | // erase is a noop 143 | (void)block; 144 | 145 | LFS_FILEBD_TRACE("lfs_filebd_erase -> %d", 0); 146 | return 0; 147 | } 148 | 149 | int lfs_filebd_sync(const struct lfs_config *cfg) { 150 | LFS_FILEBD_TRACE("lfs_filebd_sync(%p)", (void*)cfg); 151 | 152 | // file sync 153 | lfs_filebd_t *bd = cfg->context; 154 | #ifdef _WIN32 155 | int err = FlushFileBuffers((HANDLE) _get_osfhandle(bd->fd)) ? 0 : -1; 156 | #else 157 | int err = fsync(bd->fd); 158 | #endif 159 | if (err) { 160 | err = -errno; 161 | LFS_FILEBD_TRACE("lfs_filebd_sync -> %d", 0); 162 | return err; 163 | } 164 | 165 | LFS_FILEBD_TRACE("lfs_filebd_sync -> %d", 0); 166 | return 0; 167 | } 168 | -------------------------------------------------------------------------------- /bd/lfs_filebd.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Block device emulated in a file 3 | * 4 | * Copyright (c) 2022, The littlefs authors. 5 | * Copyright (c) 2017, Arm Limited. All rights reserved. 6 | * SPDX-License-Identifier: BSD-3-Clause 7 | */ 8 | #ifndef LFS_FILEBD_H 9 | #define LFS_FILEBD_H 10 | 11 | #include "lfs.h" 12 | #include "lfs_util.h" 13 | 14 | #ifdef __cplusplus 15 | extern "C" 16 | { 17 | #endif 18 | 19 | 20 | // Block device specific tracing 21 | #ifndef LFS_FILEBD_TRACE 22 | #ifdef LFS_FILEBD_YES_TRACE 23 | #define LFS_FILEBD_TRACE(...) LFS_TRACE(__VA_ARGS__) 24 | #else 25 | #define LFS_FILEBD_TRACE(...) 26 | #endif 27 | #endif 28 | 29 | // filebd config 30 | struct lfs_filebd_config { 31 | // Minimum size of a read operation in bytes. 32 | lfs_size_t read_size; 33 | 34 | // Minimum size of a program operation in bytes. 35 | lfs_size_t prog_size; 36 | 37 | // Size of an erase operation in bytes. 38 | lfs_size_t erase_size; 39 | 40 | // Number of erase blocks on the device. 41 | lfs_size_t erase_count; 42 | }; 43 | 44 | // filebd state 45 | typedef struct lfs_filebd { 46 | int fd; 47 | const struct lfs_filebd_config *cfg; 48 | } lfs_filebd_t; 49 | 50 | 51 | // Create a file block device 52 | int lfs_filebd_create(const struct lfs_config *cfg, const char *path, 53 | const struct lfs_filebd_config *bdcfg); 54 | 55 | // Clean up memory associated with block device 56 | int lfs_filebd_destroy(const struct lfs_config *cfg); 57 | 58 | // Read a block 59 | int lfs_filebd_read(const struct lfs_config *cfg, lfs_block_t block, 60 | lfs_off_t off, void *buffer, lfs_size_t size); 61 | 62 | // Program a block 63 | // 64 | // The block must have previously been erased. 65 | int lfs_filebd_prog(const struct lfs_config *cfg, lfs_block_t block, 66 | lfs_off_t off, const void *buffer, lfs_size_t size); 67 | 68 | // Erase a block 69 | // 70 | // A block must be erased before being programmed. The 71 | // state of an erased block is undefined. 72 | int lfs_filebd_erase(const struct lfs_config *cfg, lfs_block_t block); 73 | 74 | // Sync the block device 75 | int lfs_filebd_sync(const struct lfs_config *cfg); 76 | 77 | 78 | #ifdef __cplusplus 79 | } /* extern "C" */ 80 | #endif 81 | 82 | #endif 83 | -------------------------------------------------------------------------------- /bd/lfs_rambd.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Block device emulated in RAM 3 | * 4 | * Copyright (c) 2022, The littlefs authors. 5 | * Copyright (c) 2017, Arm Limited. All rights reserved. 6 | * SPDX-License-Identifier: BSD-3-Clause 7 | */ 8 | #include "bd/lfs_rambd.h" 9 | 10 | int lfs_rambd_create(const struct lfs_config *cfg, 11 | const struct lfs_rambd_config *bdcfg) { 12 | LFS_RAMBD_TRACE("lfs_rambd_create(%p {.context=%p, " 13 | ".read=%p, .prog=%p, .erase=%p, .sync=%p}, " 14 | "%p {.read_size=%"PRIu32", .prog_size=%"PRIu32", " 15 | ".erase_size=%"PRIu32", .erase_count=%"PRIu32", " 16 | ".buffer=%p})", 17 | (void*)cfg, cfg->context, 18 | (void*)(uintptr_t)cfg->read, (void*)(uintptr_t)cfg->prog, 19 | (void*)(uintptr_t)cfg->erase, (void*)(uintptr_t)cfg->sync, 20 | (void*)bdcfg, 21 | bdcfg->read_size, bdcfg->prog_size, bdcfg->erase_size, 22 | bdcfg->erase_count, bdcfg->buffer); 23 | lfs_rambd_t *bd = cfg->context; 24 | bd->cfg = bdcfg; 25 | 26 | // allocate buffer? 27 | if (bd->cfg->buffer) { 28 | bd->buffer = bd->cfg->buffer; 29 | } else { 30 | bd->buffer = lfs_malloc(bd->cfg->erase_size * bd->cfg->erase_count); 31 | if (!bd->buffer) { 32 | LFS_RAMBD_TRACE("lfs_rambd_create -> %d", LFS_ERR_NOMEM); 33 | return LFS_ERR_NOMEM; 34 | } 35 | } 36 | 37 | // zero for reproducibility 38 | memset(bd->buffer, 0, bd->cfg->erase_size * bd->cfg->erase_count); 39 | 40 | LFS_RAMBD_TRACE("lfs_rambd_create -> %d", 0); 41 | return 0; 42 | } 43 | 44 | int lfs_rambd_destroy(const struct lfs_config *cfg) { 45 | LFS_RAMBD_TRACE("lfs_rambd_destroy(%p)", (void*)cfg); 46 | // clean up memory 47 | lfs_rambd_t *bd = cfg->context; 48 | if (!bd->cfg->buffer) { 49 | lfs_free(bd->buffer); 50 | } 51 | LFS_RAMBD_TRACE("lfs_rambd_destroy -> %d", 0); 52 | return 0; 53 | } 54 | 55 | int lfs_rambd_read(const struct lfs_config *cfg, lfs_block_t block, 56 | lfs_off_t off, void *buffer, lfs_size_t size) { 57 | LFS_RAMBD_TRACE("lfs_rambd_read(%p, " 58 | "0x%"PRIx32", %"PRIu32", %p, %"PRIu32")", 59 | (void*)cfg, block, off, buffer, size); 60 | lfs_rambd_t *bd = cfg->context; 61 | 62 | // check if read is valid 63 | LFS_ASSERT(block < bd->cfg->erase_count); 64 | LFS_ASSERT(off % bd->cfg->read_size == 0); 65 | LFS_ASSERT(size % bd->cfg->read_size == 0); 66 | LFS_ASSERT(off+size <= bd->cfg->erase_size); 67 | 68 | // read data 69 | memcpy(buffer, &bd->buffer[block*bd->cfg->erase_size + off], size); 70 | 71 | LFS_RAMBD_TRACE("lfs_rambd_read -> %d", 0); 72 | return 0; 73 | } 74 | 75 | int lfs_rambd_prog(const struct lfs_config *cfg, lfs_block_t block, 76 | lfs_off_t off, const void *buffer, lfs_size_t size) { 77 | LFS_RAMBD_TRACE("lfs_rambd_prog(%p, " 78 | "0x%"PRIx32", %"PRIu32", %p, %"PRIu32")", 79 | (void*)cfg, block, off, buffer, size); 80 | lfs_rambd_t *bd = cfg->context; 81 | 82 | // check if write is valid 83 | LFS_ASSERT(block < bd->cfg->erase_count); 84 | LFS_ASSERT(off % bd->cfg->prog_size == 0); 85 | LFS_ASSERT(size % bd->cfg->prog_size == 0); 86 | LFS_ASSERT(off+size <= bd->cfg->erase_size); 87 | 88 | // program data 89 | memcpy(&bd->buffer[block*bd->cfg->erase_size + off], buffer, size); 90 | 91 | LFS_RAMBD_TRACE("lfs_rambd_prog -> %d", 0); 92 | return 0; 93 | } 94 | 95 | int lfs_rambd_erase(const struct lfs_config *cfg, lfs_block_t block) { 96 | LFS_RAMBD_TRACE("lfs_rambd_erase(%p, 0x%"PRIx32" (%"PRIu32"))", 97 | (void*)cfg, block, ((lfs_rambd_t*)cfg->context)->cfg->erase_size); 98 | lfs_rambd_t *bd = cfg->context; 99 | 100 | // check if erase is valid 101 | LFS_ASSERT(block < bd->cfg->erase_count); 102 | 103 | // erase is a noop 104 | (void)block; 105 | 106 | LFS_RAMBD_TRACE("lfs_rambd_erase -> %d", 0); 107 | return 0; 108 | } 109 | 110 | int lfs_rambd_sync(const struct lfs_config *cfg) { 111 | LFS_RAMBD_TRACE("lfs_rambd_sync(%p)", (void*)cfg); 112 | 113 | // sync is a noop 114 | (void)cfg; 115 | 116 | LFS_RAMBD_TRACE("lfs_rambd_sync -> %d", 0); 117 | return 0; 118 | } 119 | -------------------------------------------------------------------------------- /bd/lfs_rambd.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Block device emulated in RAM 3 | * 4 | * Copyright (c) 2022, The littlefs authors. 5 | * Copyright (c) 2017, Arm Limited. All rights reserved. 6 | * SPDX-License-Identifier: BSD-3-Clause 7 | */ 8 | #ifndef LFS_RAMBD_H 9 | #define LFS_RAMBD_H 10 | 11 | #include "lfs.h" 12 | #include "lfs_util.h" 13 | 14 | #ifdef __cplusplus 15 | extern "C" 16 | { 17 | #endif 18 | 19 | 20 | // Block device specific tracing 21 | #ifndef LFS_RAMBD_TRACE 22 | #ifdef LFS_RAMBD_YES_TRACE 23 | #define LFS_RAMBD_TRACE(...) LFS_TRACE(__VA_ARGS__) 24 | #else 25 | #define LFS_RAMBD_TRACE(...) 26 | #endif 27 | #endif 28 | 29 | // rambd config 30 | struct lfs_rambd_config { 31 | // Minimum size of a read operation in bytes. 32 | lfs_size_t read_size; 33 | 34 | // Minimum size of a program operation in bytes. 35 | lfs_size_t prog_size; 36 | 37 | // Size of an erase operation in bytes. 38 | lfs_size_t erase_size; 39 | 40 | // Number of erase blocks on the device. 41 | lfs_size_t erase_count; 42 | 43 | // Optional statically allocated buffer for the block device. 44 | void *buffer; 45 | }; 46 | 47 | // rambd state 48 | typedef struct lfs_rambd { 49 | uint8_t *buffer; 50 | const struct lfs_rambd_config *cfg; 51 | } lfs_rambd_t; 52 | 53 | 54 | // Create a RAM block device 55 | int lfs_rambd_create(const struct lfs_config *cfg, 56 | const struct lfs_rambd_config *bdcfg); 57 | 58 | // Clean up memory associated with block device 59 | int lfs_rambd_destroy(const struct lfs_config *cfg); 60 | 61 | // Read a block 62 | int lfs_rambd_read(const struct lfs_config *cfg, lfs_block_t block, 63 | lfs_off_t off, void *buffer, lfs_size_t size); 64 | 65 | // Program a block 66 | // 67 | // The block must have previously been erased. 68 | int lfs_rambd_prog(const struct lfs_config *cfg, lfs_block_t block, 69 | lfs_off_t off, const void *buffer, lfs_size_t size); 70 | 71 | // Erase a block 72 | // 73 | // A block must be erased before being programmed. The 74 | // state of an erased block is undefined. 75 | int lfs_rambd_erase(const struct lfs_config *cfg, lfs_block_t block); 76 | 77 | // Sync the block device 78 | int lfs_rambd_sync(const struct lfs_config *cfg); 79 | 80 | 81 | #ifdef __cplusplus 82 | } /* extern "C" */ 83 | #endif 84 | 85 | #endif 86 | -------------------------------------------------------------------------------- /benches/bench_dir.toml: -------------------------------------------------------------------------------- 1 | [cases.bench_dir_open] 2 | # 0 = in-order 3 | # 1 = reversed-order 4 | # 2 = random-order 5 | defines.ORDER = [0, 1, 2] 6 | defines.N = 1024 7 | defines.FILE_SIZE = 8 8 | defines.CHUNK_SIZE = 8 9 | code = ''' 10 | lfs_t lfs; 11 | lfs_format(&lfs, cfg) => 0; 12 | lfs_mount(&lfs, cfg) => 0; 13 | 14 | // first create the files 15 | char name[256]; 16 | uint8_t buffer[CHUNK_SIZE]; 17 | for (lfs_size_t i = 0; i < N; i++) { 18 | sprintf(name, "file%08x", i); 19 | lfs_file_t file; 20 | lfs_file_open(&lfs, &file, name, 21 | LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; 22 | 23 | uint32_t file_prng = i; 24 | for (lfs_size_t j = 0; j < FILE_SIZE; j += CHUNK_SIZE) { 25 | for (lfs_size_t k = 0; k < CHUNK_SIZE; k++) { 26 | buffer[k] = BENCH_PRNG(&file_prng); 27 | } 28 | lfs_file_write(&lfs, &file, buffer, CHUNK_SIZE) => CHUNK_SIZE; 29 | } 30 | 31 | lfs_file_close(&lfs, &file) => 0; 32 | } 33 | 34 | // then read the files 35 | BENCH_START(); 36 | uint32_t prng = 42; 37 | for (lfs_size_t i = 0; i < N; i++) { 38 | lfs_off_t i_ 39 | = (ORDER == 0) ? i 40 | : (ORDER == 1) ? (N-1-i) 41 | : BENCH_PRNG(&prng) % N; 42 | sprintf(name, "file%08x", i_); 43 | lfs_file_t file; 44 | lfs_file_open(&lfs, &file, name, LFS_O_RDONLY) => 0; 45 | 46 | uint32_t file_prng = i_; 47 | for (lfs_size_t j = 0; j < FILE_SIZE; j += CHUNK_SIZE) { 48 | lfs_file_read(&lfs, &file, buffer, CHUNK_SIZE) => CHUNK_SIZE; 49 | for (lfs_size_t k = 0; k < CHUNK_SIZE; k++) { 50 | assert(buffer[k] == BENCH_PRNG(&file_prng)); 51 | } 52 | } 53 | 54 | lfs_file_close(&lfs, &file) => 0; 55 | } 56 | BENCH_STOP(); 57 | 58 | lfs_unmount(&lfs) => 0; 59 | ''' 60 | 61 | [cases.bench_dir_creat] 62 | # 0 = in-order 63 | # 1 = reversed-order 64 | # 2 = random-order 65 | defines.ORDER = [0, 1, 2] 66 | defines.N = 1024 67 | defines.FILE_SIZE = 8 68 | defines.CHUNK_SIZE = 8 69 | code = ''' 70 | lfs_t lfs; 71 | lfs_format(&lfs, cfg) => 0; 72 | lfs_mount(&lfs, cfg) => 0; 73 | 74 | BENCH_START(); 75 | uint32_t prng = 42; 76 | char name[256]; 77 | uint8_t buffer[CHUNK_SIZE]; 78 | for (lfs_size_t i = 0; i < N; i++) { 79 | lfs_off_t i_ 80 | = (ORDER == 0) ? i 81 | : (ORDER == 1) ? (N-1-i) 82 | : BENCH_PRNG(&prng) % N; 83 | sprintf(name, "file%08x", i_); 84 | lfs_file_t file; 85 | lfs_file_open(&lfs, &file, name, 86 | LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0; 87 | 88 | uint32_t file_prng = i_; 89 | for (lfs_size_t j = 0; j < FILE_SIZE; j += CHUNK_SIZE) { 90 | for (lfs_size_t k = 0; k < CHUNK_SIZE; k++) { 91 | buffer[k] = BENCH_PRNG(&file_prng); 92 | } 93 | lfs_file_write(&lfs, &file, buffer, CHUNK_SIZE) => CHUNK_SIZE; 94 | } 95 | 96 | lfs_file_close(&lfs, &file) => 0; 97 | } 98 | BENCH_STOP(); 99 | 100 | lfs_unmount(&lfs) => 0; 101 | ''' 102 | 103 | [cases.bench_dir_remove] 104 | # 0 = in-order 105 | # 1 = reversed-order 106 | # 2 = random-order 107 | defines.ORDER = [0, 1, 2] 108 | defines.N = 1024 109 | defines.FILE_SIZE = 8 110 | defines.CHUNK_SIZE = 8 111 | code = ''' 112 | lfs_t lfs; 113 | lfs_format(&lfs, cfg) => 0; 114 | lfs_mount(&lfs, cfg) => 0; 115 | 116 | // first create the files 117 | char name[256]; 118 | uint8_t buffer[CHUNK_SIZE]; 119 | for (lfs_size_t i = 0; i < N; i++) { 120 | sprintf(name, "file%08x", i); 121 | lfs_file_t file; 122 | lfs_file_open(&lfs, &file, name, 123 | LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; 124 | 125 | uint32_t file_prng = i; 126 | for (lfs_size_t j = 0; j < FILE_SIZE; j += CHUNK_SIZE) { 127 | for (lfs_size_t k = 0; k < CHUNK_SIZE; k++) { 128 | buffer[k] = BENCH_PRNG(&file_prng); 129 | } 130 | lfs_file_write(&lfs, &file, buffer, CHUNK_SIZE) => CHUNK_SIZE; 131 | } 132 | 133 | lfs_file_close(&lfs, &file) => 0; 134 | } 135 | 136 | // then remove the files 137 | BENCH_START(); 138 | uint32_t prng = 42; 139 | for (lfs_size_t i = 0; i < N; i++) { 140 | lfs_off_t i_ 141 | = (ORDER == 0) ? i 142 | : (ORDER == 1) ? (N-1-i) 143 | : BENCH_PRNG(&prng) % N; 144 | sprintf(name, "file%08x", i_); 145 | int err = lfs_remove(&lfs, name); 146 | assert(!err || err == LFS_ERR_NOENT); 147 | } 148 | BENCH_STOP(); 149 | 150 | lfs_unmount(&lfs) => 0; 151 | ''' 152 | 153 | [cases.bench_dir_read] 154 | defines.N = 1024 155 | defines.FILE_SIZE = 8 156 | defines.CHUNK_SIZE = 8 157 | code = ''' 158 | lfs_t lfs; 159 | lfs_format(&lfs, cfg) => 0; 160 | lfs_mount(&lfs, cfg) => 0; 161 | 162 | // first create the files 163 | char name[256]; 164 | uint8_t buffer[CHUNK_SIZE]; 165 | for (lfs_size_t i = 0; i < N; i++) { 166 | sprintf(name, "file%08x", i); 167 | lfs_file_t file; 168 | lfs_file_open(&lfs, &file, name, 169 | LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; 170 | 171 | uint32_t file_prng = i; 172 | for (lfs_size_t j = 0; j < FILE_SIZE; j += CHUNK_SIZE) { 173 | for (lfs_size_t k = 0; k < CHUNK_SIZE; k++) { 174 | buffer[k] = BENCH_PRNG(&file_prng); 175 | } 176 | lfs_file_write(&lfs, &file, buffer, CHUNK_SIZE) => CHUNK_SIZE; 177 | } 178 | 179 | lfs_file_close(&lfs, &file) => 0; 180 | } 181 | 182 | // then read the directory 183 | BENCH_START(); 184 | lfs_dir_t dir; 185 | lfs_dir_open(&lfs, &dir, "/") => 0; 186 | struct lfs_info info; 187 | lfs_dir_read(&lfs, &dir, &info) => 1; 188 | assert(info.type == LFS_TYPE_DIR); 189 | assert(strcmp(info.name, ".") == 0); 190 | lfs_dir_read(&lfs, &dir, &info) => 1; 191 | assert(info.type == LFS_TYPE_DIR); 192 | assert(strcmp(info.name, "..") == 0); 193 | for (int i = 0; i < N; i++) { 194 | sprintf(name, "file%08x", i); 195 | lfs_dir_read(&lfs, &dir, &info) => 1; 196 | assert(info.type == LFS_TYPE_REG); 197 | assert(strcmp(info.name, name) == 0); 198 | } 199 | lfs_dir_read(&lfs, &dir, &info) => 0; 200 | lfs_dir_close(&lfs, &dir) => 0; 201 | BENCH_STOP(); 202 | 203 | lfs_unmount(&lfs) => 0; 204 | ''' 205 | 206 | [cases.bench_dir_mkdir] 207 | # 0 = in-order 208 | # 1 = reversed-order 209 | # 2 = random-order 210 | defines.ORDER = [0, 1, 2] 211 | defines.N = 8 212 | code = ''' 213 | lfs_t lfs; 214 | lfs_format(&lfs, cfg) => 0; 215 | lfs_mount(&lfs, cfg) => 0; 216 | 217 | BENCH_START(); 218 | uint32_t prng = 42; 219 | char name[256]; 220 | for (lfs_size_t i = 0; i < N; i++) { 221 | lfs_off_t i_ 222 | = (ORDER == 0) ? i 223 | : (ORDER == 1) ? (N-1-i) 224 | : BENCH_PRNG(&prng) % N; 225 | printf("hm %d\n", i); 226 | sprintf(name, "dir%08x", i_); 227 | int err = lfs_mkdir(&lfs, name); 228 | assert(!err || err == LFS_ERR_EXIST); 229 | } 230 | BENCH_STOP(); 231 | 232 | lfs_unmount(&lfs) => 0; 233 | ''' 234 | 235 | [cases.bench_dir_rmdir] 236 | # 0 = in-order 237 | # 1 = reversed-order 238 | # 2 = random-order 239 | defines.ORDER = [0, 1, 2] 240 | defines.N = 8 241 | code = ''' 242 | lfs_t lfs; 243 | lfs_format(&lfs, cfg) => 0; 244 | lfs_mount(&lfs, cfg) => 0; 245 | 246 | // first create the dirs 247 | char name[256]; 248 | for (lfs_size_t i = 0; i < N; i++) { 249 | sprintf(name, "dir%08x", i); 250 | lfs_mkdir(&lfs, name) => 0; 251 | } 252 | 253 | // then remove the dirs 254 | BENCH_START(); 255 | uint32_t prng = 42; 256 | for (lfs_size_t i = 0; i < N; i++) { 257 | lfs_off_t i_ 258 | = (ORDER == 0) ? i 259 | : (ORDER == 1) ? (N-1-i) 260 | : BENCH_PRNG(&prng) % N; 261 | sprintf(name, "dir%08x", i_); 262 | int err = lfs_remove(&lfs, name); 263 | assert(!err || err == LFS_ERR_NOENT); 264 | } 265 | BENCH_STOP(); 266 | 267 | lfs_unmount(&lfs) => 0; 268 | ''' 269 | 270 | 271 | -------------------------------------------------------------------------------- /benches/bench_file.toml: -------------------------------------------------------------------------------- 1 | [cases.bench_file_read] 2 | # 0 = in-order 3 | # 1 = reversed-order 4 | # 2 = random-order 5 | defines.ORDER = [0, 1, 2] 6 | defines.SIZE = '128*1024' 7 | defines.CHUNK_SIZE = 64 8 | code = ''' 9 | lfs_t lfs; 10 | lfs_format(&lfs, cfg) => 0; 11 | lfs_mount(&lfs, cfg) => 0; 12 | lfs_size_t chunks = (SIZE+CHUNK_SIZE-1)/CHUNK_SIZE; 13 | 14 | // first write the file 15 | lfs_file_t file; 16 | uint8_t buffer[CHUNK_SIZE]; 17 | lfs_file_open(&lfs, &file, "file", 18 | LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; 19 | for (lfs_size_t i = 0; i < chunks; i++) { 20 | uint32_t chunk_prng = i; 21 | for (lfs_size_t j = 0; j < CHUNK_SIZE; j++) { 22 | buffer[j] = BENCH_PRNG(&chunk_prng); 23 | } 24 | 25 | lfs_file_write(&lfs, &file, buffer, CHUNK_SIZE) => CHUNK_SIZE; 26 | } 27 | lfs_file_write(&lfs, &file, buffer, CHUNK_SIZE) => CHUNK_SIZE; 28 | lfs_file_close(&lfs, &file) => 0; 29 | 30 | // then read the file 31 | BENCH_START(); 32 | lfs_file_open(&lfs, &file, "file", LFS_O_RDONLY) => 0; 33 | 34 | uint32_t prng = 42; 35 | for (lfs_size_t i = 0; i < chunks; i++) { 36 | lfs_off_t i_ 37 | = (ORDER == 0) ? i 38 | : (ORDER == 1) ? (chunks-1-i) 39 | : BENCH_PRNG(&prng) % chunks; 40 | lfs_file_seek(&lfs, &file, i_*CHUNK_SIZE, LFS_SEEK_SET) 41 | => i_*CHUNK_SIZE; 42 | lfs_file_read(&lfs, &file, buffer, CHUNK_SIZE) => CHUNK_SIZE; 43 | 44 | uint32_t chunk_prng = i_; 45 | for (lfs_size_t j = 0; j < CHUNK_SIZE; j++) { 46 | assert(buffer[j] == BENCH_PRNG(&chunk_prng)); 47 | } 48 | } 49 | 50 | lfs_file_close(&lfs, &file) => 0; 51 | BENCH_STOP(); 52 | 53 | lfs_unmount(&lfs) => 0; 54 | ''' 55 | 56 | [cases.bench_file_write] 57 | # 0 = in-order 58 | # 1 = reversed-order 59 | # 2 = random-order 60 | defines.ORDER = [0, 1, 2] 61 | defines.SIZE = '128*1024' 62 | defines.CHUNK_SIZE = 64 63 | code = ''' 64 | lfs_t lfs; 65 | lfs_format(&lfs, cfg) => 0; 66 | lfs_mount(&lfs, cfg) => 0; 67 | lfs_size_t chunks = (SIZE+CHUNK_SIZE-1)/CHUNK_SIZE; 68 | 69 | BENCH_START(); 70 | lfs_file_t file; 71 | lfs_file_open(&lfs, &file, "file", 72 | LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; 73 | 74 | uint8_t buffer[CHUNK_SIZE]; 75 | uint32_t prng = 42; 76 | for (lfs_size_t i = 0; i < chunks; i++) { 77 | lfs_off_t i_ 78 | = (ORDER == 0) ? i 79 | : (ORDER == 1) ? (chunks-1-i) 80 | : BENCH_PRNG(&prng) % chunks; 81 | uint32_t chunk_prng = i_; 82 | for (lfs_size_t j = 0; j < CHUNK_SIZE; j++) { 83 | buffer[j] = BENCH_PRNG(&chunk_prng); 84 | } 85 | 86 | lfs_file_seek(&lfs, &file, i_*CHUNK_SIZE, LFS_SEEK_SET) 87 | => i_*CHUNK_SIZE; 88 | lfs_file_write(&lfs, &file, buffer, CHUNK_SIZE) => CHUNK_SIZE; 89 | } 90 | 91 | lfs_file_close(&lfs, &file) => 0; 92 | BENCH_STOP(); 93 | 94 | lfs_unmount(&lfs) => 0; 95 | ''' 96 | -------------------------------------------------------------------------------- /benches/bench_superblock.toml: -------------------------------------------------------------------------------- 1 | [cases.bench_superblocks_found] 2 | # support benchmarking with files 3 | defines.N = [0, 1024] 4 | defines.FILE_SIZE = 8 5 | defines.CHUNK_SIZE = 8 6 | code = ''' 7 | lfs_t lfs; 8 | lfs_format(&lfs, cfg) => 0; 9 | 10 | // create files? 11 | lfs_mount(&lfs, cfg) => 0; 12 | char name[256]; 13 | uint8_t buffer[CHUNK_SIZE]; 14 | for (lfs_size_t i = 0; i < N; i++) { 15 | sprintf(name, "file%08x", i); 16 | lfs_file_t file; 17 | lfs_file_open(&lfs, &file, name, 18 | LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; 19 | 20 | for (lfs_size_t j = 0; j < FILE_SIZE; j += CHUNK_SIZE) { 21 | for (lfs_size_t k = 0; k < CHUNK_SIZE; k++) { 22 | buffer[k] = i+j+k; 23 | } 24 | lfs_file_write(&lfs, &file, buffer, CHUNK_SIZE) => CHUNK_SIZE; 25 | } 26 | 27 | lfs_file_close(&lfs, &file) => 0; 28 | } 29 | lfs_unmount(&lfs) => 0; 30 | 31 | BENCH_START(); 32 | lfs_mount(&lfs, cfg) => 0; 33 | BENCH_STOP(); 34 | 35 | lfs_unmount(&lfs) => 0; 36 | ''' 37 | 38 | [cases.bench_superblocks_missing] 39 | code = ''' 40 | lfs_t lfs; 41 | 42 | BENCH_START(); 43 | int err = lfs_mount(&lfs, cfg); 44 | assert(err != 0); 45 | BENCH_STOP(); 46 | ''' 47 | 48 | [cases.bench_superblocks_format] 49 | code = ''' 50 | lfs_t lfs; 51 | 52 | BENCH_START(); 53 | lfs_format(&lfs, cfg) => 0; 54 | BENCH_STOP(); 55 | ''' 56 | 57 | -------------------------------------------------------------------------------- /lfs_config.h: -------------------------------------------------------------------------------- 1 | #ifndef _LFS_CONFIG_H_ 2 | #define _LFS_CONFIG_H_ 3 | 4 | #include 5 | 6 | // System includes 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #ifdef __cplusplus 13 | extern "C" 14 | { 15 | #endif 16 | // Macros, may be replaced by system specific wrappers. Arguments to these 17 | // macros must not have side-effects as the macros can be removed for a smaller 18 | // code footprint 19 | 20 | // Logging functions 21 | #ifdef LFS_YES_TRACE 22 | #define LFS_TRACE(fmt, ...) \ 23 | rt_kprintf("%s:%d:trace: " fmt "%s\n", __FILE__, __LINE__, __VA_ARGS__) 24 | #define LFS_TRACE(...) LFS_TRACE_(__VA_ARGS__, "") 25 | #else 26 | #define LFS_TRACE(...) 27 | #endif 28 | 29 | #ifndef LFS_NO_DEBUG 30 | #define LFS_DEBUG_(fmt, ...) \ 31 | rt_kprintf("%s:%d:debug: " fmt "%s\n", __FILE__, __LINE__, __VA_ARGS__) 32 | #define LFS_DEBUG(...) LFS_DEBUG_(__VA_ARGS__, "") 33 | #else 34 | #define LFS_DEBUG(...) 35 | #endif 36 | 37 | #ifndef LFS_NO_WARN 38 | #define LFS_WARN_(fmt, ...) \ 39 | rt_kprintf("%s:%d:warn: " fmt "%s\n", __FILE__, __LINE__, __VA_ARGS__) 40 | #define LFS_WARN(...) LFS_WARN_(__VA_ARGS__, "") 41 | #else 42 | #define LFS_WARN(...) 43 | #endif 44 | 45 | #ifndef LFS_NO_ERROR 46 | #define LFS_ERROR_(fmt, ...) \ 47 | rt_kprintf("%s:%d:error: " fmt "%s\n", __FILE__, __LINE__, __VA_ARGS__) 48 | #define LFS_ERROR(...) LFS_ERROR_(__VA_ARGS__, "") 49 | #else 50 | #define LFS_ERROR(...) 51 | #endif 52 | 53 | // Runtime assertions 54 | #ifndef LFS_NO_ASSERT 55 | #define LFS_ASSERT(test) RT_ASSERT(test) 56 | #else 57 | #define LFS_ASSERT(test) 58 | #endif 59 | 60 | 61 | // Builtin functions, these may be replaced by more efficient 62 | // toolchain-specific implementations. LFS_NO_INTRINSICS falls back to a more 63 | // expensive basic C implementation for debugging purposes 64 | 65 | // Min/max functions for unsigned 32-bit numbers 66 | static inline uint32_t lfs_max(uint32_t a, uint32_t b) { 67 | return (a > b) ? a : b; 68 | } 69 | 70 | static inline uint32_t lfs_min(uint32_t a, uint32_t b) { 71 | return (a < b) ? a : b; 72 | } 73 | 74 | // Align to nearest multiple of a size 75 | static inline uint32_t lfs_aligndown(uint32_t a, uint32_t alignment) { 76 | return a - (a % alignment); 77 | } 78 | 79 | static inline uint32_t lfs_alignup(uint32_t a, uint32_t alignment) { 80 | return lfs_aligndown(a + alignment-1, alignment); 81 | } 82 | 83 | // Find the smallest power of 2 greater than or equal to a 84 | static inline uint32_t lfs_npw2(uint32_t a) { 85 | #if !defined(LFS_NO_INTRINSICS) && (defined(__GNUC__) || defined(__CC_ARM)) 86 | return 32 - __builtin_clz(a-1); 87 | #else 88 | uint32_t r = 0; 89 | uint32_t s; 90 | a -= 1; 91 | s = (a > 0xffff) << 4; a >>= s; r |= s; 92 | s = (a > 0xff ) << 3; a >>= s; r |= s; 93 | s = (a > 0xf ) << 2; a >>= s; r |= s; 94 | s = (a > 0x3 ) << 1; a >>= s; r |= s; 95 | return (r | (a >> 1)) + 1; 96 | #endif 97 | } 98 | 99 | // Count the number of trailing binary zeros in a 100 | // lfs_ctz(0) may be undefined 101 | static inline uint32_t lfs_ctz(uint32_t a) { 102 | #if !defined(LFS_NO_INTRINSICS) && defined(__GNUC__) 103 | return __builtin_ctz(a); 104 | #else 105 | return lfs_npw2((a & -a) + 1) - 1; 106 | #endif 107 | } 108 | 109 | // Count the number of binary ones in a 110 | static inline uint32_t lfs_popc(uint32_t a) { 111 | #if !defined(LFS_NO_INTRINSICS) && (defined(__GNUC__) || defined(__CC_ARM)) 112 | return __builtin_popcount(a); 113 | #else 114 | a = a - ((a >> 1) & 0x55555555); 115 | a = (a & 0x33333333) + ((a >> 2) & 0x33333333); 116 | return (((a + (a >> 4)) & 0xf0f0f0f) * 0x1010101) >> 24; 117 | #endif 118 | } 119 | 120 | // Find the sequence comparison of a and b, this is the distance 121 | // between a and b ignoring overflow 122 | static inline int lfs_scmp(uint32_t a, uint32_t b) { 123 | return (int)(unsigned)(a - b); 124 | } 125 | 126 | // Convert between 32-bit little-endian and native order 127 | static inline uint32_t lfs_fromle32(uint32_t a) { 128 | #if !defined(LFS_NO_INTRINSICS) && ( \ 129 | (defined( BYTE_ORDER ) && defined( ORDER_LITTLE_ENDIAN ) && BYTE_ORDER == ORDER_LITTLE_ENDIAN ) || \ 130 | (defined(__BYTE_ORDER ) && defined(__ORDER_LITTLE_ENDIAN ) && __BYTE_ORDER == __ORDER_LITTLE_ENDIAN ) || \ 131 | (defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)) 132 | return a; 133 | #elif !defined(LFS_NO_INTRINSICS) && ( \ 134 | (defined( BYTE_ORDER ) && defined( ORDER_BIG_ENDIAN ) && BYTE_ORDER == ORDER_BIG_ENDIAN ) || \ 135 | (defined(__BYTE_ORDER ) && defined(__ORDER_BIG_ENDIAN ) && __BYTE_ORDER == __ORDER_BIG_ENDIAN ) || \ 136 | (defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)) 137 | return __builtin_bswap32(a); 138 | #else 139 | return (((uint8_t*)&a)[0] << 0) | 140 | (((uint8_t*)&a)[1] << 8) | 141 | (((uint8_t*)&a)[2] << 16) | 142 | (((uint8_t*)&a)[3] << 24); 143 | #endif 144 | } 145 | 146 | static inline uint32_t lfs_tole32(uint32_t a) { 147 | return lfs_fromle32(a); 148 | } 149 | 150 | // Convert between 32-bit big-endian and native order 151 | static inline uint32_t lfs_frombe32(uint32_t a) { 152 | #if !defined(LFS_NO_INTRINSICS) && ( \ 153 | (defined( BYTE_ORDER ) && defined( ORDER_LITTLE_ENDIAN ) && BYTE_ORDER == ORDER_LITTLE_ENDIAN ) || \ 154 | (defined(__BYTE_ORDER ) && defined(__ORDER_LITTLE_ENDIAN ) && __BYTE_ORDER == __ORDER_LITTLE_ENDIAN ) || \ 155 | (defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)) 156 | return __builtin_bswap32(a); 157 | #elif !defined(LFS_NO_INTRINSICS) && ( \ 158 | (defined( BYTE_ORDER ) && defined( ORDER_BIG_ENDIAN ) && BYTE_ORDER == ORDER_BIG_ENDIAN ) || \ 159 | (defined(__BYTE_ORDER ) && defined(__ORDER_BIG_ENDIAN ) && __BYTE_ORDER == __ORDER_BIG_ENDIAN ) || \ 160 | (defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)) 161 | return a; 162 | #else 163 | return (((uint8_t*)&a)[0] << 24) | 164 | (((uint8_t*)&a)[1] << 16) | 165 | (((uint8_t*)&a)[2] << 8) | 166 | (((uint8_t*)&a)[3] << 0); 167 | #endif 168 | } 169 | 170 | static inline uint32_t lfs_tobe32(uint32_t a) { 171 | return lfs_frombe32(a); 172 | } 173 | 174 | // Calculate CRC-32 with polynomial = 0x04c11db7 175 | uint32_t lfs_crc(uint32_t crc, const void *buffer, size_t size); 176 | 177 | // Allocate memory, only used if buffers are not provided to littlefs 178 | // Note, memory must be 64-bit aligned 179 | static inline void *lfs_malloc(size_t size) { 180 | #ifndef LFS_NO_MALLOC 181 | return rt_malloc(size); 182 | #else 183 | (void)size; 184 | return NULL; 185 | #endif 186 | } 187 | 188 | // Deallocate memory, only used if buffers are not provided to littlefs 189 | static inline void lfs_free(void *p) { 190 | #ifndef LFS_NO_MALLOC 191 | rt_free(p); 192 | #else 193 | (void)p; 194 | #endif 195 | } 196 | 197 | 198 | #ifdef __cplusplus 199 | } /* extern "C" */ 200 | #endif 201 | 202 | #endif 203 | -------------------------------------------------------------------------------- /lfs_crc.c: -------------------------------------------------------------------------------- 1 | #include "lfs_util.h" 2 | 3 | // Software CRC implementation with small lookup table 4 | uint32_t lfs_crc(uint32_t crc, const void *buffer, size_t size) { 5 | static const uint32_t rtable[16] = { 6 | 0x00000000, 0x1db71064, 0x3b6e20c8, 0x26d930ac, 7 | 0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c, 8 | 0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c, 9 | 0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c, 10 | }; 11 | 12 | const uint8_t *data = buffer; 13 | 14 | for (size_t i = 0; i < size; i++) { 15 | crc = (crc >> 4) ^ rtable[(crc ^ (data[i] >> 0)) & 0xf]; 16 | crc = (crc >> 4) ^ rtable[(crc ^ (data[i] >> 4)) & 0xf]; 17 | } 18 | 19 | return crc; 20 | } 21 | -------------------------------------------------------------------------------- /lfs_util.c: -------------------------------------------------------------------------------- 1 | /* 2 | * lfs util functions 3 | * 4 | * Copyright (c) 2022, The littlefs authors. 5 | * Copyright (c) 2017, Arm Limited. All rights reserved. 6 | * SPDX-License-Identifier: BSD-3-Clause 7 | */ 8 | #include "lfs_util.h" 9 | 10 | // Only compile if user does not provide custom config 11 | #ifndef LFS_CONFIG 12 | 13 | 14 | // If user provides their own CRC impl we don't need this 15 | #ifndef LFS_CRC 16 | // Software CRC implementation with small lookup table 17 | uint32_t lfs_crc(uint32_t crc, const void *buffer, size_t size) { 18 | static const uint32_t rtable[16] = { 19 | 0x00000000, 0x1db71064, 0x3b6e20c8, 0x26d930ac, 20 | 0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c, 21 | 0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c, 22 | 0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c, 23 | }; 24 | 25 | const uint8_t *data = buffer; 26 | 27 | for (size_t i = 0; i < size; i++) { 28 | crc = (crc >> 4) ^ rtable[(crc ^ (data[i] >> 0)) & 0xf]; 29 | crc = (crc >> 4) ^ rtable[(crc ^ (data[i] >> 4)) & 0xf]; 30 | } 31 | 32 | return crc; 33 | } 34 | #endif 35 | 36 | 37 | #endif 38 | -------------------------------------------------------------------------------- /lfs_util.h: -------------------------------------------------------------------------------- 1 | /* 2 | * lfs utility functions 3 | * 4 | * Copyright (c) 2022, The littlefs authors. 5 | * Copyright (c) 2017, Arm Limited. All rights reserved. 6 | * SPDX-License-Identifier: BSD-3-Clause 7 | */ 8 | #ifndef LFS_UTIL_H 9 | #define LFS_UTIL_H 10 | 11 | // Users can override lfs_util.h with their own configuration by defining 12 | // LFS_CONFIG as a header file to include (-DLFS_CONFIG=lfs_config.h). 13 | // 14 | // If LFS_CONFIG is used, none of the default utils will be emitted and must be 15 | // provided by the config file. To start, I would suggest copying lfs_util.h 16 | // and modifying as needed. 17 | #ifdef LFS_CONFIG 18 | #define LFS_STRINGIZE(x) LFS_STRINGIZE2(x) 19 | #define LFS_STRINGIZE2(x) #x 20 | #include LFS_STRINGIZE(LFS_CONFIG) 21 | #else 22 | 23 | // System includes 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | #ifndef LFS_NO_MALLOC 30 | #include 31 | #endif 32 | #ifndef LFS_NO_ASSERT 33 | #include 34 | #endif 35 | #if !defined(LFS_NO_DEBUG) || \ 36 | !defined(LFS_NO_WARN) || \ 37 | !defined(LFS_NO_ERROR) || \ 38 | defined(LFS_YES_TRACE) 39 | #include 40 | #endif 41 | 42 | #ifdef __cplusplus 43 | extern "C" 44 | { 45 | #endif 46 | 47 | 48 | // Macros, may be replaced by system specific wrappers. Arguments to these 49 | // macros must not have side-effects as the macros can be removed for a smaller 50 | // code footprint 51 | 52 | // Logging functions 53 | #ifndef LFS_TRACE 54 | #ifdef LFS_YES_TRACE 55 | #define LFS_TRACE_(fmt, ...) \ 56 | printf("%s:%d:trace: " fmt "%s\n", __FILE__, __LINE__, __VA_ARGS__) 57 | #define LFS_TRACE(...) LFS_TRACE_(__VA_ARGS__, "") 58 | #else 59 | #define LFS_TRACE(...) 60 | #endif 61 | #endif 62 | 63 | #ifndef LFS_DEBUG 64 | #ifndef LFS_NO_DEBUG 65 | #define LFS_DEBUG_(fmt, ...) \ 66 | printf("%s:%d:debug: " fmt "%s\n", __FILE__, __LINE__, __VA_ARGS__) 67 | #define LFS_DEBUG(...) LFS_DEBUG_(__VA_ARGS__, "") 68 | #else 69 | #define LFS_DEBUG(...) 70 | #endif 71 | #endif 72 | 73 | #ifndef LFS_WARN 74 | #ifndef LFS_NO_WARN 75 | #define LFS_WARN_(fmt, ...) \ 76 | printf("%s:%d:warn: " fmt "%s\n", __FILE__, __LINE__, __VA_ARGS__) 77 | #define LFS_WARN(...) LFS_WARN_(__VA_ARGS__, "") 78 | #else 79 | #define LFS_WARN(...) 80 | #endif 81 | #endif 82 | 83 | #ifndef LFS_ERROR 84 | #ifndef LFS_NO_ERROR 85 | #define LFS_ERROR_(fmt, ...) \ 86 | printf("%s:%d:error: " fmt "%s\n", __FILE__, __LINE__, __VA_ARGS__) 87 | #define LFS_ERROR(...) LFS_ERROR_(__VA_ARGS__, "") 88 | #else 89 | #define LFS_ERROR(...) 90 | #endif 91 | #endif 92 | 93 | // Runtime assertions 94 | #ifndef LFS_ASSERT 95 | #ifndef LFS_NO_ASSERT 96 | #define LFS_ASSERT(test) assert(test) 97 | #else 98 | #define LFS_ASSERT(test) 99 | #endif 100 | #endif 101 | 102 | 103 | // Builtin functions, these may be replaced by more efficient 104 | // toolchain-specific implementations. LFS_NO_INTRINSICS falls back to a more 105 | // expensive basic C implementation for debugging purposes 106 | 107 | // Min/max functions for unsigned 32-bit numbers 108 | static inline uint32_t lfs_max(uint32_t a, uint32_t b) { 109 | return (a > b) ? a : b; 110 | } 111 | 112 | static inline uint32_t lfs_min(uint32_t a, uint32_t b) { 113 | return (a < b) ? a : b; 114 | } 115 | 116 | // Align to nearest multiple of a size 117 | static inline uint32_t lfs_aligndown(uint32_t a, uint32_t alignment) { 118 | return a - (a % alignment); 119 | } 120 | 121 | static inline uint32_t lfs_alignup(uint32_t a, uint32_t alignment) { 122 | return lfs_aligndown(a + alignment-1, alignment); 123 | } 124 | 125 | // Find the smallest power of 2 greater than or equal to a 126 | static inline uint32_t lfs_npw2(uint32_t a) { 127 | #if !defined(LFS_NO_INTRINSICS) && (defined(__GNUC__) || defined(__CC_ARM)) 128 | return 32 - __builtin_clz(a-1); 129 | #else 130 | uint32_t r = 0; 131 | uint32_t s; 132 | a -= 1; 133 | s = (a > 0xffff) << 4; a >>= s; r |= s; 134 | s = (a > 0xff ) << 3; a >>= s; r |= s; 135 | s = (a > 0xf ) << 2; a >>= s; r |= s; 136 | s = (a > 0x3 ) << 1; a >>= s; r |= s; 137 | return (r | (a >> 1)) + 1; 138 | #endif 139 | } 140 | 141 | // Count the number of trailing binary zeros in a 142 | // lfs_ctz(0) may be undefined 143 | static inline uint32_t lfs_ctz(uint32_t a) { 144 | #if !defined(LFS_NO_INTRINSICS) && defined(__GNUC__) 145 | return __builtin_ctz(a); 146 | #else 147 | return lfs_npw2((a & -a) + 1) - 1; 148 | #endif 149 | } 150 | 151 | // Count the number of binary ones in a 152 | static inline uint32_t lfs_popc(uint32_t a) { 153 | #if !defined(LFS_NO_INTRINSICS) && (defined(__GNUC__) || defined(__CC_ARM)) 154 | return __builtin_popcount(a); 155 | #else 156 | a = a - ((a >> 1) & 0x55555555); 157 | a = (a & 0x33333333) + ((a >> 2) & 0x33333333); 158 | return (((a + (a >> 4)) & 0xf0f0f0f) * 0x1010101) >> 24; 159 | #endif 160 | } 161 | 162 | // Find the sequence comparison of a and b, this is the distance 163 | // between a and b ignoring overflow 164 | static inline int lfs_scmp(uint32_t a, uint32_t b) { 165 | return (int)(unsigned)(a - b); 166 | } 167 | 168 | // Convert between 32-bit little-endian and native order 169 | static inline uint32_t lfs_fromle32(uint32_t a) { 170 | #if (defined( BYTE_ORDER ) && defined( ORDER_LITTLE_ENDIAN ) && BYTE_ORDER == ORDER_LITTLE_ENDIAN ) || \ 171 | (defined(__BYTE_ORDER ) && defined(__ORDER_LITTLE_ENDIAN ) && __BYTE_ORDER == __ORDER_LITTLE_ENDIAN ) || \ 172 | (defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) 173 | return a; 174 | #elif !defined(LFS_NO_INTRINSICS) && ( \ 175 | (defined( BYTE_ORDER ) && defined( ORDER_BIG_ENDIAN ) && BYTE_ORDER == ORDER_BIG_ENDIAN ) || \ 176 | (defined(__BYTE_ORDER ) && defined(__ORDER_BIG_ENDIAN ) && __BYTE_ORDER == __ORDER_BIG_ENDIAN ) || \ 177 | (defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)) 178 | return __builtin_bswap32(a); 179 | #else 180 | return (((uint8_t*)&a)[0] << 0) | 181 | (((uint8_t*)&a)[1] << 8) | 182 | (((uint8_t*)&a)[2] << 16) | 183 | (((uint8_t*)&a)[3] << 24); 184 | #endif 185 | } 186 | 187 | static inline uint32_t lfs_tole32(uint32_t a) { 188 | return lfs_fromle32(a); 189 | } 190 | 191 | // Convert between 32-bit big-endian and native order 192 | static inline uint32_t lfs_frombe32(uint32_t a) { 193 | #if !defined(LFS_NO_INTRINSICS) && ( \ 194 | (defined( BYTE_ORDER ) && defined( ORDER_LITTLE_ENDIAN ) && BYTE_ORDER == ORDER_LITTLE_ENDIAN ) || \ 195 | (defined(__BYTE_ORDER ) && defined(__ORDER_LITTLE_ENDIAN ) && __BYTE_ORDER == __ORDER_LITTLE_ENDIAN ) || \ 196 | (defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)) 197 | return __builtin_bswap32(a); 198 | #elif (defined( BYTE_ORDER ) && defined( ORDER_BIG_ENDIAN ) && BYTE_ORDER == ORDER_BIG_ENDIAN ) || \ 199 | (defined(__BYTE_ORDER ) && defined(__ORDER_BIG_ENDIAN ) && __BYTE_ORDER == __ORDER_BIG_ENDIAN ) || \ 200 | (defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) 201 | return a; 202 | #else 203 | return (((uint8_t*)&a)[0] << 24) | 204 | (((uint8_t*)&a)[1] << 16) | 205 | (((uint8_t*)&a)[2] << 8) | 206 | (((uint8_t*)&a)[3] << 0); 207 | #endif 208 | } 209 | 210 | static inline uint32_t lfs_tobe32(uint32_t a) { 211 | return lfs_frombe32(a); 212 | } 213 | 214 | // Calculate CRC-32 with polynomial = 0x04c11db7 215 | #ifdef LFS_CRC 216 | uint32_t lfs_crc(uint32_t crc, const void *buffer, size_t size) { 217 | return LFS_CRC(crc, buffer, size) 218 | } 219 | #else 220 | uint32_t lfs_crc(uint32_t crc, const void *buffer, size_t size); 221 | #endif 222 | 223 | // Allocate memory, only used if buffers are not provided to littlefs 224 | // 225 | // littlefs current has no alignment requirements, as it only allocates 226 | // byte-level buffers. 227 | static inline void *lfs_malloc(size_t size) { 228 | #if defined(LFS_MALLOC) 229 | return LFS_MALLOC(size); 230 | #elif !defined(LFS_NO_MALLOC) 231 | return malloc(size); 232 | #else 233 | (void)size; 234 | return NULL; 235 | #endif 236 | } 237 | 238 | // Deallocate memory, only used if buffers are not provided to littlefs 239 | static inline void lfs_free(void *p) { 240 | #if defined(LFS_FREE) 241 | LFS_FREE(p); 242 | #elif !defined(LFS_NO_MALLOC) 243 | free(p); 244 | #else 245 | (void)p; 246 | #endif 247 | } 248 | 249 | 250 | #ifdef __cplusplus 251 | } /* extern "C" */ 252 | #endif 253 | 254 | #endif 255 | #endif 256 | -------------------------------------------------------------------------------- /runners/bench_runner.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Runner for littlefs benchmarks 3 | * 4 | * Copyright (c) 2022, The littlefs authors. 5 | * SPDX-License-Identifier: BSD-3-Clause 6 | */ 7 | #ifndef BENCH_RUNNER_H 8 | #define BENCH_RUNNER_H 9 | 10 | 11 | // override LFS_TRACE 12 | void bench_trace(const char *fmt, ...); 13 | 14 | #define LFS_TRACE_(fmt, ...) \ 15 | bench_trace("%s:%d:trace: " fmt "%s\n", \ 16 | __FILE__, \ 17 | __LINE__, \ 18 | __VA_ARGS__) 19 | #define LFS_TRACE(...) LFS_TRACE_(__VA_ARGS__, "") 20 | #define LFS_EMUBD_TRACE(...) LFS_TRACE_(__VA_ARGS__, "") 21 | 22 | // provide BENCH_START/BENCH_STOP macros 23 | void bench_start(void); 24 | void bench_stop(void); 25 | 26 | #define BENCH_START() bench_start() 27 | #define BENCH_STOP() bench_stop() 28 | 29 | 30 | // note these are indirectly included in any generated files 31 | #include "bd/lfs_emubd.h" 32 | #include 33 | 34 | // give source a chance to define feature macros 35 | #undef _FEATURES_H 36 | #undef _STDIO_H 37 | 38 | 39 | // generated bench configurations 40 | struct lfs_config; 41 | 42 | enum bench_flags { 43 | BENCH_REENTRANT = 0x1, 44 | }; 45 | typedef uint8_t bench_flags_t; 46 | 47 | typedef struct bench_define { 48 | intmax_t (*cb)(void *data); 49 | void *data; 50 | } bench_define_t; 51 | 52 | struct bench_case { 53 | const char *name; 54 | const char *path; 55 | bench_flags_t flags; 56 | size_t permutations; 57 | 58 | const bench_define_t *defines; 59 | 60 | bool (*filter)(void); 61 | void (*run)(struct lfs_config *cfg); 62 | }; 63 | 64 | struct bench_suite { 65 | const char *name; 66 | const char *path; 67 | bench_flags_t flags; 68 | 69 | const char *const *define_names; 70 | size_t define_count; 71 | 72 | const struct bench_case *cases; 73 | size_t case_count; 74 | }; 75 | 76 | 77 | // deterministic prng for pseudo-randomness in benches 78 | uint32_t bench_prng(uint32_t *state); 79 | 80 | #define BENCH_PRNG(state) bench_prng(state) 81 | 82 | 83 | // access generated bench defines 84 | intmax_t bench_define(size_t define); 85 | 86 | #define BENCH_DEFINE(i) bench_define(i) 87 | 88 | // a few preconfigured defines that control how benches run 89 | 90 | #define READ_SIZE_i 0 91 | #define PROG_SIZE_i 1 92 | #define ERASE_SIZE_i 2 93 | #define ERASE_COUNT_i 3 94 | #define BLOCK_SIZE_i 4 95 | #define BLOCK_COUNT_i 5 96 | #define CACHE_SIZE_i 6 97 | #define LOOKAHEAD_SIZE_i 7 98 | #define COMPACT_THRESH_i 8 99 | #define INLINE_MAX_i 9 100 | #define BLOCK_CYCLES_i 10 101 | #define ERASE_VALUE_i 11 102 | #define ERASE_CYCLES_i 12 103 | #define BADBLOCK_BEHAVIOR_i 13 104 | #define POWERLOSS_BEHAVIOR_i 14 105 | 106 | #define READ_SIZE bench_define(READ_SIZE_i) 107 | #define PROG_SIZE bench_define(PROG_SIZE_i) 108 | #define ERASE_SIZE bench_define(ERASE_SIZE_i) 109 | #define ERASE_COUNT bench_define(ERASE_COUNT_i) 110 | #define BLOCK_SIZE bench_define(BLOCK_SIZE_i) 111 | #define BLOCK_COUNT bench_define(BLOCK_COUNT_i) 112 | #define CACHE_SIZE bench_define(CACHE_SIZE_i) 113 | #define LOOKAHEAD_SIZE bench_define(LOOKAHEAD_SIZE_i) 114 | #define COMPACT_THRESH bench_define(COMPACT_THRESH_i) 115 | #define INLINE_MAX bench_define(INLINE_MAX_i) 116 | #define BLOCK_CYCLES bench_define(BLOCK_CYCLES_i) 117 | #define ERASE_VALUE bench_define(ERASE_VALUE_i) 118 | #define ERASE_CYCLES bench_define(ERASE_CYCLES_i) 119 | #define BADBLOCK_BEHAVIOR bench_define(BADBLOCK_BEHAVIOR_i) 120 | #define POWERLOSS_BEHAVIOR bench_define(POWERLOSS_BEHAVIOR_i) 121 | 122 | #define BENCH_IMPLICIT_DEFINES \ 123 | BENCH_DEF(READ_SIZE, PROG_SIZE) \ 124 | BENCH_DEF(PROG_SIZE, ERASE_SIZE) \ 125 | BENCH_DEF(ERASE_SIZE, 0) \ 126 | BENCH_DEF(ERASE_COUNT, (1024*1024)/BLOCK_SIZE) \ 127 | BENCH_DEF(BLOCK_SIZE, ERASE_SIZE) \ 128 | BENCH_DEF(BLOCK_COUNT, ERASE_COUNT/lfs_max(BLOCK_SIZE/ERASE_SIZE,1))\ 129 | BENCH_DEF(CACHE_SIZE, lfs_max(64,lfs_max(READ_SIZE,PROG_SIZE))) \ 130 | BENCH_DEF(LOOKAHEAD_SIZE, 16) \ 131 | BENCH_DEF(COMPACT_THRESH, 0) \ 132 | BENCH_DEF(INLINE_MAX, 0) \ 133 | BENCH_DEF(BLOCK_CYCLES, -1) \ 134 | BENCH_DEF(ERASE_VALUE, 0xff) \ 135 | BENCH_DEF(ERASE_CYCLES, 0) \ 136 | BENCH_DEF(BADBLOCK_BEHAVIOR, LFS_EMUBD_BADBLOCK_PROGERROR) \ 137 | BENCH_DEF(POWERLOSS_BEHAVIOR, LFS_EMUBD_POWERLOSS_NOOP) 138 | 139 | #define BENCH_GEOMETRY_DEFINE_COUNT 4 140 | #define BENCH_IMPLICIT_DEFINE_COUNT 15 141 | 142 | 143 | #endif 144 | -------------------------------------------------------------------------------- /runners/test_runner.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Runner for littlefs tests 3 | * 4 | * Copyright (c) 2022, The littlefs authors. 5 | * SPDX-License-Identifier: BSD-3-Clause 6 | */ 7 | #ifndef TEST_RUNNER_H 8 | #define TEST_RUNNER_H 9 | 10 | 11 | // override LFS_TRACE 12 | void test_trace(const char *fmt, ...); 13 | 14 | #define LFS_TRACE_(fmt, ...) \ 15 | test_trace("%s:%d:trace: " fmt "%s\n", \ 16 | __FILE__, \ 17 | __LINE__, \ 18 | __VA_ARGS__) 19 | #define LFS_TRACE(...) LFS_TRACE_(__VA_ARGS__, "") 20 | #define LFS_EMUBD_TRACE(...) LFS_TRACE_(__VA_ARGS__, "") 21 | 22 | 23 | // note these are indirectly included in any generated files 24 | #include "bd/lfs_emubd.h" 25 | #include 26 | 27 | // give source a chance to define feature macros 28 | #undef _FEATURES_H 29 | #undef _STDIO_H 30 | 31 | 32 | // generated test configurations 33 | struct lfs_config; 34 | 35 | enum test_flags { 36 | TEST_REENTRANT = 0x1, 37 | }; 38 | typedef uint8_t test_flags_t; 39 | 40 | typedef struct test_define { 41 | intmax_t (*cb)(void *data); 42 | void *data; 43 | } test_define_t; 44 | 45 | struct test_case { 46 | const char *name; 47 | const char *path; 48 | test_flags_t flags; 49 | size_t permutations; 50 | 51 | const test_define_t *defines; 52 | 53 | bool (*filter)(void); 54 | void (*run)(struct lfs_config *cfg); 55 | }; 56 | 57 | struct test_suite { 58 | const char *name; 59 | const char *path; 60 | test_flags_t flags; 61 | 62 | const char *const *define_names; 63 | size_t define_count; 64 | 65 | const struct test_case *cases; 66 | size_t case_count; 67 | }; 68 | 69 | 70 | // deterministic prng for pseudo-randomness in testes 71 | uint32_t test_prng(uint32_t *state); 72 | 73 | #define TEST_PRNG(state) test_prng(state) 74 | 75 | 76 | // access generated test defines 77 | intmax_t test_define(size_t define); 78 | 79 | #define TEST_DEFINE(i) test_define(i) 80 | 81 | // a few preconfigured defines that control how tests run 82 | 83 | #define READ_SIZE_i 0 84 | #define PROG_SIZE_i 1 85 | #define ERASE_SIZE_i 2 86 | #define ERASE_COUNT_i 3 87 | #define BLOCK_SIZE_i 4 88 | #define BLOCK_COUNT_i 5 89 | #define CACHE_SIZE_i 6 90 | #define LOOKAHEAD_SIZE_i 7 91 | #define COMPACT_THRESH_i 8 92 | #define INLINE_MAX_i 9 93 | #define BLOCK_CYCLES_i 10 94 | #define ERASE_VALUE_i 11 95 | #define ERASE_CYCLES_i 12 96 | #define BADBLOCK_BEHAVIOR_i 13 97 | #define POWERLOSS_BEHAVIOR_i 14 98 | #define DISK_VERSION_i 15 99 | 100 | #define READ_SIZE TEST_DEFINE(READ_SIZE_i) 101 | #define PROG_SIZE TEST_DEFINE(PROG_SIZE_i) 102 | #define ERASE_SIZE TEST_DEFINE(ERASE_SIZE_i) 103 | #define ERASE_COUNT TEST_DEFINE(ERASE_COUNT_i) 104 | #define BLOCK_SIZE TEST_DEFINE(BLOCK_SIZE_i) 105 | #define BLOCK_COUNT TEST_DEFINE(BLOCK_COUNT_i) 106 | #define CACHE_SIZE TEST_DEFINE(CACHE_SIZE_i) 107 | #define LOOKAHEAD_SIZE TEST_DEFINE(LOOKAHEAD_SIZE_i) 108 | #define COMPACT_THRESH TEST_DEFINE(COMPACT_THRESH_i) 109 | #define INLINE_MAX TEST_DEFINE(INLINE_MAX_i) 110 | #define BLOCK_CYCLES TEST_DEFINE(BLOCK_CYCLES_i) 111 | #define ERASE_VALUE TEST_DEFINE(ERASE_VALUE_i) 112 | #define ERASE_CYCLES TEST_DEFINE(ERASE_CYCLES_i) 113 | #define BADBLOCK_BEHAVIOR TEST_DEFINE(BADBLOCK_BEHAVIOR_i) 114 | #define POWERLOSS_BEHAVIOR TEST_DEFINE(POWERLOSS_BEHAVIOR_i) 115 | #define DISK_VERSION TEST_DEFINE(DISK_VERSION_i) 116 | 117 | #define TEST_IMPLICIT_DEFINES \ 118 | TEST_DEF(READ_SIZE, PROG_SIZE) \ 119 | TEST_DEF(PROG_SIZE, ERASE_SIZE) \ 120 | TEST_DEF(ERASE_SIZE, 0) \ 121 | TEST_DEF(ERASE_COUNT, (1024*1024)/ERASE_SIZE) \ 122 | TEST_DEF(BLOCK_SIZE, ERASE_SIZE) \ 123 | TEST_DEF(BLOCK_COUNT, ERASE_COUNT/lfs_max(BLOCK_SIZE/ERASE_SIZE,1)) \ 124 | TEST_DEF(CACHE_SIZE, lfs_max(64,lfs_max(READ_SIZE,PROG_SIZE))) \ 125 | TEST_DEF(LOOKAHEAD_SIZE, 16) \ 126 | TEST_DEF(COMPACT_THRESH, 0) \ 127 | TEST_DEF(INLINE_MAX, 0) \ 128 | TEST_DEF(BLOCK_CYCLES, -1) \ 129 | TEST_DEF(ERASE_VALUE, 0xff) \ 130 | TEST_DEF(ERASE_CYCLES, 0) \ 131 | TEST_DEF(BADBLOCK_BEHAVIOR, LFS_EMUBD_BADBLOCK_PROGERROR) \ 132 | TEST_DEF(POWERLOSS_BEHAVIOR, LFS_EMUBD_POWERLOSS_NOOP) \ 133 | TEST_DEF(DISK_VERSION, 0) 134 | 135 | #define TEST_GEOMETRY_DEFINE_COUNT 4 136 | #define TEST_IMPLICIT_DEFINE_COUNT 16 137 | 138 | 139 | #endif 140 | -------------------------------------------------------------------------------- /scripts/changeprefix.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # Change prefixes in files/filenames. Useful for creating different versions 4 | # of a codebase that don't conflict at compile time. 5 | # 6 | # Example: 7 | # $ ./scripts/changeprefix.py lfs lfs3 8 | # 9 | # Copyright (c) 2022, The littlefs authors. 10 | # Copyright (c) 2019, Arm Limited. All rights reserved. 11 | # SPDX-License-Identifier: BSD-3-Clause 12 | # 13 | 14 | import glob 15 | import itertools 16 | import os 17 | import os.path 18 | import re 19 | import shlex 20 | import shutil 21 | import subprocess 22 | import tempfile 23 | 24 | GIT_PATH = ['git'] 25 | 26 | 27 | def openio(path, mode='r', buffering=-1): 28 | # allow '-' for stdin/stdout 29 | if path == '-': 30 | if mode == 'r': 31 | return os.fdopen(os.dup(sys.stdin.fileno()), mode, buffering) 32 | else: 33 | return os.fdopen(os.dup(sys.stdout.fileno()), mode, buffering) 34 | else: 35 | return open(path, mode, buffering) 36 | 37 | def changeprefix(from_prefix, to_prefix, line): 38 | line, count1 = re.subn( 39 | '\\b'+from_prefix, 40 | to_prefix, 41 | line) 42 | line, count2 = re.subn( 43 | '\\b'+from_prefix.upper(), 44 | to_prefix.upper(), 45 | line) 46 | line, count3 = re.subn( 47 | '\\B-D'+from_prefix.upper(), 48 | '-D'+to_prefix.upper(), 49 | line) 50 | return line, count1+count2+count3 51 | 52 | def changefile(from_prefix, to_prefix, from_path, to_path, *, 53 | no_replacements=False): 54 | # rename any prefixes in file 55 | count = 0 56 | 57 | # create a temporary file to avoid overwriting ourself 58 | if from_path == to_path and to_path != '-': 59 | to_path_temp = tempfile.NamedTemporaryFile('w', delete=False) 60 | to_path = to_path_temp.name 61 | else: 62 | to_path_temp = None 63 | 64 | with openio(from_path) as from_f: 65 | with openio(to_path, 'w') as to_f: 66 | for line in from_f: 67 | if not no_replacements: 68 | line, n = changeprefix(from_prefix, to_prefix, line) 69 | count += n 70 | to_f.write(line) 71 | 72 | if from_path != '-' and to_path != '-': 73 | shutil.copystat(from_path, to_path) 74 | 75 | if to_path_temp: 76 | os.rename(to_path, from_path) 77 | elif from_path != '-': 78 | os.remove(from_path) 79 | 80 | # Summary 81 | print('%s: %d replacements' % ( 82 | '%s -> %s' % (from_path, to_path) if not to_path_temp else from_path, 83 | count)) 84 | 85 | def main(from_prefix, to_prefix, paths=[], *, 86 | verbose=False, 87 | output=None, 88 | no_replacements=False, 89 | no_renames=False, 90 | git=False, 91 | no_stage=False, 92 | git_path=GIT_PATH): 93 | if not paths: 94 | if git: 95 | cmd = git_path + ['ls-tree', '-r', '--name-only', 'HEAD'] 96 | if verbose: 97 | print(' '.join(shlex.quote(c) for c in cmd)) 98 | paths = subprocess.check_output(cmd, encoding='utf8').split() 99 | else: 100 | print('no paths?', file=sys.stderr) 101 | sys.exit(1) 102 | 103 | for from_path in paths: 104 | # rename filename? 105 | if output: 106 | to_path = output 107 | elif no_renames: 108 | to_path = from_path 109 | else: 110 | to_path = os.path.join( 111 | os.path.dirname(from_path), 112 | changeprefix(from_prefix, to_prefix, 113 | os.path.basename(from_path))[0]) 114 | 115 | # rename contents 116 | changefile(from_prefix, to_prefix, from_path, to_path, 117 | no_replacements=no_replacements) 118 | 119 | # stage? 120 | if git and not no_stage: 121 | if from_path != to_path: 122 | cmd = git_path + ['rm', '-q', from_path] 123 | if verbose: 124 | print(' '.join(shlex.quote(c) for c in cmd)) 125 | subprocess.check_call(cmd) 126 | cmd = git_path + ['add', to_path] 127 | if verbose: 128 | print(' '.join(shlex.quote(c) for c in cmd)) 129 | subprocess.check_call(cmd) 130 | 131 | 132 | if __name__ == "__main__": 133 | import argparse 134 | import sys 135 | parser = argparse.ArgumentParser( 136 | description="Change prefixes in files/filenames. Useful for creating " 137 | "different versions of a codebase that don't conflict at compile " 138 | "time.", 139 | allow_abbrev=False) 140 | parser.add_argument( 141 | 'from_prefix', 142 | help="Prefix to replace.") 143 | parser.add_argument( 144 | 'to_prefix', 145 | help="Prefix to replace with.") 146 | parser.add_argument( 147 | 'paths', 148 | nargs='*', 149 | help="Files to operate on.") 150 | parser.add_argument( 151 | '-v', '--verbose', 152 | action='store_true', 153 | help="Output commands that run behind the scenes.") 154 | parser.add_argument( 155 | '-o', '--output', 156 | help="Output file.") 157 | parser.add_argument( 158 | '-N', '--no-replacements', 159 | action='store_true', 160 | help="Don't change prefixes in files") 161 | parser.add_argument( 162 | '-R', '--no-renames', 163 | action='store_true', 164 | help="Don't rename files") 165 | parser.add_argument( 166 | '--git', 167 | action='store_true', 168 | help="Use git to find/update files.") 169 | parser.add_argument( 170 | '--no-stage', 171 | action='store_true', 172 | help="Don't stage changes with git.") 173 | parser.add_argument( 174 | '--git-path', 175 | type=lambda x: x.split(), 176 | default=GIT_PATH, 177 | help="Path to git executable, may include flags. " 178 | "Defaults to %r." % GIT_PATH) 179 | sys.exit(main(**{k: v 180 | for k, v in vars(parser.parse_intermixed_args()).items() 181 | if v is not None})) 182 | -------------------------------------------------------------------------------- /scripts/readblock.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import subprocess as sp 4 | 5 | def main(args): 6 | with open(args.disk, 'rb') as f: 7 | f.seek(args.block * args.block_size) 8 | block = (f.read(args.block_size) 9 | .ljust(args.block_size, b'\xff')) 10 | 11 | # what did you expect? 12 | print("%-8s %-s" % ('off', 'data')) 13 | return sp.run(['xxd', '-g1', '-'], input=block).returncode 14 | 15 | if __name__ == "__main__": 16 | import argparse 17 | import sys 18 | parser = argparse.ArgumentParser( 19 | description="Hex dump a specific block in a disk.") 20 | parser.add_argument('disk', 21 | help="File representing the block device.") 22 | parser.add_argument('block_size', type=lambda x: int(x, 0), 23 | help="Size of a block in bytes.") 24 | parser.add_argument('block', type=lambda x: int(x, 0), 25 | help="Address of block to dump.") 26 | sys.exit(main(parser.parse_args())) 27 | -------------------------------------------------------------------------------- /scripts/readtree.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import struct 4 | import sys 5 | import json 6 | import io 7 | import itertools as it 8 | from readmdir import Tag, MetadataPair 9 | 10 | def main(args): 11 | superblock = None 12 | gstate = b'\0\0\0\0\0\0\0\0\0\0\0\0' 13 | dirs = [] 14 | mdirs = [] 15 | corrupted = [] 16 | cycle = False 17 | with open(args.disk, 'rb') as f: 18 | tail = (args.block1, args.block2) 19 | hard = False 20 | while True: 21 | for m in it.chain((m for d in dirs for m in d), mdirs): 22 | if set(m.blocks) == set(tail): 23 | # cycle detected 24 | cycle = m.blocks 25 | if cycle: 26 | break 27 | 28 | # load mdir 29 | data = [] 30 | blocks = {} 31 | for block in tail: 32 | f.seek(block * args.block_size) 33 | data.append(f.read(args.block_size) 34 | .ljust(args.block_size, b'\xff')) 35 | blocks[id(data[-1])] = block 36 | 37 | mdir = MetadataPair(data) 38 | mdir.blocks = tuple(blocks[id(p.data)] for p in mdir.pair) 39 | 40 | # fetch some key metadata as a we scan 41 | try: 42 | mdir.tail = mdir[Tag('tail', 0, 0)] 43 | if mdir.tail.size != 8 or mdir.tail.data == 8*b'\xff': 44 | mdir.tail = None 45 | except KeyError: 46 | mdir.tail = None 47 | 48 | # have superblock? 49 | try: 50 | nsuperblock = mdir[ 51 | Tag(0x7ff, 0x3ff, 0), Tag('superblock', 0, 0)] 52 | superblock = nsuperblock, mdir[Tag('inlinestruct', 0, 0)] 53 | except KeyError: 54 | pass 55 | 56 | # have gstate? 57 | try: 58 | ngstate = mdir[Tag('movestate', 0, 0)] 59 | gstate = bytes((a or 0) ^ (b or 0) 60 | for a,b in it.zip_longest(gstate, ngstate.data)) 61 | except KeyError: 62 | pass 63 | 64 | # corrupted? 65 | if not mdir: 66 | corrupted.append(mdir) 67 | 68 | # add to directories 69 | mdirs.append(mdir) 70 | if mdir.tail is None or not mdir.tail.is_('hardtail'): 71 | dirs.append(mdirs) 72 | mdirs = [] 73 | 74 | if mdir.tail is None: 75 | break 76 | 77 | tail = struct.unpack('=%d" % max(tag.size, 1)) 117 | if tag.type: 118 | print(" move dir {%#x, %#x} id %d" % ( 119 | blocks[0], blocks[1], tag.id)) 120 | 121 | # print mdir info 122 | for i, dir in enumerate(dirs): 123 | print("dir %s" % (json.dumps(dir[0].path) 124 | if hasattr(dir[0], 'path') else '(orphan)')) 125 | 126 | for j, mdir in enumerate(dir): 127 | print("mdir {%#x, %#x} rev %d (was %d)%s%s" % ( 128 | mdir.blocks[0], mdir.blocks[1], mdir.rev, mdir.pair[1].rev, 129 | ' (corrupted!)' if not mdir else '', 130 | ' -> {%#x, %#x}' % struct.unpack(' 1 and self.tail.getvalue(): 47 | self.tail.write(lines[0]) 48 | lines[0] = self.tail.getvalue() 49 | self.tail = io.StringIO() 50 | 51 | self.lines.extend(lines[:-1]) 52 | 53 | if lines[-1]: 54 | self.tail.write(lines[-1]) 55 | 56 | def resize(self, maxlen): 57 | self.maxlen = maxlen 58 | if maxlen == 0: 59 | maxlen = shutil.get_terminal_size((80, 5))[1] 60 | if maxlen != self.lines.maxlen: 61 | self.lines = co.deque(self.lines, maxlen=maxlen) 62 | 63 | canvas_lines = 1 64 | def draw(self): 65 | # did terminal size change? 66 | if self.maxlen == 0: 67 | self.resize(0) 68 | 69 | # first thing first, give ourself a canvas 70 | while LinesIO.canvas_lines < len(self.lines): 71 | sys.stdout.write('\n') 72 | LinesIO.canvas_lines += 1 73 | 74 | # clear the bottom of the canvas if we shrink 75 | shrink = LinesIO.canvas_lines - len(self.lines) 76 | if shrink > 0: 77 | for i in range(shrink): 78 | sys.stdout.write('\r') 79 | if shrink-1-i > 0: 80 | sys.stdout.write('\x1b[%dA' % (shrink-1-i)) 81 | sys.stdout.write('\x1b[K') 82 | if shrink-1-i > 0: 83 | sys.stdout.write('\x1b[%dB' % (shrink-1-i)) 84 | sys.stdout.write('\x1b[%dA' % shrink) 85 | LinesIO.canvas_lines = len(self.lines) 86 | 87 | for i, line in enumerate(self.lines): 88 | # move cursor, clear line, disable/reenable line wrapping 89 | sys.stdout.write('\r') 90 | if len(self.lines)-1-i > 0: 91 | sys.stdout.write('\x1b[%dA' % (len(self.lines)-1-i)) 92 | sys.stdout.write('\x1b[K') 93 | sys.stdout.write('\x1b[?7l') 94 | sys.stdout.write(line) 95 | sys.stdout.write('\x1b[?7h') 96 | if len(self.lines)-1-i > 0: 97 | sys.stdout.write('\x1b[%dB' % (len(self.lines)-1-i)) 98 | sys.stdout.flush() 99 | 100 | 101 | def main(path='-', *, lines=5, cat=False, sleep=None, keep_open=False): 102 | if cat: 103 | ring = sys.stdout 104 | else: 105 | ring = LinesIO(lines) 106 | 107 | # if sleep print in background thread to avoid getting stuck in a read call 108 | event = th.Event() 109 | lock = th.Lock() 110 | if not cat: 111 | done = False 112 | def background(): 113 | while not done: 114 | event.wait() 115 | event.clear() 116 | with lock: 117 | ring.draw() 118 | time.sleep(sleep or 0.01) 119 | th.Thread(target=background, daemon=True).start() 120 | 121 | try: 122 | while True: 123 | with openio(path) as f: 124 | for line in f: 125 | with lock: 126 | ring.write(line) 127 | event.set() 128 | 129 | if not keep_open: 130 | break 131 | # don't just flood open calls 132 | time.sleep(sleep or 0.1) 133 | except FileNotFoundError as e: 134 | print("error: file not found %r" % path) 135 | sys.exit(-1) 136 | except KeyboardInterrupt: 137 | pass 138 | 139 | if not cat: 140 | done = True 141 | lock.acquire() # avoids https://bugs.python.org/issue42717 142 | sys.stdout.write('\n') 143 | 144 | 145 | if __name__ == "__main__": 146 | import sys 147 | import argparse 148 | parser = argparse.ArgumentParser( 149 | description="Efficiently displays the last n lines of a file/pipe.", 150 | allow_abbrev=False) 151 | parser.add_argument( 152 | 'path', 153 | nargs='?', 154 | help="Path to read from.") 155 | parser.add_argument( 156 | '-n', '--lines', 157 | nargs='?', 158 | type=lambda x: int(x, 0), 159 | const=0, 160 | help="Show this many lines of history. 0 uses the terminal height. " 161 | "Defaults to 5.") 162 | parser.add_argument( 163 | '-z', '--cat', 164 | action='store_true', 165 | help="Pipe directly to stdout.") 166 | parser.add_argument( 167 | '-s', '--sleep', 168 | type=float, 169 | help="Seconds to sleep between reads. Defaults to 0.01.") 170 | parser.add_argument( 171 | '-k', '--keep-open', 172 | action='store_true', 173 | help="Reopen the pipe on EOF, useful when multiple " 174 | "processes are writing.") 175 | sys.exit(main(**{k: v 176 | for k, v in vars(parser.parse_intermixed_args()).items() 177 | if v is not None})) 178 | -------------------------------------------------------------------------------- /scripts/teepipe.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # tee, but for pipes 4 | # 5 | # Example: 6 | # ./scripts/tee.py in_pipe out_pipe1 out_pipe2 7 | # 8 | # Copyright (c) 2022, The littlefs authors. 9 | # SPDX-License-Identifier: BSD-3-Clause 10 | # 11 | 12 | import os 13 | import io 14 | import time 15 | import sys 16 | 17 | 18 | def openio(path, mode='r', buffering=-1): 19 | # allow '-' for stdin/stdout 20 | if path == '-': 21 | if mode == 'r': 22 | return os.fdopen(os.dup(sys.stdin.fileno()), mode, buffering) 23 | else: 24 | return os.fdopen(os.dup(sys.stdout.fileno()), mode, buffering) 25 | else: 26 | return open(path, mode, buffering) 27 | 28 | def main(in_path, out_paths, *, keep_open=False): 29 | out_pipes = [openio(p, 'wb', 0) for p in out_paths] 30 | try: 31 | with openio(in_path, 'rb', 0) as f: 32 | while True: 33 | buf = f.read(io.DEFAULT_BUFFER_SIZE) 34 | if not buf: 35 | if not keep_open: 36 | break 37 | # don't just flood reads 38 | time.sleep(0.1) 39 | continue 40 | 41 | for p in out_pipes: 42 | try: 43 | p.write(buf) 44 | except BrokenPipeError: 45 | pass 46 | except FileNotFoundError as e: 47 | print("error: file not found %r" % in_path) 48 | sys.exit(-1) 49 | except KeyboardInterrupt: 50 | pass 51 | 52 | 53 | if __name__ == "__main__": 54 | import sys 55 | import argparse 56 | parser = argparse.ArgumentParser( 57 | description="tee, but for pipes.", 58 | allow_abbrev=False) 59 | parser.add_argument( 60 | 'in_path', 61 | help="Path to read from.") 62 | parser.add_argument( 63 | 'out_paths', 64 | nargs='+', 65 | help="Path to write to.") 66 | parser.add_argument( 67 | '-k', '--keep-open', 68 | action='store_true', 69 | help="Reopen the pipe on EOF, useful when multiple " 70 | "processes are writing.") 71 | sys.exit(main(**{k: v 72 | for k, v in vars(parser.parse_intermixed_args()).items() 73 | if v is not None})) 74 | -------------------------------------------------------------------------------- /scripts/watch.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # Traditional watch command, but with higher resolution updates and a bit 4 | # different options/output format 5 | # 6 | # Example: 7 | # ./scripts/watch.py -s0.1 date 8 | # 9 | # Copyright (c) 2022, The littlefs authors. 10 | # SPDX-License-Identifier: BSD-3-Clause 11 | # 12 | 13 | import collections as co 14 | import errno 15 | import fcntl 16 | import io 17 | import os 18 | import pty 19 | import re 20 | import shutil 21 | import struct 22 | import subprocess as sp 23 | import sys 24 | import termios 25 | import time 26 | 27 | try: 28 | import inotify_simple 29 | except ModuleNotFoundError: 30 | inotify_simple = None 31 | 32 | 33 | def openio(path, mode='r', buffering=-1): 34 | # allow '-' for stdin/stdout 35 | if path == '-': 36 | if mode == 'r': 37 | return os.fdopen(os.dup(sys.stdin.fileno()), mode, buffering) 38 | else: 39 | return os.fdopen(os.dup(sys.stdout.fileno()), mode, buffering) 40 | else: 41 | return open(path, mode, buffering) 42 | 43 | def inotifywait(paths): 44 | # wait for interesting events 45 | inotify = inotify_simple.INotify() 46 | flags = (inotify_simple.flags.ATTRIB 47 | | inotify_simple.flags.CREATE 48 | | inotify_simple.flags.DELETE 49 | | inotify_simple.flags.DELETE_SELF 50 | | inotify_simple.flags.MODIFY 51 | | inotify_simple.flags.MOVED_FROM 52 | | inotify_simple.flags.MOVED_TO 53 | | inotify_simple.flags.MOVE_SELF) 54 | 55 | # recurse into directories 56 | for path in paths: 57 | if os.path.isdir(path): 58 | for dir, _, files in os.walk(path): 59 | inotify.add_watch(dir, flags) 60 | for f in files: 61 | inotify.add_watch(os.path.join(dir, f), flags) 62 | else: 63 | inotify.add_watch(path, flags) 64 | 65 | # wait for event 66 | inotify.read() 67 | 68 | class LinesIO: 69 | def __init__(self, maxlen=None): 70 | self.maxlen = maxlen 71 | self.lines = co.deque(maxlen=maxlen) 72 | self.tail = io.StringIO() 73 | 74 | # trigger automatic sizing 75 | if maxlen == 0: 76 | self.resize(0) 77 | 78 | def write(self, s): 79 | # note using split here ensures the trailing string has no newline 80 | lines = s.split('\n') 81 | 82 | if len(lines) > 1 and self.tail.getvalue(): 83 | self.tail.write(lines[0]) 84 | lines[0] = self.tail.getvalue() 85 | self.tail = io.StringIO() 86 | 87 | self.lines.extend(lines[:-1]) 88 | 89 | if lines[-1]: 90 | self.tail.write(lines[-1]) 91 | 92 | def resize(self, maxlen): 93 | self.maxlen = maxlen 94 | if maxlen == 0: 95 | maxlen = shutil.get_terminal_size((80, 5))[1] 96 | if maxlen != self.lines.maxlen: 97 | self.lines = co.deque(self.lines, maxlen=maxlen) 98 | 99 | canvas_lines = 1 100 | def draw(self): 101 | # did terminal size change? 102 | if self.maxlen == 0: 103 | self.resize(0) 104 | 105 | # first thing first, give ourself a canvas 106 | while LinesIO.canvas_lines < len(self.lines): 107 | sys.stdout.write('\n') 108 | LinesIO.canvas_lines += 1 109 | 110 | # clear the bottom of the canvas if we shrink 111 | shrink = LinesIO.canvas_lines - len(self.lines) 112 | if shrink > 0: 113 | for i in range(shrink): 114 | sys.stdout.write('\r') 115 | if shrink-1-i > 0: 116 | sys.stdout.write('\x1b[%dA' % (shrink-1-i)) 117 | sys.stdout.write('\x1b[K') 118 | if shrink-1-i > 0: 119 | sys.stdout.write('\x1b[%dB' % (shrink-1-i)) 120 | sys.stdout.write('\x1b[%dA' % shrink) 121 | LinesIO.canvas_lines = len(self.lines) 122 | 123 | for i, line in enumerate(self.lines): 124 | # move cursor, clear line, disable/reenable line wrapping 125 | sys.stdout.write('\r') 126 | if len(self.lines)-1-i > 0: 127 | sys.stdout.write('\x1b[%dA' % (len(self.lines)-1-i)) 128 | sys.stdout.write('\x1b[K') 129 | sys.stdout.write('\x1b[?7l') 130 | sys.stdout.write(line) 131 | sys.stdout.write('\x1b[?7h') 132 | if len(self.lines)-1-i > 0: 133 | sys.stdout.write('\x1b[%dB' % (len(self.lines)-1-i)) 134 | sys.stdout.flush() 135 | 136 | 137 | def main(command, *, 138 | lines=0, 139 | cat=False, 140 | sleep=None, 141 | keep_open=False, 142 | keep_open_paths=None, 143 | exit_on_error=False): 144 | returncode = 0 145 | try: 146 | while True: 147 | # reset ring each run 148 | if cat: 149 | ring = sys.stdout 150 | else: 151 | ring = LinesIO(lines) 152 | 153 | try: 154 | # run the command under a pseudoterminal 155 | mpty, spty = pty.openpty() 156 | 157 | # forward terminal size 158 | w, h = shutil.get_terminal_size((80, 5)) 159 | if lines: 160 | h = lines 161 | fcntl.ioctl(spty, termios.TIOCSWINSZ, 162 | struct.pack('HHHH', h, w, 0, 0)) 163 | 164 | proc = sp.Popen(command, 165 | stdout=spty, 166 | stderr=spty, 167 | close_fds=False) 168 | os.close(spty) 169 | mpty = os.fdopen(mpty, 'r', 1) 170 | 171 | while True: 172 | try: 173 | line = mpty.readline() 174 | except OSError as e: 175 | if e.errno != errno.EIO: 176 | raise 177 | break 178 | if not line: 179 | break 180 | 181 | ring.write(line) 182 | if not cat: 183 | ring.draw() 184 | 185 | mpty.close() 186 | proc.wait() 187 | if exit_on_error and proc.returncode != 0: 188 | returncode = proc.returncode 189 | break 190 | except OSError as e: 191 | if e.errno != errno.ETXTBSY: 192 | raise 193 | pass 194 | 195 | # try to inotifywait 196 | if keep_open and inotify_simple is not None: 197 | if keep_open_paths: 198 | paths = set(keep_paths) 199 | else: 200 | # guess inotify paths from command 201 | paths = set() 202 | for p in command: 203 | for p in { 204 | p, 205 | re.sub('^-.', '', p), 206 | re.sub('^--[^=]+=', '', p)}: 207 | if p and os.path.exists(p): 208 | paths.add(p) 209 | ptime = time.time() 210 | inotifywait(paths) 211 | # sleep for a minimum amount of time, this helps issues around 212 | # rapidly updating files 213 | time.sleep(max(0, (sleep or 0.1) - (time.time()-ptime))) 214 | else: 215 | time.sleep(sleep or 0.1) 216 | except KeyboardInterrupt: 217 | pass 218 | 219 | if not cat: 220 | sys.stdout.write('\n') 221 | sys.exit(returncode) 222 | 223 | 224 | if __name__ == "__main__": 225 | import sys 226 | import argparse 227 | parser = argparse.ArgumentParser( 228 | description="Traditional watch command, but with higher resolution " 229 | "updates and a bit different options/output format.", 230 | allow_abbrev=False) 231 | parser.add_argument( 232 | 'command', 233 | nargs=argparse.REMAINDER, 234 | help="Command to run.") 235 | parser.add_argument( 236 | '-n', '--lines', 237 | nargs='?', 238 | type=lambda x: int(x, 0), 239 | const=0, 240 | help="Show this many lines of history. 0 uses the terminal height. " 241 | "Defaults to 0.") 242 | parser.add_argument( 243 | '-z', '--cat', 244 | action='store_true', 245 | help="Pipe directly to stdout.") 246 | parser.add_argument( 247 | '-s', '--sleep', 248 | type=float, 249 | help="Seconds to sleep between runs. Defaults to 0.1.") 250 | parser.add_argument( 251 | '-k', '--keep-open', 252 | action='store_true', 253 | help="Try to use inotify to wait for changes.") 254 | parser.add_argument( 255 | '-K', '--keep-open-path', 256 | dest='keep_open_paths', 257 | action='append', 258 | help="Use this path for inotify. Defaults to guessing.") 259 | parser.add_argument( 260 | '-e', '--exit-on-error', 261 | action='store_true', 262 | help="Exit on error.") 263 | sys.exit(main(**{k: v 264 | for k, v in vars(parser.parse_args()).items() 265 | if v is not None})) 266 | -------------------------------------------------------------------------------- /tests/test_attrs.toml: -------------------------------------------------------------------------------- 1 | [cases.test_attrs_get_set] 2 | code = ''' 3 | lfs_t lfs; 4 | lfs_format(&lfs, cfg) => 0; 5 | lfs_mount(&lfs, cfg) => 0; 6 | lfs_mkdir(&lfs, "hello") => 0; 7 | lfs_file_t file; 8 | lfs_file_open(&lfs, &file, "hello/hello", LFS_O_WRONLY | LFS_O_CREAT) => 0; 9 | lfs_file_write(&lfs, &file, "hello", strlen("hello")) => strlen("hello"); 10 | lfs_file_close(&lfs, &file); 11 | lfs_unmount(&lfs) => 0; 12 | 13 | lfs_mount(&lfs, cfg) => 0; 14 | uint8_t buffer[1024]; 15 | memset(buffer, 0, sizeof(buffer)); 16 | lfs_setattr(&lfs, "hello", 'A', "aaaa", 4) => 0; 17 | lfs_setattr(&lfs, "hello", 'B', "bbbbbb", 6) => 0; 18 | lfs_setattr(&lfs, "hello", 'C', "ccccc", 5) => 0; 19 | lfs_getattr(&lfs, "hello", 'A', buffer, 4) => 4; 20 | lfs_getattr(&lfs, "hello", 'B', buffer+4, 6) => 6; 21 | lfs_getattr(&lfs, "hello", 'C', buffer+10, 5) => 5; 22 | memcmp(buffer, "aaaa", 4) => 0; 23 | memcmp(buffer+4, "bbbbbb", 6) => 0; 24 | memcmp(buffer+10, "ccccc", 5) => 0; 25 | 26 | lfs_setattr(&lfs, "hello", 'B', "", 0) => 0; 27 | lfs_getattr(&lfs, "hello", 'A', buffer, 4) => 4; 28 | lfs_getattr(&lfs, "hello", 'B', buffer+4, 6) => 0; 29 | lfs_getattr(&lfs, "hello", 'C', buffer+10, 5) => 5; 30 | memcmp(buffer, "aaaa", 4) => 0; 31 | memcmp(buffer+4, "\0\0\0\0\0\0", 6) => 0; 32 | memcmp(buffer+10, "ccccc", 5) => 0; 33 | 34 | lfs_removeattr(&lfs, "hello", 'B') => 0; 35 | lfs_getattr(&lfs, "hello", 'A', buffer, 4) => 4; 36 | lfs_getattr(&lfs, "hello", 'B', buffer+4, 6) => LFS_ERR_NOATTR; 37 | lfs_getattr(&lfs, "hello", 'C', buffer+10, 5) => 5; 38 | memcmp(buffer, "aaaa", 4) => 0; 39 | memcmp(buffer+4, "\0\0\0\0\0\0", 6) => 0; 40 | memcmp(buffer+10, "ccccc", 5) => 0; 41 | 42 | lfs_setattr(&lfs, "hello", 'B', "dddddd", 6) => 0; 43 | lfs_getattr(&lfs, "hello", 'A', buffer, 4) => 4; 44 | lfs_getattr(&lfs, "hello", 'B', buffer+4, 6) => 6; 45 | lfs_getattr(&lfs, "hello", 'C', buffer+10, 5) => 5; 46 | memcmp(buffer, "aaaa", 4) => 0; 47 | memcmp(buffer+4, "dddddd", 6) => 0; 48 | memcmp(buffer+10, "ccccc", 5) => 0; 49 | 50 | lfs_setattr(&lfs, "hello", 'B', "eee", 3) => 0; 51 | lfs_getattr(&lfs, "hello", 'A', buffer, 4) => 4; 52 | lfs_getattr(&lfs, "hello", 'B', buffer+4, 6) => 3; 53 | lfs_getattr(&lfs, "hello", 'C', buffer+10, 5) => 5; 54 | memcmp(buffer, "aaaa", 4) => 0; 55 | memcmp(buffer+4, "eee\0\0\0", 6) => 0; 56 | memcmp(buffer+10, "ccccc", 5) => 0; 57 | 58 | lfs_setattr(&lfs, "hello", 'A', buffer, LFS_ATTR_MAX+1) => LFS_ERR_NOSPC; 59 | lfs_setattr(&lfs, "hello", 'B', "fffffffff", 9) => 0; 60 | lfs_getattr(&lfs, "hello", 'A', buffer, 4) => 4; 61 | lfs_getattr(&lfs, "hello", 'B', buffer+4, 6) => 9; 62 | lfs_getattr(&lfs, "hello", 'C', buffer+10, 5) => 5; 63 | 64 | lfs_unmount(&lfs) => 0; 65 | 66 | lfs_mount(&lfs, cfg) => 0; 67 | memset(buffer, 0, sizeof(buffer)); 68 | lfs_getattr(&lfs, "hello", 'A', buffer, 4) => 4; 69 | lfs_getattr(&lfs, "hello", 'B', buffer+4, 9) => 9; 70 | lfs_getattr(&lfs, "hello", 'C', buffer+13, 5) => 5; 71 | memcmp(buffer, "aaaa", 4) => 0; 72 | memcmp(buffer+4, "fffffffff", 9) => 0; 73 | memcmp(buffer+13, "ccccc", 5) => 0; 74 | 75 | lfs_file_open(&lfs, &file, "hello/hello", LFS_O_RDONLY) => 0; 76 | lfs_file_read(&lfs, &file, buffer, sizeof(buffer)) => strlen("hello"); 77 | memcmp(buffer, "hello", strlen("hello")) => 0; 78 | lfs_file_close(&lfs, &file); 79 | lfs_unmount(&lfs) => 0; 80 | ''' 81 | 82 | [cases.test_attrs_get_set_root] 83 | code = ''' 84 | lfs_t lfs; 85 | lfs_format(&lfs, cfg) => 0; 86 | lfs_mount(&lfs, cfg) => 0; 87 | lfs_mkdir(&lfs, "hello") => 0; 88 | lfs_file_t file; 89 | lfs_file_open(&lfs, &file, "hello/hello", LFS_O_WRONLY | LFS_O_CREAT) => 0; 90 | lfs_file_write(&lfs, &file, "hello", strlen("hello")) => strlen("hello"); 91 | lfs_file_close(&lfs, &file); 92 | lfs_unmount(&lfs) => 0; 93 | 94 | lfs_mount(&lfs, cfg) => 0; 95 | uint8_t buffer[1024]; 96 | memset(buffer, 0, sizeof(buffer)); 97 | lfs_setattr(&lfs, "/", 'A', "aaaa", 4) => 0; 98 | lfs_setattr(&lfs, "/", 'B', "bbbbbb", 6) => 0; 99 | lfs_setattr(&lfs, "/", 'C', "ccccc", 5) => 0; 100 | lfs_getattr(&lfs, "/", 'A', buffer, 4) => 4; 101 | lfs_getattr(&lfs, "/", 'B', buffer+4, 6) => 6; 102 | lfs_getattr(&lfs, "/", 'C', buffer+10, 5) => 5; 103 | memcmp(buffer, "aaaa", 4) => 0; 104 | memcmp(buffer+4, "bbbbbb", 6) => 0; 105 | memcmp(buffer+10, "ccccc", 5) => 0; 106 | 107 | lfs_setattr(&lfs, "/", 'B', "", 0) => 0; 108 | lfs_getattr(&lfs, "/", 'A', buffer, 4) => 4; 109 | lfs_getattr(&lfs, "/", 'B', buffer+4, 6) => 0; 110 | lfs_getattr(&lfs, "/", 'C', buffer+10, 5) => 5; 111 | memcmp(buffer, "aaaa", 4) => 0; 112 | memcmp(buffer+4, "\0\0\0\0\0\0", 6) => 0; 113 | memcmp(buffer+10, "ccccc", 5) => 0; 114 | 115 | lfs_removeattr(&lfs, "/", 'B') => 0; 116 | lfs_getattr(&lfs, "/", 'A', buffer, 4) => 4; 117 | lfs_getattr(&lfs, "/", 'B', buffer+4, 6) => LFS_ERR_NOATTR; 118 | lfs_getattr(&lfs, "/", 'C', buffer+10, 5) => 5; 119 | memcmp(buffer, "aaaa", 4) => 0; 120 | memcmp(buffer+4, "\0\0\0\0\0\0", 6) => 0; 121 | memcmp(buffer+10, "ccccc", 5) => 0; 122 | 123 | lfs_setattr(&lfs, "/", 'B', "dddddd", 6) => 0; 124 | lfs_getattr(&lfs, "/", 'A', buffer, 4) => 4; 125 | lfs_getattr(&lfs, "/", 'B', buffer+4, 6) => 6; 126 | lfs_getattr(&lfs, "/", 'C', buffer+10, 5) => 5; 127 | memcmp(buffer, "aaaa", 4) => 0; 128 | memcmp(buffer+4, "dddddd", 6) => 0; 129 | memcmp(buffer+10, "ccccc", 5) => 0; 130 | 131 | lfs_setattr(&lfs, "/", 'B', "eee", 3) => 0; 132 | lfs_getattr(&lfs, "/", 'A', buffer, 4) => 4; 133 | lfs_getattr(&lfs, "/", 'B', buffer+4, 6) => 3; 134 | lfs_getattr(&lfs, "/", 'C', buffer+10, 5) => 5; 135 | memcmp(buffer, "aaaa", 4) => 0; 136 | memcmp(buffer+4, "eee\0\0\0", 6) => 0; 137 | memcmp(buffer+10, "ccccc", 5) => 0; 138 | 139 | lfs_setattr(&lfs, "/", 'A', buffer, LFS_ATTR_MAX+1) => LFS_ERR_NOSPC; 140 | lfs_setattr(&lfs, "/", 'B', "fffffffff", 9) => 0; 141 | lfs_getattr(&lfs, "/", 'A', buffer, 4) => 4; 142 | lfs_getattr(&lfs, "/", 'B', buffer+4, 6) => 9; 143 | lfs_getattr(&lfs, "/", 'C', buffer+10, 5) => 5; 144 | lfs_unmount(&lfs) => 0; 145 | 146 | lfs_mount(&lfs, cfg) => 0; 147 | memset(buffer, 0, sizeof(buffer)); 148 | lfs_getattr(&lfs, "/", 'A', buffer, 4) => 4; 149 | lfs_getattr(&lfs, "/", 'B', buffer+4, 9) => 9; 150 | lfs_getattr(&lfs, "/", 'C', buffer+13, 5) => 5; 151 | memcmp(buffer, "aaaa", 4) => 0; 152 | memcmp(buffer+4, "fffffffff", 9) => 0; 153 | memcmp(buffer+13, "ccccc", 5) => 0; 154 | 155 | lfs_file_open(&lfs, &file, "hello/hello", LFS_O_RDONLY) => 0; 156 | lfs_file_read(&lfs, &file, buffer, sizeof(buffer)) => strlen("hello"); 157 | memcmp(buffer, "hello", strlen("hello")) => 0; 158 | lfs_file_close(&lfs, &file); 159 | lfs_unmount(&lfs) => 0; 160 | ''' 161 | 162 | [cases.test_attrs_get_set_file] 163 | code = ''' 164 | lfs_t lfs; 165 | lfs_format(&lfs, cfg) => 0; 166 | lfs_mount(&lfs, cfg) => 0; 167 | lfs_mkdir(&lfs, "hello") => 0; 168 | lfs_file_t file; 169 | lfs_file_open(&lfs, &file, "hello/hello", LFS_O_WRONLY | LFS_O_CREAT) => 0; 170 | lfs_file_write(&lfs, &file, "hello", strlen("hello")) => strlen("hello"); 171 | lfs_file_close(&lfs, &file); 172 | lfs_unmount(&lfs) => 0; 173 | 174 | lfs_mount(&lfs, cfg) => 0; 175 | uint8_t buffer[1024]; 176 | memset(buffer, 0, sizeof(buffer)); 177 | struct lfs_attr attrs1[] = { 178 | {'A', buffer, 4}, 179 | {'B', buffer+4, 6}, 180 | {'C', buffer+10, 5}, 181 | }; 182 | struct lfs_file_config cfg1 = {.attrs=attrs1, .attr_count=3}; 183 | 184 | lfs_file_opencfg(&lfs, &file, "hello/hello", LFS_O_WRONLY, &cfg1) => 0; 185 | memcpy(buffer, "aaaa", 4); 186 | memcpy(buffer+4, "bbbbbb", 6); 187 | memcpy(buffer+10, "ccccc", 5); 188 | lfs_file_close(&lfs, &file) => 0; 189 | memset(buffer, 0, 15); 190 | lfs_file_opencfg(&lfs, &file, "hello/hello", LFS_O_RDONLY, &cfg1) => 0; 191 | lfs_file_close(&lfs, &file) => 0; 192 | memcmp(buffer, "aaaa", 4) => 0; 193 | memcmp(buffer+4, "bbbbbb", 6) => 0; 194 | memcmp(buffer+10, "ccccc", 5) => 0; 195 | 196 | attrs1[1].size = 0; 197 | lfs_file_opencfg(&lfs, &file, "hello/hello", LFS_O_WRONLY, &cfg1) => 0; 198 | lfs_file_close(&lfs, &file) => 0; 199 | memset(buffer, 0, 15); 200 | attrs1[1].size = 6; 201 | lfs_file_opencfg(&lfs, &file, "hello/hello", LFS_O_RDONLY, &cfg1) => 0; 202 | lfs_file_close(&lfs, &file) => 0; 203 | memcmp(buffer, "aaaa", 4) => 0; 204 | memcmp(buffer+4, "\0\0\0\0\0\0", 6) => 0; 205 | memcmp(buffer+10, "ccccc", 5) => 0; 206 | 207 | attrs1[1].size = 6; 208 | lfs_file_opencfg(&lfs, &file, "hello/hello", LFS_O_WRONLY, &cfg1) => 0; 209 | memcpy(buffer+4, "dddddd", 6); 210 | lfs_file_close(&lfs, &file) => 0; 211 | memset(buffer, 0, 15); 212 | attrs1[1].size = 6; 213 | lfs_file_opencfg(&lfs, &file, "hello/hello", LFS_O_RDONLY, &cfg1) => 0; 214 | lfs_file_close(&lfs, &file) => 0; 215 | memcmp(buffer, "aaaa", 4) => 0; 216 | memcmp(buffer+4, "dddddd", 6) => 0; 217 | memcmp(buffer+10, "ccccc", 5) => 0; 218 | 219 | attrs1[1].size = 3; 220 | lfs_file_opencfg(&lfs, &file, "hello/hello", LFS_O_WRONLY, &cfg1) => 0; 221 | memcpy(buffer+4, "eee", 3); 222 | lfs_file_close(&lfs, &file) => 0; 223 | memset(buffer, 0, 15); 224 | attrs1[1].size = 6; 225 | lfs_file_opencfg(&lfs, &file, "hello/hello", LFS_O_RDONLY, &cfg1) => 0; 226 | lfs_file_close(&lfs, &file) => 0; 227 | memcmp(buffer, "aaaa", 4) => 0; 228 | memcmp(buffer+4, "eee\0\0\0", 6) => 0; 229 | memcmp(buffer+10, "ccccc", 5) => 0; 230 | 231 | attrs1[0].size = LFS_ATTR_MAX+1; 232 | lfs_file_opencfg(&lfs, &file, "hello/hello", LFS_O_WRONLY, &cfg1) 233 | => LFS_ERR_NOSPC; 234 | 235 | struct lfs_attr attrs2[] = { 236 | {'A', buffer, 4}, 237 | {'B', buffer+4, 9}, 238 | {'C', buffer+13, 5}, 239 | }; 240 | struct lfs_file_config cfg2 = {.attrs=attrs2, .attr_count=3}; 241 | lfs_file_opencfg(&lfs, &file, "hello/hello", LFS_O_RDWR, &cfg2) => 0; 242 | memcpy(buffer+4, "fffffffff", 9); 243 | lfs_file_close(&lfs, &file) => 0; 244 | attrs1[0].size = 4; 245 | lfs_file_opencfg(&lfs, &file, "hello/hello", LFS_O_RDONLY, &cfg1) => 0; 246 | lfs_file_close(&lfs, &file) => 0; 247 | 248 | lfs_unmount(&lfs) => 0; 249 | 250 | lfs_mount(&lfs, cfg) => 0; 251 | memset(buffer, 0, sizeof(buffer)); 252 | struct lfs_attr attrs3[] = { 253 | {'A', buffer, 4}, 254 | {'B', buffer+4, 9}, 255 | {'C', buffer+13, 5}, 256 | }; 257 | struct lfs_file_config cfg3 = {.attrs=attrs3, .attr_count=3}; 258 | 259 | lfs_file_opencfg(&lfs, &file, "hello/hello", LFS_O_RDONLY, &cfg3) => 0; 260 | lfs_file_close(&lfs, &file) => 0; 261 | memcmp(buffer, "aaaa", 4) => 0; 262 | memcmp(buffer+4, "fffffffff", 9) => 0; 263 | memcmp(buffer+13, "ccccc", 5) => 0; 264 | 265 | lfs_file_open(&lfs, &file, "hello/hello", LFS_O_RDONLY) => 0; 266 | lfs_file_read(&lfs, &file, buffer, sizeof(buffer)) => strlen("hello"); 267 | memcmp(buffer, "hello", strlen("hello")) => 0; 268 | lfs_file_close(&lfs, &file); 269 | lfs_unmount(&lfs) => 0; 270 | ''' 271 | 272 | [cases.test_attrs_deferred_file] 273 | code = ''' 274 | lfs_t lfs; 275 | lfs_format(&lfs, cfg) => 0; 276 | lfs_mount(&lfs, cfg) => 0; 277 | lfs_mkdir(&lfs, "hello") => 0; 278 | lfs_file_t file; 279 | lfs_file_open(&lfs, &file, "hello/hello", LFS_O_WRONLY | LFS_O_CREAT) => 0; 280 | lfs_file_write(&lfs, &file, "hello", strlen("hello")) => strlen("hello"); 281 | lfs_file_close(&lfs, &file); 282 | lfs_unmount(&lfs) => 0; 283 | 284 | lfs_mount(&lfs, cfg) => 0; 285 | lfs_setattr(&lfs, "hello/hello", 'B', "fffffffff", 9) => 0; 286 | lfs_setattr(&lfs, "hello/hello", 'C', "ccccc", 5) => 0; 287 | 288 | uint8_t buffer[1024]; 289 | memset(buffer, 0, sizeof(buffer)); 290 | struct lfs_attr attrs1[] = { 291 | {'B', "gggg", 4}, 292 | {'C', "", 0}, 293 | {'D', "hhhh", 4}, 294 | }; 295 | struct lfs_file_config cfg1 = {.attrs=attrs1, .attr_count=3}; 296 | 297 | lfs_file_opencfg(&lfs, &file, "hello/hello", LFS_O_WRONLY, &cfg1) => 0; 298 | 299 | lfs_getattr(&lfs, "hello/hello", 'B', buffer, 9) => 9; 300 | lfs_getattr(&lfs, "hello/hello", 'C', buffer+9, 9) => 5; 301 | lfs_getattr(&lfs, "hello/hello", 'D', buffer+18, 9) => LFS_ERR_NOATTR; 302 | memcmp(buffer, "fffffffff", 9) => 0; 303 | memcmp(buffer+9, "ccccc\0\0\0\0", 9) => 0; 304 | memcmp(buffer+18, "\0\0\0\0\0\0\0\0\0", 9) => 0; 305 | 306 | lfs_file_sync(&lfs, &file) => 0; 307 | lfs_getattr(&lfs, "hello/hello", 'B', buffer, 9) => 4; 308 | lfs_getattr(&lfs, "hello/hello", 'C', buffer+9, 9) => 0; 309 | lfs_getattr(&lfs, "hello/hello", 'D', buffer+18, 9) => 4; 310 | memcmp(buffer, "gggg\0\0\0\0\0", 9) => 0; 311 | memcmp(buffer+9, "\0\0\0\0\0\0\0\0\0", 9) => 0; 312 | memcmp(buffer+18, "hhhh\0\0\0\0\0", 9) => 0; 313 | 314 | lfs_file_close(&lfs, &file) => 0; 315 | lfs_unmount(&lfs) => 0; 316 | ''' 317 | -------------------------------------------------------------------------------- /tests/test_badblocks.toml: -------------------------------------------------------------------------------- 1 | # bad blocks with block cycles should be tested in test_relocations 2 | if = '(int32_t)BLOCK_CYCLES == -1' 3 | 4 | [cases.test_badblocks_single] 5 | defines.ERASE_COUNT = 256 # small bd so test runs faster 6 | defines.ERASE_CYCLES = 0xffffffff 7 | defines.ERASE_VALUE = [0x00, 0xff, -1] 8 | defines.BADBLOCK_BEHAVIOR = [ 9 | 'LFS_EMUBD_BADBLOCK_PROGERROR', 10 | 'LFS_EMUBD_BADBLOCK_ERASEERROR', 11 | 'LFS_EMUBD_BADBLOCK_READERROR', 12 | 'LFS_EMUBD_BADBLOCK_PROGNOOP', 13 | 'LFS_EMUBD_BADBLOCK_ERASENOOP', 14 | ] 15 | defines.NAMEMULT = 64 16 | defines.FILEMULT = 1 17 | code = ''' 18 | for (lfs_block_t badblock = 2; badblock < BLOCK_COUNT; badblock++) { 19 | lfs_emubd_setwear(cfg, badblock-1, 0) => 0; 20 | lfs_emubd_setwear(cfg, badblock, 0xffffffff) => 0; 21 | 22 | lfs_t lfs; 23 | lfs_format(&lfs, cfg) => 0; 24 | 25 | lfs_mount(&lfs, cfg) => 0; 26 | for (int i = 1; i < 10; i++) { 27 | uint8_t buffer[1024]; 28 | for (int j = 0; j < NAMEMULT; j++) { 29 | buffer[j] = '0'+i; 30 | } 31 | buffer[NAMEMULT] = '\0'; 32 | lfs_mkdir(&lfs, (char*)buffer) => 0; 33 | 34 | buffer[NAMEMULT] = '/'; 35 | for (int j = 0; j < NAMEMULT; j++) { 36 | buffer[j+NAMEMULT+1] = '0'+i; 37 | } 38 | buffer[2*NAMEMULT+1] = '\0'; 39 | lfs_file_t file; 40 | lfs_file_open(&lfs, &file, (char*)buffer, 41 | LFS_O_WRONLY | LFS_O_CREAT) => 0; 42 | 43 | lfs_size_t size = NAMEMULT; 44 | for (int j = 0; j < i*FILEMULT; j++) { 45 | lfs_file_write(&lfs, &file, buffer, size) => size; 46 | } 47 | 48 | lfs_file_close(&lfs, &file) => 0; 49 | } 50 | lfs_unmount(&lfs) => 0; 51 | 52 | lfs_mount(&lfs, cfg) => 0; 53 | for (int i = 1; i < 10; i++) { 54 | uint8_t buffer[1024]; 55 | for (int j = 0; j < NAMEMULT; j++) { 56 | buffer[j] = '0'+i; 57 | } 58 | buffer[NAMEMULT] = '\0'; 59 | struct lfs_info info; 60 | lfs_stat(&lfs, (char*)buffer, &info) => 0; 61 | info.type => LFS_TYPE_DIR; 62 | 63 | buffer[NAMEMULT] = '/'; 64 | for (int j = 0; j < NAMEMULT; j++) { 65 | buffer[j+NAMEMULT+1] = '0'+i; 66 | } 67 | buffer[2*NAMEMULT+1] = '\0'; 68 | lfs_file_t file; 69 | lfs_file_open(&lfs, &file, (char*)buffer, LFS_O_RDONLY) => 0; 70 | 71 | int size = NAMEMULT; 72 | for (int j = 0; j < i*FILEMULT; j++) { 73 | uint8_t rbuffer[1024]; 74 | lfs_file_read(&lfs, &file, rbuffer, size) => size; 75 | memcmp(buffer, rbuffer, size) => 0; 76 | } 77 | 78 | lfs_file_close(&lfs, &file) => 0; 79 | } 80 | lfs_unmount(&lfs) => 0; 81 | } 82 | ''' 83 | 84 | [cases.test_badblocks_region_corruption] # (causes cascading failures) 85 | defines.ERASE_COUNT = 256 # small bd so test runs faster 86 | defines.ERASE_CYCLES = 0xffffffff 87 | defines.ERASE_VALUE = [0x00, 0xff, -1] 88 | defines.BADBLOCK_BEHAVIOR = [ 89 | 'LFS_EMUBD_BADBLOCK_PROGERROR', 90 | 'LFS_EMUBD_BADBLOCK_ERASEERROR', 91 | 'LFS_EMUBD_BADBLOCK_READERROR', 92 | 'LFS_EMUBD_BADBLOCK_PROGNOOP', 93 | 'LFS_EMUBD_BADBLOCK_ERASENOOP', 94 | ] 95 | defines.NAMEMULT = 64 96 | defines.FILEMULT = 1 97 | code = ''' 98 | for (lfs_block_t i = 0; i < (BLOCK_COUNT-2)/2; i++) { 99 | lfs_emubd_setwear(cfg, i+2, 0xffffffff) => 0; 100 | } 101 | 102 | lfs_t lfs; 103 | lfs_format(&lfs, cfg) => 0; 104 | 105 | lfs_mount(&lfs, cfg) => 0; 106 | for (int i = 1; i < 10; i++) { 107 | uint8_t buffer[1024]; 108 | for (int j = 0; j < NAMEMULT; j++) { 109 | buffer[j] = '0'+i; 110 | } 111 | buffer[NAMEMULT] = '\0'; 112 | lfs_mkdir(&lfs, (char*)buffer) => 0; 113 | 114 | buffer[NAMEMULT] = '/'; 115 | for (int j = 0; j < NAMEMULT; j++) { 116 | buffer[j+NAMEMULT+1] = '0'+i; 117 | } 118 | buffer[2*NAMEMULT+1] = '\0'; 119 | lfs_file_t file; 120 | lfs_file_open(&lfs, &file, (char*)buffer, 121 | LFS_O_WRONLY | LFS_O_CREAT) => 0; 122 | 123 | lfs_size_t size = NAMEMULT; 124 | for (int j = 0; j < i*FILEMULT; j++) { 125 | lfs_file_write(&lfs, &file, buffer, size) => size; 126 | } 127 | 128 | lfs_file_close(&lfs, &file) => 0; 129 | } 130 | lfs_unmount(&lfs) => 0; 131 | 132 | lfs_mount(&lfs, cfg) => 0; 133 | for (int i = 1; i < 10; i++) { 134 | uint8_t buffer[1024]; 135 | for (int j = 0; j < NAMEMULT; j++) { 136 | buffer[j] = '0'+i; 137 | } 138 | buffer[NAMEMULT] = '\0'; 139 | struct lfs_info info; 140 | lfs_stat(&lfs, (char*)buffer, &info) => 0; 141 | info.type => LFS_TYPE_DIR; 142 | 143 | buffer[NAMEMULT] = '/'; 144 | for (int j = 0; j < NAMEMULT; j++) { 145 | buffer[j+NAMEMULT+1] = '0'+i; 146 | } 147 | buffer[2*NAMEMULT+1] = '\0'; 148 | lfs_file_t file; 149 | lfs_file_open(&lfs, &file, (char*)buffer, LFS_O_RDONLY) => 0; 150 | 151 | lfs_size_t size = NAMEMULT; 152 | for (int j = 0; j < i*FILEMULT; j++) { 153 | uint8_t rbuffer[1024]; 154 | lfs_file_read(&lfs, &file, rbuffer, size) => size; 155 | memcmp(buffer, rbuffer, size) => 0; 156 | } 157 | 158 | lfs_file_close(&lfs, &file) => 0; 159 | } 160 | lfs_unmount(&lfs) => 0; 161 | ''' 162 | 163 | [cases.test_badblocks_alternating_corruption] # (causes cascading failures) 164 | defines.ERASE_COUNT = 256 # small bd so test runs faster 165 | defines.ERASE_CYCLES = 0xffffffff 166 | defines.ERASE_VALUE = [0x00, 0xff, -1] 167 | defines.BADBLOCK_BEHAVIOR = [ 168 | 'LFS_EMUBD_BADBLOCK_PROGERROR', 169 | 'LFS_EMUBD_BADBLOCK_ERASEERROR', 170 | 'LFS_EMUBD_BADBLOCK_READERROR', 171 | 'LFS_EMUBD_BADBLOCK_PROGNOOP', 172 | 'LFS_EMUBD_BADBLOCK_ERASENOOP', 173 | ] 174 | defines.NAMEMULT = 64 175 | defines.FILEMULT = 1 176 | code = ''' 177 | for (lfs_block_t i = 0; i < (BLOCK_COUNT-2)/2; i++) { 178 | lfs_emubd_setwear(cfg, (2*i) + 2, 0xffffffff) => 0; 179 | } 180 | 181 | lfs_t lfs; 182 | lfs_format(&lfs, cfg) => 0; 183 | 184 | lfs_mount(&lfs, cfg) => 0; 185 | for (int i = 1; i < 10; i++) { 186 | uint8_t buffer[1024]; 187 | for (int j = 0; j < NAMEMULT; j++) { 188 | buffer[j] = '0'+i; 189 | } 190 | buffer[NAMEMULT] = '\0'; 191 | lfs_mkdir(&lfs, (char*)buffer) => 0; 192 | 193 | buffer[NAMEMULT] = '/'; 194 | for (int j = 0; j < NAMEMULT; j++) { 195 | buffer[j+NAMEMULT+1] = '0'+i; 196 | } 197 | buffer[2*NAMEMULT+1] = '\0'; 198 | lfs_file_t file; 199 | lfs_file_open(&lfs, &file, (char*)buffer, 200 | LFS_O_WRONLY | LFS_O_CREAT) => 0; 201 | 202 | lfs_size_t size = NAMEMULT; 203 | for (int j = 0; j < i*FILEMULT; j++) { 204 | lfs_file_write(&lfs, &file, buffer, size) => size; 205 | } 206 | 207 | lfs_file_close(&lfs, &file) => 0; 208 | } 209 | lfs_unmount(&lfs) => 0; 210 | 211 | lfs_mount(&lfs, cfg) => 0; 212 | for (int i = 1; i < 10; i++) { 213 | uint8_t buffer[1024]; 214 | for (int j = 0; j < NAMEMULT; j++) { 215 | buffer[j] = '0'+i; 216 | } 217 | buffer[NAMEMULT] = '\0'; 218 | struct lfs_info info; 219 | lfs_stat(&lfs, (char*)buffer, &info) => 0; 220 | info.type => LFS_TYPE_DIR; 221 | 222 | buffer[NAMEMULT] = '/'; 223 | for (int j = 0; j < NAMEMULT; j++) { 224 | buffer[j+NAMEMULT+1] = '0'+i; 225 | } 226 | buffer[2*NAMEMULT+1] = '\0'; 227 | lfs_file_t file; 228 | lfs_file_open(&lfs, &file, (char*)buffer, LFS_O_RDONLY) => 0; 229 | 230 | lfs_size_t size = NAMEMULT; 231 | for (int j = 0; j < i*FILEMULT; j++) { 232 | uint8_t rbuffer[1024]; 233 | lfs_file_read(&lfs, &file, rbuffer, size) => size; 234 | memcmp(buffer, rbuffer, size) => 0; 235 | } 236 | 237 | lfs_file_close(&lfs, &file) => 0; 238 | } 239 | lfs_unmount(&lfs) => 0; 240 | ''' 241 | 242 | # other corner cases 243 | [cases.test_badblocks_superblocks] # (corrupt 1 or 0) 244 | defines.ERASE_CYCLES = 0xffffffff 245 | defines.ERASE_VALUE = [0x00, 0xff, -1] 246 | defines.BADBLOCK_BEHAVIOR = [ 247 | 'LFS_EMUBD_BADBLOCK_PROGERROR', 248 | 'LFS_EMUBD_BADBLOCK_ERASEERROR', 249 | 'LFS_EMUBD_BADBLOCK_READERROR', 250 | 'LFS_EMUBD_BADBLOCK_PROGNOOP', 251 | 'LFS_EMUBD_BADBLOCK_ERASENOOP', 252 | ] 253 | code = ''' 254 | lfs_emubd_setwear(cfg, 0, 0xffffffff) => 0; 255 | lfs_emubd_setwear(cfg, 1, 0xffffffff) => 0; 256 | 257 | lfs_t lfs; 258 | lfs_format(&lfs, cfg) => LFS_ERR_NOSPC; 259 | lfs_mount(&lfs, cfg) => LFS_ERR_CORRUPT; 260 | ''' 261 | -------------------------------------------------------------------------------- /tests/test_bd.toml: -------------------------------------------------------------------------------- 1 | # These tests don't really test littlefs at all, they are here only to make 2 | # sure the underlying block device is working. 3 | # 4 | # Note we use 251, a prime, in places to avoid aliasing powers of 2. 5 | # 6 | 7 | [cases.test_bd_one_block] 8 | defines.READ = ['READ_SIZE', 'BLOCK_SIZE'] 9 | defines.PROG = ['PROG_SIZE', 'BLOCK_SIZE'] 10 | code = ''' 11 | uint8_t buffer[lfs_max(READ, PROG)]; 12 | 13 | // write data 14 | cfg->erase(cfg, 0) => 0; 15 | for (lfs_off_t i = 0; i < cfg->block_size; i += PROG) { 16 | for (lfs_off_t j = 0; j < PROG; j++) { 17 | buffer[j] = (i+j) % 251; 18 | } 19 | cfg->prog(cfg, 0, i, buffer, PROG) => 0; 20 | } 21 | 22 | // read data 23 | for (lfs_off_t i = 0; i < cfg->block_size; i += READ) { 24 | cfg->read(cfg, 0, i, buffer, READ) => 0; 25 | 26 | for (lfs_off_t j = 0; j < READ; j++) { 27 | LFS_ASSERT(buffer[j] == (i+j) % 251); 28 | } 29 | } 30 | ''' 31 | 32 | [cases.test_bd_two_block] 33 | defines.READ = ['READ_SIZE', 'BLOCK_SIZE'] 34 | defines.PROG = ['PROG_SIZE', 'BLOCK_SIZE'] 35 | code = ''' 36 | uint8_t buffer[lfs_max(READ, PROG)]; 37 | lfs_block_t block; 38 | 39 | // write block 0 40 | block = 0; 41 | cfg->erase(cfg, block) => 0; 42 | for (lfs_off_t i = 0; i < cfg->block_size; i += PROG) { 43 | for (lfs_off_t j = 0; j < PROG; j++) { 44 | buffer[j] = (block+i+j) % 251; 45 | } 46 | cfg->prog(cfg, block, i, buffer, PROG) => 0; 47 | } 48 | 49 | // read block 0 50 | block = 0; 51 | for (lfs_off_t i = 0; i < cfg->block_size; i += READ) { 52 | cfg->read(cfg, block, i, buffer, READ) => 0; 53 | 54 | for (lfs_off_t j = 0; j < READ; j++) { 55 | LFS_ASSERT(buffer[j] == (block+i+j) % 251); 56 | } 57 | } 58 | 59 | // write block 1 60 | block = 1; 61 | cfg->erase(cfg, block) => 0; 62 | for (lfs_off_t i = 0; i < cfg->block_size; i += PROG) { 63 | for (lfs_off_t j = 0; j < PROG; j++) { 64 | buffer[j] = (block+i+j) % 251; 65 | } 66 | cfg->prog(cfg, block, i, buffer, PROG) => 0; 67 | } 68 | 69 | // read block 1 70 | block = 1; 71 | for (lfs_off_t i = 0; i < cfg->block_size; i += READ) { 72 | cfg->read(cfg, block, i, buffer, READ) => 0; 73 | 74 | for (lfs_off_t j = 0; j < READ; j++) { 75 | LFS_ASSERT(buffer[j] == (block+i+j) % 251); 76 | } 77 | } 78 | 79 | // read block 0 again 80 | block = 0; 81 | for (lfs_off_t i = 0; i < cfg->block_size; i += READ) { 82 | cfg->read(cfg, block, i, buffer, READ) => 0; 83 | 84 | for (lfs_off_t j = 0; j < READ; j++) { 85 | LFS_ASSERT(buffer[j] == (block+i+j) % 251); 86 | } 87 | } 88 | ''' 89 | 90 | [cases.test_bd_last_block] 91 | defines.READ = ['READ_SIZE', 'BLOCK_SIZE'] 92 | defines.PROG = ['PROG_SIZE', 'BLOCK_SIZE'] 93 | code = ''' 94 | uint8_t buffer[lfs_max(READ, PROG)]; 95 | lfs_block_t block; 96 | 97 | // write block 0 98 | block = 0; 99 | cfg->erase(cfg, block) => 0; 100 | for (lfs_off_t i = 0; i < cfg->block_size; i += PROG) { 101 | for (lfs_off_t j = 0; j < PROG; j++) { 102 | buffer[j] = (block+i+j) % 251; 103 | } 104 | cfg->prog(cfg, block, i, buffer, PROG) => 0; 105 | } 106 | 107 | // read block 0 108 | block = 0; 109 | for (lfs_off_t i = 0; i < cfg->block_size; i += READ) { 110 | cfg->read(cfg, block, i, buffer, READ) => 0; 111 | 112 | for (lfs_off_t j = 0; j < READ; j++) { 113 | LFS_ASSERT(buffer[j] == (block+i+j) % 251); 114 | } 115 | } 116 | 117 | // write block n-1 118 | block = cfg->block_count-1; 119 | cfg->erase(cfg, block) => 0; 120 | for (lfs_off_t i = 0; i < cfg->block_size; i += PROG) { 121 | for (lfs_off_t j = 0; j < PROG; j++) { 122 | buffer[j] = (block+i+j) % 251; 123 | } 124 | cfg->prog(cfg, block, i, buffer, PROG) => 0; 125 | } 126 | 127 | // read block n-1 128 | block = cfg->block_count-1; 129 | for (lfs_off_t i = 0; i < cfg->block_size; i += READ) { 130 | cfg->read(cfg, block, i, buffer, READ) => 0; 131 | 132 | for (lfs_off_t j = 0; j < READ; j++) { 133 | LFS_ASSERT(buffer[j] == (block+i+j) % 251); 134 | } 135 | } 136 | 137 | // read block 0 again 138 | block = 0; 139 | for (lfs_off_t i = 0; i < cfg->block_size; i += READ) { 140 | cfg->read(cfg, block, i, buffer, READ) => 0; 141 | 142 | for (lfs_off_t j = 0; j < READ; j++) { 143 | LFS_ASSERT(buffer[j] == (block+i+j) % 251); 144 | } 145 | } 146 | ''' 147 | 148 | [cases.test_bd_powers_of_two] 149 | defines.READ = ['READ_SIZE', 'BLOCK_SIZE'] 150 | defines.PROG = ['PROG_SIZE', 'BLOCK_SIZE'] 151 | code = ''' 152 | uint8_t buffer[lfs_max(READ, PROG)]; 153 | 154 | // write/read every power of 2 155 | lfs_block_t block = 1; 156 | while (block < cfg->block_count) { 157 | // write 158 | cfg->erase(cfg, block) => 0; 159 | for (lfs_off_t i = 0; i < cfg->block_size; i += PROG) { 160 | for (lfs_off_t j = 0; j < PROG; j++) { 161 | buffer[j] = (block+i+j) % 251; 162 | } 163 | cfg->prog(cfg, block, i, buffer, PROG) => 0; 164 | } 165 | 166 | // read 167 | for (lfs_off_t i = 0; i < cfg->block_size; i += READ) { 168 | cfg->read(cfg, block, i, buffer, READ) => 0; 169 | 170 | for (lfs_off_t j = 0; j < READ; j++) { 171 | LFS_ASSERT(buffer[j] == (block+i+j) % 251); 172 | } 173 | } 174 | 175 | block *= 2; 176 | } 177 | 178 | // read every power of 2 again 179 | block = 1; 180 | while (block < cfg->block_count) { 181 | // read 182 | for (lfs_off_t i = 0; i < cfg->block_size; i += READ) { 183 | cfg->read(cfg, block, i, buffer, READ) => 0; 184 | 185 | for (lfs_off_t j = 0; j < READ; j++) { 186 | LFS_ASSERT(buffer[j] == (block+i+j) % 251); 187 | } 188 | } 189 | 190 | block *= 2; 191 | } 192 | ''' 193 | 194 | [cases.test_bd_fibonacci] 195 | defines.READ = ['READ_SIZE', 'BLOCK_SIZE'] 196 | defines.PROG = ['PROG_SIZE', 'BLOCK_SIZE'] 197 | code = ''' 198 | uint8_t buffer[lfs_max(READ, PROG)]; 199 | 200 | // write/read every fibonacci number on our device 201 | lfs_block_t block = 1; 202 | lfs_block_t block_ = 1; 203 | while (block < cfg->block_count) { 204 | // write 205 | cfg->erase(cfg, block) => 0; 206 | for (lfs_off_t i = 0; i < cfg->block_size; i += PROG) { 207 | for (lfs_off_t j = 0; j < PROG; j++) { 208 | buffer[j] = (block+i+j) % 251; 209 | } 210 | cfg->prog(cfg, block, i, buffer, PROG) => 0; 211 | } 212 | 213 | // read 214 | for (lfs_off_t i = 0; i < cfg->block_size; i += READ) { 215 | cfg->read(cfg, block, i, buffer, READ) => 0; 216 | 217 | for (lfs_off_t j = 0; j < READ; j++) { 218 | LFS_ASSERT(buffer[j] == (block+i+j) % 251); 219 | } 220 | } 221 | 222 | lfs_block_t nblock = block + block_; 223 | block_ = block; 224 | block = nblock; 225 | } 226 | 227 | // read every fibonacci number again 228 | block = 1; 229 | block_ = 1; 230 | while (block < cfg->block_count) { 231 | // read 232 | for (lfs_off_t i = 0; i < cfg->block_size; i += READ) { 233 | cfg->read(cfg, block, i, buffer, READ) => 0; 234 | 235 | for (lfs_off_t j = 0; j < READ; j++) { 236 | LFS_ASSERT(buffer[j] == (block+i+j) % 251); 237 | } 238 | } 239 | 240 | lfs_block_t nblock = block + block_; 241 | block_ = block; 242 | block = nblock; 243 | } 244 | ''' 245 | 246 | 247 | 248 | 249 | -------------------------------------------------------------------------------- /tests/test_evil.toml: -------------------------------------------------------------------------------- 1 | # Tests for recovering from conditions which shouldn't normally 2 | # happen during normal operation of littlefs 3 | 4 | # invalid pointer tests (outside of block_count) 5 | 6 | [cases.test_evil_invalid_tail_pointer] 7 | defines.TAIL_TYPE = ['LFS_TYPE_HARDTAIL', 'LFS_TYPE_SOFTTAIL'] 8 | defines.INVALSET = [0x3, 0x1, 0x2] 9 | in = "lfs.c" 10 | code = ''' 11 | // create littlefs 12 | lfs_t lfs; 13 | lfs_format(&lfs, cfg) => 0; 14 | 15 | // change tail-pointer to invalid pointers 16 | lfs_init(&lfs, cfg) => 0; 17 | lfs_mdir_t mdir; 18 | lfs_dir_fetch(&lfs, &mdir, (lfs_block_t[2]){0, 1}) => 0; 19 | lfs_dir_commit(&lfs, &mdir, LFS_MKATTRS( 20 | {LFS_MKTAG(LFS_TYPE_HARDTAIL, 0x3ff, 8), 21 | (lfs_block_t[2]){ 22 | (INVALSET & 0x1) ? 0xcccccccc : 0, 23 | (INVALSET & 0x2) ? 0xcccccccc : 0}})) => 0; 24 | lfs_deinit(&lfs) => 0; 25 | 26 | // test that mount fails gracefully 27 | lfs_mount(&lfs, cfg) => LFS_ERR_CORRUPT; 28 | ''' 29 | 30 | [cases.test_evil_invalid_dir_pointer] 31 | defines.INVALSET = [0x3, 0x1, 0x2] 32 | in = "lfs.c" 33 | code = ''' 34 | // create littlefs 35 | lfs_t lfs; 36 | lfs_format(&lfs, cfg) => 0; 37 | // make a dir 38 | lfs_mount(&lfs, cfg) => 0; 39 | lfs_mkdir(&lfs, "dir_here") => 0; 40 | lfs_unmount(&lfs) => 0; 41 | 42 | // change the dir pointer to be invalid 43 | lfs_init(&lfs, cfg) => 0; 44 | lfs_mdir_t mdir; 45 | lfs_dir_fetch(&lfs, &mdir, (lfs_block_t[2]){0, 1}) => 0; 46 | // make sure id 1 == our directory 47 | uint8_t buffer[1024]; 48 | lfs_dir_get(&lfs, &mdir, 49 | LFS_MKTAG(0x700, 0x3ff, 0), 50 | LFS_MKTAG(LFS_TYPE_NAME, 1, strlen("dir_here")), buffer) 51 | => LFS_MKTAG(LFS_TYPE_DIR, 1, strlen("dir_here")); 52 | assert(memcmp((char*)buffer, "dir_here", strlen("dir_here")) == 0); 53 | // change dir pointer 54 | lfs_dir_commit(&lfs, &mdir, LFS_MKATTRS( 55 | {LFS_MKTAG(LFS_TYPE_DIRSTRUCT, 1, 8), 56 | (lfs_block_t[2]){ 57 | (INVALSET & 0x1) ? 0xcccccccc : 0, 58 | (INVALSET & 0x2) ? 0xcccccccc : 0}})) => 0; 59 | lfs_deinit(&lfs) => 0; 60 | 61 | // test that accessing our bad dir fails, note there's a number 62 | // of ways to access the dir, some can fail, but some don't 63 | lfs_mount(&lfs, cfg) => 0; 64 | struct lfs_info info; 65 | lfs_stat(&lfs, "dir_here", &info) => 0; 66 | assert(strcmp(info.name, "dir_here") == 0); 67 | assert(info.type == LFS_TYPE_DIR); 68 | 69 | lfs_dir_t dir; 70 | lfs_dir_open(&lfs, &dir, "dir_here") => LFS_ERR_CORRUPT; 71 | lfs_stat(&lfs, "dir_here/file_here", &info) => LFS_ERR_CORRUPT; 72 | lfs_dir_open(&lfs, &dir, "dir_here/dir_here") => LFS_ERR_CORRUPT; 73 | lfs_file_t file; 74 | lfs_file_open(&lfs, &file, "dir_here/file_here", 75 | LFS_O_RDONLY) => LFS_ERR_CORRUPT; 76 | lfs_file_open(&lfs, &file, "dir_here/file_here", 77 | LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_CORRUPT; 78 | lfs_unmount(&lfs) => 0; 79 | ''' 80 | 81 | [cases.test_evil_invalid_file_pointer] 82 | in = "lfs.c" 83 | defines.SIZE = [10, 1000, 100000] # faked file size 84 | code = ''' 85 | // create littlefs 86 | lfs_t lfs; 87 | lfs_format(&lfs, cfg) => 0; 88 | // make a file 89 | lfs_mount(&lfs, cfg) => 0; 90 | lfs_file_t file; 91 | lfs_file_open(&lfs, &file, "file_here", 92 | LFS_O_WRONLY | LFS_O_CREAT) => 0; 93 | lfs_file_close(&lfs, &file) => 0; 94 | lfs_unmount(&lfs) => 0; 95 | 96 | // change the file pointer to be invalid 97 | lfs_init(&lfs, cfg) => 0; 98 | lfs_mdir_t mdir; 99 | lfs_dir_fetch(&lfs, &mdir, (lfs_block_t[2]){0, 1}) => 0; 100 | // make sure id 1 == our file 101 | uint8_t buffer[1024]; 102 | lfs_dir_get(&lfs, &mdir, 103 | LFS_MKTAG(0x700, 0x3ff, 0), 104 | LFS_MKTAG(LFS_TYPE_NAME, 1, strlen("file_here")), buffer) 105 | => LFS_MKTAG(LFS_TYPE_REG, 1, strlen("file_here")); 106 | assert(memcmp((char*)buffer, "file_here", strlen("file_here")) == 0); 107 | // change file pointer 108 | lfs_dir_commit(&lfs, &mdir, LFS_MKATTRS( 109 | {LFS_MKTAG(LFS_TYPE_CTZSTRUCT, 1, sizeof(struct lfs_ctz)), 110 | &(struct lfs_ctz){0xcccccccc, lfs_tole32(SIZE)}})) => 0; 111 | lfs_deinit(&lfs) => 0; 112 | 113 | // test that accessing our bad file fails, note there's a number 114 | // of ways to access the dir, some can fail, but some don't 115 | lfs_mount(&lfs, cfg) => 0; 116 | struct lfs_info info; 117 | lfs_stat(&lfs, "file_here", &info) => 0; 118 | assert(strcmp(info.name, "file_here") == 0); 119 | assert(info.type == LFS_TYPE_REG); 120 | assert(info.size == SIZE); 121 | 122 | lfs_file_open(&lfs, &file, "file_here", LFS_O_RDONLY) => 0; 123 | lfs_file_read(&lfs, &file, buffer, SIZE) => LFS_ERR_CORRUPT; 124 | lfs_file_close(&lfs, &file) => 0; 125 | 126 | // any allocs that traverse CTZ must unfortunately must fail 127 | if (SIZE > 2*BLOCK_SIZE) { 128 | lfs_mkdir(&lfs, "dir_here") => LFS_ERR_CORRUPT; 129 | } 130 | lfs_unmount(&lfs) => 0; 131 | ''' 132 | 133 | [cases.test_evil_invalid_ctz_pointer] # invalid pointer in CTZ skip-list test 134 | defines.SIZE = ['2*BLOCK_SIZE', '3*BLOCK_SIZE', '4*BLOCK_SIZE'] 135 | in = "lfs.c" 136 | code = ''' 137 | // create littlefs 138 | lfs_t lfs; 139 | lfs_format(&lfs, cfg) => 0; 140 | // make a file 141 | lfs_mount(&lfs, cfg) => 0; 142 | lfs_file_t file; 143 | lfs_file_open(&lfs, &file, "file_here", 144 | LFS_O_WRONLY | LFS_O_CREAT) => 0; 145 | for (int i = 0; i < SIZE; i++) { 146 | char c = 'c'; 147 | lfs_file_write(&lfs, &file, &c, 1) => 1; 148 | } 149 | lfs_file_close(&lfs, &file) => 0; 150 | lfs_unmount(&lfs) => 0; 151 | // change pointer in CTZ skip-list to be invalid 152 | lfs_init(&lfs, cfg) => 0; 153 | lfs_mdir_t mdir; 154 | lfs_dir_fetch(&lfs, &mdir, (lfs_block_t[2]){0, 1}) => 0; 155 | // make sure id 1 == our file and get our CTZ structure 156 | uint8_t buffer[4*BLOCK_SIZE]; 157 | lfs_dir_get(&lfs, &mdir, 158 | LFS_MKTAG(0x700, 0x3ff, 0), 159 | LFS_MKTAG(LFS_TYPE_NAME, 1, strlen("file_here")), buffer) 160 | => LFS_MKTAG(LFS_TYPE_REG, 1, strlen("file_here")); 161 | assert(memcmp((char*)buffer, "file_here", strlen("file_here")) == 0); 162 | struct lfs_ctz ctz; 163 | lfs_dir_get(&lfs, &mdir, 164 | LFS_MKTAG(0x700, 0x3ff, 0), 165 | LFS_MKTAG(LFS_TYPE_STRUCT, 1, sizeof(struct lfs_ctz)), &ctz) 166 | => LFS_MKTAG(LFS_TYPE_CTZSTRUCT, 1, sizeof(struct lfs_ctz)); 167 | lfs_ctz_fromle32(&ctz); 168 | // rewrite block to contain bad pointer 169 | uint8_t bbuffer[BLOCK_SIZE]; 170 | cfg->read(cfg, ctz.head, 0, bbuffer, BLOCK_SIZE) => 0; 171 | uint32_t bad = lfs_tole32(0xcccccccc); 172 | memcpy(&bbuffer[0], &bad, sizeof(bad)); 173 | memcpy(&bbuffer[4], &bad, sizeof(bad)); 174 | cfg->erase(cfg, ctz.head) => 0; 175 | cfg->prog(cfg, ctz.head, 0, bbuffer, BLOCK_SIZE) => 0; 176 | lfs_deinit(&lfs) => 0; 177 | 178 | // test that accessing our bad file fails, note there's a number 179 | // of ways to access the dir, some can fail, but some don't 180 | lfs_mount(&lfs, cfg) => 0; 181 | struct lfs_info info; 182 | lfs_stat(&lfs, "file_here", &info) => 0; 183 | assert(strcmp(info.name, "file_here") == 0); 184 | assert(info.type == LFS_TYPE_REG); 185 | assert(info.size == SIZE); 186 | 187 | lfs_file_open(&lfs, &file, "file_here", LFS_O_RDONLY) => 0; 188 | lfs_file_read(&lfs, &file, buffer, SIZE) => LFS_ERR_CORRUPT; 189 | lfs_file_close(&lfs, &file) => 0; 190 | 191 | // any allocs that traverse CTZ must unfortunately must fail 192 | if (SIZE > 2*BLOCK_SIZE) { 193 | lfs_mkdir(&lfs, "dir_here") => LFS_ERR_CORRUPT; 194 | } 195 | lfs_unmount(&lfs) => 0; 196 | ''' 197 | 198 | 199 | [cases.test_evil_invalid_gstate_pointer] 200 | defines.INVALSET = [0x3, 0x1, 0x2] 201 | in = "lfs.c" 202 | code = ''' 203 | // create littlefs 204 | lfs_t lfs; 205 | lfs_format(&lfs, cfg) => 0; 206 | 207 | // create an invalid gstate 208 | lfs_init(&lfs, cfg) => 0; 209 | lfs_mdir_t mdir; 210 | lfs_dir_fetch(&lfs, &mdir, (lfs_block_t[2]){0, 1}) => 0; 211 | lfs_fs_prepmove(&lfs, 1, (lfs_block_t [2]){ 212 | (INVALSET & 0x1) ? 0xcccccccc : 0, 213 | (INVALSET & 0x2) ? 0xcccccccc : 0}); 214 | lfs_dir_commit(&lfs, &mdir, NULL, 0) => 0; 215 | lfs_deinit(&lfs) => 0; 216 | 217 | // test that mount fails gracefully 218 | // mount may not fail, but our first alloc should fail when 219 | // we try to fix the gstate 220 | lfs_mount(&lfs, cfg) => 0; 221 | lfs_mkdir(&lfs, "should_fail") => LFS_ERR_CORRUPT; 222 | lfs_unmount(&lfs) => 0; 223 | ''' 224 | 225 | # cycle detection/recovery tests 226 | 227 | [cases.test_evil_mdir_loop] # metadata-pair threaded-list loop test 228 | in = "lfs.c" 229 | code = ''' 230 | // create littlefs 231 | lfs_t lfs; 232 | lfs_format(&lfs, cfg) => 0; 233 | 234 | // change tail-pointer to point to ourself 235 | lfs_init(&lfs, cfg) => 0; 236 | lfs_mdir_t mdir; 237 | lfs_dir_fetch(&lfs, &mdir, (lfs_block_t[2]){0, 1}) => 0; 238 | lfs_dir_commit(&lfs, &mdir, LFS_MKATTRS( 239 | {LFS_MKTAG(LFS_TYPE_HARDTAIL, 0x3ff, 8), 240 | (lfs_block_t[2]){0, 1}})) => 0; 241 | lfs_deinit(&lfs) => 0; 242 | 243 | // test that mount fails gracefully 244 | lfs_mount(&lfs, cfg) => LFS_ERR_CORRUPT; 245 | ''' 246 | 247 | [cases.test_evil_mdir_loop2] # metadata-pair threaded-list 2-length loop test 248 | in = "lfs.c" 249 | code = ''' 250 | // create littlefs with child dir 251 | lfs_t lfs; 252 | lfs_format(&lfs, cfg) => 0; 253 | lfs_mount(&lfs, cfg) => 0; 254 | lfs_mkdir(&lfs, "child") => 0; 255 | lfs_unmount(&lfs) => 0; 256 | 257 | // find child 258 | lfs_init(&lfs, cfg) => 0; 259 | lfs_mdir_t mdir; 260 | lfs_block_t pair[2]; 261 | lfs_dir_fetch(&lfs, &mdir, (lfs_block_t[2]){0, 1}) => 0; 262 | lfs_dir_get(&lfs, &mdir, 263 | LFS_MKTAG(0x7ff, 0x3ff, 0), 264 | LFS_MKTAG(LFS_TYPE_DIRSTRUCT, 1, sizeof(pair)), pair) 265 | => LFS_MKTAG(LFS_TYPE_DIRSTRUCT, 1, sizeof(pair)); 266 | lfs_pair_fromle32(pair); 267 | // change tail-pointer to point to root 268 | lfs_dir_fetch(&lfs, &mdir, pair) => 0; 269 | lfs_dir_commit(&lfs, &mdir, LFS_MKATTRS( 270 | {LFS_MKTAG(LFS_TYPE_HARDTAIL, 0x3ff, 8), 271 | (lfs_block_t[2]){0, 1}})) => 0; 272 | lfs_deinit(&lfs) => 0; 273 | 274 | // test that mount fails gracefully 275 | lfs_mount(&lfs, cfg) => LFS_ERR_CORRUPT; 276 | ''' 277 | 278 | [cases.test_evil_mdir_loop_child] # metadata-pair threaded-list 1-length child loop test 279 | in = "lfs.c" 280 | code = ''' 281 | // create littlefs with child dir 282 | lfs_t lfs; 283 | lfs_format(&lfs, cfg) => 0; 284 | lfs_mount(&lfs, cfg) => 0; 285 | lfs_mkdir(&lfs, "child") => 0; 286 | lfs_unmount(&lfs) => 0; 287 | 288 | // find child 289 | lfs_init(&lfs, cfg) => 0; 290 | lfs_mdir_t mdir; 291 | lfs_block_t pair[2]; 292 | lfs_dir_fetch(&lfs, &mdir, (lfs_block_t[2]){0, 1}) => 0; 293 | lfs_dir_get(&lfs, &mdir, 294 | LFS_MKTAG(0x7ff, 0x3ff, 0), 295 | LFS_MKTAG(LFS_TYPE_DIRSTRUCT, 1, sizeof(pair)), pair) 296 | => LFS_MKTAG(LFS_TYPE_DIRSTRUCT, 1, sizeof(pair)); 297 | lfs_pair_fromle32(pair); 298 | // change tail-pointer to point to ourself 299 | lfs_dir_fetch(&lfs, &mdir, pair) => 0; 300 | lfs_dir_commit(&lfs, &mdir, LFS_MKATTRS( 301 | {LFS_MKTAG(LFS_TYPE_HARDTAIL, 0x3ff, 8), pair})) => 0; 302 | lfs_deinit(&lfs) => 0; 303 | 304 | // test that mount fails gracefully 305 | lfs_mount(&lfs, cfg) => LFS_ERR_CORRUPT; 306 | ''' 307 | -------------------------------------------------------------------------------- /tests/test_interspersed.toml: -------------------------------------------------------------------------------- 1 | 2 | [cases.test_interspersed_files] 3 | defines.SIZE = [10, 100] 4 | defines.FILES = [4, 10, 26] 5 | code = ''' 6 | lfs_t lfs; 7 | lfs_file_t files[FILES]; 8 | const char alphas[] = "abcdefghijklmnopqrstuvwxyz"; 9 | lfs_format(&lfs, cfg) => 0; 10 | lfs_mount(&lfs, cfg) => 0; 11 | for (int j = 0; j < FILES; j++) { 12 | char path[1024]; 13 | sprintf(path, "%c", alphas[j]); 14 | lfs_file_open(&lfs, &files[j], path, 15 | LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; 16 | } 17 | 18 | for (int i = 0; i < SIZE; i++) { 19 | for (int j = 0; j < FILES; j++) { 20 | lfs_file_write(&lfs, &files[j], &alphas[j], 1) => 1; 21 | } 22 | } 23 | 24 | for (int j = 0; j < FILES; j++) { 25 | lfs_file_close(&lfs, &files[j]); 26 | } 27 | 28 | lfs_dir_t dir; 29 | lfs_dir_open(&lfs, &dir, "/") => 0; 30 | struct lfs_info info; 31 | lfs_dir_read(&lfs, &dir, &info) => 1; 32 | assert(strcmp(info.name, ".") == 0); 33 | assert(info.type == LFS_TYPE_DIR); 34 | lfs_dir_read(&lfs, &dir, &info) => 1; 35 | assert(strcmp(info.name, "..") == 0); 36 | assert(info.type == LFS_TYPE_DIR); 37 | for (int j = 0; j < FILES; j++) { 38 | char path[1024]; 39 | sprintf(path, "%c", alphas[j]); 40 | lfs_dir_read(&lfs, &dir, &info) => 1; 41 | assert(strcmp(info.name, path) == 0); 42 | assert(info.type == LFS_TYPE_REG); 43 | assert(info.size == SIZE); 44 | } 45 | lfs_dir_read(&lfs, &dir, &info) => 0; 46 | lfs_dir_close(&lfs, &dir) => 0; 47 | 48 | for (int j = 0; j < FILES; j++) { 49 | char path[1024]; 50 | sprintf(path, "%c", alphas[j]); 51 | lfs_file_open(&lfs, &files[j], path, LFS_O_RDONLY) => 0; 52 | } 53 | 54 | for (int i = 0; i < 10; i++) { 55 | for (int j = 0; j < FILES; j++) { 56 | uint8_t buffer[1024]; 57 | lfs_file_read(&lfs, &files[j], buffer, 1) => 1; 58 | assert(buffer[0] == alphas[j]); 59 | } 60 | } 61 | 62 | for (int j = 0; j < FILES; j++) { 63 | lfs_file_close(&lfs, &files[j]); 64 | } 65 | 66 | lfs_unmount(&lfs) => 0; 67 | ''' 68 | 69 | [cases.test_interspersed_remove_files] 70 | defines.SIZE = [10, 100] 71 | defines.FILES = [4, 10, 26] 72 | code = ''' 73 | lfs_t lfs; 74 | const char alphas[] = "abcdefghijklmnopqrstuvwxyz"; 75 | lfs_format(&lfs, cfg) => 0; 76 | lfs_mount(&lfs, cfg) => 0; 77 | for (int j = 0; j < FILES; j++) { 78 | char path[1024]; 79 | sprintf(path, "%c", alphas[j]); 80 | lfs_file_t file; 81 | lfs_file_open(&lfs, &file, path, 82 | LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; 83 | for (int i = 0; i < SIZE; i++) { 84 | lfs_file_write(&lfs, &file, &alphas[j], 1) => 1; 85 | } 86 | lfs_file_close(&lfs, &file); 87 | } 88 | lfs_unmount(&lfs) => 0; 89 | 90 | lfs_mount(&lfs, cfg) => 0; 91 | lfs_file_t file; 92 | lfs_file_open(&lfs, &file, "zzz", LFS_O_WRONLY | LFS_O_CREAT) => 0; 93 | for (int j = 0; j < FILES; j++) { 94 | lfs_file_write(&lfs, &file, (const void*)"~", 1) => 1; 95 | lfs_file_sync(&lfs, &file) => 0; 96 | 97 | char path[1024]; 98 | sprintf(path, "%c", alphas[j]); 99 | lfs_remove(&lfs, path) => 0; 100 | } 101 | lfs_file_close(&lfs, &file); 102 | 103 | lfs_dir_t dir; 104 | lfs_dir_open(&lfs, &dir, "/") => 0; 105 | struct lfs_info info; 106 | lfs_dir_read(&lfs, &dir, &info) => 1; 107 | assert(strcmp(info.name, ".") == 0); 108 | assert(info.type == LFS_TYPE_DIR); 109 | lfs_dir_read(&lfs, &dir, &info) => 1; 110 | assert(strcmp(info.name, "..") == 0); 111 | assert(info.type == LFS_TYPE_DIR); 112 | lfs_dir_read(&lfs, &dir, &info) => 1; 113 | assert(strcmp(info.name, "zzz") == 0); 114 | assert(info.type == LFS_TYPE_REG); 115 | assert(info.size == FILES); 116 | lfs_dir_read(&lfs, &dir, &info) => 0; 117 | lfs_dir_close(&lfs, &dir) => 0; 118 | 119 | lfs_file_open(&lfs, &file, "zzz", LFS_O_RDONLY) => 0; 120 | for (int i = 0; i < FILES; i++) { 121 | uint8_t buffer[1024]; 122 | lfs_file_read(&lfs, &file, buffer, 1) => 1; 123 | assert(buffer[0] == '~'); 124 | } 125 | lfs_file_close(&lfs, &file); 126 | 127 | lfs_unmount(&lfs) => 0; 128 | ''' 129 | 130 | [cases.test_interspersed_remove_inconveniently] 131 | defines.SIZE = [10, 100] 132 | code = ''' 133 | lfs_t lfs; 134 | lfs_format(&lfs, cfg) => 0; 135 | lfs_mount(&lfs, cfg) => 0; 136 | lfs_file_t files[3]; 137 | lfs_file_open(&lfs, &files[0], "e", LFS_O_WRONLY | LFS_O_CREAT) => 0; 138 | lfs_file_open(&lfs, &files[1], "f", LFS_O_WRONLY | LFS_O_CREAT) => 0; 139 | lfs_file_open(&lfs, &files[2], "g", LFS_O_WRONLY | LFS_O_CREAT) => 0; 140 | 141 | for (int i = 0; i < SIZE/2; i++) { 142 | lfs_file_write(&lfs, &files[0], (const void*)"e", 1) => 1; 143 | lfs_file_write(&lfs, &files[1], (const void*)"f", 1) => 1; 144 | lfs_file_write(&lfs, &files[2], (const void*)"g", 1) => 1; 145 | } 146 | 147 | lfs_remove(&lfs, "f") => 0; 148 | 149 | for (int i = 0; i < SIZE/2; i++) { 150 | lfs_file_write(&lfs, &files[0], (const void*)"e", 1) => 1; 151 | lfs_file_write(&lfs, &files[1], (const void*)"f", 1) => 1; 152 | lfs_file_write(&lfs, &files[2], (const void*)"g", 1) => 1; 153 | } 154 | 155 | lfs_file_close(&lfs, &files[0]); 156 | lfs_file_close(&lfs, &files[1]); 157 | lfs_file_close(&lfs, &files[2]); 158 | 159 | lfs_dir_t dir; 160 | lfs_dir_open(&lfs, &dir, "/") => 0; 161 | struct lfs_info info; 162 | lfs_dir_read(&lfs, &dir, &info) => 1; 163 | assert(strcmp(info.name, ".") == 0); 164 | assert(info.type == LFS_TYPE_DIR); 165 | lfs_dir_read(&lfs, &dir, &info) => 1; 166 | assert(strcmp(info.name, "..") == 0); 167 | assert(info.type == LFS_TYPE_DIR); 168 | lfs_dir_read(&lfs, &dir, &info) => 1; 169 | assert(strcmp(info.name, "e") == 0); 170 | assert(info.type == LFS_TYPE_REG); 171 | assert(info.size == SIZE); 172 | lfs_dir_read(&lfs, &dir, &info) => 1; 173 | assert(strcmp(info.name, "g") == 0); 174 | assert(info.type == LFS_TYPE_REG); 175 | assert(info.size == SIZE); 176 | lfs_dir_read(&lfs, &dir, &info) => 0; 177 | lfs_dir_close(&lfs, &dir) => 0; 178 | 179 | lfs_file_open(&lfs, &files[0], "e", LFS_O_RDONLY) => 0; 180 | lfs_file_open(&lfs, &files[1], "g", LFS_O_RDONLY) => 0; 181 | for (int i = 0; i < SIZE; i++) { 182 | uint8_t buffer[1024]; 183 | lfs_file_read(&lfs, &files[0], buffer, 1) => 1; 184 | assert(buffer[0] == 'e'); 185 | lfs_file_read(&lfs, &files[1], buffer, 1) => 1; 186 | assert(buffer[0] == 'g'); 187 | } 188 | lfs_file_close(&lfs, &files[0]); 189 | lfs_file_close(&lfs, &files[1]); 190 | 191 | lfs_unmount(&lfs) => 0; 192 | ''' 193 | 194 | [cases.test_interspersed_reentrant_files] 195 | defines.SIZE = [10, 100] 196 | defines.FILES = [4, 10, 26] 197 | reentrant = true 198 | defines.POWERLOSS_BEHAVIOR = [ 199 | 'LFS_EMUBD_POWERLOSS_NOOP', 200 | 'LFS_EMUBD_POWERLOSS_OOO', 201 | ] 202 | code = ''' 203 | lfs_t lfs; 204 | lfs_file_t files[FILES]; 205 | const char alphas[] = "abcdefghijklmnopqrstuvwxyz"; 206 | 207 | int err = lfs_mount(&lfs, cfg); 208 | if (err) { 209 | lfs_format(&lfs, cfg) => 0; 210 | lfs_mount(&lfs, cfg) => 0; 211 | } 212 | 213 | for (int j = 0; j < FILES; j++) { 214 | char path[1024]; 215 | sprintf(path, "%c", alphas[j]); 216 | lfs_file_open(&lfs, &files[j], path, 217 | LFS_O_WRONLY | LFS_O_CREAT | LFS_O_APPEND) => 0; 218 | } 219 | 220 | for (int i = 0; i < SIZE; i++) { 221 | for (int j = 0; j < FILES; j++) { 222 | lfs_ssize_t size = lfs_file_size(&lfs, &files[j]); 223 | assert(size >= 0); 224 | if ((int)size <= i) { 225 | lfs_file_write(&lfs, &files[j], &alphas[j], 1) => 1; 226 | lfs_file_sync(&lfs, &files[j]) => 0; 227 | } 228 | } 229 | } 230 | 231 | for (int j = 0; j < FILES; j++) { 232 | lfs_file_close(&lfs, &files[j]); 233 | } 234 | 235 | lfs_dir_t dir; 236 | lfs_dir_open(&lfs, &dir, "/") => 0; 237 | struct lfs_info info; 238 | lfs_dir_read(&lfs, &dir, &info) => 1; 239 | assert(strcmp(info.name, ".") == 0); 240 | assert(info.type == LFS_TYPE_DIR); 241 | lfs_dir_read(&lfs, &dir, &info) => 1; 242 | assert(strcmp(info.name, "..") == 0); 243 | assert(info.type == LFS_TYPE_DIR); 244 | for (int j = 0; j < FILES; j++) { 245 | char path[1024]; 246 | sprintf(path, "%c", alphas[j]); 247 | lfs_dir_read(&lfs, &dir, &info) => 1; 248 | assert(strcmp(info.name, path) == 0); 249 | assert(info.type == LFS_TYPE_REG); 250 | assert(info.size == SIZE); 251 | } 252 | lfs_dir_read(&lfs, &dir, &info) => 0; 253 | lfs_dir_close(&lfs, &dir) => 0; 254 | 255 | for (int j = 0; j < FILES; j++) { 256 | char path[1024]; 257 | sprintf(path, "%c", alphas[j]); 258 | lfs_file_open(&lfs, &files[j], path, LFS_O_RDONLY) => 0; 259 | } 260 | 261 | for (int i = 0; i < 10; i++) { 262 | for (int j = 0; j < FILES; j++) { 263 | uint8_t buffer[1024]; 264 | lfs_file_read(&lfs, &files[j], buffer, 1) => 1; 265 | assert(buffer[0] == alphas[j]); 266 | } 267 | } 268 | 269 | for (int j = 0; j < FILES; j++) { 270 | lfs_file_close(&lfs, &files[j]); 271 | } 272 | 273 | lfs_unmount(&lfs) => 0; 274 | ''' 275 | -------------------------------------------------------------------------------- /tests/test_orphans.toml: -------------------------------------------------------------------------------- 1 | [cases.test_orphans_normal] 2 | in = "lfs.c" 3 | if = 'PROG_SIZE <= 0x3fe' # only works with one crc per commit 4 | code = ''' 5 | lfs_t lfs; 6 | lfs_format(&lfs, cfg) => 0; 7 | lfs_mount(&lfs, cfg) => 0; 8 | lfs_mkdir(&lfs, "parent") => 0; 9 | lfs_mkdir(&lfs, "parent/orphan") => 0; 10 | lfs_mkdir(&lfs, "parent/child") => 0; 11 | lfs_remove(&lfs, "parent/orphan") => 0; 12 | lfs_unmount(&lfs) => 0; 13 | 14 | // corrupt the child's most recent commit, this should be the update 15 | // to the linked-list entry, which should orphan the orphan. Note this 16 | // makes a lot of assumptions about the remove operation. 17 | lfs_mount(&lfs, cfg) => 0; 18 | lfs_dir_t dir; 19 | lfs_dir_open(&lfs, &dir, "parent/child") => 0; 20 | lfs_block_t block = dir.m.pair[0]; 21 | lfs_dir_close(&lfs, &dir) => 0; 22 | lfs_unmount(&lfs) => 0; 23 | uint8_t buffer[BLOCK_SIZE]; 24 | cfg->read(cfg, block, 0, buffer, BLOCK_SIZE) => 0; 25 | int off = BLOCK_SIZE-1; 26 | while (off >= 0 && buffer[off] == ERASE_VALUE) { 27 | off -= 1; 28 | } 29 | memset(&buffer[off-3], BLOCK_SIZE, 3); 30 | cfg->erase(cfg, block) => 0; 31 | cfg->prog(cfg, block, 0, buffer, BLOCK_SIZE) => 0; 32 | cfg->sync(cfg) => 0; 33 | 34 | lfs_mount(&lfs, cfg) => 0; 35 | struct lfs_info info; 36 | lfs_stat(&lfs, "parent/orphan", &info) => LFS_ERR_NOENT; 37 | lfs_stat(&lfs, "parent/child", &info) => 0; 38 | lfs_fs_size(&lfs) => 8; 39 | lfs_unmount(&lfs) => 0; 40 | 41 | lfs_mount(&lfs, cfg) => 0; 42 | lfs_stat(&lfs, "parent/orphan", &info) => LFS_ERR_NOENT; 43 | lfs_stat(&lfs, "parent/child", &info) => 0; 44 | lfs_fs_size(&lfs) => 8; 45 | // this mkdir should both create a dir and deorphan, so size 46 | // should be unchanged 47 | lfs_mkdir(&lfs, "parent/otherchild") => 0; 48 | lfs_stat(&lfs, "parent/orphan", &info) => LFS_ERR_NOENT; 49 | lfs_stat(&lfs, "parent/child", &info) => 0; 50 | lfs_stat(&lfs, "parent/otherchild", &info) => 0; 51 | lfs_fs_size(&lfs) => 8; 52 | lfs_unmount(&lfs) => 0; 53 | 54 | lfs_mount(&lfs, cfg) => 0; 55 | lfs_stat(&lfs, "parent/orphan", &info) => LFS_ERR_NOENT; 56 | lfs_stat(&lfs, "parent/child", &info) => 0; 57 | lfs_stat(&lfs, "parent/otherchild", &info) => 0; 58 | lfs_fs_size(&lfs) => 8; 59 | lfs_unmount(&lfs) => 0; 60 | ''' 61 | 62 | # test that we only run deorphan once per power-cycle 63 | [cases.test_orphans_no_orphans] 64 | in = 'lfs.c' 65 | code = ''' 66 | lfs_t lfs; 67 | lfs_format(&lfs, cfg) => 0; 68 | 69 | lfs_mount(&lfs, cfg) => 0; 70 | // mark the filesystem as having orphans 71 | lfs_fs_preporphans(&lfs, +1) => 0; 72 | lfs_mdir_t mdir; 73 | lfs_dir_fetch(&lfs, &mdir, (lfs_block_t[2]){0, 1}) => 0; 74 | lfs_dir_commit(&lfs, &mdir, NULL, 0) => 0; 75 | 76 | // we should have orphans at this state 77 | assert(lfs_gstate_hasorphans(&lfs.gstate)); 78 | lfs_unmount(&lfs) => 0; 79 | 80 | // mount 81 | lfs_mount(&lfs, cfg) => 0; 82 | // we should detect orphans 83 | assert(lfs_gstate_hasorphans(&lfs.gstate)); 84 | // force consistency 85 | lfs_fs_forceconsistency(&lfs) => 0; 86 | // we should no longer have orphans 87 | assert(!lfs_gstate_hasorphans(&lfs.gstate)); 88 | 89 | lfs_unmount(&lfs) => 0; 90 | ''' 91 | 92 | [cases.test_orphans_one_orphan] 93 | in = 'lfs.c' 94 | code = ''' 95 | lfs_t lfs; 96 | lfs_format(&lfs, cfg) => 0; 97 | 98 | lfs_mount(&lfs, cfg) => 0; 99 | // create an orphan 100 | lfs_mdir_t orphan; 101 | lfs_alloc_ckpoint(&lfs); 102 | lfs_dir_alloc(&lfs, &orphan) => 0; 103 | lfs_dir_commit(&lfs, &orphan, NULL, 0) => 0; 104 | 105 | // append our orphan and mark the filesystem as having orphans 106 | lfs_fs_preporphans(&lfs, +1) => 0; 107 | lfs_mdir_t mdir; 108 | lfs_dir_fetch(&lfs, &mdir, (lfs_block_t[2]){0, 1}) => 0; 109 | lfs_pair_tole32(orphan.pair); 110 | lfs_dir_commit(&lfs, &mdir, LFS_MKATTRS( 111 | {LFS_MKTAG(LFS_TYPE_SOFTTAIL, 0x3ff, 8), orphan.pair})) => 0; 112 | 113 | // we should have orphans at this state 114 | assert(lfs_gstate_hasorphans(&lfs.gstate)); 115 | lfs_unmount(&lfs) => 0; 116 | 117 | // mount 118 | lfs_mount(&lfs, cfg) => 0; 119 | // we should detect orphans 120 | assert(lfs_gstate_hasorphans(&lfs.gstate)); 121 | // force consistency 122 | lfs_fs_forceconsistency(&lfs) => 0; 123 | // we should no longer have orphans 124 | assert(!lfs_gstate_hasorphans(&lfs.gstate)); 125 | 126 | lfs_unmount(&lfs) => 0; 127 | ''' 128 | 129 | # test that we can persist gstate with lfs_fs_mkconsistent 130 | [cases.test_orphans_mkconsistent_no_orphans] 131 | in = 'lfs.c' 132 | code = ''' 133 | lfs_t lfs; 134 | lfs_format(&lfs, cfg) => 0; 135 | 136 | lfs_mount(&lfs, cfg) => 0; 137 | // mark the filesystem as having orphans 138 | lfs_fs_preporphans(&lfs, +1) => 0; 139 | lfs_mdir_t mdir; 140 | lfs_dir_fetch(&lfs, &mdir, (lfs_block_t[2]){0, 1}) => 0; 141 | lfs_dir_commit(&lfs, &mdir, NULL, 0) => 0; 142 | 143 | // we should have orphans at this state 144 | assert(lfs_gstate_hasorphans(&lfs.gstate)); 145 | lfs_unmount(&lfs) => 0; 146 | 147 | // mount 148 | lfs_mount(&lfs, cfg) => 0; 149 | // we should detect orphans 150 | assert(lfs_gstate_hasorphans(&lfs.gstate)); 151 | // force consistency 152 | lfs_fs_mkconsistent(&lfs) => 0; 153 | // we should no longer have orphans 154 | assert(!lfs_gstate_hasorphans(&lfs.gstate)); 155 | 156 | // remount 157 | lfs_unmount(&lfs) => 0; 158 | lfs_mount(&lfs, cfg) => 0; 159 | // we should still have no orphans 160 | assert(!lfs_gstate_hasorphans(&lfs.gstate)); 161 | lfs_unmount(&lfs) => 0; 162 | ''' 163 | 164 | [cases.test_orphans_mkconsistent_one_orphan] 165 | in = 'lfs.c' 166 | code = ''' 167 | lfs_t lfs; 168 | lfs_format(&lfs, cfg) => 0; 169 | 170 | lfs_mount(&lfs, cfg) => 0; 171 | // create an orphan 172 | lfs_mdir_t orphan; 173 | lfs_alloc_ckpoint(&lfs); 174 | lfs_dir_alloc(&lfs, &orphan) => 0; 175 | lfs_dir_commit(&lfs, &orphan, NULL, 0) => 0; 176 | 177 | // append our orphan and mark the filesystem as having orphans 178 | lfs_fs_preporphans(&lfs, +1) => 0; 179 | lfs_mdir_t mdir; 180 | lfs_dir_fetch(&lfs, &mdir, (lfs_block_t[2]){0, 1}) => 0; 181 | lfs_pair_tole32(orphan.pair); 182 | lfs_dir_commit(&lfs, &mdir, LFS_MKATTRS( 183 | {LFS_MKTAG(LFS_TYPE_SOFTTAIL, 0x3ff, 8), orphan.pair})) => 0; 184 | 185 | // we should have orphans at this state 186 | assert(lfs_gstate_hasorphans(&lfs.gstate)); 187 | lfs_unmount(&lfs) => 0; 188 | 189 | // mount 190 | lfs_mount(&lfs, cfg) => 0; 191 | // we should detect orphans 192 | assert(lfs_gstate_hasorphans(&lfs.gstate)); 193 | // force consistency 194 | lfs_fs_mkconsistent(&lfs) => 0; 195 | // we should no longer have orphans 196 | assert(!lfs_gstate_hasorphans(&lfs.gstate)); 197 | 198 | // remount 199 | lfs_unmount(&lfs) => 0; 200 | lfs_mount(&lfs, cfg) => 0; 201 | // we should still have no orphans 202 | assert(!lfs_gstate_hasorphans(&lfs.gstate)); 203 | lfs_unmount(&lfs) => 0; 204 | ''' 205 | 206 | # reentrant testing for orphans, basically just spam mkdir/remove 207 | [cases.test_orphans_reentrant] 208 | reentrant = true 209 | # TODO fix this case, caused by non-DAG trees 210 | if = '!(DEPTH == 3 && CACHE_SIZE != 64)' 211 | defines = [ 212 | {FILES=6, DEPTH=1, CYCLES=20}, 213 | {FILES=26, DEPTH=1, CYCLES=20}, 214 | {FILES=3, DEPTH=3, CYCLES=20}, 215 | ] 216 | code = ''' 217 | lfs_t lfs; 218 | int err = lfs_mount(&lfs, cfg); 219 | if (err) { 220 | lfs_format(&lfs, cfg) => 0; 221 | lfs_mount(&lfs, cfg) => 0; 222 | } 223 | 224 | uint32_t prng = 1; 225 | const char alpha[] = "abcdefghijklmnopqrstuvwxyz"; 226 | for (unsigned i = 0; i < CYCLES; i++) { 227 | // create random path 228 | char full_path[256]; 229 | for (unsigned d = 0; d < DEPTH; d++) { 230 | sprintf(&full_path[2*d], "/%c", alpha[TEST_PRNG(&prng) % FILES]); 231 | } 232 | 233 | // if it does not exist, we create it, else we destroy 234 | struct lfs_info info; 235 | int res = lfs_stat(&lfs, full_path, &info); 236 | if (res == LFS_ERR_NOENT) { 237 | // create each directory in turn, ignore if dir already exists 238 | for (unsigned d = 0; d < DEPTH; d++) { 239 | char path[1024]; 240 | strcpy(path, full_path); 241 | path[2*d+2] = '\0'; 242 | err = lfs_mkdir(&lfs, path); 243 | assert(!err || err == LFS_ERR_EXIST); 244 | } 245 | 246 | for (unsigned d = 0; d < DEPTH; d++) { 247 | char path[1024]; 248 | strcpy(path, full_path); 249 | path[2*d+2] = '\0'; 250 | lfs_stat(&lfs, path, &info) => 0; 251 | assert(strcmp(info.name, &path[2*d+1]) == 0); 252 | assert(info.type == LFS_TYPE_DIR); 253 | } 254 | } else { 255 | // is valid dir? 256 | assert(strcmp(info.name, &full_path[2*(DEPTH-1)+1]) == 0); 257 | assert(info.type == LFS_TYPE_DIR); 258 | 259 | // try to delete path in reverse order, ignore if dir is not empty 260 | for (int d = DEPTH-1; d >= 0; d--) { 261 | char path[1024]; 262 | strcpy(path, full_path); 263 | path[2*d+2] = '\0'; 264 | err = lfs_remove(&lfs, path); 265 | assert(!err || err == LFS_ERR_NOTEMPTY); 266 | } 267 | 268 | lfs_stat(&lfs, full_path, &info) => LFS_ERR_NOENT; 269 | } 270 | } 271 | lfs_unmount(&lfs) => 0; 272 | ''' 273 | 274 | -------------------------------------------------------------------------------- /tests/test_paths.toml: -------------------------------------------------------------------------------- 1 | 2 | # simple path test 3 | [cases.test_paths_normal] 4 | code = ''' 5 | lfs_t lfs; 6 | lfs_format(&lfs, cfg) => 0; 7 | lfs_mount(&lfs, cfg) => 0; 8 | lfs_mkdir(&lfs, "tea") => 0; 9 | lfs_mkdir(&lfs, "tea/hottea") => 0; 10 | lfs_mkdir(&lfs, "tea/warmtea") => 0; 11 | lfs_mkdir(&lfs, "tea/coldtea") => 0; 12 | 13 | struct lfs_info info; 14 | lfs_stat(&lfs, "tea/hottea", &info) => 0; 15 | assert(strcmp(info.name, "hottea") == 0); 16 | lfs_stat(&lfs, "/tea/hottea", &info) => 0; 17 | assert(strcmp(info.name, "hottea") == 0); 18 | 19 | lfs_mkdir(&lfs, "/milk") => 0; 20 | lfs_stat(&lfs, "/milk", &info) => 0; 21 | assert(strcmp(info.name, "milk") == 0); 22 | lfs_stat(&lfs, "milk", &info) => 0; 23 | assert(strcmp(info.name, "milk") == 0); 24 | lfs_unmount(&lfs) => 0; 25 | ''' 26 | 27 | # redundant slashes 28 | [cases.test_paths_redundant_slashes] 29 | code = ''' 30 | lfs_t lfs; 31 | lfs_format(&lfs, cfg) => 0; 32 | lfs_mount(&lfs, cfg) => 0; 33 | lfs_mkdir(&lfs, "tea") => 0; 34 | lfs_mkdir(&lfs, "tea/hottea") => 0; 35 | lfs_mkdir(&lfs, "tea/warmtea") => 0; 36 | lfs_mkdir(&lfs, "tea/coldtea") => 0; 37 | 38 | struct lfs_info info; 39 | lfs_stat(&lfs, "/tea/hottea", &info) => 0; 40 | assert(strcmp(info.name, "hottea") == 0); 41 | lfs_stat(&lfs, "//tea//hottea", &info) => 0; 42 | assert(strcmp(info.name, "hottea") == 0); 43 | lfs_stat(&lfs, "///tea///hottea", &info) => 0; 44 | assert(strcmp(info.name, "hottea") == 0); 45 | 46 | lfs_mkdir(&lfs, "////milk") => 0; 47 | lfs_stat(&lfs, "////milk", &info) => 0; 48 | assert(strcmp(info.name, "milk") == 0); 49 | lfs_stat(&lfs, "milk", &info) => 0; 50 | assert(strcmp(info.name, "milk") == 0); 51 | lfs_unmount(&lfs) => 0; 52 | ''' 53 | 54 | # dot path test 55 | [cases.test_paths_dot] 56 | code = ''' 57 | lfs_t lfs; 58 | lfs_format(&lfs, cfg) => 0; 59 | lfs_mount(&lfs, cfg) => 0; 60 | lfs_mkdir(&lfs, "tea") => 0; 61 | lfs_mkdir(&lfs, "tea/hottea") => 0; 62 | lfs_mkdir(&lfs, "tea/warmtea") => 0; 63 | lfs_mkdir(&lfs, "tea/coldtea") => 0; 64 | 65 | struct lfs_info info; 66 | lfs_stat(&lfs, "./tea/hottea", &info) => 0; 67 | assert(strcmp(info.name, "hottea") == 0); 68 | lfs_stat(&lfs, "/./tea/hottea", &info) => 0; 69 | assert(strcmp(info.name, "hottea") == 0); 70 | lfs_stat(&lfs, "/././tea/hottea", &info) => 0; 71 | assert(strcmp(info.name, "hottea") == 0); 72 | lfs_stat(&lfs, "/./tea/./hottea", &info) => 0; 73 | assert(strcmp(info.name, "hottea") == 0); 74 | 75 | lfs_mkdir(&lfs, "/./milk") => 0; 76 | lfs_stat(&lfs, "/./milk", &info) => 0; 77 | assert(strcmp(info.name, "milk") == 0); 78 | lfs_stat(&lfs, "milk", &info) => 0; 79 | assert(strcmp(info.name, "milk") == 0); 80 | lfs_unmount(&lfs) => 0; 81 | ''' 82 | 83 | # dot dot path test 84 | [cases.test_paths_dot_dot] 85 | code = ''' 86 | lfs_t lfs; 87 | lfs_format(&lfs, cfg) => 0; 88 | lfs_mount(&lfs, cfg) => 0; 89 | lfs_mkdir(&lfs, "tea") => 0; 90 | lfs_mkdir(&lfs, "tea/hottea") => 0; 91 | lfs_mkdir(&lfs, "tea/warmtea") => 0; 92 | lfs_mkdir(&lfs, "tea/coldtea") => 0; 93 | lfs_mkdir(&lfs, "coffee") => 0; 94 | lfs_mkdir(&lfs, "coffee/hotcoffee") => 0; 95 | lfs_mkdir(&lfs, "coffee/warmcoffee") => 0; 96 | lfs_mkdir(&lfs, "coffee/coldcoffee") => 0; 97 | 98 | struct lfs_info info; 99 | lfs_stat(&lfs, "coffee/../tea/hottea", &info) => 0; 100 | assert(strcmp(info.name, "hottea") == 0); 101 | lfs_stat(&lfs, "tea/coldtea/../hottea", &info) => 0; 102 | assert(strcmp(info.name, "hottea") == 0); 103 | lfs_stat(&lfs, "coffee/coldcoffee/../../tea/hottea", &info) => 0; 104 | assert(strcmp(info.name, "hottea") == 0); 105 | lfs_stat(&lfs, "coffee/../coffee/../tea/hottea", &info) => 0; 106 | assert(strcmp(info.name, "hottea") == 0); 107 | 108 | lfs_mkdir(&lfs, "coffee/../milk") => 0; 109 | lfs_stat(&lfs, "coffee/../milk", &info) => 0; 110 | strcmp(info.name, "milk") => 0; 111 | lfs_stat(&lfs, "milk", &info) => 0; 112 | strcmp(info.name, "milk") => 0; 113 | lfs_unmount(&lfs) => 0; 114 | ''' 115 | 116 | # trailing dot path test 117 | [cases.test_paths_trailing_dot] 118 | code = ''' 119 | lfs_t lfs; 120 | lfs_format(&lfs, cfg) => 0; 121 | lfs_mount(&lfs, cfg) => 0; 122 | lfs_mkdir(&lfs, "tea") => 0; 123 | lfs_mkdir(&lfs, "tea/hottea") => 0; 124 | lfs_mkdir(&lfs, "tea/warmtea") => 0; 125 | lfs_mkdir(&lfs, "tea/coldtea") => 0; 126 | 127 | struct lfs_info info; 128 | lfs_stat(&lfs, "tea/hottea/", &info) => 0; 129 | assert(strcmp(info.name, "hottea") == 0); 130 | lfs_stat(&lfs, "tea/hottea/.", &info) => 0; 131 | assert(strcmp(info.name, "hottea") == 0); 132 | lfs_stat(&lfs, "tea/hottea/./.", &info) => 0; 133 | assert(strcmp(info.name, "hottea") == 0); 134 | lfs_stat(&lfs, "tea/hottea/..", &info) => 0; 135 | assert(strcmp(info.name, "tea") == 0); 136 | lfs_stat(&lfs, "tea/hottea/../.", &info) => 0; 137 | assert(strcmp(info.name, "tea") == 0); 138 | lfs_unmount(&lfs) => 0; 139 | ''' 140 | 141 | # leading dot path test 142 | [cases.test_paths_leading_dot] 143 | code = ''' 144 | lfs_t lfs; 145 | lfs_format(&lfs, cfg) => 0; 146 | lfs_mount(&lfs, cfg) => 0; 147 | lfs_mkdir(&lfs, ".milk") => 0; 148 | struct lfs_info info; 149 | lfs_stat(&lfs, ".milk", &info) => 0; 150 | strcmp(info.name, ".milk") => 0; 151 | lfs_stat(&lfs, "tea/.././.milk", &info) => 0; 152 | strcmp(info.name, ".milk") => 0; 153 | lfs_unmount(&lfs) => 0; 154 | ''' 155 | 156 | # root dot dot path test 157 | [cases.test_paths_root_dot_dot] 158 | code = ''' 159 | lfs_t lfs; 160 | lfs_format(&lfs, cfg) => 0; 161 | lfs_mount(&lfs, cfg) => 0; 162 | lfs_mkdir(&lfs, "tea") => 0; 163 | lfs_mkdir(&lfs, "tea/hottea") => 0; 164 | lfs_mkdir(&lfs, "tea/warmtea") => 0; 165 | lfs_mkdir(&lfs, "tea/coldtea") => 0; 166 | lfs_mkdir(&lfs, "coffee") => 0; 167 | lfs_mkdir(&lfs, "coffee/hotcoffee") => 0; 168 | lfs_mkdir(&lfs, "coffee/warmcoffee") => 0; 169 | lfs_mkdir(&lfs, "coffee/coldcoffee") => 0; 170 | 171 | struct lfs_info info; 172 | lfs_stat(&lfs, "coffee/../../../../../../tea/hottea", &info) => 0; 173 | strcmp(info.name, "hottea") => 0; 174 | 175 | lfs_mkdir(&lfs, "coffee/../../../../../../milk") => 0; 176 | lfs_stat(&lfs, "coffee/../../../../../../milk", &info) => 0; 177 | strcmp(info.name, "milk") => 0; 178 | lfs_stat(&lfs, "milk", &info) => 0; 179 | strcmp(info.name, "milk") => 0; 180 | lfs_unmount(&lfs) => 0; 181 | ''' 182 | 183 | # invalid path tests 184 | [cases.test_paths_invalid] 185 | code = ''' 186 | lfs_t lfs; 187 | lfs_format(&lfs, cfg); 188 | lfs_mount(&lfs, cfg) => 0; 189 | struct lfs_info info; 190 | lfs_stat(&lfs, "dirt", &info) => LFS_ERR_NOENT; 191 | lfs_stat(&lfs, "dirt/ground", &info) => LFS_ERR_NOENT; 192 | lfs_stat(&lfs, "dirt/ground/earth", &info) => LFS_ERR_NOENT; 193 | 194 | lfs_remove(&lfs, "dirt") => LFS_ERR_NOENT; 195 | lfs_remove(&lfs, "dirt/ground") => LFS_ERR_NOENT; 196 | lfs_remove(&lfs, "dirt/ground/earth") => LFS_ERR_NOENT; 197 | 198 | lfs_mkdir(&lfs, "dirt/ground") => LFS_ERR_NOENT; 199 | lfs_file_t file; 200 | lfs_file_open(&lfs, &file, "dirt/ground", LFS_O_WRONLY | LFS_O_CREAT) 201 | => LFS_ERR_NOENT; 202 | lfs_mkdir(&lfs, "dirt/ground/earth") => LFS_ERR_NOENT; 203 | lfs_file_open(&lfs, &file, "dirt/ground/earth", LFS_O_WRONLY | LFS_O_CREAT) 204 | => LFS_ERR_NOENT; 205 | lfs_unmount(&lfs) => 0; 206 | ''' 207 | 208 | # root operations 209 | [cases.test_paths_root] 210 | code = ''' 211 | lfs_t lfs; 212 | lfs_format(&lfs, cfg) => 0; 213 | lfs_mount(&lfs, cfg) => 0; 214 | struct lfs_info info; 215 | lfs_stat(&lfs, "/", &info) => 0; 216 | assert(strcmp(info.name, "/") == 0); 217 | assert(info.type == LFS_TYPE_DIR); 218 | 219 | lfs_mkdir(&lfs, "/") => LFS_ERR_EXIST; 220 | lfs_file_t file; 221 | lfs_file_open(&lfs, &file, "/", LFS_O_WRONLY | LFS_O_CREAT) 222 | => LFS_ERR_ISDIR; 223 | 224 | lfs_remove(&lfs, "/") => LFS_ERR_INVAL; 225 | lfs_unmount(&lfs) => 0; 226 | ''' 227 | 228 | # root representations 229 | [cases.test_paths_root_reprs] 230 | code = ''' 231 | lfs_t lfs; 232 | lfs_format(&lfs, cfg) => 0; 233 | lfs_mount(&lfs, cfg) => 0; 234 | struct lfs_info info; 235 | lfs_stat(&lfs, "/", &info) => 0; 236 | assert(strcmp(info.name, "/") == 0); 237 | assert(info.type == LFS_TYPE_DIR); 238 | lfs_stat(&lfs, "", &info) => 0; 239 | assert(strcmp(info.name, "/") == 0); 240 | assert(info.type == LFS_TYPE_DIR); 241 | lfs_stat(&lfs, ".", &info) => 0; 242 | assert(strcmp(info.name, "/") == 0); 243 | assert(info.type == LFS_TYPE_DIR); 244 | lfs_stat(&lfs, "..", &info) => 0; 245 | assert(strcmp(info.name, "/") == 0); 246 | assert(info.type == LFS_TYPE_DIR); 247 | lfs_stat(&lfs, "//", &info) => 0; 248 | assert(strcmp(info.name, "/") == 0); 249 | assert(info.type == LFS_TYPE_DIR); 250 | lfs_stat(&lfs, "./", &info) => 0; 251 | assert(strcmp(info.name, "/") == 0); 252 | assert(info.type == LFS_TYPE_DIR); 253 | lfs_unmount(&lfs) => 0; 254 | ''' 255 | 256 | # superblock conflict test 257 | [cases.test_paths_superblock_conflict] 258 | code = ''' 259 | lfs_t lfs; 260 | lfs_format(&lfs, cfg) => 0; 261 | lfs_mount(&lfs, cfg) => 0; 262 | struct lfs_info info; 263 | lfs_stat(&lfs, "littlefs", &info) => LFS_ERR_NOENT; 264 | lfs_remove(&lfs, "littlefs") => LFS_ERR_NOENT; 265 | 266 | lfs_mkdir(&lfs, "littlefs") => 0; 267 | lfs_stat(&lfs, "littlefs", &info) => 0; 268 | assert(strcmp(info.name, "littlefs") == 0); 269 | assert(info.type == LFS_TYPE_DIR); 270 | lfs_remove(&lfs, "littlefs") => 0; 271 | lfs_stat(&lfs, "littlefs", &info) => LFS_ERR_NOENT; 272 | lfs_unmount(&lfs) => 0; 273 | ''' 274 | 275 | # max path test 276 | [cases.test_paths_max] 277 | code = ''' 278 | lfs_t lfs; 279 | lfs_format(&lfs, cfg) => 0; 280 | lfs_mount(&lfs, cfg) => 0; 281 | lfs_mkdir(&lfs, "coffee") => 0; 282 | lfs_mkdir(&lfs, "coffee/hotcoffee") => 0; 283 | lfs_mkdir(&lfs, "coffee/warmcoffee") => 0; 284 | lfs_mkdir(&lfs, "coffee/coldcoffee") => 0; 285 | 286 | char path[1024]; 287 | memset(path, 'w', LFS_NAME_MAX+1); 288 | path[LFS_NAME_MAX+1] = '\0'; 289 | lfs_mkdir(&lfs, path) => LFS_ERR_NAMETOOLONG; 290 | lfs_file_t file; 291 | lfs_file_open(&lfs, &file, path, LFS_O_WRONLY | LFS_O_CREAT) 292 | => LFS_ERR_NAMETOOLONG; 293 | 294 | memcpy(path, "coffee/", strlen("coffee/")); 295 | memset(path+strlen("coffee/"), 'w', LFS_NAME_MAX+1); 296 | path[strlen("coffee/")+LFS_NAME_MAX+1] = '\0'; 297 | lfs_mkdir(&lfs, path) => LFS_ERR_NAMETOOLONG; 298 | lfs_file_open(&lfs, &file, path, LFS_O_WRONLY | LFS_O_CREAT) 299 | => LFS_ERR_NAMETOOLONG; 300 | lfs_unmount(&lfs) => 0; 301 | ''' 302 | 303 | # really big path test 304 | [cases.test_paths_really_big] 305 | code = ''' 306 | lfs_t lfs; 307 | lfs_format(&lfs, cfg) => 0; 308 | lfs_mount(&lfs, cfg) => 0; 309 | lfs_mkdir(&lfs, "coffee") => 0; 310 | lfs_mkdir(&lfs, "coffee/hotcoffee") => 0; 311 | lfs_mkdir(&lfs, "coffee/warmcoffee") => 0; 312 | lfs_mkdir(&lfs, "coffee/coldcoffee") => 0; 313 | 314 | char path[1024]; 315 | memset(path, 'w', LFS_NAME_MAX); 316 | path[LFS_NAME_MAX] = '\0'; 317 | lfs_mkdir(&lfs, path) => 0; 318 | lfs_remove(&lfs, path) => 0; 319 | lfs_file_t file; 320 | lfs_file_open(&lfs, &file, path, 321 | LFS_O_WRONLY | LFS_O_CREAT) => 0; 322 | lfs_file_close(&lfs, &file) => 0; 323 | lfs_remove(&lfs, path) => 0; 324 | 325 | memcpy(path, "coffee/", strlen("coffee/")); 326 | memset(path+strlen("coffee/"), 'w', LFS_NAME_MAX); 327 | path[strlen("coffee/")+LFS_NAME_MAX] = '\0'; 328 | lfs_mkdir(&lfs, path) => 0; 329 | lfs_remove(&lfs, path) => 0; 330 | lfs_file_open(&lfs, &file, path, 331 | LFS_O_WRONLY | LFS_O_CREAT) => 0; 332 | lfs_file_close(&lfs, &file) => 0; 333 | lfs_remove(&lfs, path) => 0; 334 | lfs_unmount(&lfs) => 0; 335 | ''' 336 | 337 | -------------------------------------------------------------------------------- /tests/test_powerloss.toml: -------------------------------------------------------------------------------- 1 | # There are already a number of tests that test general operations under 2 | # power-loss (see the reentrant attribute). These tests are for explicitly 3 | # testing specific corner cases. 4 | 5 | # only a revision count 6 | [cases.test_powerloss_only_rev] 7 | code = ''' 8 | lfs_t lfs; 9 | lfs_format(&lfs, cfg) => 0; 10 | 11 | lfs_mount(&lfs, cfg) => 0; 12 | lfs_mkdir(&lfs, "notebook") => 0; 13 | lfs_file_t file; 14 | lfs_file_open(&lfs, &file, "notebook/paper", 15 | LFS_O_WRONLY | LFS_O_CREAT | LFS_O_APPEND) => 0; 16 | char buffer[256]; 17 | strcpy(buffer, "hello"); 18 | lfs_size_t size = strlen("hello"); 19 | for (int i = 0; i < 5; i++) { 20 | lfs_file_write(&lfs, &file, buffer, size) => size; 21 | lfs_file_sync(&lfs, &file) => 0; 22 | } 23 | lfs_file_close(&lfs, &file) => 0; 24 | 25 | char rbuffer[256]; 26 | lfs_file_open(&lfs, &file, "notebook/paper", LFS_O_RDONLY) => 0; 27 | for (int i = 0; i < 5; i++) { 28 | lfs_file_read(&lfs, &file, rbuffer, size) => size; 29 | assert(memcmp(rbuffer, buffer, size) == 0); 30 | } 31 | lfs_file_close(&lfs, &file) => 0; 32 | lfs_unmount(&lfs) => 0; 33 | 34 | // get pair/rev count 35 | lfs_mount(&lfs, cfg) => 0; 36 | lfs_dir_t dir; 37 | lfs_dir_open(&lfs, &dir, "notebook") => 0; 38 | lfs_block_t pair[2] = {dir.m.pair[0], dir.m.pair[1]}; 39 | uint32_t rev = dir.m.rev; 40 | lfs_dir_close(&lfs, &dir) => 0; 41 | lfs_unmount(&lfs) => 0; 42 | 43 | // write just the revision count 44 | uint8_t bbuffer[BLOCK_SIZE]; 45 | cfg->read(cfg, pair[1], 0, bbuffer, BLOCK_SIZE) => 0; 46 | 47 | memcpy(bbuffer, &(uint32_t){lfs_tole32(rev+1)}, sizeof(uint32_t)); 48 | 49 | cfg->erase(cfg, pair[1]) => 0; 50 | cfg->prog(cfg, pair[1], 0, bbuffer, BLOCK_SIZE) => 0; 51 | 52 | lfs_mount(&lfs, cfg) => 0; 53 | 54 | // can read? 55 | lfs_file_open(&lfs, &file, "notebook/paper", LFS_O_RDONLY) => 0; 56 | for (int i = 0; i < 5; i++) { 57 | lfs_file_read(&lfs, &file, rbuffer, size) => size; 58 | assert(memcmp(rbuffer, buffer, size) == 0); 59 | } 60 | lfs_file_close(&lfs, &file) => 0; 61 | 62 | // can write? 63 | lfs_file_open(&lfs, &file, "notebook/paper", 64 | LFS_O_WRONLY | LFS_O_APPEND) => 0; 65 | strcpy(buffer, "goodbye"); 66 | size = strlen("goodbye"); 67 | for (int i = 0; i < 5; i++) { 68 | lfs_file_write(&lfs, &file, buffer, size) => size; 69 | lfs_file_sync(&lfs, &file) => 0; 70 | } 71 | lfs_file_close(&lfs, &file) => 0; 72 | 73 | lfs_file_open(&lfs, &file, "notebook/paper", LFS_O_RDONLY) => 0; 74 | strcpy(buffer, "hello"); 75 | size = strlen("hello"); 76 | for (int i = 0; i < 5; i++) { 77 | lfs_file_read(&lfs, &file, rbuffer, size) => size; 78 | assert(memcmp(rbuffer, buffer, size) == 0); 79 | } 80 | strcpy(buffer, "goodbye"); 81 | size = strlen("goodbye"); 82 | for (int i = 0; i < 5; i++) { 83 | lfs_file_read(&lfs, &file, rbuffer, size) => size; 84 | assert(memcmp(rbuffer, buffer, size) == 0); 85 | } 86 | lfs_file_close(&lfs, &file) => 0; 87 | 88 | lfs_unmount(&lfs) => 0; 89 | ''' 90 | 91 | # partial prog, may not be byte in order! 92 | [cases.test_powerloss_partial_prog] 93 | if = ''' 94 | PROG_SIZE < BLOCK_SIZE 95 | && (DISK_VERSION == 0 || DISK_VERSION >= 0x00020001) 96 | ''' 97 | defines.BYTE_OFF = ["0", "PROG_SIZE-1", "PROG_SIZE/2"] 98 | defines.BYTE_VALUE = [0x33, 0xcc] 99 | in = "lfs.c" 100 | code = ''' 101 | lfs_t lfs; 102 | lfs_format(&lfs, cfg) => 0; 103 | 104 | lfs_mount(&lfs, cfg) => 0; 105 | lfs_mkdir(&lfs, "notebook") => 0; 106 | lfs_file_t file; 107 | lfs_file_open(&lfs, &file, "notebook/paper", 108 | LFS_O_WRONLY | LFS_O_CREAT | LFS_O_APPEND) => 0; 109 | char buffer[256]; 110 | strcpy(buffer, "hello"); 111 | lfs_size_t size = strlen("hello"); 112 | for (int i = 0; i < 5; i++) { 113 | lfs_file_write(&lfs, &file, buffer, size) => size; 114 | lfs_file_sync(&lfs, &file) => 0; 115 | } 116 | lfs_file_close(&lfs, &file) => 0; 117 | 118 | char rbuffer[256]; 119 | lfs_file_open(&lfs, &file, "notebook/paper", LFS_O_RDONLY) => 0; 120 | for (int i = 0; i < 5; i++) { 121 | lfs_file_read(&lfs, &file, rbuffer, size) => size; 122 | assert(memcmp(rbuffer, buffer, size) == 0); 123 | } 124 | lfs_file_close(&lfs, &file) => 0; 125 | lfs_unmount(&lfs) => 0; 126 | 127 | // imitate a partial prog, value should not matter, if littlefs 128 | // doesn't notice the partial prog testbd will assert 129 | 130 | // get offset to next prog 131 | lfs_mount(&lfs, cfg) => 0; 132 | lfs_dir_t dir; 133 | lfs_dir_open(&lfs, &dir, "notebook") => 0; 134 | lfs_block_t block = dir.m.pair[0]; 135 | lfs_off_t off = dir.m.off; 136 | lfs_dir_close(&lfs, &dir) => 0; 137 | lfs_unmount(&lfs) => 0; 138 | 139 | // tweak byte 140 | uint8_t bbuffer[BLOCK_SIZE]; 141 | cfg->read(cfg, block, 0, bbuffer, BLOCK_SIZE) => 0; 142 | 143 | bbuffer[off + BYTE_OFF] = BYTE_VALUE; 144 | 145 | cfg->erase(cfg, block) => 0; 146 | cfg->prog(cfg, block, 0, bbuffer, BLOCK_SIZE) => 0; 147 | 148 | lfs_mount(&lfs, cfg) => 0; 149 | 150 | // can read? 151 | lfs_file_open(&lfs, &file, "notebook/paper", LFS_O_RDONLY) => 0; 152 | for (int i = 0; i < 5; i++) { 153 | lfs_file_read(&lfs, &file, rbuffer, size) => size; 154 | assert(memcmp(rbuffer, buffer, size) == 0); 155 | } 156 | lfs_file_close(&lfs, &file) => 0; 157 | 158 | // can write? 159 | lfs_file_open(&lfs, &file, "notebook/paper", 160 | LFS_O_WRONLY | LFS_O_APPEND) => 0; 161 | strcpy(buffer, "goodbye"); 162 | size = strlen("goodbye"); 163 | for (int i = 0; i < 5; i++) { 164 | lfs_file_write(&lfs, &file, buffer, size) => size; 165 | lfs_file_sync(&lfs, &file) => 0; 166 | } 167 | lfs_file_close(&lfs, &file) => 0; 168 | 169 | lfs_file_open(&lfs, &file, "notebook/paper", LFS_O_RDONLY) => 0; 170 | strcpy(buffer, "hello"); 171 | size = strlen("hello"); 172 | for (int i = 0; i < 5; i++) { 173 | lfs_file_read(&lfs, &file, rbuffer, size) => size; 174 | assert(memcmp(rbuffer, buffer, size) == 0); 175 | } 176 | strcpy(buffer, "goodbye"); 177 | size = strlen("goodbye"); 178 | for (int i = 0; i < 5; i++) { 179 | lfs_file_read(&lfs, &file, rbuffer, size) => size; 180 | assert(memcmp(rbuffer, buffer, size) == 0); 181 | } 182 | lfs_file_close(&lfs, &file) => 0; 183 | 184 | lfs_unmount(&lfs) => 0; 185 | ''' 186 | -------------------------------------------------------------------------------- /tests/test_relocations.toml: -------------------------------------------------------------------------------- 1 | # specific corner cases worth explicitly testing for 2 | [cases.test_relocations_dangling_split_dir] 3 | defines.ITERATIONS = 20 4 | defines.COUNT = 10 5 | defines.BLOCK_CYCLES = [8, 1] 6 | code = ''' 7 | lfs_t lfs; 8 | lfs_format(&lfs, cfg) => 0; 9 | // fill up filesystem so only ~16 blocks are left 10 | lfs_mount(&lfs, cfg) => 0; 11 | lfs_file_t file; 12 | lfs_file_open(&lfs, &file, "padding", LFS_O_CREAT | LFS_O_WRONLY) => 0; 13 | uint8_t buffer[512]; 14 | memset(buffer, 0, 512); 15 | while (BLOCK_COUNT - lfs_fs_size(&lfs) > 16) { 16 | lfs_file_write(&lfs, &file, buffer, 512) => 512; 17 | } 18 | lfs_file_close(&lfs, &file) => 0; 19 | // make a child dir to use in bounded space 20 | lfs_mkdir(&lfs, "child") => 0; 21 | lfs_unmount(&lfs) => 0; 22 | 23 | lfs_mount(&lfs, cfg) => 0; 24 | for (unsigned j = 0; j < ITERATIONS; j++) { 25 | for (unsigned i = 0; i < COUNT; i++) { 26 | char path[1024]; 27 | sprintf(path, "child/test%03d_loooooooooooooooooong_name", i); 28 | lfs_file_open(&lfs, &file, path, LFS_O_CREAT | LFS_O_WRONLY) => 0; 29 | lfs_file_close(&lfs, &file) => 0; 30 | } 31 | 32 | lfs_dir_t dir; 33 | struct lfs_info info; 34 | lfs_dir_open(&lfs, &dir, "child") => 0; 35 | lfs_dir_read(&lfs, &dir, &info) => 1; 36 | lfs_dir_read(&lfs, &dir, &info) => 1; 37 | for (unsigned i = 0; i < COUNT; i++) { 38 | char path[1024]; 39 | sprintf(path, "test%03d_loooooooooooooooooong_name", i); 40 | lfs_dir_read(&lfs, &dir, &info) => 1; 41 | strcmp(info.name, path) => 0; 42 | } 43 | lfs_dir_read(&lfs, &dir, &info) => 0; 44 | lfs_dir_close(&lfs, &dir) => 0; 45 | 46 | if (j == (unsigned)ITERATIONS-1) { 47 | break; 48 | } 49 | 50 | for (unsigned i = 0; i < COUNT; i++) { 51 | char path[1024]; 52 | sprintf(path, "child/test%03d_loooooooooooooooooong_name", i); 53 | lfs_remove(&lfs, path) => 0; 54 | } 55 | } 56 | lfs_unmount(&lfs) => 0; 57 | 58 | lfs_mount(&lfs, cfg) => 0; 59 | lfs_dir_t dir; 60 | struct lfs_info info; 61 | lfs_dir_open(&lfs, &dir, "child") => 0; 62 | lfs_dir_read(&lfs, &dir, &info) => 1; 63 | lfs_dir_read(&lfs, &dir, &info) => 1; 64 | for (unsigned i = 0; i < COUNT; i++) { 65 | char path[1024]; 66 | sprintf(path, "test%03d_loooooooooooooooooong_name", i); 67 | lfs_dir_read(&lfs, &dir, &info) => 1; 68 | strcmp(info.name, path) => 0; 69 | } 70 | lfs_dir_read(&lfs, &dir, &info) => 0; 71 | lfs_dir_close(&lfs, &dir) => 0; 72 | for (unsigned i = 0; i < COUNT; i++) { 73 | char path[1024]; 74 | sprintf(path, "child/test%03d_loooooooooooooooooong_name", i); 75 | lfs_remove(&lfs, path) => 0; 76 | } 77 | lfs_unmount(&lfs) => 0; 78 | ''' 79 | 80 | [cases.test_relocations_outdated_head] 81 | defines.ITERATIONS = 20 82 | defines.COUNT = 10 83 | defines.BLOCK_CYCLES = [8, 1] 84 | code = ''' 85 | lfs_t lfs; 86 | lfs_format(&lfs, cfg) => 0; 87 | // fill up filesystem so only ~16 blocks are left 88 | lfs_mount(&lfs, cfg) => 0; 89 | lfs_file_t file; 90 | lfs_file_open(&lfs, &file, "padding", LFS_O_CREAT | LFS_O_WRONLY) => 0; 91 | uint8_t buffer[512]; 92 | memset(buffer, 0, 512); 93 | while (BLOCK_COUNT - lfs_fs_size(&lfs) > 16) { 94 | lfs_file_write(&lfs, &file, buffer, 512) => 512; 95 | } 96 | lfs_file_close(&lfs, &file) => 0; 97 | // make a child dir to use in bounded space 98 | lfs_mkdir(&lfs, "child") => 0; 99 | lfs_unmount(&lfs) => 0; 100 | 101 | lfs_mount(&lfs, cfg) => 0; 102 | for (unsigned j = 0; j < ITERATIONS; j++) { 103 | for (unsigned i = 0; i < COUNT; i++) { 104 | char path[1024]; 105 | sprintf(path, "child/test%03d_loooooooooooooooooong_name", i); 106 | lfs_file_open(&lfs, &file, path, LFS_O_CREAT | LFS_O_WRONLY) => 0; 107 | lfs_file_close(&lfs, &file) => 0; 108 | } 109 | 110 | lfs_dir_t dir; 111 | struct lfs_info info; 112 | lfs_dir_open(&lfs, &dir, "child") => 0; 113 | lfs_dir_read(&lfs, &dir, &info) => 1; 114 | lfs_dir_read(&lfs, &dir, &info) => 1; 115 | for (unsigned i = 0; i < COUNT; i++) { 116 | char path[1024]; 117 | sprintf(path, "test%03d_loooooooooooooooooong_name", i); 118 | lfs_dir_read(&lfs, &dir, &info) => 1; 119 | strcmp(info.name, path) => 0; 120 | info.size => 0; 121 | 122 | sprintf(path, "child/test%03d_loooooooooooooooooong_name", i); 123 | lfs_file_open(&lfs, &file, path, LFS_O_WRONLY) => 0; 124 | lfs_file_write(&lfs, &file, "hi", 2) => 2; 125 | lfs_file_close(&lfs, &file) => 0; 126 | } 127 | lfs_dir_read(&lfs, &dir, &info) => 0; 128 | 129 | lfs_dir_rewind(&lfs, &dir) => 0; 130 | lfs_dir_read(&lfs, &dir, &info) => 1; 131 | lfs_dir_read(&lfs, &dir, &info) => 1; 132 | for (unsigned i = 0; i < COUNT; i++) { 133 | char path[1024]; 134 | sprintf(path, "test%03d_loooooooooooooooooong_name", i); 135 | lfs_dir_read(&lfs, &dir, &info) => 1; 136 | strcmp(info.name, path) => 0; 137 | info.size => 2; 138 | 139 | sprintf(path, "child/test%03d_loooooooooooooooooong_name", i); 140 | lfs_file_open(&lfs, &file, path, LFS_O_WRONLY) => 0; 141 | lfs_file_write(&lfs, &file, "hi", 2) => 2; 142 | lfs_file_close(&lfs, &file) => 0; 143 | } 144 | lfs_dir_read(&lfs, &dir, &info) => 0; 145 | 146 | lfs_dir_rewind(&lfs, &dir) => 0; 147 | lfs_dir_read(&lfs, &dir, &info) => 1; 148 | lfs_dir_read(&lfs, &dir, &info) => 1; 149 | for (unsigned i = 0; i < COUNT; i++) { 150 | char path[1024]; 151 | sprintf(path, "test%03d_loooooooooooooooooong_name", i); 152 | lfs_dir_read(&lfs, &dir, &info) => 1; 153 | strcmp(info.name, path) => 0; 154 | info.size => 2; 155 | } 156 | lfs_dir_read(&lfs, &dir, &info) => 0; 157 | lfs_dir_close(&lfs, &dir) => 0; 158 | 159 | for (unsigned i = 0; i < COUNT; i++) { 160 | char path[1024]; 161 | sprintf(path, "child/test%03d_loooooooooooooooooong_name", i); 162 | lfs_remove(&lfs, path) => 0; 163 | } 164 | } 165 | lfs_unmount(&lfs) => 0; 166 | ''' 167 | 168 | # reentrant testing for relocations, this is the same as the 169 | # orphan testing, except here we also set block_cycles so that 170 | # almost every tree operation needs a relocation 171 | [cases.test_relocations_reentrant] 172 | reentrant = true 173 | # TODO fix this case, caused by non-DAG trees 174 | # NOTE the second condition is required 175 | if = '!(DEPTH == 3 && CACHE_SIZE != 64) && 2*FILES < BLOCK_COUNT' 176 | defines = [ 177 | {FILES=6, DEPTH=1, CYCLES=20, BLOCK_CYCLES=1}, 178 | {FILES=26, DEPTH=1, CYCLES=20, BLOCK_CYCLES=1}, 179 | {FILES=3, DEPTH=3, CYCLES=20, BLOCK_CYCLES=1}, 180 | ] 181 | code = ''' 182 | lfs_t lfs; 183 | int err = lfs_mount(&lfs, cfg); 184 | if (err) { 185 | lfs_format(&lfs, cfg) => 0; 186 | lfs_mount(&lfs, cfg) => 0; 187 | } 188 | 189 | uint32_t prng = 1; 190 | const char alpha[] = "abcdefghijklmnopqrstuvwxyz"; 191 | for (unsigned i = 0; i < CYCLES; i++) { 192 | // create random path 193 | char full_path[256]; 194 | for (unsigned d = 0; d < DEPTH; d++) { 195 | sprintf(&full_path[2*d], "/%c", alpha[TEST_PRNG(&prng) % FILES]); 196 | } 197 | 198 | // if it does not exist, we create it, else we destroy 199 | struct lfs_info info; 200 | int res = lfs_stat(&lfs, full_path, &info); 201 | if (res == LFS_ERR_NOENT) { 202 | // create each directory in turn, ignore if dir already exists 203 | for (unsigned d = 0; d < DEPTH; d++) { 204 | char path[1024]; 205 | strcpy(path, full_path); 206 | path[2*d+2] = '\0'; 207 | err = lfs_mkdir(&lfs, path); 208 | assert(!err || err == LFS_ERR_EXIST); 209 | } 210 | 211 | for (unsigned d = 0; d < DEPTH; d++) { 212 | char path[1024]; 213 | strcpy(path, full_path); 214 | path[2*d+2] = '\0'; 215 | lfs_stat(&lfs, path, &info) => 0; 216 | assert(strcmp(info.name, &path[2*d+1]) == 0); 217 | assert(info.type == LFS_TYPE_DIR); 218 | } 219 | } else { 220 | // is valid dir? 221 | assert(strcmp(info.name, &full_path[2*(DEPTH-1)+1]) == 0); 222 | assert(info.type == LFS_TYPE_DIR); 223 | 224 | // try to delete path in reverse order, ignore if dir is not empty 225 | for (unsigned d = DEPTH-1; d+1 > 0; d--) { 226 | char path[1024]; 227 | strcpy(path, full_path); 228 | path[2*d+2] = '\0'; 229 | err = lfs_remove(&lfs, path); 230 | assert(!err || err == LFS_ERR_NOTEMPTY); 231 | } 232 | 233 | lfs_stat(&lfs, full_path, &info) => LFS_ERR_NOENT; 234 | } 235 | } 236 | lfs_unmount(&lfs) => 0; 237 | ''' 238 | 239 | # reentrant testing for relocations, but now with random renames! 240 | [cases.test_relocations_reentrant_renames] 241 | reentrant = true 242 | # TODO fix this case, caused by non-DAG trees 243 | # NOTE the second condition is required 244 | if = '!(DEPTH == 3 && CACHE_SIZE != 64) && 2*FILES < BLOCK_COUNT' 245 | defines = [ 246 | {FILES=6, DEPTH=1, CYCLES=20, BLOCK_CYCLES=1}, 247 | {FILES=26, DEPTH=1, CYCLES=20, BLOCK_CYCLES=1}, 248 | {FILES=3, DEPTH=3, CYCLES=20, BLOCK_CYCLES=1}, 249 | ] 250 | code = ''' 251 | lfs_t lfs; 252 | int err = lfs_mount(&lfs, cfg); 253 | if (err) { 254 | lfs_format(&lfs, cfg) => 0; 255 | lfs_mount(&lfs, cfg) => 0; 256 | } 257 | 258 | uint32_t prng = 1; 259 | const char alpha[] = "abcdefghijklmnopqrstuvwxyz"; 260 | for (unsigned i = 0; i < CYCLES; i++) { 261 | // create random path 262 | char full_path[256]; 263 | for (unsigned d = 0; d < DEPTH; d++) { 264 | sprintf(&full_path[2*d], "/%c", alpha[TEST_PRNG(&prng) % FILES]); 265 | } 266 | 267 | // if it does not exist, we create it, else we destroy 268 | struct lfs_info info; 269 | int res = lfs_stat(&lfs, full_path, &info); 270 | assert(!res || res == LFS_ERR_NOENT); 271 | if (res == LFS_ERR_NOENT) { 272 | // create each directory in turn, ignore if dir already exists 273 | for (unsigned d = 0; d < DEPTH; d++) { 274 | char path[1024]; 275 | strcpy(path, full_path); 276 | path[2*d+2] = '\0'; 277 | err = lfs_mkdir(&lfs, path); 278 | assert(!err || err == LFS_ERR_EXIST); 279 | } 280 | 281 | for (unsigned d = 0; d < DEPTH; d++) { 282 | char path[1024]; 283 | strcpy(path, full_path); 284 | path[2*d+2] = '\0'; 285 | lfs_stat(&lfs, path, &info) => 0; 286 | assert(strcmp(info.name, &path[2*d+1]) == 0); 287 | assert(info.type == LFS_TYPE_DIR); 288 | } 289 | } else { 290 | assert(strcmp(info.name, &full_path[2*(DEPTH-1)+1]) == 0); 291 | assert(info.type == LFS_TYPE_DIR); 292 | 293 | // create new random path 294 | char new_path[256]; 295 | for (unsigned d = 0; d < DEPTH; d++) { 296 | sprintf(&new_path[2*d], "/%c", alpha[TEST_PRNG(&prng) % FILES]); 297 | } 298 | 299 | // if new path does not exist, rename, otherwise destroy 300 | res = lfs_stat(&lfs, new_path, &info); 301 | assert(!res || res == LFS_ERR_NOENT); 302 | if (res == LFS_ERR_NOENT) { 303 | // stop once some dir is renamed 304 | for (unsigned d = 0; d < DEPTH; d++) { 305 | char path[1024]; 306 | strcpy(&path[2*d], &full_path[2*d]); 307 | path[2*d+2] = '\0'; 308 | strcpy(&path[128+2*d], &new_path[2*d]); 309 | path[128+2*d+2] = '\0'; 310 | err = lfs_rename(&lfs, path, path+128); 311 | assert(!err || err == LFS_ERR_NOTEMPTY); 312 | if (!err) { 313 | strcpy(path, path+128); 314 | } 315 | } 316 | 317 | for (unsigned d = 0; d < DEPTH; d++) { 318 | char path[1024]; 319 | strcpy(path, new_path); 320 | path[2*d+2] = '\0'; 321 | lfs_stat(&lfs, path, &info) => 0; 322 | assert(strcmp(info.name, &path[2*d+1]) == 0); 323 | assert(info.type == LFS_TYPE_DIR); 324 | } 325 | 326 | lfs_stat(&lfs, full_path, &info) => LFS_ERR_NOENT; 327 | } else { 328 | // try to delete path in reverse order, 329 | // ignore if dir is not empty 330 | for (unsigned d = DEPTH-1; d+1 > 0; d--) { 331 | char path[1024]; 332 | strcpy(path, full_path); 333 | path[2*d+2] = '\0'; 334 | err = lfs_remove(&lfs, path); 335 | assert(!err || err == LFS_ERR_NOTEMPTY); 336 | } 337 | 338 | lfs_stat(&lfs, full_path, &info) => LFS_ERR_NOENT; 339 | } 340 | } 341 | } 342 | lfs_unmount(&lfs) => 0; 343 | ''' 344 | --------------------------------------------------------------------------------