forked from Imagelibrary/littlefs
Compare commits
66 Commits
rbyd-ro-le
...
v2.7.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
130790fa91 | ||
|
|
531d5e5073 | ||
|
|
e40d8f5410 | ||
|
|
5a834b6fc1 | ||
|
|
d775b46e3d | ||
|
|
96fb8bec85 | ||
|
|
611c9b20db | ||
|
|
a942cdba66 | ||
|
|
225fc31a17 | ||
|
|
5db368c0a2 | ||
|
|
f09c6a4eb7 | ||
|
|
79cc75d18f | ||
|
|
eb9af7abe5 | ||
|
|
b72c96d440 | ||
|
|
265692e709 | ||
|
|
08a132e048 | ||
|
|
c5fb3f181b | ||
|
|
8610f7c36b | ||
|
|
a51be18765 | ||
|
|
a7ccc1df59 | ||
|
|
fdee127f74 | ||
|
|
87bbf1d374 | ||
|
|
66f07563c3 | ||
|
|
5eed341059 | ||
|
|
97e2526a81 | ||
|
|
8a4ee65fc3 | ||
|
|
6fda813ce8 | ||
|
|
f2bc6a8e88 | ||
|
|
ec3ec86bcc | ||
|
|
405f33214a | ||
|
|
3dca02911f | ||
|
|
259535ee73 | ||
|
|
94d9e097a6 | ||
|
|
dd03c27476 | ||
|
|
23a4a089b5 | ||
|
|
b6773e68bf | ||
|
|
922a35b3a5 | ||
|
|
92298c749d | ||
|
|
50b394ca36 | ||
|
|
a99574cd5b | ||
|
|
363a8b56cf | ||
|
|
e43d381135 | ||
|
|
ee6a51bbbe | ||
|
|
01ac033d47 | ||
|
|
2a18e03cb8 | ||
|
|
6f074ebe31 | ||
|
|
0a7eca0bd5 | ||
|
|
3e25dfc16c | ||
|
|
9e28c75482 | ||
|
|
4c9360020e | ||
|
|
ca0da3d490 | ||
|
|
116332d3f7 | ||
|
|
f0cc1db793 | ||
|
|
bf045dd13c | ||
|
|
b33a5b3f85 | ||
|
|
384a498762 | ||
|
|
b0a4a44e5b | ||
|
|
aae897ffd0 | ||
|
|
e57402c8e9 | ||
|
|
6dc18c38c1 | ||
|
|
d5dc4872cb | ||
|
|
24795e6b74 | ||
|
|
7b151e1abb | ||
|
|
1278ec1d08 | ||
|
|
d9333ecbd4 | ||
|
|
3ae87f4e29 |
4
.gitattributes
vendored
Normal file
4
.gitattributes
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
# GitHub really wants to mark littlefs as a python project, telling it to
|
||||||
|
# reclassify our test .toml files as C code (which they are 95% of anyways)
|
||||||
|
# remedies this
|
||||||
|
*.toml linguist-language=c
|
||||||
7
.github/workflows/release.yml
vendored
7
.github/workflows/release.yml
vendored
@@ -81,7 +81,9 @@ jobs:
|
|||||||
- name: find-prev-version
|
- name: find-prev-version
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
run: |
|
run: |
|
||||||
LFS_PREV_VERSION="$(git describe --tags --abbrev=0 --match 'v*')"
|
LFS_PREV_VERSION="$( \
|
||||||
|
git describe --tags --abbrev=0 --match 'v*' \
|
||||||
|
|| true)"
|
||||||
echo "LFS_PREV_VERSION=$LFS_PREV_VERSION"
|
echo "LFS_PREV_VERSION=$LFS_PREV_VERSION"
|
||||||
echo "LFS_PREV_VERSION=$LFS_PREV_VERSION" >> $GITHUB_ENV
|
echo "LFS_PREV_VERSION=$LFS_PREV_VERSION" >> $GITHUB_ENV
|
||||||
|
|
||||||
@@ -102,7 +104,7 @@ jobs:
|
|||||||
# sizes table
|
# sizes table
|
||||||
i=0
|
i=0
|
||||||
j=0
|
j=0
|
||||||
for c in "" readonly threadsafe migrate error-asserts
|
for c in "" readonly threadsafe multiversion migrate error-asserts
|
||||||
do
|
do
|
||||||
# per-config results
|
# per-config results
|
||||||
c_or_default=${c:-default}
|
c_or_default=${c:-default}
|
||||||
@@ -240,6 +242,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
# create release and patch version tag (vN.N.N)
|
# create release and patch version tag (vN.N.N)
|
||||||
# only draft if not a patch release
|
# only draft if not a patch release
|
||||||
|
touch release.txt
|
||||||
[ -e table.txt ] && cat table.txt >> release.txt
|
[ -e table.txt ] && cat table.txt >> release.txt
|
||||||
echo >> release.txt
|
echo >> release.txt
|
||||||
[ -e changes.txt ] && cat changes.txt >> release.txt
|
[ -e changes.txt ] && cat changes.txt >> release.txt
|
||||||
|
|||||||
98
.github/workflows/test.yml
vendored
98
.github/workflows/test.yml
vendored
@@ -170,6 +170,27 @@ jobs:
|
|||||||
cp lfs.data.csv sizes/${{matrix.arch}}-threadsafe.data.csv
|
cp lfs.data.csv sizes/${{matrix.arch}}-threadsafe.data.csv
|
||||||
cp lfs.stack.csv sizes/${{matrix.arch}}-threadsafe.stack.csv
|
cp lfs.stack.csv sizes/${{matrix.arch}}-threadsafe.stack.csv
|
||||||
cp lfs.structs.csv sizes/${{matrix.arch}}-threadsafe.structs.csv
|
cp lfs.structs.csv sizes/${{matrix.arch}}-threadsafe.structs.csv
|
||||||
|
- name: sizes-multiversion
|
||||||
|
run: |
|
||||||
|
make clean
|
||||||
|
CFLAGS="$CFLAGS \
|
||||||
|
-DLFS_NO_ASSERT \
|
||||||
|
-DLFS_NO_DEBUG \
|
||||||
|
-DLFS_NO_WARN \
|
||||||
|
-DLFS_NO_ERROR \
|
||||||
|
-DLFS_MULTIVERSION" \
|
||||||
|
make lfs.code.csv lfs.data.csv lfs.stack.csv lfs.structs.csv
|
||||||
|
./scripts/structs.py -u lfs.structs.csv
|
||||||
|
./scripts/summary.py lfs.code.csv lfs.data.csv lfs.stack.csv \
|
||||||
|
-bfunction \
|
||||||
|
-fcode=code_size \
|
||||||
|
-fdata=data_size \
|
||||||
|
-fstack=stack_limit --max=stack_limit
|
||||||
|
mkdir -p sizes
|
||||||
|
cp lfs.code.csv sizes/${{matrix.arch}}-multiversion.code.csv
|
||||||
|
cp lfs.data.csv sizes/${{matrix.arch}}-multiversion.data.csv
|
||||||
|
cp lfs.stack.csv sizes/${{matrix.arch}}-multiversion.stack.csv
|
||||||
|
cp lfs.structs.csv sizes/${{matrix.arch}}-multiversion.structs.csv
|
||||||
- name: sizes-migrate
|
- name: sizes-migrate
|
||||||
run: |
|
run: |
|
||||||
make clean
|
make clean
|
||||||
@@ -353,6 +374,42 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
CFLAGS="$CFLAGS -DLFS_NO_INTRINSICS" make test
|
CFLAGS="$CFLAGS -DLFS_NO_INTRINSICS" make test
|
||||||
|
|
||||||
|
# run LFS_MULTIVERSION tests
|
||||||
|
test-multiversion:
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: install
|
||||||
|
run: |
|
||||||
|
# need a few things
|
||||||
|
sudo apt-get update -qq
|
||||||
|
sudo apt-get install -qq gcc python3 python3-pip
|
||||||
|
pip3 install toml
|
||||||
|
gcc --version
|
||||||
|
python3 --version
|
||||||
|
- name: test-multiversion
|
||||||
|
run: |
|
||||||
|
CFLAGS="$CFLAGS -DLFS_MULTIVERSION" make test
|
||||||
|
|
||||||
|
# run tests on the older version lfs2.0
|
||||||
|
test-lfs2_0:
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: install
|
||||||
|
run: |
|
||||||
|
# need a few things
|
||||||
|
sudo apt-get update -qq
|
||||||
|
sudo apt-get install -qq gcc python3 python3-pip
|
||||||
|
pip3 install toml
|
||||||
|
gcc --version
|
||||||
|
python3 --version
|
||||||
|
- name: test-lfs2_0
|
||||||
|
run: |
|
||||||
|
CFLAGS="$CFLAGS -DLFS_MULTIVERSION" \
|
||||||
|
TESTFLAGS="$TESTFLAGS -DDISK_VERSION=0x00020000" \
|
||||||
|
make test
|
||||||
|
|
||||||
# run under Valgrind to check for memory errors
|
# run under Valgrind to check for memory errors
|
||||||
test-valgrind:
|
test-valgrind:
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
@@ -371,7 +428,8 @@ jobs:
|
|||||||
# on one geometry
|
# on one geometry
|
||||||
- name: test-valgrind
|
- name: test-valgrind
|
||||||
run: |
|
run: |
|
||||||
TESTFLAGS="$TESTFLAGS --valgrind -Gdefault -Pnone" make test
|
TESTFLAGS="$TESTFLAGS --valgrind --context=1024 -Gdefault -Pnone" \
|
||||||
|
make test
|
||||||
|
|
||||||
# test that compilation is warning free under clang
|
# test that compilation is warning free under clang
|
||||||
# run with Clang, mostly to check for Clang-specific warnings
|
# run with Clang, mostly to check for Clang-specific warnings
|
||||||
@@ -473,6 +531,42 @@ jobs:
|
|||||||
path: status
|
path: status
|
||||||
retention-days: 1
|
retention-days: 1
|
||||||
|
|
||||||
|
# run compatibility tests using the current master as the previous version
|
||||||
|
test-compat:
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
if: ${{github.event_name == 'pull_request'}}
|
||||||
|
# checkout the current pr target into lfsp
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
if: ${{github.event_name == 'pull_request'}}
|
||||||
|
with:
|
||||||
|
ref: ${{github.event.pull_request.base.ref}}
|
||||||
|
path: lfsp
|
||||||
|
- name: install
|
||||||
|
if: ${{github.event_name == 'pull_request'}}
|
||||||
|
run: |
|
||||||
|
# need a few things
|
||||||
|
sudo apt-get update -qq
|
||||||
|
sudo apt-get install -qq gcc python3 python3-pip
|
||||||
|
pip3 install toml
|
||||||
|
gcc --version
|
||||||
|
python3 --version
|
||||||
|
# adjust prefix of lfsp
|
||||||
|
- name: changeprefix
|
||||||
|
if: ${{github.event_name == 'pull_request'}}
|
||||||
|
run: |
|
||||||
|
./scripts/changeprefix.py lfs lfsp lfsp/*.h lfsp/*.c
|
||||||
|
- name: test-compat
|
||||||
|
if: ${{github.event_name == 'pull_request'}}
|
||||||
|
run: |
|
||||||
|
TESTS=tests/test_compat.toml \
|
||||||
|
SRC="$(find . lfsp -name '*.c' -maxdepth 1 \
|
||||||
|
-and -not -name '*.t.*' \
|
||||||
|
-and -not -name '*.b.*')" \
|
||||||
|
CFLAGS="-DLFSP=lfsp/lfsp.h" \
|
||||||
|
make test
|
||||||
|
|
||||||
# self-host with littlefs-fuse for a fuzz-like test
|
# self-host with littlefs-fuse for a fuzz-like test
|
||||||
fuse:
|
fuse:
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
@@ -648,7 +742,7 @@ jobs:
|
|||||||
# sizes table
|
# sizes table
|
||||||
i=0
|
i=0
|
||||||
j=0
|
j=0
|
||||||
for c in "" readonly threadsafe migrate error-asserts
|
for c in "" readonly threadsafe multiversion migrate error-asserts
|
||||||
do
|
do
|
||||||
# per-config results
|
# per-config results
|
||||||
c_or_default=${c:-default}
|
c_or_default=${c:-default}
|
||||||
|
|||||||
27
Makefile
27
Makefile
@@ -1,15 +1,5 @@
|
|||||||
ifdef BUILDDIR
|
# overrideable build dir, default is in-place
|
||||||
# bit of a hack, but we want to make sure BUILDDIR directory structure
|
|
||||||
# is correct before any commands
|
|
||||||
$(if $(findstring n,$(MAKEFLAGS)),, $(shell mkdir -p \
|
|
||||||
$(BUILDDIR)/ \
|
|
||||||
$(BUILDDIR)/bd \
|
|
||||||
$(BUILDDIR)/runners \
|
|
||||||
$(BUILDDIR)/tests \
|
|
||||||
$(BUILDDIR)/benches))
|
|
||||||
endif
|
|
||||||
BUILDDIR ?= .
|
BUILDDIR ?= .
|
||||||
|
|
||||||
# overridable target/src/tools/flags/etc
|
# overridable target/src/tools/flags/etc
|
||||||
ifneq ($(wildcard test.c main.c),)
|
ifneq ($(wildcard test.c main.c),)
|
||||||
TARGET ?= $(BUILDDIR)/lfs
|
TARGET ?= $(BUILDDIR)/lfs
|
||||||
@@ -163,6 +153,18 @@ TESTFLAGS += --perf-path="$(PERF)"
|
|||||||
BENCHFLAGS += --perf-path="$(PERF)"
|
BENCHFLAGS += --perf-path="$(PERF)"
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
# this is a bit of a hack, but we want to make sure the BUILDDIR
|
||||||
|
# directory structure is correct before we run any commands
|
||||||
|
ifneq ($(BUILDDIR),.)
|
||||||
|
$(if $(findstring n,$(MAKEFLAGS)),, $(shell mkdir -p \
|
||||||
|
$(addprefix $(BUILDDIR)/,$(dir \
|
||||||
|
$(SRC) \
|
||||||
|
$(TESTS) \
|
||||||
|
$(TEST_SRC) \
|
||||||
|
$(BENCHES) \
|
||||||
|
$(BENCH_SRC)))))
|
||||||
|
endif
|
||||||
|
|
||||||
|
|
||||||
# commands
|
# commands
|
||||||
|
|
||||||
@@ -514,6 +516,9 @@ $(BUILDDIR)/runners/bench_runner: $(BENCH_OBJ)
|
|||||||
$(BUILDDIR)/%.o $(BUILDDIR)/%.ci: %.c
|
$(BUILDDIR)/%.o $(BUILDDIR)/%.ci: %.c
|
||||||
$(CC) -c -MMD $(CFLAGS) $< -o $(BUILDDIR)/$*.o
|
$(CC) -c -MMD $(CFLAGS) $< -o $(BUILDDIR)/$*.o
|
||||||
|
|
||||||
|
$(BUILDDIR)/%.o $(BUILDDIR)/%.ci: $(BUILDDIR)/%.c
|
||||||
|
$(CC) -c -MMD $(CFLAGS) $< -o $(BUILDDIR)/$*.o
|
||||||
|
|
||||||
$(BUILDDIR)/%.s: %.c
|
$(BUILDDIR)/%.s: %.c
|
||||||
$(CC) -S $(CFLAGS) $< -o $@
|
$(CC) -S $(CFLAGS) $< -o $@
|
||||||
|
|
||||||
|
|||||||
18
README.md
18
README.md
@@ -226,6 +226,13 @@ License Identifiers that are here available: http://spdx.org/licenses/
|
|||||||
to create images of the filesystem on your PC. Check if littlefs will fit
|
to create images of the filesystem on your PC. Check if littlefs will fit
|
||||||
your needs, create images for a later download to the target memory or
|
your needs, create images for a later download to the target memory or
|
||||||
inspect the content of a binary image of the target memory.
|
inspect the content of a binary image of the target memory.
|
||||||
|
|
||||||
|
- [littlefs2-rust] - A Rust wrapper for littlefs. This project allows you
|
||||||
|
to use littlefs in a Rust-friendly API, reaping the benefits of Rust's memory
|
||||||
|
safety and other guarantees.
|
||||||
|
|
||||||
|
- [littlefs-disk-img-viewer] - A memory-efficient web application for viewing
|
||||||
|
littlefs disk images in your web browser.
|
||||||
|
|
||||||
- [mklfs] - A command line tool built by the [Lua RTOS] guys for making
|
- [mklfs] - A command line tool built by the [Lua RTOS] guys for making
|
||||||
littlefs images from a host PC. Supports Windows, Mac OS, and Linux.
|
littlefs images from a host PC. Supports Windows, Mac OS, and Linux.
|
||||||
@@ -243,8 +250,16 @@ License Identifiers that are here available: http://spdx.org/licenses/
|
|||||||
MCUs. It offers static wear-leveling and power-resilience with only a fixed
|
MCUs. It offers static wear-leveling and power-resilience with only a fixed
|
||||||
_O(|address|)_ pointer structure stored on each block and in RAM.
|
_O(|address|)_ pointer structure stored on each block and in RAM.
|
||||||
|
|
||||||
|
- [ChaN's FatFs] - A lightweight reimplementation of the infamous FAT filesystem
|
||||||
|
for microcontroller-scale devices. Due to limitations of FAT it can't provide
|
||||||
|
power-loss resilience, but it does allow easy interop with PCs.
|
||||||
|
|
||||||
|
- [chamelon] - A pure-OCaml implementation of (most of) littlefs, designed for
|
||||||
|
use with the MirageOS library operating system project. It is interoperable
|
||||||
|
with the reference implementation, with some caveats.
|
||||||
|
|
||||||
[BSD-3-Clause]: https://spdx.org/licenses/BSD-3-Clause.html
|
[BSD-3-Clause]: https://spdx.org/licenses/BSD-3-Clause.html
|
||||||
|
[littlefs-disk-img-viewer]: https://github.com/tniessen/littlefs-disk-img-viewer
|
||||||
[littlefs-fuse]: https://github.com/geky/littlefs-fuse
|
[littlefs-fuse]: https://github.com/geky/littlefs-fuse
|
||||||
[FUSE]: https://github.com/libfuse/libfuse
|
[FUSE]: https://github.com/libfuse/libfuse
|
||||||
[littlefs-js]: https://github.com/geky/littlefs-js
|
[littlefs-js]: https://github.com/geky/littlefs-js
|
||||||
@@ -255,4 +270,7 @@ License Identifiers that are here available: http://spdx.org/licenses/
|
|||||||
[LittleFileSystem]: https://os.mbed.com/docs/mbed-os/latest/apis/littlefilesystem.html
|
[LittleFileSystem]: https://os.mbed.com/docs/mbed-os/latest/apis/littlefilesystem.html
|
||||||
[SPIFFS]: https://github.com/pellepl/spiffs
|
[SPIFFS]: https://github.com/pellepl/spiffs
|
||||||
[Dhara]: https://github.com/dlbeer/dhara
|
[Dhara]: https://github.com/dlbeer/dhara
|
||||||
|
[ChaN's FatFs]: http://elm-chan.org/fsw/ff/00index_e.html
|
||||||
[littlefs-python]: https://pypi.org/project/littlefs-python/
|
[littlefs-python]: https://pypi.org/project/littlefs-python/
|
||||||
|
[littlefs2-rust]: https://crates.io/crates/littlefs2
|
||||||
|
[chamelon]: https://github.com/yomimono/chamelon
|
||||||
|
|||||||
101
SPEC.md
101
SPEC.md
@@ -1,10 +1,10 @@
|
|||||||
## littlefs technical specification
|
## littlefs technical specification
|
||||||
|
|
||||||
This is the technical specification of the little filesystem. This document
|
This is the technical specification of the little filesystem with on-disk
|
||||||
covers the technical details of how the littlefs is stored on disk for
|
version lfs2.1. This document covers the technical details of how the littlefs
|
||||||
introspection and tooling. This document assumes you are familiar with the
|
is stored on disk for introspection and tooling. This document assumes you are
|
||||||
design of the littlefs, for more info on how littlefs works check
|
familiar with the design of the littlefs, for more info on how littlefs works
|
||||||
out [DESIGN.md](DESIGN.md).
|
check out [DESIGN.md](DESIGN.md).
|
||||||
|
|
||||||
```
|
```
|
||||||
| | | .---._____
|
| | | .---._____
|
||||||
@@ -133,12 +133,6 @@ tags XORed together, starting with `0xffffffff`.
|
|||||||
'-------------------' '-------------------'
|
'-------------------' '-------------------'
|
||||||
```
|
```
|
||||||
|
|
||||||
One last thing to note before we get into the details around tag encoding. Each
|
|
||||||
tag contains a valid bit used to indicate if the tag and containing commit is
|
|
||||||
valid. This valid bit is the first bit found in the tag and the commit and can
|
|
||||||
be used to tell if we've attempted to write to the remaining space in the
|
|
||||||
block.
|
|
||||||
|
|
||||||
Here's a more complete example of metadata block containing 4 entries:
|
Here's a more complete example of metadata block containing 4 entries:
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -191,6 +185,53 @@ Here's a more complete example of metadata block containing 4 entries:
|
|||||||
'---- most recent D
|
'---- most recent D
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Two things to note before we get into the details around tag encoding:
|
||||||
|
|
||||||
|
1. Each tag contains a valid bit used to indicate if the tag and containing
|
||||||
|
commit is valid. After XORing, this bit should always be zero.
|
||||||
|
|
||||||
|
At the end of each commit, the valid bit of the previous tag is XORed
|
||||||
|
with the lowest bit in the type field of the CRC tag. This allows
|
||||||
|
the CRC tag to force the next commit to fail the valid bit test if it
|
||||||
|
has not yet been written to.
|
||||||
|
|
||||||
|
2. The valid bit alone is not enough info to know if the next commit has been
|
||||||
|
erased. We don't know the order bits will be programmed in a program block,
|
||||||
|
so it's possible that the next commit had an attempted program that left the
|
||||||
|
valid bit unchanged.
|
||||||
|
|
||||||
|
To ensure we only ever program erased bytes, each commit can contain an
|
||||||
|
optional forward-CRC (FCRC). An FCRC contains a checksum of some amount of
|
||||||
|
bytes in the next commit at the time it was erased.
|
||||||
|
|
||||||
|
```
|
||||||
|
.-------------------. \ \
|
||||||
|
| revision count | | |
|
||||||
|
|-------------------| | |
|
||||||
|
| metadata | | |
|
||||||
|
| | +---. +-- current commit
|
||||||
|
| | | | |
|
||||||
|
|-------------------| | | |
|
||||||
|
| FCRC ---|-. | |
|
||||||
|
|-------------------| / | | |
|
||||||
|
| CRC -----|-' /
|
||||||
|
|-------------------| |
|
||||||
|
| padding | | padding (does't need CRC)
|
||||||
|
| | |
|
||||||
|
|-------------------| \ | \
|
||||||
|
| erased? | +-' |
|
||||||
|
| | | | +-- next commit
|
||||||
|
| v | / |
|
||||||
|
| | /
|
||||||
|
| |
|
||||||
|
'-------------------'
|
||||||
|
```
|
||||||
|
|
||||||
|
If the FCRC is missing or the checksum does not match, we must assume a
|
||||||
|
commit was attempted but failed due to power-loss.
|
||||||
|
|
||||||
|
Note that end-of-block commits do not need an FCRC.
|
||||||
|
|
||||||
## Metadata tags
|
## Metadata tags
|
||||||
|
|
||||||
So in littlefs, 32-bit tags describe every type of metadata. And this means
|
So in littlefs, 32-bit tags describe every type of metadata. And this means
|
||||||
@@ -785,3 +826,41 @@ CRC fields:
|
|||||||
are made about the contents.
|
are made about the contents.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
#### `0x5ff` LFS_TYPE_FCRC
|
||||||
|
|
||||||
|
Added in lfs2.1, the optional FCRC tag contains a checksum of some amount of
|
||||||
|
bytes in the next commit at the time it was erased. This allows us to ensure
|
||||||
|
that we only ever program erased bytes, even if a previous commit failed due
|
||||||
|
to power-loss.
|
||||||
|
|
||||||
|
When programming a commit, the FCRC size must be at least as large as the
|
||||||
|
program block size. However, the program block is not saved on disk, and can
|
||||||
|
change between mounts, so the FCRC size on disk may be different than the
|
||||||
|
current program block size.
|
||||||
|
|
||||||
|
If the FCRC is missing or the checksum does not match, we must assume a
|
||||||
|
commit was attempted but failed due to power-loss.
|
||||||
|
|
||||||
|
Layout of the FCRC tag:
|
||||||
|
|
||||||
|
```
|
||||||
|
tag data
|
||||||
|
[-- 32 --][-- 32 --|-- 32 --]
|
||||||
|
[1|- 11 -| 10 | 10 ][-- 32 --|-- 32 --]
|
||||||
|
^ ^ ^ ^ ^- fcrc size ^- fcrc
|
||||||
|
| | | '- size (8)
|
||||||
|
| | '------ id (0x3ff)
|
||||||
|
| '------------ type (0x5ff)
|
||||||
|
'----------------- valid bit
|
||||||
|
```
|
||||||
|
|
||||||
|
FCRC fields:
|
||||||
|
|
||||||
|
1. **FCRC size (32-bits)** - Number of bytes after this commit's CRC tag's
|
||||||
|
padding to include in the FCRC.
|
||||||
|
|
||||||
|
2. **FCRC (32-bits)** - CRC of the bytes after this commit's CRC tag's padding
|
||||||
|
when erased. Like the CRC tag, this uses a CRC-32 with a polynomial of
|
||||||
|
`0x04c11db7` initialized with `0xffffffff`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|||||||
485
lfs.c
485
lfs.c
@@ -300,14 +300,12 @@ static inline int lfs_pair_cmp(
|
|||||||
paira[0] == pairb[1] || paira[1] == pairb[0]);
|
paira[0] == pairb[1] || paira[1] == pairb[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifndef LFS_READONLY
|
static inline bool lfs_pair_issync(
|
||||||
static inline bool lfs_pair_sync(
|
|
||||||
const lfs_block_t paira[2],
|
const lfs_block_t paira[2],
|
||||||
const lfs_block_t pairb[2]) {
|
const lfs_block_t pairb[2]) {
|
||||||
return (paira[0] == pairb[0] && paira[1] == pairb[1]) ||
|
return (paira[0] == pairb[0] && paira[1] == pairb[1]) ||
|
||||||
(paira[0] == pairb[1] && paira[1] == pairb[0]);
|
(paira[0] == pairb[1] && paira[1] == pairb[0]);
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
static inline void lfs_pair_fromle32(lfs_block_t pair[2]) {
|
static inline void lfs_pair_fromle32(lfs_block_t pair[2]) {
|
||||||
pair[0] = lfs_fromle32(pair[0]);
|
pair[0] = lfs_fromle32(pair[0]);
|
||||||
@@ -411,7 +409,7 @@ static inline bool lfs_gstate_hasorphans(const lfs_gstate_t *a) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static inline uint8_t lfs_gstate_getorphans(const lfs_gstate_t *a) {
|
static inline uint8_t lfs_gstate_getorphans(const lfs_gstate_t *a) {
|
||||||
return lfs_tag_size(a->tag);
|
return lfs_tag_size(a->tag) & 0x1ff;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline bool lfs_gstate_hasmove(const lfs_gstate_t *a) {
|
static inline bool lfs_gstate_hasmove(const lfs_gstate_t *a) {
|
||||||
@@ -419,6 +417,10 @@ static inline bool lfs_gstate_hasmove(const lfs_gstate_t *a) {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
static inline bool lfs_gstate_needssuperblock(const lfs_gstate_t *a) {
|
||||||
|
return lfs_tag_size(a->tag) >> 9;
|
||||||
|
}
|
||||||
|
|
||||||
static inline bool lfs_gstate_hasmovehere(const lfs_gstate_t *a,
|
static inline bool lfs_gstate_hasmovehere(const lfs_gstate_t *a,
|
||||||
const lfs_block_t *pair) {
|
const lfs_block_t *pair) {
|
||||||
return lfs_tag_type1(a->tag) && lfs_pair_cmp(a->pair, pair) == 0;
|
return lfs_tag_type1(a->tag) && lfs_pair_cmp(a->pair, pair) == 0;
|
||||||
@@ -516,6 +518,28 @@ static void lfs_mlist_append(lfs_t *lfs, struct lfs_mlist *mlist) {
|
|||||||
lfs->mlist = mlist;
|
lfs->mlist = mlist;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// some other filesystem operations
|
||||||
|
static uint32_t lfs_fs_disk_version(lfs_t *lfs) {
|
||||||
|
(void)lfs;
|
||||||
|
#ifdef LFS_MULTIVERSION
|
||||||
|
if (lfs->cfg->disk_version) {
|
||||||
|
return lfs->cfg->disk_version;
|
||||||
|
} else
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
return LFS_DISK_VERSION;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint16_t lfs_fs_disk_version_major(lfs_t *lfs) {
|
||||||
|
return 0xffff & (lfs_fs_disk_version(lfs) >> 16);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint16_t lfs_fs_disk_version_minor(lfs_t *lfs) {
|
||||||
|
return 0xffff & (lfs_fs_disk_version(lfs) >> 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Internal operations predeclared here ///
|
/// Internal operations predeclared here ///
|
||||||
#ifndef LFS_READONLY
|
#ifndef LFS_READONLY
|
||||||
@@ -543,6 +567,8 @@ static lfs_stag_t lfs_fs_parent(lfs_t *lfs, const lfs_block_t dir[2],
|
|||||||
static int lfs_fs_forceconsistency(lfs_t *lfs);
|
static int lfs_fs_forceconsistency(lfs_t *lfs);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
static void lfs_fs_prepsuperblock(lfs_t *lfs, bool needssuperblock);
|
||||||
|
|
||||||
#ifdef LFS_MIGRATE
|
#ifdef LFS_MIGRATE
|
||||||
static int lfs1_traverse(lfs_t *lfs,
|
static int lfs1_traverse(lfs_t *lfs,
|
||||||
int (*cb)(void*, lfs_block_t), void *data);
|
int (*cb)(void*, lfs_block_t), void *data);
|
||||||
@@ -851,7 +877,7 @@ static int lfs_dir_traverse(lfs_t *lfs,
|
|||||||
// iterate over directory and attrs
|
// iterate over directory and attrs
|
||||||
lfs_tag_t tag;
|
lfs_tag_t tag;
|
||||||
const void *buffer;
|
const void *buffer;
|
||||||
struct lfs_diskoff disk;
|
struct lfs_diskoff disk = {0};
|
||||||
while (true) {
|
while (true) {
|
||||||
{
|
{
|
||||||
if (off+lfs_tag_dsize(ptag) < dir->off) {
|
if (off+lfs_tag_dsize(ptag) < dir->off) {
|
||||||
@@ -1107,7 +1133,8 @@ static lfs_stag_t lfs_dir_fetchmatch(lfs_t *lfs,
|
|||||||
|
|
||||||
// next commit not yet programmed?
|
// next commit not yet programmed?
|
||||||
if (!lfs_tag_isvalid(tag)) {
|
if (!lfs_tag_isvalid(tag)) {
|
||||||
maybeerased = true;
|
// we only might be erased if the last tag was a crc
|
||||||
|
maybeerased = (lfs_tag_type2(ptag) == LFS_TYPE_CCRC);
|
||||||
break;
|
break;
|
||||||
// out of range?
|
// out of range?
|
||||||
} else if (off + lfs_tag_dsize(tag) > lfs->cfg->block_size) {
|
} else if (off + lfs_tag_dsize(tag) > lfs->cfg->block_size) {
|
||||||
@@ -1152,14 +1179,11 @@ static lfs_stag_t lfs_dir_fetchmatch(lfs_t *lfs,
|
|||||||
dir->tail[1] = temptail[1];
|
dir->tail[1] = temptail[1];
|
||||||
dir->split = tempsplit;
|
dir->split = tempsplit;
|
||||||
|
|
||||||
// reset crc
|
// reset crc, hasfcrc
|
||||||
crc = 0xffffffff;
|
crc = 0xffffffff;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// fcrc is only valid when last tag was a crc
|
|
||||||
hasfcrc = false;
|
|
||||||
|
|
||||||
// crc the entry first, hopefully leaving it in the cache
|
// crc the entry first, hopefully leaving it in the cache
|
||||||
err = lfs_bd_crc(lfs,
|
err = lfs_bd_crc(lfs,
|
||||||
NULL, &lfs->rcache, lfs->cfg->block_size,
|
NULL, &lfs->rcache, lfs->cfg->block_size,
|
||||||
@@ -1253,20 +1277,33 @@ static lfs_stag_t lfs_dir_fetchmatch(lfs_t *lfs,
|
|||||||
|
|
||||||
// did we end on a valid commit? we may have an erased block
|
// did we end on a valid commit? we may have an erased block
|
||||||
dir->erased = false;
|
dir->erased = false;
|
||||||
if (maybeerased && hasfcrc && dir->off % lfs->cfg->prog_size == 0) {
|
if (maybeerased && dir->off % lfs->cfg->prog_size == 0) {
|
||||||
// check for an fcrc matching the next prog's erased state, if
|
#ifdef LFS_MULTIVERSION
|
||||||
// this failed most likely a previous prog was interrupted, we
|
// note versions < lfs2.1 did not have fcrc tags, if
|
||||||
// need a new erase
|
// we're < lfs2.1 treat missing fcrc as erased data
|
||||||
uint32_t fcrc_ = 0xffffffff;
|
//
|
||||||
int err = lfs_bd_crc(lfs,
|
// we don't strictly need to do this, but otherwise writing
|
||||||
NULL, &lfs->rcache, lfs->cfg->block_size,
|
// to lfs2.0 disks becomes very inefficient
|
||||||
dir->pair[0], dir->off, fcrc.size, &fcrc_);
|
if (lfs_fs_disk_version(lfs) < 0x00020001) {
|
||||||
if (err && err != LFS_ERR_CORRUPT) {
|
dir->erased = true;
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
// found beginning of erased part?
|
} else
|
||||||
dir->erased = (fcrc_ == fcrc.crc);
|
#endif
|
||||||
|
if (hasfcrc) {
|
||||||
|
// check for an fcrc matching the next prog's erased state, if
|
||||||
|
// this failed most likely a previous prog was interrupted, we
|
||||||
|
// need a new erase
|
||||||
|
uint32_t fcrc_ = 0xffffffff;
|
||||||
|
int err = lfs_bd_crc(lfs,
|
||||||
|
NULL, &lfs->rcache, lfs->cfg->block_size,
|
||||||
|
dir->pair[0], dir->off, fcrc.size, &fcrc_);
|
||||||
|
if (err && err != LFS_ERR_CORRUPT) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
// found beginning of erased part?
|
||||||
|
dir->erased = (fcrc_ == fcrc.crc);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// synthetic move
|
// synthetic move
|
||||||
@@ -1591,7 +1628,7 @@ static int lfs_dir_commitcrc(lfs_t *lfs, struct lfs_commit *commit) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// space for fcrc?
|
// space for fcrc?
|
||||||
uint8_t eperturb = -1;
|
uint8_t eperturb = (uint8_t)-1;
|
||||||
if (noff >= end && noff <= lfs->cfg->block_size - lfs->cfg->prog_size) {
|
if (noff >= end && noff <= lfs->cfg->block_size - lfs->cfg->prog_size) {
|
||||||
// first read the leading byte, this always contains a bit
|
// first read the leading byte, this always contains a bit
|
||||||
// we can perturb to avoid writes that don't change the fcrc
|
// we can perturb to avoid writes that don't change the fcrc
|
||||||
@@ -1602,22 +1639,34 @@ static int lfs_dir_commitcrc(lfs_t *lfs, struct lfs_commit *commit) {
|
|||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
// find the expected fcrc, don't bother avoiding a reread
|
#ifdef LFS_MULTIVERSION
|
||||||
// of the eperturb, it should still be in our cache
|
// unfortunately fcrcs break mdir fetching < lfs2.1, so only write
|
||||||
struct lfs_fcrc fcrc = {.size=lfs->cfg->prog_size, .crc=0xffffffff};
|
// these if we're a >= lfs2.1 filesystem
|
||||||
err = lfs_bd_crc(lfs,
|
if (lfs_fs_disk_version(lfs) <= 0x00020000) {
|
||||||
NULL, &lfs->rcache, lfs->cfg->prog_size,
|
// don't write fcrc
|
||||||
commit->block, noff, fcrc.size, &fcrc.crc);
|
} else
|
||||||
if (err && err != LFS_ERR_CORRUPT) {
|
#endif
|
||||||
return err;
|
{
|
||||||
}
|
// find the expected fcrc, don't bother avoiding a reread
|
||||||
|
// of the eperturb, it should still be in our cache
|
||||||
|
struct lfs_fcrc fcrc = {
|
||||||
|
.size = lfs->cfg->prog_size,
|
||||||
|
.crc = 0xffffffff
|
||||||
|
};
|
||||||
|
err = lfs_bd_crc(lfs,
|
||||||
|
NULL, &lfs->rcache, lfs->cfg->prog_size,
|
||||||
|
commit->block, noff, fcrc.size, &fcrc.crc);
|
||||||
|
if (err && err != LFS_ERR_CORRUPT) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
lfs_fcrc_tole32(&fcrc);
|
lfs_fcrc_tole32(&fcrc);
|
||||||
err = lfs_dir_commitattr(lfs, commit,
|
err = lfs_dir_commitattr(lfs, commit,
|
||||||
LFS_MKTAG(LFS_TYPE_FCRC, 0x3ff, sizeof(struct lfs_fcrc)),
|
LFS_MKTAG(LFS_TYPE_FCRC, 0x3ff, sizeof(struct lfs_fcrc)),
|
||||||
&fcrc);
|
&fcrc);
|
||||||
if (err) {
|
if (err) {
|
||||||
return err;
|
return err;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1648,7 +1697,7 @@ static int lfs_dir_commitcrc(lfs_t *lfs, struct lfs_commit *commit) {
|
|||||||
|
|
||||||
commit->off = noff;
|
commit->off = noff;
|
||||||
// perturb valid bit?
|
// perturb valid bit?
|
||||||
commit->ptag = ntag ^ ((0x80 & ~eperturb) << 24);
|
commit->ptag = ntag ^ ((0x80UL & ~eperturb) << 24);
|
||||||
// reset crc for next commit
|
// reset crc for next commit
|
||||||
commit->crc = 0xffffffff;
|
commit->crc = 0xffffffff;
|
||||||
|
|
||||||
@@ -2709,11 +2758,6 @@ static int lfs_dir_rawseek(lfs_t *lfs, lfs_dir_t *dir, lfs_off_t off) {
|
|||||||
dir->id = (off > 0 && lfs_pair_cmp(dir->head, lfs->root) == 0);
|
dir->id = (off > 0 && lfs_pair_cmp(dir->head, lfs->root) == 0);
|
||||||
|
|
||||||
while (off > 0) {
|
while (off > 0) {
|
||||||
int diff = lfs_min(dir->m.count - dir->id, off);
|
|
||||||
dir->id += diff;
|
|
||||||
dir->pos += diff;
|
|
||||||
off -= diff;
|
|
||||||
|
|
||||||
if (dir->id == dir->m.count) {
|
if (dir->id == dir->m.count) {
|
||||||
if (!dir->m.split) {
|
if (!dir->m.split) {
|
||||||
return LFS_ERR_INVAL;
|
return LFS_ERR_INVAL;
|
||||||
@@ -2726,6 +2770,11 @@ static int lfs_dir_rawseek(lfs_t *lfs, lfs_dir_t *dir, lfs_off_t off) {
|
|||||||
|
|
||||||
dir->id = 0;
|
dir->id = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int diff = lfs_min(dir->m.count - dir->id, off);
|
||||||
|
dir->id += diff;
|
||||||
|
dir->pos += diff;
|
||||||
|
off -= diff;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
@@ -3462,7 +3511,7 @@ static lfs_ssize_t lfs_file_flushedwrite(lfs_t *lfs, lfs_file_t *file,
|
|||||||
// find out which block we're extending from
|
// find out which block we're extending from
|
||||||
int err = lfs_ctz_find(lfs, NULL, &file->cache,
|
int err = lfs_ctz_find(lfs, NULL, &file->cache,
|
||||||
file->ctz.head, file->ctz.size,
|
file->ctz.head, file->ctz.size,
|
||||||
file->pos-1, &file->block, &file->off);
|
file->pos-1, &file->block, &(lfs_off_t){0});
|
||||||
if (err) {
|
if (err) {
|
||||||
file->flags |= LFS_F_ERRED;
|
file->flags |= LFS_F_ERRED;
|
||||||
return err;
|
return err;
|
||||||
@@ -3640,26 +3689,55 @@ static int lfs_file_rawtruncate(lfs_t *lfs, lfs_file_t *file, lfs_off_t size) {
|
|||||||
lfs_off_t pos = file->pos;
|
lfs_off_t pos = file->pos;
|
||||||
lfs_off_t oldsize = lfs_file_rawsize(lfs, file);
|
lfs_off_t oldsize = lfs_file_rawsize(lfs, file);
|
||||||
if (size < oldsize) {
|
if (size < oldsize) {
|
||||||
// need to flush since directly changing metadata
|
// revert to inline file?
|
||||||
int err = lfs_file_flush(lfs, file);
|
if (size <= lfs_min(0x3fe, lfs_min(
|
||||||
if (err) {
|
lfs->cfg->cache_size,
|
||||||
return err;
|
(lfs->cfg->metadata_max ?
|
||||||
}
|
lfs->cfg->metadata_max : lfs->cfg->block_size) / 8))) {
|
||||||
|
// flush+seek to head
|
||||||
|
lfs_soff_t res = lfs_file_rawseek(lfs, file, 0, LFS_SEEK_SET);
|
||||||
|
if (res < 0) {
|
||||||
|
return (int)res;
|
||||||
|
}
|
||||||
|
|
||||||
// lookup new head in ctz skip list
|
// read our data into rcache temporarily
|
||||||
err = lfs_ctz_find(lfs, NULL, &file->cache,
|
lfs_cache_drop(lfs, &lfs->rcache);
|
||||||
file->ctz.head, file->ctz.size,
|
res = lfs_file_flushedread(lfs, file,
|
||||||
size, &file->block, &file->off);
|
lfs->rcache.buffer, size);
|
||||||
if (err) {
|
if (res < 0) {
|
||||||
return err;
|
return (int)res;
|
||||||
}
|
}
|
||||||
|
|
||||||
// need to set pos/block/off consistently so seeking back to
|
file->ctz.head = LFS_BLOCK_INLINE;
|
||||||
// the old position does not get confused
|
file->ctz.size = size;
|
||||||
file->pos = size;
|
file->flags |= LFS_F_DIRTY | LFS_F_READING | LFS_F_INLINE;
|
||||||
file->ctz.head = file->block;
|
file->cache.block = file->ctz.head;
|
||||||
file->ctz.size = size;
|
file->cache.off = 0;
|
||||||
file->flags |= LFS_F_DIRTY | LFS_F_READING;
|
file->cache.size = lfs->cfg->cache_size;
|
||||||
|
memcpy(file->cache.buffer, lfs->rcache.buffer, size);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// need to flush since directly changing metadata
|
||||||
|
int err = lfs_file_flush(lfs, file);
|
||||||
|
if (err) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
// lookup new head in ctz skip list
|
||||||
|
err = lfs_ctz_find(lfs, NULL, &file->cache,
|
||||||
|
file->ctz.head, file->ctz.size,
|
||||||
|
size-1, &file->block, &(lfs_off_t){0});
|
||||||
|
if (err) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
// need to set pos/block/off consistently so seeking back to
|
||||||
|
// the old position does not get confused
|
||||||
|
file->pos = size;
|
||||||
|
file->ctz.head = file->block;
|
||||||
|
file->ctz.size = size;
|
||||||
|
file->flags |= LFS_F_DIRTY | LFS_F_READING;
|
||||||
|
}
|
||||||
} else if (size > oldsize) {
|
} else if (size > oldsize) {
|
||||||
// flush+seek if not already at end
|
// flush+seek if not already at end
|
||||||
lfs_soff_t res = lfs_file_rawseek(lfs, file, 0, LFS_SEEK_END);
|
lfs_soff_t res = lfs_file_rawseek(lfs, file, 0, LFS_SEEK_END);
|
||||||
@@ -4019,6 +4097,21 @@ static int lfs_init(lfs_t *lfs, const struct lfs_config *cfg) {
|
|||||||
lfs->cfg = cfg;
|
lfs->cfg = cfg;
|
||||||
int err = 0;
|
int err = 0;
|
||||||
|
|
||||||
|
#ifdef LFS_MULTIVERSION
|
||||||
|
// this driver only supports minor version < current minor version
|
||||||
|
LFS_ASSERT(!lfs->cfg->disk_version || (
|
||||||
|
(0xffff & (lfs->cfg->disk_version >> 16))
|
||||||
|
== LFS_DISK_VERSION_MAJOR
|
||||||
|
&& (0xffff & (lfs->cfg->disk_version >> 0))
|
||||||
|
<= LFS_DISK_VERSION_MINOR));
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// check that bool is a truthy-preserving type
|
||||||
|
//
|
||||||
|
// note the most common reason for this failure is a before-c99 compiler,
|
||||||
|
// which littlefs currently does not support
|
||||||
|
LFS_ASSERT((bool)0x80000000);
|
||||||
|
|
||||||
// validate that the lfs-cfg sizes were initiated properly before
|
// validate that the lfs-cfg sizes were initiated properly before
|
||||||
// performing any arithmetic logics with them
|
// performing any arithmetic logics with them
|
||||||
LFS_ASSERT(lfs->cfg->read_size != 0);
|
LFS_ASSERT(lfs->cfg->read_size != 0);
|
||||||
@@ -4031,7 +4124,10 @@ static int lfs_init(lfs_t *lfs, const struct lfs_config *cfg) {
|
|||||||
LFS_ASSERT(lfs->cfg->cache_size % lfs->cfg->prog_size == 0);
|
LFS_ASSERT(lfs->cfg->cache_size % lfs->cfg->prog_size == 0);
|
||||||
LFS_ASSERT(lfs->cfg->block_size % lfs->cfg->cache_size == 0);
|
LFS_ASSERT(lfs->cfg->block_size % lfs->cfg->cache_size == 0);
|
||||||
|
|
||||||
// check that the block size is large enough to fit ctz pointers
|
// check that the block size is large enough to fit all ctz pointers
|
||||||
|
LFS_ASSERT(lfs->cfg->block_size >= 128);
|
||||||
|
// this is the exact calculation for all ctz pointers, if this fails
|
||||||
|
// and the simpler assert above does not, math must be broken
|
||||||
LFS_ASSERT(4*lfs_npw2(0xffffffff / (lfs->cfg->block_size-2*4))
|
LFS_ASSERT(4*lfs_npw2(0xffffffff / (lfs->cfg->block_size-2*4))
|
||||||
<= lfs->cfg->block_size);
|
<= lfs->cfg->block_size);
|
||||||
|
|
||||||
@@ -4167,7 +4263,7 @@ static int lfs_rawformat(lfs_t *lfs, const struct lfs_config *cfg) {
|
|||||||
|
|
||||||
// write one superblock
|
// write one superblock
|
||||||
lfs_superblock_t superblock = {
|
lfs_superblock_t superblock = {
|
||||||
.version = LFS_DISK_VERSION,
|
.version = lfs_fs_disk_version(lfs),
|
||||||
.block_size = lfs->cfg->block_size,
|
.block_size = lfs->cfg->block_size,
|
||||||
.block_count = lfs->cfg->block_count,
|
.block_count = lfs->cfg->block_count,
|
||||||
.name_max = lfs->name_max,
|
.name_max = lfs->name_max,
|
||||||
@@ -4215,14 +4311,23 @@ static int lfs_rawmount(lfs_t *lfs, const struct lfs_config *cfg) {
|
|||||||
|
|
||||||
// scan directory blocks for superblock and any global updates
|
// scan directory blocks for superblock and any global updates
|
||||||
lfs_mdir_t dir = {.tail = {0, 1}};
|
lfs_mdir_t dir = {.tail = {0, 1}};
|
||||||
lfs_block_t cycle = 0;
|
lfs_block_t tortoise[2] = {LFS_BLOCK_NULL, LFS_BLOCK_NULL};
|
||||||
|
lfs_size_t tortoise_i = 1;
|
||||||
|
lfs_size_t tortoise_period = 1;
|
||||||
while (!lfs_pair_isnull(dir.tail)) {
|
while (!lfs_pair_isnull(dir.tail)) {
|
||||||
if (cycle >= lfs->cfg->block_count/2) {
|
// detect cycles with Brent's algorithm
|
||||||
// loop detected
|
if (lfs_pair_issync(dir.tail, tortoise)) {
|
||||||
|
LFS_WARN("Cycle detected in tail list");
|
||||||
err = LFS_ERR_CORRUPT;
|
err = LFS_ERR_CORRUPT;
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
}
|
}
|
||||||
cycle += 1;
|
if (tortoise_i == tortoise_period) {
|
||||||
|
tortoise[0] = dir.tail[0];
|
||||||
|
tortoise[1] = dir.tail[1];
|
||||||
|
tortoise_i = 0;
|
||||||
|
tortoise_period *= 2;
|
||||||
|
}
|
||||||
|
tortoise_i += 1;
|
||||||
|
|
||||||
// fetch next block in tail list
|
// fetch next block in tail list
|
||||||
lfs_stag_t tag = lfs_dir_fetchmatch(lfs, &dir, dir.tail,
|
lfs_stag_t tag = lfs_dir_fetchmatch(lfs, &dir, dir.tail,
|
||||||
@@ -4256,14 +4361,33 @@ static int lfs_rawmount(lfs_t *lfs, const struct lfs_config *cfg) {
|
|||||||
// check version
|
// check version
|
||||||
uint16_t major_version = (0xffff & (superblock.version >> 16));
|
uint16_t major_version = (0xffff & (superblock.version >> 16));
|
||||||
uint16_t minor_version = (0xffff & (superblock.version >> 0));
|
uint16_t minor_version = (0xffff & (superblock.version >> 0));
|
||||||
if ((major_version != LFS_DISK_VERSION_MAJOR ||
|
if (major_version != lfs_fs_disk_version_major(lfs)
|
||||||
minor_version > LFS_DISK_VERSION_MINOR)) {
|
|| minor_version > lfs_fs_disk_version_minor(lfs)) {
|
||||||
LFS_ERROR("Invalid version v%"PRIu16".%"PRIu16,
|
LFS_ERROR("Invalid version "
|
||||||
major_version, minor_version);
|
"v%"PRIu16".%"PRIu16" != v%"PRIu16".%"PRIu16,
|
||||||
|
major_version,
|
||||||
|
minor_version,
|
||||||
|
lfs_fs_disk_version_major(lfs),
|
||||||
|
lfs_fs_disk_version_minor(lfs));
|
||||||
err = LFS_ERR_INVAL;
|
err = LFS_ERR_INVAL;
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// found older minor version? set an in-device only bit in the
|
||||||
|
// gstate so we know we need to rewrite the superblock before
|
||||||
|
// the first write
|
||||||
|
if (minor_version < lfs_fs_disk_version_minor(lfs)) {
|
||||||
|
LFS_DEBUG("Found older minor version "
|
||||||
|
"v%"PRIu16".%"PRIu16" < v%"PRIu16".%"PRIu16,
|
||||||
|
major_version,
|
||||||
|
minor_version,
|
||||||
|
lfs_fs_disk_version_major(lfs),
|
||||||
|
lfs_fs_disk_version_minor(lfs));
|
||||||
|
// note this bit is reserved on disk, so fetching more gstate
|
||||||
|
// will not interfere here
|
||||||
|
lfs_fs_prepsuperblock(lfs, true);
|
||||||
|
}
|
||||||
|
|
||||||
// check superblock configuration
|
// check superblock configuration
|
||||||
if (superblock.name_max) {
|
if (superblock.name_max) {
|
||||||
if (superblock.name_max > lfs->name_max) {
|
if (superblock.name_max > lfs->name_max) {
|
||||||
@@ -4354,6 +4478,42 @@ static int lfs_rawunmount(lfs_t *lfs) {
|
|||||||
|
|
||||||
|
|
||||||
/// Filesystem filesystem operations ///
|
/// Filesystem filesystem operations ///
|
||||||
|
static int lfs_fs_rawstat(lfs_t *lfs, struct lfs_fsinfo *fsinfo) {
|
||||||
|
// if the superblock is up-to-date, we must be on the most recent
|
||||||
|
// minor version of littlefs
|
||||||
|
if (!lfs_gstate_needssuperblock(&lfs->gstate)) {
|
||||||
|
fsinfo->disk_version = lfs_fs_disk_version(lfs);
|
||||||
|
|
||||||
|
// otherwise we need to read the minor version on disk
|
||||||
|
} else {
|
||||||
|
// fetch the superblock
|
||||||
|
lfs_mdir_t dir;
|
||||||
|
int err = lfs_dir_fetch(lfs, &dir, lfs->root);
|
||||||
|
if (err) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
lfs_superblock_t superblock;
|
||||||
|
lfs_stag_t tag = lfs_dir_get(lfs, &dir, LFS_MKTAG(0x7ff, 0x3ff, 0),
|
||||||
|
LFS_MKTAG(LFS_TYPE_INLINESTRUCT, 0, sizeof(superblock)),
|
||||||
|
&superblock);
|
||||||
|
if (tag < 0) {
|
||||||
|
return tag;
|
||||||
|
}
|
||||||
|
lfs_superblock_fromle32(&superblock);
|
||||||
|
|
||||||
|
// read the on-disk version
|
||||||
|
fsinfo->disk_version = superblock.version;
|
||||||
|
}
|
||||||
|
|
||||||
|
// other on-disk configuration, we cache all of these for internal use
|
||||||
|
fsinfo->name_max = lfs->name_max;
|
||||||
|
fsinfo->file_max = lfs->file_max;
|
||||||
|
fsinfo->attr_max = lfs->attr_max;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
int lfs_fs_rawtraverse(lfs_t *lfs,
|
int lfs_fs_rawtraverse(lfs_t *lfs,
|
||||||
int (*cb)(void *data, lfs_block_t block), void *data,
|
int (*cb)(void *data, lfs_block_t block), void *data,
|
||||||
bool includeorphans) {
|
bool includeorphans) {
|
||||||
@@ -4373,13 +4533,22 @@ int lfs_fs_rawtraverse(lfs_t *lfs,
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
lfs_block_t cycle = 0;
|
lfs_block_t tortoise[2] = {LFS_BLOCK_NULL, LFS_BLOCK_NULL};
|
||||||
|
lfs_size_t tortoise_i = 1;
|
||||||
|
lfs_size_t tortoise_period = 1;
|
||||||
while (!lfs_pair_isnull(dir.tail)) {
|
while (!lfs_pair_isnull(dir.tail)) {
|
||||||
if (cycle >= lfs->cfg->block_count/2) {
|
// detect cycles with Brent's algorithm
|
||||||
// loop detected
|
if (lfs_pair_issync(dir.tail, tortoise)) {
|
||||||
|
LFS_WARN("Cycle detected in tail list");
|
||||||
return LFS_ERR_CORRUPT;
|
return LFS_ERR_CORRUPT;
|
||||||
}
|
}
|
||||||
cycle += 1;
|
if (tortoise_i == tortoise_period) {
|
||||||
|
tortoise[0] = dir.tail[0];
|
||||||
|
tortoise[1] = dir.tail[1];
|
||||||
|
tortoise_i = 0;
|
||||||
|
tortoise_period *= 2;
|
||||||
|
}
|
||||||
|
tortoise_i += 1;
|
||||||
|
|
||||||
for (int i = 0; i < 2; i++) {
|
for (int i = 0; i < 2; i++) {
|
||||||
int err = cb(data, dir.tail[i]);
|
int err = cb(data, dir.tail[i]);
|
||||||
@@ -4458,13 +4627,22 @@ static int lfs_fs_pred(lfs_t *lfs,
|
|||||||
// iterate over all directory directory entries
|
// iterate over all directory directory entries
|
||||||
pdir->tail[0] = 0;
|
pdir->tail[0] = 0;
|
||||||
pdir->tail[1] = 1;
|
pdir->tail[1] = 1;
|
||||||
lfs_block_t cycle = 0;
|
lfs_block_t tortoise[2] = {LFS_BLOCK_NULL, LFS_BLOCK_NULL};
|
||||||
|
lfs_size_t tortoise_i = 1;
|
||||||
|
lfs_size_t tortoise_period = 1;
|
||||||
while (!lfs_pair_isnull(pdir->tail)) {
|
while (!lfs_pair_isnull(pdir->tail)) {
|
||||||
if (cycle >= lfs->cfg->block_count/2) {
|
// detect cycles with Brent's algorithm
|
||||||
// loop detected
|
if (lfs_pair_issync(pdir->tail, tortoise)) {
|
||||||
|
LFS_WARN("Cycle detected in tail list");
|
||||||
return LFS_ERR_CORRUPT;
|
return LFS_ERR_CORRUPT;
|
||||||
}
|
}
|
||||||
cycle += 1;
|
if (tortoise_i == tortoise_period) {
|
||||||
|
tortoise[0] = pdir->tail[0];
|
||||||
|
tortoise[1] = pdir->tail[1];
|
||||||
|
tortoise_i = 0;
|
||||||
|
tortoise_period *= 2;
|
||||||
|
}
|
||||||
|
tortoise_i += 1;
|
||||||
|
|
||||||
if (lfs_pair_cmp(pdir->tail, pair) == 0) {
|
if (lfs_pair_cmp(pdir->tail, pair) == 0) {
|
||||||
return 0;
|
return 0;
|
||||||
@@ -4514,13 +4692,22 @@ static lfs_stag_t lfs_fs_parent(lfs_t *lfs, const lfs_block_t pair[2],
|
|||||||
// use fetchmatch with callback to find pairs
|
// use fetchmatch with callback to find pairs
|
||||||
parent->tail[0] = 0;
|
parent->tail[0] = 0;
|
||||||
parent->tail[1] = 1;
|
parent->tail[1] = 1;
|
||||||
lfs_block_t cycle = 0;
|
lfs_block_t tortoise[2] = {LFS_BLOCK_NULL, LFS_BLOCK_NULL};
|
||||||
|
lfs_size_t tortoise_i = 1;
|
||||||
|
lfs_size_t tortoise_period = 1;
|
||||||
while (!lfs_pair_isnull(parent->tail)) {
|
while (!lfs_pair_isnull(parent->tail)) {
|
||||||
if (cycle >= lfs->cfg->block_count/2) {
|
// detect cycles with Brent's algorithm
|
||||||
// loop detected
|
if (lfs_pair_issync(parent->tail, tortoise)) {
|
||||||
|
LFS_WARN("Cycle detected in tail list");
|
||||||
return LFS_ERR_CORRUPT;
|
return LFS_ERR_CORRUPT;
|
||||||
}
|
}
|
||||||
cycle += 1;
|
if (tortoise_i == tortoise_period) {
|
||||||
|
tortoise[0] = parent->tail[0];
|
||||||
|
tortoise[1] = parent->tail[1];
|
||||||
|
tortoise_i = 0;
|
||||||
|
tortoise_period *= 2;
|
||||||
|
}
|
||||||
|
tortoise_i += 1;
|
||||||
|
|
||||||
lfs_stag_t tag = lfs_dir_fetchmatch(lfs, parent, parent->tail,
|
lfs_stag_t tag = lfs_dir_fetchmatch(lfs, parent, parent->tail,
|
||||||
LFS_MKTAG(0x7ff, 0, 0x3ff),
|
LFS_MKTAG(0x7ff, 0, 0x3ff),
|
||||||
@@ -4537,10 +4724,15 @@ static lfs_stag_t lfs_fs_parent(lfs_t *lfs, const lfs_block_t pair[2],
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
static void lfs_fs_prepsuperblock(lfs_t *lfs, bool needssuperblock) {
|
||||||
|
lfs->gstate.tag = (lfs->gstate.tag & ~LFS_MKTAG(0, 0, 0x200))
|
||||||
|
| (uint32_t)needssuperblock << 9;
|
||||||
|
}
|
||||||
|
|
||||||
#ifndef LFS_READONLY
|
#ifndef LFS_READONLY
|
||||||
static int lfs_fs_preporphans(lfs_t *lfs, int8_t orphans) {
|
static int lfs_fs_preporphans(lfs_t *lfs, int8_t orphans) {
|
||||||
LFS_ASSERT(lfs_tag_size(lfs->gstate.tag) > 0x000 || orphans >= 0);
|
LFS_ASSERT(lfs_tag_size(lfs->gstate.tag) > 0x000 || orphans >= 0);
|
||||||
LFS_ASSERT(lfs_tag_size(lfs->gstate.tag) < 0x3ff || orphans <= 0);
|
LFS_ASSERT(lfs_tag_size(lfs->gstate.tag) < 0x1ff || orphans <= 0);
|
||||||
lfs->gstate.tag += orphans;
|
lfs->gstate.tag += orphans;
|
||||||
lfs->gstate.tag = ((lfs->gstate.tag & ~LFS_MKTAG(0x800, 0, 0)) |
|
lfs->gstate.tag = ((lfs->gstate.tag & ~LFS_MKTAG(0x800, 0, 0)) |
|
||||||
((uint32_t)lfs_gstate_hasorphans(&lfs->gstate) << 31));
|
((uint32_t)lfs_gstate_hasorphans(&lfs->gstate) << 31));
|
||||||
@@ -4559,6 +4751,45 @@ static void lfs_fs_prepmove(lfs_t *lfs,
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifndef LFS_READONLY
|
||||||
|
static int lfs_fs_desuperblock(lfs_t *lfs) {
|
||||||
|
if (!lfs_gstate_needssuperblock(&lfs->gstate)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
LFS_DEBUG("Rewriting superblock {0x%"PRIx32", 0x%"PRIx32"}",
|
||||||
|
lfs->root[0],
|
||||||
|
lfs->root[1]);
|
||||||
|
|
||||||
|
lfs_mdir_t root;
|
||||||
|
int err = lfs_dir_fetch(lfs, &root, lfs->root);
|
||||||
|
if (err) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
// write a new superblock
|
||||||
|
lfs_superblock_t superblock = {
|
||||||
|
.version = lfs_fs_disk_version(lfs),
|
||||||
|
.block_size = lfs->cfg->block_size,
|
||||||
|
.block_count = lfs->cfg->block_count,
|
||||||
|
.name_max = lfs->name_max,
|
||||||
|
.file_max = lfs->file_max,
|
||||||
|
.attr_max = lfs->attr_max,
|
||||||
|
};
|
||||||
|
|
||||||
|
lfs_superblock_tole32(&superblock);
|
||||||
|
err = lfs_dir_commit(lfs, &root, LFS_MKATTRS(
|
||||||
|
{LFS_MKTAG(LFS_TYPE_INLINESTRUCT, 0, sizeof(superblock)),
|
||||||
|
&superblock}));
|
||||||
|
if (err) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
lfs_fs_prepsuperblock(lfs, false);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifndef LFS_READONLY
|
#ifndef LFS_READONLY
|
||||||
static int lfs_fs_demove(lfs_t *lfs) {
|
static int lfs_fs_demove(lfs_t *lfs) {
|
||||||
if (!lfs_gstate_hasmove(&lfs->gdisk)) {
|
if (!lfs_gstate_hasmove(&lfs->gdisk)) {
|
||||||
@@ -4601,8 +4832,6 @@ static int lfs_fs_deorphan(lfs_t *lfs, bool powerloss) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int8_t found = 0;
|
|
||||||
|
|
||||||
// Check for orphans in two separate passes:
|
// Check for orphans in two separate passes:
|
||||||
// - 1 for half-orphans (relocations)
|
// - 1 for half-orphans (relocations)
|
||||||
// - 2 for full-orphans (removes/renames)
|
// - 2 for full-orphans (removes/renames)
|
||||||
@@ -4643,7 +4872,7 @@ static int lfs_fs_deorphan(lfs_t *lfs, bool powerloss) {
|
|||||||
}
|
}
|
||||||
lfs_pair_fromle32(pair);
|
lfs_pair_fromle32(pair);
|
||||||
|
|
||||||
if (!lfs_pair_sync(pair, pdir.tail)) {
|
if (!lfs_pair_issync(pair, pdir.tail)) {
|
||||||
// we have desynced
|
// we have desynced
|
||||||
LFS_DEBUG("Fixing half-orphan "
|
LFS_DEBUG("Fixing half-orphan "
|
||||||
"{0x%"PRIx32", 0x%"PRIx32"} "
|
"{0x%"PRIx32", 0x%"PRIx32"} "
|
||||||
@@ -4673,8 +4902,6 @@ static int lfs_fs_deorphan(lfs_t *lfs, bool powerloss) {
|
|||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
found += 1;
|
|
||||||
|
|
||||||
// did our commit create more orphans?
|
// did our commit create more orphans?
|
||||||
if (state == LFS_OK_ORPHANED) {
|
if (state == LFS_OK_ORPHANED) {
|
||||||
moreorphans = true;
|
moreorphans = true;
|
||||||
@@ -4709,8 +4936,6 @@ static int lfs_fs_deorphan(lfs_t *lfs, bool powerloss) {
|
|||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
found += 1;
|
|
||||||
|
|
||||||
// did our commit create more orphans?
|
// did our commit create more orphans?
|
||||||
if (state == LFS_OK_ORPHANED) {
|
if (state == LFS_OK_ORPHANED) {
|
||||||
moreorphans = true;
|
moreorphans = true;
|
||||||
@@ -4728,15 +4953,18 @@ static int lfs_fs_deorphan(lfs_t *lfs, bool powerloss) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// mark orphans as fixed
|
// mark orphans as fixed
|
||||||
return lfs_fs_preporphans(lfs, -lfs_min(
|
return lfs_fs_preporphans(lfs, -lfs_gstate_getorphans(&lfs->gstate));
|
||||||
lfs_gstate_getorphans(&lfs->gstate),
|
|
||||||
found));
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifndef LFS_READONLY
|
#ifndef LFS_READONLY
|
||||||
static int lfs_fs_forceconsistency(lfs_t *lfs) {
|
static int lfs_fs_forceconsistency(lfs_t *lfs) {
|
||||||
int err = lfs_fs_demove(lfs);
|
int err = lfs_fs_desuperblock(lfs);
|
||||||
|
if (err) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = lfs_fs_demove(lfs);
|
||||||
if (err) {
|
if (err) {
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
@@ -4750,6 +4978,36 @@ static int lfs_fs_forceconsistency(lfs_t *lfs) {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifndef LFS_READONLY
|
||||||
|
int lfs_fs_rawmkconsistent(lfs_t *lfs) {
|
||||||
|
// lfs_fs_forceconsistency does most of the work here
|
||||||
|
int err = lfs_fs_forceconsistency(lfs);
|
||||||
|
if (err) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
// do we have any pending gstate?
|
||||||
|
lfs_gstate_t delta = {0};
|
||||||
|
lfs_gstate_xor(&delta, &lfs->gdisk);
|
||||||
|
lfs_gstate_xor(&delta, &lfs->gstate);
|
||||||
|
if (!lfs_gstate_iszero(&delta)) {
|
||||||
|
// lfs_dir_commit will implicitly write out any pending gstate
|
||||||
|
lfs_mdir_t root;
|
||||||
|
err = lfs_dir_fetch(lfs, &root, lfs->root);
|
||||||
|
if (err) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = lfs_dir_commit(lfs, &root, NULL, 0);
|
||||||
|
if (err) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
static int lfs_fs_size_count(void *p, lfs_block_t block) {
|
static int lfs_fs_size_count(void *p, lfs_block_t block) {
|
||||||
(void)block;
|
(void)block;
|
||||||
lfs_size_t *size = p;
|
lfs_size_t *size = p;
|
||||||
@@ -4767,6 +5025,7 @@ static lfs_ssize_t lfs_fs_rawsize(lfs_t *lfs) {
|
|||||||
return size;
|
return size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#ifdef LFS_MIGRATE
|
#ifdef LFS_MIGRATE
|
||||||
////// Migration from littelfs v1 below this //////
|
////// Migration from littelfs v1 below this //////
|
||||||
|
|
||||||
@@ -5886,6 +6145,20 @@ int lfs_dir_rewind(lfs_t *lfs, lfs_dir_t *dir) {
|
|||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int lfs_fs_stat(lfs_t *lfs, struct lfs_fsinfo *fsinfo) {
|
||||||
|
int err = LFS_LOCK(lfs->cfg);
|
||||||
|
if (err) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
LFS_TRACE("lfs_fs_stat(%p, %p)", (void*)lfs, (void*)fsinfo);
|
||||||
|
|
||||||
|
err = lfs_fs_rawstat(lfs, fsinfo);
|
||||||
|
|
||||||
|
LFS_TRACE("lfs_fs_stat -> %d", err);
|
||||||
|
LFS_UNLOCK(lfs->cfg);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
lfs_ssize_t lfs_fs_size(lfs_t *lfs) {
|
lfs_ssize_t lfs_fs_size(lfs_t *lfs) {
|
||||||
int err = LFS_LOCK(lfs->cfg);
|
int err = LFS_LOCK(lfs->cfg);
|
||||||
if (err) {
|
if (err) {
|
||||||
@@ -5915,6 +6188,22 @@ int lfs_fs_traverse(lfs_t *lfs, int (*cb)(void *, lfs_block_t), void *data) {
|
|||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifndef LFS_READONLY
|
||||||
|
int lfs_fs_mkconsistent(lfs_t *lfs) {
|
||||||
|
int err = LFS_LOCK(lfs->cfg);
|
||||||
|
if (err) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
LFS_TRACE("lfs_fs_mkconsistent(%p)", (void*)lfs);
|
||||||
|
|
||||||
|
err = lfs_fs_rawmkconsistent(lfs);
|
||||||
|
|
||||||
|
LFS_TRACE("lfs_fs_mkconsistent -> %d", err);
|
||||||
|
LFS_UNLOCK(lfs->cfg);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef LFS_MIGRATE
|
#ifdef LFS_MIGRATE
|
||||||
int lfs_migrate(lfs_t *lfs, const struct lfs_config *cfg) {
|
int lfs_migrate(lfs_t *lfs, const struct lfs_config *cfg) {
|
||||||
int err = LFS_LOCK(cfg);
|
int err = LFS_LOCK(cfg);
|
||||||
|
|||||||
45
lfs.h
45
lfs.h
@@ -21,14 +21,14 @@ extern "C"
|
|||||||
// Software library version
|
// Software library version
|
||||||
// Major (top-nibble), incremented on backwards incompatible changes
|
// Major (top-nibble), incremented on backwards incompatible changes
|
||||||
// Minor (bottom-nibble), incremented on feature additions
|
// Minor (bottom-nibble), incremented on feature additions
|
||||||
#define LFS_VERSION 0x00020005
|
#define LFS_VERSION 0x00020007
|
||||||
#define LFS_VERSION_MAJOR (0xffff & (LFS_VERSION >> 16))
|
#define LFS_VERSION_MAJOR (0xffff & (LFS_VERSION >> 16))
|
||||||
#define LFS_VERSION_MINOR (0xffff & (LFS_VERSION >> 0))
|
#define LFS_VERSION_MINOR (0xffff & (LFS_VERSION >> 0))
|
||||||
|
|
||||||
// Version of On-disk data structures
|
// Version of On-disk data structures
|
||||||
// Major (top-nibble), incremented on backwards incompatible changes
|
// Major (top-nibble), incremented on backwards incompatible changes
|
||||||
// Minor (bottom-nibble), incremented on feature additions
|
// Minor (bottom-nibble), incremented on feature additions
|
||||||
#define LFS_DISK_VERSION 0x00020000
|
#define LFS_DISK_VERSION 0x00020001
|
||||||
#define LFS_DISK_VERSION_MAJOR (0xffff & (LFS_DISK_VERSION >> 16))
|
#define LFS_DISK_VERSION_MAJOR (0xffff & (LFS_DISK_VERSION >> 16))
|
||||||
#define LFS_DISK_VERSION_MINOR (0xffff & (LFS_DISK_VERSION >> 0))
|
#define LFS_DISK_VERSION_MINOR (0xffff & (LFS_DISK_VERSION >> 0))
|
||||||
|
|
||||||
@@ -263,6 +263,14 @@ struct lfs_config {
|
|||||||
// can help bound the metadata compaction time. Must be <= block_size.
|
// can help bound the metadata compaction time. Must be <= block_size.
|
||||||
// Defaults to block_size when zero.
|
// Defaults to block_size when zero.
|
||||||
lfs_size_t metadata_max;
|
lfs_size_t metadata_max;
|
||||||
|
|
||||||
|
#ifdef LFS_MULTIVERSION
|
||||||
|
// On-disk version to use when writing in the form of 16-bit major version
|
||||||
|
// + 16-bit minor version. This limiting metadata to what is supported by
|
||||||
|
// older minor versions. Note that some features will be lost. Defaults to
|
||||||
|
// to the most recent minor version when zero.
|
||||||
|
uint32_t disk_version;
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
// File info structure
|
// File info structure
|
||||||
@@ -280,6 +288,21 @@ struct lfs_info {
|
|||||||
char name[LFS_NAME_MAX+1];
|
char name[LFS_NAME_MAX+1];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Filesystem info structure
|
||||||
|
struct lfs_fsinfo {
|
||||||
|
// On-disk version.
|
||||||
|
uint32_t disk_version;
|
||||||
|
|
||||||
|
// Upper limit on the length of file names in bytes.
|
||||||
|
lfs_size_t name_max;
|
||||||
|
|
||||||
|
// Upper limit on the size of files in bytes.
|
||||||
|
lfs_size_t file_max;
|
||||||
|
|
||||||
|
// Upper limit on the size of custom attributes in bytes.
|
||||||
|
lfs_size_t attr_max;
|
||||||
|
};
|
||||||
|
|
||||||
// Custom attribute structure, used to describe custom attributes
|
// Custom attribute structure, used to describe custom attributes
|
||||||
// committed atomically during file writes.
|
// committed atomically during file writes.
|
||||||
struct lfs_attr {
|
struct lfs_attr {
|
||||||
@@ -659,6 +682,12 @@ int lfs_dir_rewind(lfs_t *lfs, lfs_dir_t *dir);
|
|||||||
|
|
||||||
/// Filesystem-level filesystem operations
|
/// Filesystem-level filesystem operations
|
||||||
|
|
||||||
|
// Find on-disk info about the filesystem
|
||||||
|
//
|
||||||
|
// Fills out the fsinfo structure based on the filesystem found on-disk.
|
||||||
|
// Returns a negative error code on failure.
|
||||||
|
int lfs_fs_stat(lfs_t *lfs, struct lfs_fsinfo *fsinfo);
|
||||||
|
|
||||||
// Finds the current size of the filesystem
|
// Finds the current size of the filesystem
|
||||||
//
|
//
|
||||||
// Note: Result is best effort. If files share COW structures, the returned
|
// Note: Result is best effort. If files share COW structures, the returned
|
||||||
@@ -676,6 +705,18 @@ lfs_ssize_t lfs_fs_size(lfs_t *lfs);
|
|||||||
// Returns a negative error code on failure.
|
// Returns a negative error code on failure.
|
||||||
int lfs_fs_traverse(lfs_t *lfs, int (*cb)(void*, lfs_block_t), void *data);
|
int lfs_fs_traverse(lfs_t *lfs, int (*cb)(void*, lfs_block_t), void *data);
|
||||||
|
|
||||||
|
#ifndef LFS_READONLY
|
||||||
|
// Attempt to make the filesystem consistent and ready for writing
|
||||||
|
//
|
||||||
|
// Calling this function is not required, consistency will be implicitly
|
||||||
|
// enforced on the first operation that writes to the filesystem, but this
|
||||||
|
// function allows the work to be performed earlier and without other
|
||||||
|
// filesystem changes.
|
||||||
|
//
|
||||||
|
// Returns a negative error code on failure.
|
||||||
|
int lfs_fs_mkconsistent(lfs_t *lfs);
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifndef LFS_READONLY
|
#ifndef LFS_READONLY
|
||||||
#ifdef LFS_MIGRATE
|
#ifdef LFS_MIGRATE
|
||||||
// Attempts to migrate a previous version of littlefs
|
// Attempts to migrate a previous version of littlefs
|
||||||
|
|||||||
@@ -23,7 +23,6 @@
|
|||||||
// System includes
|
// System includes
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <sys/types.h>
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <inttypes.h>
|
#include <inttypes.h>
|
||||||
|
|
||||||
|
|||||||
@@ -1346,6 +1346,9 @@ static void run_powerloss_none(
|
|||||||
.block_cycles = BLOCK_CYCLES,
|
.block_cycles = BLOCK_CYCLES,
|
||||||
.cache_size = CACHE_SIZE,
|
.cache_size = CACHE_SIZE,
|
||||||
.lookahead_size = LOOKAHEAD_SIZE,
|
.lookahead_size = LOOKAHEAD_SIZE,
|
||||||
|
#ifdef LFS_MULTIVERSION
|
||||||
|
.disk_version = DISK_VERSION,
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
struct lfs_emubd_config bdcfg = {
|
struct lfs_emubd_config bdcfg = {
|
||||||
@@ -1415,6 +1418,9 @@ static void run_powerloss_linear(
|
|||||||
.block_cycles = BLOCK_CYCLES,
|
.block_cycles = BLOCK_CYCLES,
|
||||||
.cache_size = CACHE_SIZE,
|
.cache_size = CACHE_SIZE,
|
||||||
.lookahead_size = LOOKAHEAD_SIZE,
|
.lookahead_size = LOOKAHEAD_SIZE,
|
||||||
|
#ifdef LFS_MULTIVERSION
|
||||||
|
.disk_version = DISK_VERSION,
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
struct lfs_emubd_config bdcfg = {
|
struct lfs_emubd_config bdcfg = {
|
||||||
@@ -1501,6 +1507,9 @@ static void run_powerloss_log(
|
|||||||
.block_cycles = BLOCK_CYCLES,
|
.block_cycles = BLOCK_CYCLES,
|
||||||
.cache_size = CACHE_SIZE,
|
.cache_size = CACHE_SIZE,
|
||||||
.lookahead_size = LOOKAHEAD_SIZE,
|
.lookahead_size = LOOKAHEAD_SIZE,
|
||||||
|
#ifdef LFS_MULTIVERSION
|
||||||
|
.disk_version = DISK_VERSION,
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
struct lfs_emubd_config bdcfg = {
|
struct lfs_emubd_config bdcfg = {
|
||||||
@@ -1585,6 +1594,9 @@ static void run_powerloss_cycles(
|
|||||||
.block_cycles = BLOCK_CYCLES,
|
.block_cycles = BLOCK_CYCLES,
|
||||||
.cache_size = CACHE_SIZE,
|
.cache_size = CACHE_SIZE,
|
||||||
.lookahead_size = LOOKAHEAD_SIZE,
|
.lookahead_size = LOOKAHEAD_SIZE,
|
||||||
|
#ifdef LFS_MULTIVERSION
|
||||||
|
.disk_version = DISK_VERSION,
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
struct lfs_emubd_config bdcfg = {
|
struct lfs_emubd_config bdcfg = {
|
||||||
@@ -1767,6 +1779,9 @@ static void run_powerloss_exhaustive(
|
|||||||
.block_cycles = BLOCK_CYCLES,
|
.block_cycles = BLOCK_CYCLES,
|
||||||
.cache_size = CACHE_SIZE,
|
.cache_size = CACHE_SIZE,
|
||||||
.lookahead_size = LOOKAHEAD_SIZE,
|
.lookahead_size = LOOKAHEAD_SIZE,
|
||||||
|
#ifdef LFS_MULTIVERSION
|
||||||
|
.disk_version = DISK_VERSION,
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
struct lfs_emubd_config bdcfg = {
|
struct lfs_emubd_config bdcfg = {
|
||||||
|
|||||||
@@ -91,6 +91,7 @@ intmax_t test_define(size_t define);
|
|||||||
#define ERASE_CYCLES_i 8
|
#define ERASE_CYCLES_i 8
|
||||||
#define BADBLOCK_BEHAVIOR_i 9
|
#define BADBLOCK_BEHAVIOR_i 9
|
||||||
#define POWERLOSS_BEHAVIOR_i 10
|
#define POWERLOSS_BEHAVIOR_i 10
|
||||||
|
#define DISK_VERSION_i 11
|
||||||
|
|
||||||
#define READ_SIZE TEST_DEFINE(READ_SIZE_i)
|
#define READ_SIZE TEST_DEFINE(READ_SIZE_i)
|
||||||
#define PROG_SIZE TEST_DEFINE(PROG_SIZE_i)
|
#define PROG_SIZE TEST_DEFINE(PROG_SIZE_i)
|
||||||
@@ -103,6 +104,7 @@ intmax_t test_define(size_t define);
|
|||||||
#define ERASE_CYCLES TEST_DEFINE(ERASE_CYCLES_i)
|
#define ERASE_CYCLES TEST_DEFINE(ERASE_CYCLES_i)
|
||||||
#define BADBLOCK_BEHAVIOR TEST_DEFINE(BADBLOCK_BEHAVIOR_i)
|
#define BADBLOCK_BEHAVIOR TEST_DEFINE(BADBLOCK_BEHAVIOR_i)
|
||||||
#define POWERLOSS_BEHAVIOR TEST_DEFINE(POWERLOSS_BEHAVIOR_i)
|
#define POWERLOSS_BEHAVIOR TEST_DEFINE(POWERLOSS_BEHAVIOR_i)
|
||||||
|
#define DISK_VERSION TEST_DEFINE(DISK_VERSION_i)
|
||||||
|
|
||||||
#define TEST_IMPLICIT_DEFINES \
|
#define TEST_IMPLICIT_DEFINES \
|
||||||
TEST_DEF(READ_SIZE, PROG_SIZE) \
|
TEST_DEF(READ_SIZE, PROG_SIZE) \
|
||||||
@@ -115,9 +117,10 @@ intmax_t test_define(size_t define);
|
|||||||
TEST_DEF(ERASE_VALUE, 0xff) \
|
TEST_DEF(ERASE_VALUE, 0xff) \
|
||||||
TEST_DEF(ERASE_CYCLES, 0) \
|
TEST_DEF(ERASE_CYCLES, 0) \
|
||||||
TEST_DEF(BADBLOCK_BEHAVIOR, LFS_EMUBD_BADBLOCK_PROGERROR) \
|
TEST_DEF(BADBLOCK_BEHAVIOR, LFS_EMUBD_BADBLOCK_PROGERROR) \
|
||||||
TEST_DEF(POWERLOSS_BEHAVIOR, LFS_EMUBD_POWERLOSS_NOOP)
|
TEST_DEF(POWERLOSS_BEHAVIOR, LFS_EMUBD_POWERLOSS_NOOP) \
|
||||||
|
TEST_DEF(DISK_VERSION, 0)
|
||||||
|
|
||||||
#define TEST_IMPLICIT_DEFINE_COUNT 11
|
#define TEST_IMPLICIT_DEFINE_COUNT 12
|
||||||
#define TEST_GEOMETRY_DEFINE_COUNT 4
|
#define TEST_GEOMETRY_DEFINE_COUNT 4
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -107,7 +107,10 @@ def main(from_prefix, to_prefix, paths=[], *,
|
|||||||
elif no_renames:
|
elif no_renames:
|
||||||
to_path = from_path
|
to_path = from_path
|
||||||
else:
|
else:
|
||||||
to_path, _ = changeprefix(from_prefix, to_prefix, from_path)
|
to_path = os.path.join(
|
||||||
|
os.path.dirname(from_path),
|
||||||
|
changeprefix(from_prefix, to_prefix,
|
||||||
|
os.path.basename(from_path))[0])
|
||||||
|
|
||||||
# rename contents
|
# rename contents
|
||||||
changefile(from_prefix, to_prefix, from_path, to_path,
|
changefile(from_prefix, to_prefix, from_path, to_path,
|
||||||
|
|||||||
1453
tests/test_compat.toml
Normal file
1453
tests/test_compat.toml
Normal file
File diff suppressed because it is too large
Load Diff
@@ -810,7 +810,8 @@ code = '''
|
|||||||
}
|
}
|
||||||
lfs_unmount(&lfs) => 0;
|
lfs_unmount(&lfs) => 0;
|
||||||
|
|
||||||
for (int j = 2; j < COUNT; j++) {
|
// try seeking to each dir entry
|
||||||
|
for (int j = 0; j < COUNT; j++) {
|
||||||
lfs_mount(&lfs, cfg) => 0;
|
lfs_mount(&lfs, cfg) => 0;
|
||||||
lfs_dir_t dir;
|
lfs_dir_t dir;
|
||||||
lfs_dir_open(&lfs, &dir, "hello") => 0;
|
lfs_dir_open(&lfs, &dir, "hello") => 0;
|
||||||
@@ -822,16 +823,15 @@ code = '''
|
|||||||
assert(strcmp(info.name, "..") == 0);
|
assert(strcmp(info.name, "..") == 0);
|
||||||
assert(info.type == LFS_TYPE_DIR);
|
assert(info.type == LFS_TYPE_DIR);
|
||||||
|
|
||||||
lfs_soff_t pos;
|
|
||||||
for (int i = 0; i < j; i++) {
|
for (int i = 0; i < j; i++) {
|
||||||
char path[1024];
|
char path[1024];
|
||||||
sprintf(path, "kitty%03d", i);
|
sprintf(path, "kitty%03d", i);
|
||||||
lfs_dir_read(&lfs, &dir, &info) => 1;
|
lfs_dir_read(&lfs, &dir, &info) => 1;
|
||||||
assert(strcmp(info.name, path) == 0);
|
assert(strcmp(info.name, path) == 0);
|
||||||
assert(info.type == LFS_TYPE_DIR);
|
assert(info.type == LFS_TYPE_DIR);
|
||||||
pos = lfs_dir_tell(&lfs, &dir);
|
|
||||||
assert(pos >= 0);
|
|
||||||
}
|
}
|
||||||
|
lfs_soff_t pos = lfs_dir_tell(&lfs, &dir);
|
||||||
|
assert(pos >= 0);
|
||||||
|
|
||||||
lfs_dir_seek(&lfs, &dir, pos) => 0;
|
lfs_dir_seek(&lfs, &dir, pos) => 0;
|
||||||
char path[1024];
|
char path[1024];
|
||||||
@@ -861,6 +861,52 @@ code = '''
|
|||||||
lfs_dir_close(&lfs, &dir) => 0;
|
lfs_dir_close(&lfs, &dir) => 0;
|
||||||
lfs_unmount(&lfs) => 0;
|
lfs_unmount(&lfs) => 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// try seeking to end of dir
|
||||||
|
lfs_mount(&lfs, cfg) => 0;
|
||||||
|
lfs_dir_t dir;
|
||||||
|
lfs_dir_open(&lfs, &dir, "hello") => 0;
|
||||||
|
struct lfs_info info;
|
||||||
|
lfs_dir_read(&lfs, &dir, &info) => 1;
|
||||||
|
assert(strcmp(info.name, ".") == 0);
|
||||||
|
assert(info.type == LFS_TYPE_DIR);
|
||||||
|
lfs_dir_read(&lfs, &dir, &info) => 1;
|
||||||
|
assert(strcmp(info.name, "..") == 0);
|
||||||
|
assert(info.type == LFS_TYPE_DIR);
|
||||||
|
|
||||||
|
for (int i = 0; i < COUNT; i++) {
|
||||||
|
char path[1024];
|
||||||
|
sprintf(path, "kitty%03d", i);
|
||||||
|
lfs_dir_read(&lfs, &dir, &info) => 1;
|
||||||
|
assert(strcmp(info.name, path) == 0);
|
||||||
|
assert(info.type == LFS_TYPE_DIR);
|
||||||
|
}
|
||||||
|
lfs_soff_t pos = lfs_dir_tell(&lfs, &dir);
|
||||||
|
assert(pos >= 0);
|
||||||
|
|
||||||
|
lfs_dir_read(&lfs, &dir, &info) => 0;
|
||||||
|
|
||||||
|
lfs_dir_seek(&lfs, &dir, pos) => 0;
|
||||||
|
lfs_dir_read(&lfs, &dir, &info) => 0;
|
||||||
|
|
||||||
|
lfs_dir_rewind(&lfs, &dir) => 0;
|
||||||
|
char path[1024];
|
||||||
|
sprintf(path, "kitty%03d", 0);
|
||||||
|
lfs_dir_read(&lfs, &dir, &info) => 1;
|
||||||
|
assert(strcmp(info.name, ".") == 0);
|
||||||
|
assert(info.type == LFS_TYPE_DIR);
|
||||||
|
lfs_dir_read(&lfs, &dir, &info) => 1;
|
||||||
|
assert(strcmp(info.name, "..") == 0);
|
||||||
|
assert(info.type == LFS_TYPE_DIR);
|
||||||
|
lfs_dir_read(&lfs, &dir, &info) => 1;
|
||||||
|
assert(strcmp(info.name, path) == 0);
|
||||||
|
assert(info.type == LFS_TYPE_DIR);
|
||||||
|
|
||||||
|
lfs_dir_seek(&lfs, &dir, pos) => 0;
|
||||||
|
lfs_dir_read(&lfs, &dir, &info) => 0;
|
||||||
|
|
||||||
|
lfs_dir_close(&lfs, &dir) => 0;
|
||||||
|
lfs_unmount(&lfs) => 0;
|
||||||
'''
|
'''
|
||||||
|
|
||||||
[cases.test_dirs_toot_seek]
|
[cases.test_dirs_toot_seek]
|
||||||
@@ -877,7 +923,7 @@ code = '''
|
|||||||
}
|
}
|
||||||
lfs_unmount(&lfs) => 0;
|
lfs_unmount(&lfs) => 0;
|
||||||
|
|
||||||
for (int j = 2; j < COUNT; j++) {
|
for (int j = 0; j < COUNT; j++) {
|
||||||
lfs_mount(&lfs, cfg) => 0;
|
lfs_mount(&lfs, cfg) => 0;
|
||||||
lfs_dir_t dir;
|
lfs_dir_t dir;
|
||||||
lfs_dir_open(&lfs, &dir, "/") => 0;
|
lfs_dir_open(&lfs, &dir, "/") => 0;
|
||||||
@@ -889,16 +935,15 @@ code = '''
|
|||||||
assert(strcmp(info.name, "..") == 0);
|
assert(strcmp(info.name, "..") == 0);
|
||||||
assert(info.type == LFS_TYPE_DIR);
|
assert(info.type == LFS_TYPE_DIR);
|
||||||
|
|
||||||
lfs_soff_t pos;
|
|
||||||
for (int i = 0; i < j; i++) {
|
for (int i = 0; i < j; i++) {
|
||||||
char path[1024];
|
char path[1024];
|
||||||
sprintf(path, "hi%03d", i);
|
sprintf(path, "hi%03d", i);
|
||||||
lfs_dir_read(&lfs, &dir, &info) => 1;
|
lfs_dir_read(&lfs, &dir, &info) => 1;
|
||||||
assert(strcmp(info.name, path) == 0);
|
assert(strcmp(info.name, path) == 0);
|
||||||
assert(info.type == LFS_TYPE_DIR);
|
assert(info.type == LFS_TYPE_DIR);
|
||||||
pos = lfs_dir_tell(&lfs, &dir);
|
|
||||||
assert(pos >= 0);
|
|
||||||
}
|
}
|
||||||
|
lfs_soff_t pos = lfs_dir_tell(&lfs, &dir);
|
||||||
|
assert(pos >= 0);
|
||||||
|
|
||||||
lfs_dir_seek(&lfs, &dir, pos) => 0;
|
lfs_dir_seek(&lfs, &dir, pos) => 0;
|
||||||
char path[1024];
|
char path[1024];
|
||||||
@@ -928,5 +973,51 @@ code = '''
|
|||||||
lfs_dir_close(&lfs, &dir) => 0;
|
lfs_dir_close(&lfs, &dir) => 0;
|
||||||
lfs_unmount(&lfs) => 0;
|
lfs_unmount(&lfs) => 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// try seeking to end of dir
|
||||||
|
lfs_mount(&lfs, cfg) => 0;
|
||||||
|
lfs_dir_t dir;
|
||||||
|
lfs_dir_open(&lfs, &dir, "/") => 0;
|
||||||
|
struct lfs_info info;
|
||||||
|
lfs_dir_read(&lfs, &dir, &info) => 1;
|
||||||
|
assert(strcmp(info.name, ".") == 0);
|
||||||
|
assert(info.type == LFS_TYPE_DIR);
|
||||||
|
lfs_dir_read(&lfs, &dir, &info) => 1;
|
||||||
|
assert(strcmp(info.name, "..") == 0);
|
||||||
|
assert(info.type == LFS_TYPE_DIR);
|
||||||
|
|
||||||
|
for (int i = 0; i < COUNT; i++) {
|
||||||
|
char path[1024];
|
||||||
|
sprintf(path, "hi%03d", i);
|
||||||
|
lfs_dir_read(&lfs, &dir, &info) => 1;
|
||||||
|
assert(strcmp(info.name, path) == 0);
|
||||||
|
assert(info.type == LFS_TYPE_DIR);
|
||||||
|
}
|
||||||
|
lfs_soff_t pos = lfs_dir_tell(&lfs, &dir);
|
||||||
|
assert(pos >= 0);
|
||||||
|
|
||||||
|
lfs_dir_read(&lfs, &dir, &info) => 0;
|
||||||
|
|
||||||
|
lfs_dir_seek(&lfs, &dir, pos) => 0;
|
||||||
|
lfs_dir_read(&lfs, &dir, &info) => 0;
|
||||||
|
|
||||||
|
lfs_dir_rewind(&lfs, &dir) => 0;
|
||||||
|
char path[1024];
|
||||||
|
sprintf(path, "hi%03d", 0);
|
||||||
|
lfs_dir_read(&lfs, &dir, &info) => 1;
|
||||||
|
assert(strcmp(info.name, ".") == 0);
|
||||||
|
assert(info.type == LFS_TYPE_DIR);
|
||||||
|
lfs_dir_read(&lfs, &dir, &info) => 1;
|
||||||
|
assert(strcmp(info.name, "..") == 0);
|
||||||
|
assert(info.type == LFS_TYPE_DIR);
|
||||||
|
lfs_dir_read(&lfs, &dir, &info) => 1;
|
||||||
|
assert(strcmp(info.name, path) == 0);
|
||||||
|
assert(info.type == LFS_TYPE_DIR);
|
||||||
|
|
||||||
|
lfs_dir_seek(&lfs, &dir, pos) => 0;
|
||||||
|
lfs_dir_read(&lfs, &dir, &info) => 0;
|
||||||
|
|
||||||
|
lfs_dir_close(&lfs, &dir) => 0;
|
||||||
|
lfs_unmount(&lfs) => 0;
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
|||||||
@@ -59,6 +59,150 @@ code = '''
|
|||||||
lfs_unmount(&lfs) => 0;
|
lfs_unmount(&lfs) => 0;
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
# test that we only run deorphan once per power-cycle
|
||||||
|
[cases.test_orphans_no_orphans]
|
||||||
|
in = 'lfs.c'
|
||||||
|
code = '''
|
||||||
|
lfs_t lfs;
|
||||||
|
lfs_format(&lfs, cfg) => 0;
|
||||||
|
|
||||||
|
lfs_mount(&lfs, cfg) => 0;
|
||||||
|
// mark the filesystem as having orphans
|
||||||
|
lfs_fs_preporphans(&lfs, +1) => 0;
|
||||||
|
lfs_mdir_t mdir;
|
||||||
|
lfs_dir_fetch(&lfs, &mdir, (lfs_block_t[2]){0, 1}) => 0;
|
||||||
|
lfs_dir_commit(&lfs, &mdir, NULL, 0) => 0;
|
||||||
|
|
||||||
|
// we should have orphans at this state
|
||||||
|
assert(lfs_gstate_hasorphans(&lfs.gstate));
|
||||||
|
lfs_unmount(&lfs) => 0;
|
||||||
|
|
||||||
|
// mount
|
||||||
|
lfs_mount(&lfs, cfg) => 0;
|
||||||
|
// we should detect orphans
|
||||||
|
assert(lfs_gstate_hasorphans(&lfs.gstate));
|
||||||
|
// force consistency
|
||||||
|
lfs_fs_forceconsistency(&lfs) => 0;
|
||||||
|
// we should no longer have orphans
|
||||||
|
assert(!lfs_gstate_hasorphans(&lfs.gstate));
|
||||||
|
|
||||||
|
lfs_unmount(&lfs) => 0;
|
||||||
|
'''
|
||||||
|
|
||||||
|
[cases.test_orphans_one_orphan]
|
||||||
|
in = 'lfs.c'
|
||||||
|
code = '''
|
||||||
|
lfs_t lfs;
|
||||||
|
lfs_format(&lfs, cfg) => 0;
|
||||||
|
|
||||||
|
lfs_mount(&lfs, cfg) => 0;
|
||||||
|
// create an orphan
|
||||||
|
lfs_mdir_t orphan;
|
||||||
|
lfs_alloc_ack(&lfs);
|
||||||
|
lfs_dir_alloc(&lfs, &orphan) => 0;
|
||||||
|
lfs_dir_commit(&lfs, &orphan, NULL, 0) => 0;
|
||||||
|
|
||||||
|
// append our orphan and mark the filesystem as having orphans
|
||||||
|
lfs_fs_preporphans(&lfs, +1) => 0;
|
||||||
|
lfs_mdir_t mdir;
|
||||||
|
lfs_dir_fetch(&lfs, &mdir, (lfs_block_t[2]){0, 1}) => 0;
|
||||||
|
lfs_pair_tole32(orphan.pair);
|
||||||
|
lfs_dir_commit(&lfs, &mdir, LFS_MKATTRS(
|
||||||
|
{LFS_MKTAG(LFS_TYPE_SOFTTAIL, 0x3ff, 8), orphan.pair})) => 0;
|
||||||
|
|
||||||
|
// we should have orphans at this state
|
||||||
|
assert(lfs_gstate_hasorphans(&lfs.gstate));
|
||||||
|
lfs_unmount(&lfs) => 0;
|
||||||
|
|
||||||
|
// mount
|
||||||
|
lfs_mount(&lfs, cfg) => 0;
|
||||||
|
// we should detect orphans
|
||||||
|
assert(lfs_gstate_hasorphans(&lfs.gstate));
|
||||||
|
// force consistency
|
||||||
|
lfs_fs_forceconsistency(&lfs) => 0;
|
||||||
|
// we should no longer have orphans
|
||||||
|
assert(!lfs_gstate_hasorphans(&lfs.gstate));
|
||||||
|
|
||||||
|
lfs_unmount(&lfs) => 0;
|
||||||
|
'''
|
||||||
|
|
||||||
|
# test that we can persist gstate with lfs_fs_mkconsistent
|
||||||
|
[cases.test_orphans_mkconsistent_no_orphans]
|
||||||
|
in = 'lfs.c'
|
||||||
|
code = '''
|
||||||
|
lfs_t lfs;
|
||||||
|
lfs_format(&lfs, cfg) => 0;
|
||||||
|
|
||||||
|
lfs_mount(&lfs, cfg) => 0;
|
||||||
|
// mark the filesystem as having orphans
|
||||||
|
lfs_fs_preporphans(&lfs, +1) => 0;
|
||||||
|
lfs_mdir_t mdir;
|
||||||
|
lfs_dir_fetch(&lfs, &mdir, (lfs_block_t[2]){0, 1}) => 0;
|
||||||
|
lfs_dir_commit(&lfs, &mdir, NULL, 0) => 0;
|
||||||
|
|
||||||
|
// we should have orphans at this state
|
||||||
|
assert(lfs_gstate_hasorphans(&lfs.gstate));
|
||||||
|
lfs_unmount(&lfs) => 0;
|
||||||
|
|
||||||
|
// mount
|
||||||
|
lfs_mount(&lfs, cfg) => 0;
|
||||||
|
// we should detect orphans
|
||||||
|
assert(lfs_gstate_hasorphans(&lfs.gstate));
|
||||||
|
// force consistency
|
||||||
|
lfs_fs_mkconsistent(&lfs) => 0;
|
||||||
|
// we should no longer have orphans
|
||||||
|
assert(!lfs_gstate_hasorphans(&lfs.gstate));
|
||||||
|
|
||||||
|
// remount
|
||||||
|
lfs_unmount(&lfs) => 0;
|
||||||
|
lfs_mount(&lfs, cfg) => 0;
|
||||||
|
// we should still have no orphans
|
||||||
|
assert(!lfs_gstate_hasorphans(&lfs.gstate));
|
||||||
|
lfs_unmount(&lfs) => 0;
|
||||||
|
'''
|
||||||
|
|
||||||
|
[cases.test_orphans_mkconsistent_one_orphan]
|
||||||
|
in = 'lfs.c'
|
||||||
|
code = '''
|
||||||
|
lfs_t lfs;
|
||||||
|
lfs_format(&lfs, cfg) => 0;
|
||||||
|
|
||||||
|
lfs_mount(&lfs, cfg) => 0;
|
||||||
|
// create an orphan
|
||||||
|
lfs_mdir_t orphan;
|
||||||
|
lfs_alloc_ack(&lfs);
|
||||||
|
lfs_dir_alloc(&lfs, &orphan) => 0;
|
||||||
|
lfs_dir_commit(&lfs, &orphan, NULL, 0) => 0;
|
||||||
|
|
||||||
|
// append our orphan and mark the filesystem as having orphans
|
||||||
|
lfs_fs_preporphans(&lfs, +1) => 0;
|
||||||
|
lfs_mdir_t mdir;
|
||||||
|
lfs_dir_fetch(&lfs, &mdir, (lfs_block_t[2]){0, 1}) => 0;
|
||||||
|
lfs_pair_tole32(orphan.pair);
|
||||||
|
lfs_dir_commit(&lfs, &mdir, LFS_MKATTRS(
|
||||||
|
{LFS_MKTAG(LFS_TYPE_SOFTTAIL, 0x3ff, 8), orphan.pair})) => 0;
|
||||||
|
|
||||||
|
// we should have orphans at this state
|
||||||
|
assert(lfs_gstate_hasorphans(&lfs.gstate));
|
||||||
|
lfs_unmount(&lfs) => 0;
|
||||||
|
|
||||||
|
// mount
|
||||||
|
lfs_mount(&lfs, cfg) => 0;
|
||||||
|
// we should detect orphans
|
||||||
|
assert(lfs_gstate_hasorphans(&lfs.gstate));
|
||||||
|
// force consistency
|
||||||
|
lfs_fs_mkconsistent(&lfs) => 0;
|
||||||
|
// we should no longer have orphans
|
||||||
|
assert(!lfs_gstate_hasorphans(&lfs.gstate));
|
||||||
|
|
||||||
|
// remount
|
||||||
|
lfs_unmount(&lfs) => 0;
|
||||||
|
lfs_mount(&lfs, cfg) => 0;
|
||||||
|
// we should still have no orphans
|
||||||
|
assert(!lfs_gstate_hasorphans(&lfs.gstate));
|
||||||
|
lfs_unmount(&lfs) => 0;
|
||||||
|
'''
|
||||||
|
|
||||||
# reentrant testing for orphans, basically just spam mkdir/remove
|
# reentrant testing for orphans, basically just spam mkdir/remove
|
||||||
[cases.test_orphans_reentrant]
|
[cases.test_orphans_reentrant]
|
||||||
reentrant = true
|
reentrant = true
|
||||||
|
|||||||
@@ -90,7 +90,10 @@ code = '''
|
|||||||
|
|
||||||
# partial prog, may not be byte in order!
|
# partial prog, may not be byte in order!
|
||||||
[cases.test_powerloss_partial_prog]
|
[cases.test_powerloss_partial_prog]
|
||||||
if = "PROG_SIZE < BLOCK_SIZE"
|
if = '''
|
||||||
|
PROG_SIZE < BLOCK_SIZE
|
||||||
|
&& (DISK_VERSION == 0 || DISK_VERSION >= 0x00020001)
|
||||||
|
'''
|
||||||
defines.BYTE_OFF = ["0", "PROG_SIZE-1", "PROG_SIZE/2"]
|
defines.BYTE_OFF = ["0", "PROG_SIZE-1", "PROG_SIZE/2"]
|
||||||
defines.BYTE_VALUE = [0x33, 0xcc]
|
defines.BYTE_VALUE = [0x33, 0xcc]
|
||||||
in = "lfs.c"
|
in = "lfs.c"
|
||||||
|
|||||||
@@ -34,6 +34,54 @@ code = '''
|
|||||||
lfs_mount(&lfs, cfg) => LFS_ERR_CORRUPT;
|
lfs_mount(&lfs, cfg) => LFS_ERR_CORRUPT;
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
# test we can read superblock info through lfs_fs_stat
|
||||||
|
[cases.test_superblocks_stat]
|
||||||
|
if = 'DISK_VERSION == 0'
|
||||||
|
code = '''
|
||||||
|
lfs_t lfs;
|
||||||
|
lfs_format(&lfs, cfg) => 0;
|
||||||
|
|
||||||
|
// test we can mount and read fsinfo
|
||||||
|
lfs_mount(&lfs, cfg) => 0;
|
||||||
|
|
||||||
|
struct lfs_fsinfo fsinfo;
|
||||||
|
lfs_fs_stat(&lfs, &fsinfo) => 0;
|
||||||
|
assert(fsinfo.disk_version == LFS_DISK_VERSION);
|
||||||
|
assert(fsinfo.name_max == LFS_NAME_MAX);
|
||||||
|
assert(fsinfo.file_max == LFS_FILE_MAX);
|
||||||
|
assert(fsinfo.attr_max == LFS_ATTR_MAX);
|
||||||
|
|
||||||
|
lfs_unmount(&lfs) => 0;
|
||||||
|
'''
|
||||||
|
|
||||||
|
[cases.test_superblocks_stat_tweaked]
|
||||||
|
if = 'DISK_VERSION == 0'
|
||||||
|
defines.TWEAKED_NAME_MAX = 63
|
||||||
|
defines.TWEAKED_FILE_MAX = '(1 << 16)-1'
|
||||||
|
defines.TWEAKED_ATTR_MAX = 512
|
||||||
|
code = '''
|
||||||
|
// create filesystem with tweaked params
|
||||||
|
struct lfs_config tweaked_cfg = *cfg;
|
||||||
|
tweaked_cfg.name_max = TWEAKED_NAME_MAX;
|
||||||
|
tweaked_cfg.file_max = TWEAKED_FILE_MAX;
|
||||||
|
tweaked_cfg.attr_max = TWEAKED_ATTR_MAX;
|
||||||
|
|
||||||
|
lfs_t lfs;
|
||||||
|
lfs_format(&lfs, &tweaked_cfg) => 0;
|
||||||
|
|
||||||
|
// test we can mount and read these params with the original config
|
||||||
|
lfs_mount(&lfs, cfg) => 0;
|
||||||
|
|
||||||
|
struct lfs_fsinfo fsinfo;
|
||||||
|
lfs_fs_stat(&lfs, &fsinfo) => 0;
|
||||||
|
assert(fsinfo.disk_version == LFS_DISK_VERSION);
|
||||||
|
assert(fsinfo.name_max == TWEAKED_NAME_MAX);
|
||||||
|
assert(fsinfo.file_max == TWEAKED_FILE_MAX);
|
||||||
|
assert(fsinfo.attr_max == TWEAKED_ATTR_MAX);
|
||||||
|
|
||||||
|
lfs_unmount(&lfs) => 0;
|
||||||
|
'''
|
||||||
|
|
||||||
# expanding superblock
|
# expanding superblock
|
||||||
[cases.test_superblocks_expand]
|
[cases.test_superblocks_expand]
|
||||||
defines.BLOCK_CYCLES = [32, 33, 1]
|
defines.BLOCK_CYCLES = [32, 33, 1]
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
# simple truncate
|
# simple truncate
|
||||||
[cases.test_truncate_simple]
|
[cases.test_truncate_simple]
|
||||||
defines.MEDIUMSIZE = [32, 2048]
|
defines.MEDIUMSIZE = [31, 32, 33, 511, 512, 513, 2047, 2048, 2049]
|
||||||
defines.LARGESIZE = 8192
|
defines.LARGESIZE = [32, 33, 512, 513, 2048, 2049, 8192, 8193]
|
||||||
|
if = 'MEDIUMSIZE < LARGESIZE'
|
||||||
code = '''
|
code = '''
|
||||||
lfs_t lfs;
|
lfs_t lfs;
|
||||||
lfs_format(&lfs, cfg) => 0;
|
lfs_format(&lfs, cfg) => 0;
|
||||||
@@ -14,7 +15,8 @@ code = '''
|
|||||||
strcpy((char*)buffer, "hair");
|
strcpy((char*)buffer, "hair");
|
||||||
size_t size = strlen((char*)buffer);
|
size_t size = strlen((char*)buffer);
|
||||||
for (lfs_off_t j = 0; j < LARGESIZE; j += size) {
|
for (lfs_off_t j = 0; j < LARGESIZE; j += size) {
|
||||||
lfs_file_write(&lfs, &file, buffer, size) => size;
|
lfs_file_write(&lfs, &file, buffer, lfs_min(size, LARGESIZE-j))
|
||||||
|
=> lfs_min(size, LARGESIZE-j);
|
||||||
}
|
}
|
||||||
lfs_file_size(&lfs, &file) => LARGESIZE;
|
lfs_file_size(&lfs, &file) => LARGESIZE;
|
||||||
|
|
||||||
@@ -37,8 +39,9 @@ code = '''
|
|||||||
|
|
||||||
size = strlen("hair");
|
size = strlen("hair");
|
||||||
for (lfs_off_t j = 0; j < MEDIUMSIZE; j += size) {
|
for (lfs_off_t j = 0; j < MEDIUMSIZE; j += size) {
|
||||||
lfs_file_read(&lfs, &file, buffer, size) => size;
|
lfs_file_read(&lfs, &file, buffer, lfs_min(size, MEDIUMSIZE-j))
|
||||||
memcmp(buffer, "hair", size) => 0;
|
=> lfs_min(size, MEDIUMSIZE-j);
|
||||||
|
memcmp(buffer, "hair", lfs_min(size, MEDIUMSIZE-j)) => 0;
|
||||||
}
|
}
|
||||||
lfs_file_read(&lfs, &file, buffer, size) => 0;
|
lfs_file_read(&lfs, &file, buffer, size) => 0;
|
||||||
|
|
||||||
@@ -48,8 +51,9 @@ code = '''
|
|||||||
|
|
||||||
# truncate and read
|
# truncate and read
|
||||||
[cases.test_truncate_read]
|
[cases.test_truncate_read]
|
||||||
defines.MEDIUMSIZE = [32, 2048]
|
defines.MEDIUMSIZE = [31, 32, 33, 511, 512, 513, 2047, 2048, 2049]
|
||||||
defines.LARGESIZE = 8192
|
defines.LARGESIZE = [32, 33, 512, 513, 2048, 2049, 8192, 8193]
|
||||||
|
if = 'MEDIUMSIZE < LARGESIZE'
|
||||||
code = '''
|
code = '''
|
||||||
lfs_t lfs;
|
lfs_t lfs;
|
||||||
lfs_format(&lfs, cfg) => 0;
|
lfs_format(&lfs, cfg) => 0;
|
||||||
@@ -62,7 +66,8 @@ code = '''
|
|||||||
strcpy((char*)buffer, "hair");
|
strcpy((char*)buffer, "hair");
|
||||||
size_t size = strlen((char*)buffer);
|
size_t size = strlen((char*)buffer);
|
||||||
for (lfs_off_t j = 0; j < LARGESIZE; j += size) {
|
for (lfs_off_t j = 0; j < LARGESIZE; j += size) {
|
||||||
lfs_file_write(&lfs, &file, buffer, size) => size;
|
lfs_file_write(&lfs, &file, buffer, lfs_min(size, LARGESIZE-j))
|
||||||
|
=> lfs_min(size, LARGESIZE-j);
|
||||||
}
|
}
|
||||||
lfs_file_size(&lfs, &file) => LARGESIZE;
|
lfs_file_size(&lfs, &file) => LARGESIZE;
|
||||||
|
|
||||||
@@ -78,8 +83,9 @@ code = '''
|
|||||||
|
|
||||||
size = strlen("hair");
|
size = strlen("hair");
|
||||||
for (lfs_off_t j = 0; j < MEDIUMSIZE; j += size) {
|
for (lfs_off_t j = 0; j < MEDIUMSIZE; j += size) {
|
||||||
lfs_file_read(&lfs, &file, buffer, size) => size;
|
lfs_file_read(&lfs, &file, buffer, lfs_min(size, MEDIUMSIZE-j))
|
||||||
memcmp(buffer, "hair", size) => 0;
|
=> lfs_min(size, MEDIUMSIZE-j);
|
||||||
|
memcmp(buffer, "hair", lfs_min(size, MEDIUMSIZE-j)) => 0;
|
||||||
}
|
}
|
||||||
lfs_file_read(&lfs, &file, buffer, size) => 0;
|
lfs_file_read(&lfs, &file, buffer, size) => 0;
|
||||||
|
|
||||||
@@ -92,8 +98,9 @@ code = '''
|
|||||||
|
|
||||||
size = strlen("hair");
|
size = strlen("hair");
|
||||||
for (lfs_off_t j = 0; j < MEDIUMSIZE; j += size) {
|
for (lfs_off_t j = 0; j < MEDIUMSIZE; j += size) {
|
||||||
lfs_file_read(&lfs, &file, buffer, size) => size;
|
lfs_file_read(&lfs, &file, buffer, lfs_min(size, MEDIUMSIZE-j))
|
||||||
memcmp(buffer, "hair", size) => 0;
|
=> lfs_min(size, MEDIUMSIZE-j);
|
||||||
|
memcmp(buffer, "hair", lfs_min(size, MEDIUMSIZE-j)) => 0;
|
||||||
}
|
}
|
||||||
lfs_file_read(&lfs, &file, buffer, size) => 0;
|
lfs_file_read(&lfs, &file, buffer, size) => 0;
|
||||||
|
|
||||||
@@ -148,7 +155,7 @@ code = '''
|
|||||||
lfs_file_truncate(&lfs, &file, trunc) => 0;
|
lfs_file_truncate(&lfs, &file, trunc) => 0;
|
||||||
lfs_file_tell(&lfs, &file) => qsize;
|
lfs_file_tell(&lfs, &file) => qsize;
|
||||||
lfs_file_size(&lfs, &file) => trunc;
|
lfs_file_size(&lfs, &file) => trunc;
|
||||||
|
|
||||||
/* Read should produce second quarter */
|
/* Read should produce second quarter */
|
||||||
lfs_file_read(&lfs, &file, rb, size) => trunc - qsize;
|
lfs_file_read(&lfs, &file, rb, size) => trunc - qsize;
|
||||||
memcmp(rb, wb + qsize, trunc - qsize) => 0;
|
memcmp(rb, wb + qsize, trunc - qsize) => 0;
|
||||||
@@ -159,8 +166,9 @@ code = '''
|
|||||||
|
|
||||||
# truncate and write
|
# truncate and write
|
||||||
[cases.test_truncate_write]
|
[cases.test_truncate_write]
|
||||||
defines.MEDIUMSIZE = [32, 2048]
|
defines.MEDIUMSIZE = [31, 32, 33, 511, 512, 513, 2047, 2048, 2049]
|
||||||
defines.LARGESIZE = 8192
|
defines.LARGESIZE = [32, 33, 512, 513, 2048, 2049, 8192, 8193]
|
||||||
|
if = 'MEDIUMSIZE < LARGESIZE'
|
||||||
code = '''
|
code = '''
|
||||||
lfs_t lfs;
|
lfs_t lfs;
|
||||||
lfs_format(&lfs, cfg) => 0;
|
lfs_format(&lfs, cfg) => 0;
|
||||||
@@ -173,7 +181,8 @@ code = '''
|
|||||||
strcpy((char*)buffer, "hair");
|
strcpy((char*)buffer, "hair");
|
||||||
size_t size = strlen((char*)buffer);
|
size_t size = strlen((char*)buffer);
|
||||||
for (lfs_off_t j = 0; j < LARGESIZE; j += size) {
|
for (lfs_off_t j = 0; j < LARGESIZE; j += size) {
|
||||||
lfs_file_write(&lfs, &file, buffer, size) => size;
|
lfs_file_write(&lfs, &file, buffer, lfs_min(size, LARGESIZE-j))
|
||||||
|
=> lfs_min(size, LARGESIZE-j);
|
||||||
}
|
}
|
||||||
lfs_file_size(&lfs, &file) => LARGESIZE;
|
lfs_file_size(&lfs, &file) => LARGESIZE;
|
||||||
|
|
||||||
@@ -184,13 +193,16 @@ code = '''
|
|||||||
lfs_file_open(&lfs, &file, "baldywrite", LFS_O_RDWR) => 0;
|
lfs_file_open(&lfs, &file, "baldywrite", LFS_O_RDWR) => 0;
|
||||||
lfs_file_size(&lfs, &file) => LARGESIZE;
|
lfs_file_size(&lfs, &file) => LARGESIZE;
|
||||||
|
|
||||||
|
/* truncate */
|
||||||
lfs_file_truncate(&lfs, &file, MEDIUMSIZE) => 0;
|
lfs_file_truncate(&lfs, &file, MEDIUMSIZE) => 0;
|
||||||
lfs_file_size(&lfs, &file) => MEDIUMSIZE;
|
lfs_file_size(&lfs, &file) => MEDIUMSIZE;
|
||||||
|
|
||||||
|
/* and write */
|
||||||
strcpy((char*)buffer, "bald");
|
strcpy((char*)buffer, "bald");
|
||||||
size = strlen((char*)buffer);
|
size = strlen((char*)buffer);
|
||||||
for (lfs_off_t j = 0; j < MEDIUMSIZE; j += size) {
|
for (lfs_off_t j = 0; j < MEDIUMSIZE; j += size) {
|
||||||
lfs_file_write(&lfs, &file, buffer, size) => size;
|
lfs_file_write(&lfs, &file, buffer, lfs_min(size, MEDIUMSIZE-j))
|
||||||
|
=> lfs_min(size, MEDIUMSIZE-j);
|
||||||
}
|
}
|
||||||
lfs_file_size(&lfs, &file) => MEDIUMSIZE;
|
lfs_file_size(&lfs, &file) => MEDIUMSIZE;
|
||||||
|
|
||||||
@@ -203,8 +215,9 @@ code = '''
|
|||||||
|
|
||||||
size = strlen("bald");
|
size = strlen("bald");
|
||||||
for (lfs_off_t j = 0; j < MEDIUMSIZE; j += size) {
|
for (lfs_off_t j = 0; j < MEDIUMSIZE; j += size) {
|
||||||
lfs_file_read(&lfs, &file, buffer, size) => size;
|
lfs_file_read(&lfs, &file, buffer, lfs_min(size, MEDIUMSIZE-j))
|
||||||
memcmp(buffer, "bald", size) => 0;
|
=> lfs_min(size, MEDIUMSIZE-j);
|
||||||
|
memcmp(buffer, "bald", lfs_min(size, MEDIUMSIZE-j)) => 0;
|
||||||
}
|
}
|
||||||
lfs_file_read(&lfs, &file, buffer, size) => 0;
|
lfs_file_read(&lfs, &file, buffer, size) => 0;
|
||||||
|
|
||||||
@@ -215,7 +228,7 @@ code = '''
|
|||||||
# truncate write under powerloss
|
# truncate write under powerloss
|
||||||
[cases.test_truncate_reentrant_write]
|
[cases.test_truncate_reentrant_write]
|
||||||
defines.SMALLSIZE = [4, 512]
|
defines.SMALLSIZE = [4, 512]
|
||||||
defines.MEDIUMSIZE = [32, 1024]
|
defines.MEDIUMSIZE = [0, 3, 4, 5, 31, 32, 33, 511, 512, 513, 1023, 1024, 1025]
|
||||||
defines.LARGESIZE = 2048
|
defines.LARGESIZE = 2048
|
||||||
reentrant = true
|
reentrant = true
|
||||||
code = '''
|
code = '''
|
||||||
@@ -236,10 +249,11 @@ code = '''
|
|||||||
size == (size_t)SMALLSIZE);
|
size == (size_t)SMALLSIZE);
|
||||||
for (lfs_off_t j = 0; j < size; j += 4) {
|
for (lfs_off_t j = 0; j < size; j += 4) {
|
||||||
uint8_t buffer[1024];
|
uint8_t buffer[1024];
|
||||||
lfs_file_read(&lfs, &file, buffer, 4) => 4;
|
lfs_file_read(&lfs, &file, buffer, lfs_min(4, size-j))
|
||||||
assert(memcmp(buffer, "hair", 4) == 0 ||
|
=> lfs_min(4, size-j);
|
||||||
memcmp(buffer, "bald", 4) == 0 ||
|
assert(memcmp(buffer, "hair", lfs_min(4, size-j)) == 0 ||
|
||||||
memcmp(buffer, "comb", 4) == 0);
|
memcmp(buffer, "bald", lfs_min(4, size-j)) == 0 ||
|
||||||
|
memcmp(buffer, "comb", lfs_min(4, size-j)) == 0);
|
||||||
}
|
}
|
||||||
lfs_file_close(&lfs, &file) => 0;
|
lfs_file_close(&lfs, &file) => 0;
|
||||||
}
|
}
|
||||||
@@ -251,19 +265,23 @@ code = '''
|
|||||||
strcpy((char*)buffer, "hair");
|
strcpy((char*)buffer, "hair");
|
||||||
size_t size = strlen((char*)buffer);
|
size_t size = strlen((char*)buffer);
|
||||||
for (lfs_off_t j = 0; j < LARGESIZE; j += size) {
|
for (lfs_off_t j = 0; j < LARGESIZE; j += size) {
|
||||||
lfs_file_write(&lfs, &file, buffer, size) => size;
|
lfs_file_write(&lfs, &file, buffer, lfs_min(size, LARGESIZE-j))
|
||||||
|
=> lfs_min(size, LARGESIZE-j);
|
||||||
}
|
}
|
||||||
lfs_file_size(&lfs, &file) => LARGESIZE;
|
lfs_file_size(&lfs, &file) => LARGESIZE;
|
||||||
lfs_file_close(&lfs, &file) => 0;
|
lfs_file_close(&lfs, &file) => 0;
|
||||||
|
|
||||||
lfs_file_open(&lfs, &file, "baldy", LFS_O_RDWR) => 0;
|
lfs_file_open(&lfs, &file, "baldy", LFS_O_RDWR) => 0;
|
||||||
lfs_file_size(&lfs, &file) => LARGESIZE;
|
lfs_file_size(&lfs, &file) => LARGESIZE;
|
||||||
|
/* truncate */
|
||||||
lfs_file_truncate(&lfs, &file, MEDIUMSIZE) => 0;
|
lfs_file_truncate(&lfs, &file, MEDIUMSIZE) => 0;
|
||||||
lfs_file_size(&lfs, &file) => MEDIUMSIZE;
|
lfs_file_size(&lfs, &file) => MEDIUMSIZE;
|
||||||
|
/* and write */
|
||||||
strcpy((char*)buffer, "bald");
|
strcpy((char*)buffer, "bald");
|
||||||
size = strlen((char*)buffer);
|
size = strlen((char*)buffer);
|
||||||
for (lfs_off_t j = 0; j < MEDIUMSIZE; j += size) {
|
for (lfs_off_t j = 0; j < MEDIUMSIZE; j += size) {
|
||||||
lfs_file_write(&lfs, &file, buffer, size) => size;
|
lfs_file_write(&lfs, &file, buffer, lfs_min(size, MEDIUMSIZE-j))
|
||||||
|
=> lfs_min(size, MEDIUMSIZE-j);
|
||||||
}
|
}
|
||||||
lfs_file_size(&lfs, &file) => MEDIUMSIZE;
|
lfs_file_size(&lfs, &file) => MEDIUMSIZE;
|
||||||
lfs_file_close(&lfs, &file) => 0;
|
lfs_file_close(&lfs, &file) => 0;
|
||||||
@@ -275,7 +293,8 @@ code = '''
|
|||||||
strcpy((char*)buffer, "comb");
|
strcpy((char*)buffer, "comb");
|
||||||
size = strlen((char*)buffer);
|
size = strlen((char*)buffer);
|
||||||
for (lfs_off_t j = 0; j < SMALLSIZE; j += size) {
|
for (lfs_off_t j = 0; j < SMALLSIZE; j += size) {
|
||||||
lfs_file_write(&lfs, &file, buffer, size) => size;
|
lfs_file_write(&lfs, &file, buffer, lfs_min(size, SMALLSIZE-j))
|
||||||
|
=> lfs_min(size, SMALLSIZE-j);
|
||||||
}
|
}
|
||||||
lfs_file_size(&lfs, &file) => SMALLSIZE;
|
lfs_file_size(&lfs, &file) => SMALLSIZE;
|
||||||
lfs_file_close(&lfs, &file) => 0;
|
lfs_file_close(&lfs, &file) => 0;
|
||||||
@@ -429,7 +448,7 @@ code = '''
|
|||||||
|
|
||||||
# noop truncate
|
# noop truncate
|
||||||
[cases.test_truncate_nop]
|
[cases.test_truncate_nop]
|
||||||
defines.MEDIUMSIZE = [32, 2048]
|
defines.MEDIUMSIZE = [32, 33, 512, 513, 2048, 2049, 8192, 8193]
|
||||||
code = '''
|
code = '''
|
||||||
lfs_t lfs;
|
lfs_t lfs;
|
||||||
lfs_format(&lfs, cfg) => 0;
|
lfs_format(&lfs, cfg) => 0;
|
||||||
@@ -442,10 +461,11 @@ code = '''
|
|||||||
strcpy((char*)buffer, "hair");
|
strcpy((char*)buffer, "hair");
|
||||||
size_t size = strlen((char*)buffer);
|
size_t size = strlen((char*)buffer);
|
||||||
for (lfs_off_t j = 0; j < MEDIUMSIZE; j += size) {
|
for (lfs_off_t j = 0; j < MEDIUMSIZE; j += size) {
|
||||||
lfs_file_write(&lfs, &file, buffer, size) => size;
|
lfs_file_write(&lfs, &file, buffer, lfs_min(size, MEDIUMSIZE-j))
|
||||||
|
=> lfs_min(size, MEDIUMSIZE-j);
|
||||||
|
|
||||||
// this truncate should do nothing
|
// this truncate should do nothing
|
||||||
lfs_file_truncate(&lfs, &file, j+size) => 0;
|
lfs_file_truncate(&lfs, &file, j+lfs_min(size, MEDIUMSIZE-j)) => 0;
|
||||||
}
|
}
|
||||||
lfs_file_size(&lfs, &file) => MEDIUMSIZE;
|
lfs_file_size(&lfs, &file) => MEDIUMSIZE;
|
||||||
|
|
||||||
@@ -455,8 +475,9 @@ code = '''
|
|||||||
lfs_file_size(&lfs, &file) => MEDIUMSIZE;
|
lfs_file_size(&lfs, &file) => MEDIUMSIZE;
|
||||||
|
|
||||||
for (lfs_off_t j = 0; j < MEDIUMSIZE; j += size) {
|
for (lfs_off_t j = 0; j < MEDIUMSIZE; j += size) {
|
||||||
lfs_file_read(&lfs, &file, buffer, size) => size;
|
lfs_file_read(&lfs, &file, buffer, lfs_min(size, MEDIUMSIZE-j))
|
||||||
memcmp(buffer, "hair", size) => 0;
|
=> lfs_min(size, MEDIUMSIZE-j);
|
||||||
|
memcmp(buffer, "hair", lfs_min(size, MEDIUMSIZE-j)) => 0;
|
||||||
}
|
}
|
||||||
lfs_file_read(&lfs, &file, buffer, size) => 0;
|
lfs_file_read(&lfs, &file, buffer, size) => 0;
|
||||||
|
|
||||||
@@ -468,8 +489,9 @@ code = '''
|
|||||||
lfs_file_open(&lfs, &file, "baldynoop", LFS_O_RDWR) => 0;
|
lfs_file_open(&lfs, &file, "baldynoop", LFS_O_RDWR) => 0;
|
||||||
lfs_file_size(&lfs, &file) => MEDIUMSIZE;
|
lfs_file_size(&lfs, &file) => MEDIUMSIZE;
|
||||||
for (lfs_off_t j = 0; j < MEDIUMSIZE; j += size) {
|
for (lfs_off_t j = 0; j < MEDIUMSIZE; j += size) {
|
||||||
lfs_file_read(&lfs, &file, buffer, size) => size;
|
lfs_file_read(&lfs, &file, buffer, lfs_min(size, MEDIUMSIZE-j))
|
||||||
memcmp(buffer, "hair", size) => 0;
|
=> lfs_min(size, MEDIUMSIZE-j);
|
||||||
|
memcmp(buffer, "hair", lfs_min(size, MEDIUMSIZE-j)) => 0;
|
||||||
}
|
}
|
||||||
lfs_file_read(&lfs, &file, buffer, size) => 0;
|
lfs_file_read(&lfs, &file, buffer, size) => 0;
|
||||||
lfs_file_close(&lfs, &file) => 0;
|
lfs_file_close(&lfs, &file) => 0;
|
||||||
|
|||||||
Reference in New Issue
Block a user