Compare commits

..

9 Commits

Author SHA1 Message Date
Christopher Haster
689bdc8e06 Fixed handful of block_size-related bugs found by CI
- Valgrind-detected memory leak in test_superblocks.

- Valgrind-detected uninitialized lfs->block_size in lfs_init caused by
  an unintended sed replacement.

- Said sed replacement also changed the implementation of the v1 portion
  of lfs_migrate, this is out of scope and caused migrate to fail, which
  fortunately CI noticed.

- We also need to copy the block_size/block_count configs in v1 so that
  low-level bd operations still work.

- le-conversion in test_evil now matters due to difference in
  LFS_ERR_CORRUPT/LFS_ERR_INVAL paths during lfs_mount. This is good
  because we're actually testing for loop detection not pointer
  out-of-bounds as intended.
2022-12-17 12:41:04 -06:00
Christopher Haster
f198c855f6 Added benchmarks over mounting an unformatted disk
These locally show the expected O(d(n)) growth of the block_size search
when block_count is known.

A rough estimate of runtime for a 1 GiB device with a 90 MiB/s read bandwidth
(using the mt25q for rough numbers):

  known bs/bc       => ~1.44 us
  known block_size  => ~1.44 us
  known block_count => ~125 us
  unknown bs/bc     => ~376 ms
2022-12-17 12:41:04 -06:00
Christopher Haster
34b5b39551 Added lfs_fs_grow for growing the filesystem to a different block_count
The initial implementation for this was provided by kaetemi, originally
as a mount flag. However, it has been modified here to be self-contained
in an explicit runtime function that can be called after mount.

The reasons for an explicit function:

1. lfs_mount stays a strictly readonly operation, and avoids pulling in
   all of the write machinery.

2. filesystem-wide operations such as lfs_fs_grow can be a bit risky,
   and irreversable. The action of growing the filesystem should be very
   intentional.

---

One concern with this change is that this will be the first function
that changes metadata in the superblock. This might break tools that
expect the first valid superblock entry to contain the most recent
metadata.
2022-12-17 12:41:04 -06:00
Christopher Haster
fc10790eba Added more block_size/count inference tests and fixed a couple bugs
- Need to drop cache during block_size search.

- Removed "Corrupted" messages when root is not found to avoid too much debug
  output. This also gets rid of a long-term point of confusion arround
  "Corrupted" messages on a mount that is intended to fail.
2022-12-17 12:41:04 -06:00
Christopher Haster
c8bc6f96cd Added lfs_fs_stat for accessing configuration in the superblocks
This is necessary for accessing format-specific information stored in
the superblock after mounting.

As is common in other filesystems this also provides the filesystem
usage. However collecting the filesystem usage is more expensive in
littlefs than other filesystem (and less accurate because of CoW), so
this may need to be cached after mount in the future.
2022-12-17 12:41:04 -06:00
Christopher Haster
21e54ee458 Adopted erase_size config changes in block devices and test runners
This ended up needing a bit of API rework since littlefs no longer needs
to know the actual erase_count. We can no longer rely on lfs_config to
contain all the information necessary to configure the block devices.

Changing the lfs_bd_config structs to be required is probably a good
idea anyways as it moves us more towards separating the bds from
littlefs, though we can't quite get rid of the lfs_config parameter
because of the block-device API in lfs_config. Eventually it would be
nice to get rid of it, but that would require API breakage.
2022-12-17 12:41:04 -06:00
Christopher Haster
c16120bf5f Dropped erase_count, block_count makes it redundant
Aside from asserts (which can be implemented in block devices), erase_count
provides no useful info aside from what's known with block_count and
risks mistakes during configuration.

Each set of known/unknown block_size-related variables has a well-defined
mount behavior:

  block_size  block_count
  known       known       => known bs, O(1)
  known       unknown     => known bs, O(1)
  unknown     known       => bs search, O(d(n))
  unknown     unknown     => bs search, O(n)

If block_size is unknown, block_count uses the erase_size as a unit,
which is the only reasonable option since block_size must be
>= prog/read/cache_size.
2022-12-17 12:41:04 -06:00
Christopher Haster
03beff6c54 Changed block_size search to use erase_count when block_size is zero
This hopefully helps make it more clear that we are using erase_size as the
unit during the block_size search. However this does mean you need to
set erase_count to zero to allow any littlefs size, which might be a bit
confusing:

  block_size  block_count  erase_size  erase_count
  known       known        known       known       => known bs, O(1)
  known       unknown      known       known       => known bs, O(1)
  unknown     unknown      known       known       => bs search, O(d(n))
  unknown     unknown      known       unknown     => bs search, O(n)
2022-12-17 12:41:04 -06:00
Christopher Haster
75f80aabd7 Added block_size search in lfs_mount, moved block_size/count into lfs_t
This adds two new configuration options: erase_size and erase_count,
allowing block_size and block_count to be loaded from the superblock
during mount. For backwards compability these default to block_size and
block_count if zero.

---

Unfortunately this is a bit easier said than done. littlefs keeps its
superblock in the metadata pair located at blocks {0,1}, which is also
where the root directory lives (keep in mind small littlefs images may
only have 2 blocks in total). If we mutate blocks {0,1}, we have to
erase before programming, which means it's possible to have the only
superblock in block 1. This presents a puzzle because how do you find
block 1 if you don't know the size of a block?

One solution presented here is to search for block 1 by trying different
sizes until we find a superblock. This isn't great but there are some
properties of this search that help:

1. If we do find a superblock, the search will never take longer than a
   mount with a known block_size. This is because we stop at block 1,
   searching at most O(block_size) bytes, and metadata fetch is already
   a O(block_size) operation.

   This means the concern is limited to how long it takes to fail when
   littlefs is not present on the disk.

2. We can assume the on-disk block_size is probably a factor of the
   total size of the disk. After a bit of digging into the math, this
   apparently reduces the runtime to the divisor function, d(n), which
   is sublinear.

   According to a blog post by Terence Tao this is bounded by the
   ridiculous O(e^O(log(n)/log(log(n)))):
   https://terrytao.wordpress.com/2008/09/23/the-divisor-bound

   This is apparently somewhere between O(sqrt(n)) and O(log(n)), but
   conveniently O(log(n)) on average and O(log(n)) for powers of 2.

   I've left it as O(d(n)) in the documentation, which might be a bit
   confusing, but I'm not sure how best to capture "mostly log(n)"
   correctly.

3. If we don't know the block_size, or don't know that block_size is
   aligned to the disk size, the best we can do is a O(n) search.

   In this case I've added a warning, so at least it's distinguishable
   from an infinite loop if debugging.
2022-12-17 12:41:04 -06:00
27 changed files with 986 additions and 3142 deletions

4
.gitattributes vendored
View File

@@ -1,4 +0,0 @@
# 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

View File

@@ -81,9 +81,7 @@ jobs:
- name: find-prev-version
continue-on-error: true
run: |
LFS_PREV_VERSION="$( \
git describe --tags --abbrev=0 --match 'v*' \
|| true)"
LFS_PREV_VERSION="$(git describe --tags --abbrev=0 --match 'v*')"
echo "LFS_PREV_VERSION=$LFS_PREV_VERSION"
echo "LFS_PREV_VERSION=$LFS_PREV_VERSION" >> $GITHUB_ENV
@@ -104,7 +102,7 @@ jobs:
# sizes table
i=0
j=0
for c in "" readonly threadsafe multiversion migrate error-asserts
for c in "" readonly threadsafe migrate error-asserts
do
# per-config results
c_or_default=${c:-default}
@@ -112,7 +110,7 @@ jobs:
table[$i,$j]=$c_camel
((j+=1))
for s in code stack structs
for s in code stack struct
do
f=sizes/thumb${c:+-$c}.$s.csv
[ -e $f ] && table[$i,$j]=$( \
@@ -242,7 +240,6 @@ jobs:
run: |
# create release and patch version tag (vN.N.N)
# only draft if not a patch release
touch release.txt
[ -e table.txt ] && cat table.txt >> release.txt
echo >> release.txt
[ -e changes.txt ] && cat changes.txt >> release.txt

View File

@@ -170,27 +170,6 @@ jobs:
cp lfs.data.csv sizes/${{matrix.arch}}-threadsafe.data.csv
cp lfs.stack.csv sizes/${{matrix.arch}}-threadsafe.stack.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
run: |
make clean
@@ -374,42 +353,6 @@ jobs:
run: |
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
test-valgrind:
runs-on: ubuntu-22.04
@@ -428,8 +371,7 @@ jobs:
# on one geometry
- name: test-valgrind
run: |
TESTFLAGS="$TESTFLAGS --valgrind --context=1024 -Gdefault -Pnone" \
make test
TESTFLAGS="$TESTFLAGS --valgrind -Gdefault -Pnone" make test
# test that compilation is warning free under clang
# run with Clang, mostly to check for Clang-specific warnings
@@ -531,42 +473,6 @@ jobs:
path: status
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
fuse:
runs-on: ubuntu-22.04
@@ -742,7 +648,7 @@ jobs:
# sizes table
i=0
j=0
for c in "" readonly threadsafe multiversion migrate error-asserts
for c in "" readonly threadsafe migrate error-asserts
do
# per-config results
c_or_default=${c:-default}

View File

@@ -1,5 +1,15 @@
# overrideable build dir, default is in-place
ifdef BUILDDIR
# 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 ?= .
# overridable target/src/tools/flags/etc
ifneq ($(wildcard test.c main.c),)
TARGET ?= $(BUILDDIR)/lfs
@@ -63,7 +73,6 @@ CFLAGS += -fcallgraph-info=su
CFLAGS += -g3
CFLAGS += -I.
CFLAGS += -std=c99 -Wall -Wextra -pedantic
CFLAGS += -Wmissing-prototypes
CFLAGS += -ftrack-macro-expansion=0
ifdef DEBUG
CFLAGS += -O0
@@ -154,18 +163,6 @@ TESTFLAGS += --perf-path="$(PERF)"
BENCHFLAGS += --perf-path="$(PERF)"
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
@@ -355,7 +352,6 @@ summary-diff sizes-diff: $(OBJ) $(CI)
## Build the test-runner
.PHONY: test-runner build-test
test-runner build-test: CFLAGS+=-Wno-missing-prototypes
ifndef NO_COV
test-runner build-test: CFLAGS+=--coverage
endif
@@ -407,7 +403,6 @@ testmarks-diff: $(TEST_CSV)
## Build the bench-runner
.PHONY: bench-runner build-bench
bench-runner build-bench: CFLAGS+=-Wno-missing-prototypes
ifdef YES_COV
bench-runner build-bench: CFLAGS+=--coverage
endif
@@ -519,9 +514,6 @@ $(BUILDDIR)/runners/bench_runner: $(BENCH_OBJ)
$(BUILDDIR)/%.o $(BUILDDIR)/%.ci: %.c
$(CC) -c -MMD $(CFLAGS) $< -o $(BUILDDIR)/$*.o
$(BUILDDIR)/%.o $(BUILDDIR)/%.ci: $(BUILDDIR)/%.c
$(CC) -c -MMD $(CFLAGS) $< -o $(BUILDDIR)/$*.o
$(BUILDDIR)/%.s: %.c
$(CC) -S $(CFLAGS) $< -o $@

View File

@@ -226,13 +226,6 @@ License Identifiers that are here available: http://spdx.org/licenses/
to create images of the filesystem on your PC. Check if littlefs will fit
your needs, create images for a later download to the target memory or
inspect the content of a binary image of the target memory.
- [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
littlefs images from a host PC. Supports Windows, Mac OS, and Linux.
@@ -250,16 +243,8 @@ License Identifiers that are here available: http://spdx.org/licenses/
MCUs. It offers static wear-leveling and power-resilience with only a fixed
_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
[littlefs-disk-img-viewer]: https://github.com/tniessen/littlefs-disk-img-viewer
[littlefs-fuse]: https://github.com/geky/littlefs-fuse
[FUSE]: https://github.com/libfuse/libfuse
[littlefs-js]: https://github.com/geky/littlefs-js
@@ -270,7 +255,4 @@ License Identifiers that are here available: http://spdx.org/licenses/
[LittleFileSystem]: https://os.mbed.com/docs/mbed-os/latest/apis/littlefilesystem.html
[SPIFFS]: https://github.com/pellepl/spiffs
[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/
[littlefs2-rust]: https://crates.io/crates/littlefs2
[chamelon]: https://github.com/yomimono/chamelon

101
SPEC.md
View File

@@ -1,10 +1,10 @@
## littlefs technical specification
This is the technical specification of the little filesystem with on-disk
version lfs2.1. This document covers the technical details of how the littlefs
is stored on disk for introspection and tooling. This document assumes you are
familiar with the design of the littlefs, for more info on how littlefs works
check out [DESIGN.md](DESIGN.md).
This is the technical specification of the little filesystem. This document
covers the technical details of how the littlefs is stored on disk for
introspection and tooling. This document assumes you are familiar with the
design of the littlefs, for more info on how littlefs works check
out [DESIGN.md](DESIGN.md).
```
| | | .---._____
@@ -133,6 +133,12 @@ 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:
```
@@ -185,53 +191,6 @@ Here's a more complete example of metadata block containing 4 entries:
'---- 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
So in littlefs, 32-bit tags describe every type of metadata. And this means
@@ -826,41 +785,3 @@ CRC fields:
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`.
---

View File

@@ -220,7 +220,9 @@ int lfs_emubd_read(const struct lfs_config *cfg, lfs_block_t block,
lfs_emubd_t *bd = cfg->context;
// check if read is valid
LFS_ASSERT(block < bd->cfg->erase_count);
if (block >= bd->cfg->erase_count) {
return LFS_ERR_INVAL;
}
LFS_ASSERT(off % bd->cfg->read_size == 0);
LFS_ASSERT(size % bd->cfg->read_size == 0);
LFS_ASSERT(off+size <= bd->cfg->erase_size);
@@ -567,14 +569,13 @@ lfs_emubd_swear_t lfs_emubd_wear(const struct lfs_config *cfg,
wear = 0;
}
LFS_EMUBD_TRACE("lfs_emubd_wear -> %"PRIi32, wear);
LFS_EMUBD_TRACE("lfs_emubd_wear -> %"PRIu32, wear);
return wear;
}
int lfs_emubd_setwear(const struct lfs_config *cfg,
lfs_block_t block, lfs_emubd_wear_t wear) {
LFS_EMUBD_TRACE("lfs_emubd_setwear(%p, %"PRIu32", %"PRIi32")",
(void*)cfg, block, wear);
LFS_EMUBD_TRACE("lfs_emubd_setwear(%p, %"PRIu32")", (void*)cfg, block);
lfs_emubd_t *bd = cfg->context;
// check if block is valid
@@ -583,12 +584,12 @@ int lfs_emubd_setwear(const struct lfs_config *cfg,
// set the wear
lfs_emubd_block_t *b = lfs_emubd_mutblock(cfg, &bd->blocks[block]);
if (!b) {
LFS_EMUBD_TRACE("lfs_emubd_setwear -> %d", LFS_ERR_NOMEM);
LFS_EMUBD_TRACE("lfs_emubd_setwear -> %"PRIu32, LFS_ERR_NOMEM);
return LFS_ERR_NOMEM;
}
b->wear = wear;
LFS_EMUBD_TRACE("lfs_emubd_setwear -> %d", 0);
LFS_EMUBD_TRACE("lfs_emubd_setwear -> %"PRIu32, 0);
return 0;
}

View File

@@ -70,7 +70,9 @@ int lfs_filebd_read(const struct lfs_config *cfg, lfs_block_t block,
lfs_filebd_t *bd = cfg->context;
// check if read is valid
LFS_ASSERT(block < bd->cfg->erase_count);
if (block >= bd->cfg->erase_count) {
return LFS_ERR_INVAL;
}
LFS_ASSERT(off % bd->cfg->read_size == 0);
LFS_ASSERT(size % bd->cfg->read_size == 0);
LFS_ASSERT(off+size <= bd->cfg->erase_size);

View File

@@ -60,7 +60,9 @@ int lfs_rambd_read(const struct lfs_config *cfg, lfs_block_t block,
lfs_rambd_t *bd = cfg->context;
// check if read is valid
LFS_ASSERT(block < bd->cfg->erase_count);
if (block >= bd->cfg->erase_count) {
return LFS_ERR_INVAL;
}
LFS_ASSERT(off % bd->cfg->read_size == 0);
LFS_ASSERT(size % bd->cfg->read_size == 0);
LFS_ASSERT(off+size <= bd->cfg->erase_size);

View File

@@ -1,4 +1,15 @@
[cases.bench_superblocks_found]
[cases.bench_superblocks_format]
code = '''
lfs_t lfs;
BENCH_START();
lfs_format(&lfs, cfg) => 0;
BENCH_STOP();
'''
[cases.bench_superblocks_mount]
defines.KNOWN_BLOCK_SIZE = [true, false]
defines.KNOWN_BLOCK_COUNT = [true, false]
# support benchmarking with files
defines.N = [0, 1024]
defines.FILE_SIZE = 8
@@ -28,6 +39,18 @@ code = '''
}
lfs_unmount(&lfs) => 0;
if (KNOWN_BLOCK_SIZE) {
cfg->block_size = BLOCK_SIZE;
} else {
cfg->block_size = 0;
}
if (KNOWN_BLOCK_COUNT) {
cfg->block_count = BLOCK_COUNT;
} else {
cfg->block_count = 0;
}
BENCH_START();
lfs_mount(&lfs, cfg) => 0;
BENCH_STOP();
@@ -35,22 +58,27 @@ code = '''
lfs_unmount(&lfs) => 0;
'''
[cases.bench_superblocks_missing]
[cases.bench_superblocks_mount_missing]
defines.KNOWN_BLOCK_SIZE = [true, false]
defines.KNOWN_BLOCK_COUNT = [true, false]
code = '''
lfs_t lfs;
if (KNOWN_BLOCK_SIZE) {
cfg->block_size = BLOCK_SIZE;
} else {
cfg->block_size = 0;
}
if (KNOWN_BLOCK_COUNT) {
cfg->block_count = BLOCK_COUNT;
} else {
cfg->block_count = 0;
}
BENCH_START();
int err = lfs_mount(&lfs, cfg);
assert(err != 0);
BENCH_STOP();
'''
[cases.bench_superblocks_format]
code = '''
lfs_t lfs;
BENCH_START();
lfs_format(&lfs, cfg) => 0;
BENCH_STOP();
'''

1260
lfs.c

File diff suppressed because it is too large Load Diff

100
lfs.h
View File

@@ -21,14 +21,14 @@ extern "C"
// Software library version
// Major (top-nibble), incremented on backwards incompatible changes
// Minor (bottom-nibble), incremented on feature additions
#define LFS_VERSION 0x00020008
#define LFS_VERSION 0x00020005
#define LFS_VERSION_MAJOR (0xffff & (LFS_VERSION >> 16))
#define LFS_VERSION_MINOR (0xffff & (LFS_VERSION >> 0))
// Version of On-disk data structures
// Major (top-nibble), incremented on backwards incompatible changes
// Minor (bottom-nibble), incremented on feature additions
#define LFS_DISK_VERSION 0x00020001
#define LFS_DISK_VERSION 0x00020000
#define LFS_DISK_VERSION_MAJOR (0xffff & (LFS_DISK_VERSION >> 16))
#define LFS_DISK_VERSION_MINOR (0xffff & (LFS_DISK_VERSION >> 0))
@@ -112,8 +112,6 @@ enum lfs_type {
LFS_TYPE_SOFTTAIL = 0x600,
LFS_TYPE_HARDTAIL = 0x601,
LFS_TYPE_MOVESTATE = 0x7ff,
LFS_TYPE_CCRC = 0x500,
LFS_TYPE_FCRC = 0x5ff,
// internal chip sources
LFS_FROM_NOOP = 0x000,
@@ -191,21 +189,44 @@ struct lfs_config {
int (*unlock)(const struct lfs_config *c);
#endif
// Minimum size of a block read in bytes. All read operations will be a
// multiple of this value.
// Minimum size of a read operation in bytes. All read operations
// will be a multiple of this value.
lfs_size_t read_size;
// Minimum size of a block program in bytes. All program operations will be
// a multiple of this value.
// Minimum size of a program operation in bytes. All program operations
// will be a multiple of this value.
lfs_size_t prog_size;
// Size of an erasable block in bytes. This does not impact ram consumption
// and may be larger than the physical erase size. However, non-inlined
// files take up at minimum one block. Must be a multiple of the read and
// program sizes.
// Size of an erase operation in bytes. This must be a multiple of the
// read and program sizes.
//
// If zero, the block_size is used as the erase_size. This is mostly for
// backwards compatibility.
lfs_size_t erase_size;
// Size of a logical block in bytes. This does not impact RAM consumption
// and may be a multiple of the physical erase_size.
//
// Note, non-inlined files take up at minimum one logical block, and
// directories take up at minimum two logical blocks.
//
// If zero, littlefs attempts to find the superblock and use the the
// block_size stored there. This requires searching different block_sizes.
// If a superblock is found this takes no longer than mounting with a known
// block_size, but it can take time to fail if a superblock is not found:
//
// - O(block_size) if a superblock is found
// - O(d(block_count)) if block_count is non-zero
// - O(log(block_count)) if block_count is a power of 2
// - O(block_count) if block_count is zero
lfs_size_t block_size;
// Number of erasable blocks on the device.
// Number of logical blocks on the device.
//
// If zero, littlefs uses the block_count stored in the superblock.
//
// If non-zero, littlefs will assume block_size is a factor of
// erase_size*erase_count to speed up mount when no superblock is found.
lfs_size_t block_count;
// Number of erase cycles before littlefs evicts metadata logs and moves
@@ -263,14 +284,6 @@ struct lfs_config {
// can help bound the metadata compaction time. Must be <= block_size.
// Defaults to block_size when zero.
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
@@ -289,16 +302,22 @@ struct lfs_info {
};
// Filesystem info structure
//
// Some of these can also be found in lfs_config, but the values here respect
// what was stored in the superblock during lfs_format.
struct lfs_fsinfo {
// On-disk version.
uint32_t disk_version;
// Size of a logical block in bytes.
lfs_size_t block_size;
// Number of logical blocks in filesystem.
// Number of logical blocks on the block device.
lfs_size_t block_count;
// Number of blocks in use, this is the same as lfs_fs_size.
//
// Note: Result is best effort. If files share COW structures, the returned
// size may be larger than the filesystem actually is.
lfs_size_t block_usage;
// Upper limit on the length of file names in bytes.
lfs_size_t name_max;
@@ -439,6 +458,8 @@ typedef struct lfs {
} free;
const struct lfs_config *cfg;
lfs_size_t erase_size;
lfs_size_t block_size;
lfs_size_t block_count;
lfs_size_t name_max;
lfs_size_t file_max;
@@ -689,10 +710,9 @@ int lfs_dir_rewind(lfs_t *lfs, lfs_dir_t *dir);
/// Filesystem-level filesystem operations
// Find on-disk info about the filesystem
// Find info about the filesystem
//
// Fills out the fsinfo structure based on the filesystem found on-disk.
// Returns a negative error code on failure.
// Fills out the fsinfo structure. 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
@@ -712,30 +732,6 @@ lfs_ssize_t lfs_fs_size(lfs_t *lfs);
// Returns a negative error code on failure.
int lfs_fs_traverse(lfs_t *lfs, int (*cb)(void*, lfs_block_t), void *data);
// Attempt to proactively find free blocks
//
// Calling this function is not required, but may allowing the offloading of
// the expensive block allocation scan to a less time-critical code path.
//
// Note: littlefs currently does not persist any found free blocks to disk.
// This may change in the future.
//
// Returns a negative error code on failure. Finding no free blocks is
// not an error.
int lfs_fs_gc(lfs_t *lfs);
#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
// Grows the filesystem to a new size, updating the superblock with the new
// block count.

View File

@@ -23,6 +23,7 @@
// System includes
#include <stdint.h>
#include <stdbool.h>
#include <sys/types.h>
#include <string.h>
#include <inttypes.h>

View File

@@ -1316,6 +1316,7 @@ void perm_run(
.sync = lfs_emubd_sync,
.read_size = READ_SIZE,
.prog_size = PROG_SIZE,
.erase_size = ERASE_SIZE,
.block_size = BLOCK_SIZE,
.block_count = BLOCK_COUNT,
.block_cycles = BLOCK_CYCLES,

View File

@@ -1341,14 +1341,12 @@ static void run_powerloss_none(
.sync = lfs_emubd_sync,
.read_size = READ_SIZE,
.prog_size = PROG_SIZE,
.erase_size = ERASE_SIZE,
.block_size = BLOCK_SIZE,
.block_count = BLOCK_COUNT,
.block_cycles = BLOCK_CYCLES,
.cache_size = CACHE_SIZE,
.lookahead_size = LOOKAHEAD_SIZE,
#ifdef LFS_MULTIVERSION
.disk_version = DISK_VERSION,
#endif
};
struct lfs_emubd_config bdcfg = {
@@ -1417,14 +1415,12 @@ static void run_powerloss_linear(
.sync = lfs_emubd_sync,
.read_size = READ_SIZE,
.prog_size = PROG_SIZE,
.erase_size = ERASE_SIZE,
.block_size = BLOCK_SIZE,
.block_count = BLOCK_COUNT,
.block_cycles = BLOCK_CYCLES,
.cache_size = CACHE_SIZE,
.lookahead_size = LOOKAHEAD_SIZE,
#ifdef LFS_MULTIVERSION
.disk_version = DISK_VERSION,
#endif
};
struct lfs_emubd_config bdcfg = {
@@ -1510,14 +1506,12 @@ static void run_powerloss_log(
.sync = lfs_emubd_sync,
.read_size = READ_SIZE,
.prog_size = PROG_SIZE,
.erase_size = ERASE_SIZE,
.block_size = BLOCK_SIZE,
.block_count = BLOCK_COUNT,
.block_cycles = BLOCK_CYCLES,
.cache_size = CACHE_SIZE,
.lookahead_size = LOOKAHEAD_SIZE,
#ifdef LFS_MULTIVERSION
.disk_version = DISK_VERSION,
#endif
};
struct lfs_emubd_config bdcfg = {
@@ -1601,14 +1595,12 @@ static void run_powerloss_cycles(
.sync = lfs_emubd_sync,
.read_size = READ_SIZE,
.prog_size = PROG_SIZE,
.erase_size = ERASE_SIZE,
.block_size = BLOCK_SIZE,
.block_count = BLOCK_COUNT,
.block_cycles = BLOCK_CYCLES,
.cache_size = CACHE_SIZE,
.lookahead_size = LOOKAHEAD_SIZE,
#ifdef LFS_MULTIVERSION
.disk_version = DISK_VERSION,
#endif
};
struct lfs_emubd_config bdcfg = {
@@ -1790,14 +1782,12 @@ static void run_powerloss_exhaustive(
.sync = lfs_emubd_sync,
.read_size = READ_SIZE,
.prog_size = PROG_SIZE,
.erase_size = ERASE_SIZE,
.block_size = BLOCK_SIZE,
.block_count = BLOCK_COUNT,
.block_cycles = BLOCK_CYCLES,
.cache_size = CACHE_SIZE,
.lookahead_size = LOOKAHEAD_SIZE,
#ifdef LFS_MULTIVERSION
.disk_version = DISK_VERSION,
#endif
};
struct lfs_emubd_config bdcfg = {

View File

@@ -93,7 +93,6 @@ intmax_t test_define(size_t define);
#define ERASE_CYCLES_i 10
#define BADBLOCK_BEHAVIOR_i 11
#define POWERLOSS_BEHAVIOR_i 12
#define DISK_VERSION_i 13
#define READ_SIZE TEST_DEFINE(READ_SIZE_i)
#define PROG_SIZE TEST_DEFINE(PROG_SIZE_i)
@@ -108,7 +107,6 @@ intmax_t test_define(size_t define);
#define ERASE_CYCLES TEST_DEFINE(ERASE_CYCLES_i)
#define BADBLOCK_BEHAVIOR TEST_DEFINE(BADBLOCK_BEHAVIOR_i)
#define POWERLOSS_BEHAVIOR TEST_DEFINE(POWERLOSS_BEHAVIOR_i)
#define DISK_VERSION TEST_DEFINE(DISK_VERSION_i)
#define TEST_IMPLICIT_DEFINES \
TEST_DEF(READ_SIZE, PROG_SIZE) \
@@ -123,11 +121,10 @@ intmax_t test_define(size_t define);
TEST_DEF(ERASE_VALUE, 0xff) \
TEST_DEF(ERASE_CYCLES, 0) \
TEST_DEF(BADBLOCK_BEHAVIOR, LFS_EMUBD_BADBLOCK_PROGERROR) \
TEST_DEF(POWERLOSS_BEHAVIOR, LFS_EMUBD_POWERLOSS_NOOP) \
TEST_DEF(DISK_VERSION, 0)
TEST_DEF(POWERLOSS_BEHAVIOR, LFS_EMUBD_POWERLOSS_NOOP)
#define TEST_GEOMETRY_DEFINE_COUNT 4
#define TEST_IMPLICIT_DEFINE_COUNT 14
#define TEST_IMPLICIT_DEFINE_COUNT 13
#endif

View File

@@ -107,10 +107,7 @@ def main(from_prefix, to_prefix, paths=[], *,
elif no_renames:
to_path = from_path
else:
to_path = os.path.join(
os.path.dirname(from_path),
changeprefix(from_prefix, to_prefix,
os.path.basename(from_path))[0])
to_path, _ = changeprefix(from_prefix, to_prefix, from_path)
# rename contents
changefile(from_prefix, to_prefix, from_path, to_path,

View File

@@ -24,8 +24,6 @@ TAG_TYPES = {
'gstate': (0x700, 0x700),
'movestate': (0x7ff, 0x7ff),
'crc': (0x700, 0x500),
'ccrc': (0x780, 0x500),
'fcrc': (0x7ff, 0x5ff),
}
class Tag:
@@ -101,16 +99,7 @@ class Tag:
return struct.unpack('b', struct.pack('B', self.chunk))[0]
def is_(self, type):
try:
if ' ' in type:
type1, type3 = type.split()
return (self.is_(type1) and
(self.type & ~TAG_TYPES[type1][0]) == int(type3, 0))
return self.type == int(type, 0)
except (ValueError, KeyError):
return (self.type & TAG_TYPES[type][0]) == TAG_TYPES[type][1]
return (self.type & TAG_TYPES[type][0]) == TAG_TYPES[type][1]
def mkmask(self):
return Tag(
@@ -120,20 +109,14 @@ class Tag:
def chid(self, nid):
ntag = Tag(self.type, nid, self.size)
if hasattr(self, 'off'): ntag.off = self.off
if hasattr(self, 'data'): ntag.data = self.data
if hasattr(self, 'ccrc'): ntag.crc = self.crc
if hasattr(self, 'erased'): ntag.erased = self.erased
if hasattr(self, 'off'): ntag.off = self.off
if hasattr(self, 'data'): ntag.data = self.data
if hasattr(self, 'crc'): ntag.crc = self.crc
return ntag
def typerepr(self):
if (self.is_('ccrc')
and getattr(self, 'ccrc', 0xffffffff) != 0xffffffff):
crc_status = ' (bad)'
elif self.is_('fcrc') and getattr(self, 'erased', False):
crc_status = ' (era)'
else:
crc_status = ''
if self.is_('crc') and getattr(self, 'crc', 0xffffffff) != 0xffffffff:
return 'crc (bad)'
reverse_types = {v: k for k, v in TAG_TYPES.items()}
for prefix in range(12):
@@ -141,12 +124,12 @@ class Tag:
if (mask, self.type & mask) in reverse_types:
type = reverse_types[mask, self.type & mask]
if prefix > 0:
return '%s %#x%s' % (
type, self.type & ((1 << prefix)-1), crc_status)
return '%s %#0*x' % (
type, prefix//4, self.type & ((1 << prefix)-1))
else:
return '%s%s' % (type, crc_status)
return type
else:
return '%02x%s' % (self.type, crc_status)
return '%02x' % self.type
def idrepr(self):
return repr(self.id) if self.id != 0x3ff else '.'
@@ -189,8 +172,6 @@ class MetadataPair:
self.rev, = struct.unpack('<I', block[0:4])
crc = binascii.crc32(block[0:4])
fcrctag = None
fcrcdata = None
# parse tags
corrupt = False
@@ -201,11 +182,11 @@ class MetadataPair:
while len(block) - off >= 4:
ntag, = struct.unpack('>I', block[off:off+4])
tag = Tag((int(tag) ^ ntag) & 0x7fffffff)
tag = Tag(int(tag) ^ ntag)
tag.off = off + 4
tag.data = block[off+4:off+tag.dsize]
if tag.is_('ccrc'):
crc = binascii.crc32(block[off:off+2*4], crc)
if tag.is_('crc'):
crc = binascii.crc32(block[off:off+4+4], crc)
else:
crc = binascii.crc32(block[off:off+tag.dsize], crc)
tag.crc = crc
@@ -213,29 +194,16 @@ class MetadataPair:
self.all_.append(tag)
if tag.is_('fcrc') and len(tag.data) == 8:
fcrctag = tag
fcrcdata = struct.unpack('<II', tag.data)
elif tag.is_('ccrc'):
if tag.is_('crc'):
# is valid commit?
if crc != 0xffffffff:
corrupt = True
if not corrupt:
self.log = self.all_.copy()
# end of commit?
if fcrcdata:
fcrcsize, fcrc = fcrcdata
fcrc_ = 0xffffffff ^ binascii.crc32(
block[off:off+fcrcsize])
if fcrc_ == fcrc:
fcrctag.erased = True
corrupt = True
# reset tag parsing
crc = 0
tag = Tag(int(tag) ^ ((tag.type & 1) << 31))
fcrctag = None
fcrcdata = None
# find active ids
self.ids = list(it.takewhile(
@@ -312,7 +280,7 @@ class MetadataPair:
f.write('\n')
for tag in tags:
f.write("%08x: %08x %-14s %3s %4s" % (
f.write("%08x: %08x %-13s %4s %4s" % (
tag.off, tag,
tag.typerepr(), tag.idrepr(), tag.sizerepr()))
if truncate:

View File

@@ -6,7 +6,6 @@ if = 'BLOCK_CYCLES == -1'
[cases.test_alloc_parallel]
defines.FILES = 3
defines.SIZE = '(((BLOCK_SIZE-8)*(BLOCK_COUNT-6)) / FILES)'
defines.GC = [false, true]
code = '''
const char *names[] = {"bacon", "eggs", "pancakes"};
lfs_file_t files[FILES];
@@ -25,9 +24,6 @@ code = '''
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_APPEND) => 0;
}
for (int n = 0; n < FILES; n++) {
if (GC) {
lfs_fs_gc(&lfs) => 0;
}
size_t size = strlen(names[n]);
for (lfs_size_t i = 0; i < SIZE; i += size) {
lfs_file_write(&lfs, &files[n], names[n], size) => size;
@@ -59,7 +55,6 @@ code = '''
[cases.test_alloc_serial]
defines.FILES = 3
defines.SIZE = '(((BLOCK_SIZE-8)*(BLOCK_COUNT-6)) / FILES)'
defines.GC = [false, true]
code = '''
const char *names[] = {"bacon", "eggs", "pancakes"};
@@ -80,9 +75,6 @@ code = '''
uint8_t buffer[1024];
memcpy(buffer, names[n], size);
for (int i = 0; i < SIZE; i += size) {
if (GC) {
lfs_fs_gc(&lfs) => 0;
}
lfs_file_write(&lfs, &file, buffer, size) => size;
}
lfs_file_close(&lfs, &file) => 0;
@@ -255,9 +247,6 @@ code = '''
}
res => LFS_ERR_NOSPC;
// note that lfs_fs_gc should not error here
lfs_fs_gc(&lfs) => 0;
lfs_file_close(&lfs, &file) => 0;
lfs_unmount(&lfs) => 0;
@@ -309,9 +298,6 @@ code = '''
}
res => LFS_ERR_NOSPC;
// note that lfs_fs_gc should not error here
lfs_fs_gc(&lfs) => 0;
lfs_file_close(&lfs, &file) => 0;
lfs_unmount(&lfs) => 0;
@@ -351,8 +337,6 @@ code = '''
count += 1;
}
err => LFS_ERR_NOSPC;
// note that lfs_fs_gc should not error here
lfs_fs_gc(&lfs) => 0;
lfs_file_close(&lfs, &file) => 0;
lfs_remove(&lfs, "exhaustion") => 0;
@@ -451,8 +435,6 @@ code = '''
break;
}
}
// note that lfs_fs_gc should not error here
lfs_fs_gc(&lfs) => 0;
lfs_file_close(&lfs, &file) => 0;
lfs_unmount(&lfs) => 0;

View File

@@ -256,5 +256,5 @@ code = '''
lfs_t lfs;
lfs_format(&lfs, cfg) => LFS_ERR_NOSPC;
lfs_mount(&lfs, cfg) => LFS_ERR_CORRUPT;
lfs_mount(&lfs, cfg) => LFS_ERR_INVAL;
'''

File diff suppressed because it is too large Load Diff

View File

@@ -810,8 +810,7 @@ code = '''
}
lfs_unmount(&lfs) => 0;
// try seeking to each dir entry
for (int j = 0; j < COUNT; j++) {
for (int j = 2; j < COUNT; j++) {
lfs_mount(&lfs, cfg) => 0;
lfs_dir_t dir;
lfs_dir_open(&lfs, &dir, "hello") => 0;
@@ -823,15 +822,16 @@ code = '''
assert(strcmp(info.name, "..") == 0);
assert(info.type == LFS_TYPE_DIR);
lfs_soff_t pos;
for (int i = 0; i < j; 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);
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;
char path[1024];
@@ -861,52 +861,6 @@ code = '''
lfs_dir_close(&lfs, &dir) => 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]
@@ -923,7 +877,7 @@ code = '''
}
lfs_unmount(&lfs) => 0;
for (int j = 0; j < COUNT; j++) {
for (int j = 2; j < COUNT; j++) {
lfs_mount(&lfs, cfg) => 0;
lfs_dir_t dir;
lfs_dir_open(&lfs, &dir, "/") => 0;
@@ -935,15 +889,16 @@ code = '''
assert(strcmp(info.name, "..") == 0);
assert(info.type == LFS_TYPE_DIR);
lfs_soff_t pos;
for (int i = 0; i < j; 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);
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;
char path[1024];
@@ -973,51 +928,5 @@ code = '''
lfs_dir_close(&lfs, &dir) => 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;
'''

View File

@@ -24,7 +24,7 @@ code = '''
lfs_deinit(&lfs) => 0;
// test that mount fails gracefully
lfs_mount(&lfs, cfg) => LFS_ERR_CORRUPT;
lfs_mount(&lfs, cfg) => LFS_ERR_INVAL;
'''
[cases.test_evil_invalid_dir_pointer]
@@ -237,7 +237,7 @@ code = '''
lfs_dir_fetch(&lfs, &mdir, (lfs_block_t[2]){0, 1}) => 0;
lfs_dir_commit(&lfs, &mdir, LFS_MKATTRS(
{LFS_MKTAG(LFS_TYPE_HARDTAIL, 0x3ff, 8),
(lfs_block_t[2]){0, 1}})) => 0;
(lfs_block_t[2]){lfs_tole32(0), lfs_tole32(1)}})) => 0;
lfs_deinit(&lfs) => 0;
// test that mount fails gracefully
@@ -268,7 +268,7 @@ code = '''
lfs_dir_fetch(&lfs, &mdir, pair) => 0;
lfs_dir_commit(&lfs, &mdir, LFS_MKATTRS(
{LFS_MKTAG(LFS_TYPE_HARDTAIL, 0x3ff, 8),
(lfs_block_t[2]){0, 1}})) => 0;
(lfs_block_t[2]){lfs_tole32(0), lfs_tole32(1)}})) => 0;
lfs_deinit(&lfs) => 0;
// test that mount fails gracefully
@@ -297,6 +297,7 @@ code = '''
lfs_pair_fromle32(pair);
// change tail-pointer to point to ourself
lfs_dir_fetch(&lfs, &mdir, pair) => 0;
lfs_pair_tole32(pair);
lfs_dir_commit(&lfs, &mdir, LFS_MKATTRS(
{LFS_MKTAG(LFS_TYPE_HARDTAIL, 0x3ff, 8), pair})) => 0;
lfs_deinit(&lfs) => 0;

View File

@@ -59,150 +59,6 @@ code = '''
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
[cases.test_orphans_reentrant]
reentrant = true

View File

@@ -1,185 +0,0 @@
# There are already a number of tests that test general operations under
# power-loss (see the reentrant attribute). These tests are for explicitly
# testing specific corner cases.
# only a revision count
[cases.test_powerloss_only_rev]
code = '''
lfs_t lfs;
lfs_format(&lfs, cfg) => 0;
lfs_mount(&lfs, cfg) => 0;
lfs_mkdir(&lfs, "notebook") => 0;
lfs_file_t file;
lfs_file_open(&lfs, &file, "notebook/paper",
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_APPEND) => 0;
char buffer[256];
strcpy(buffer, "hello");
lfs_size_t size = strlen("hello");
for (int i = 0; i < 5; i++) {
lfs_file_write(&lfs, &file, buffer, size) => size;
lfs_file_sync(&lfs, &file) => 0;
}
lfs_file_close(&lfs, &file) => 0;
char rbuffer[256];
lfs_file_open(&lfs, &file, "notebook/paper", LFS_O_RDONLY) => 0;
for (int i = 0; i < 5; i++) {
lfs_file_read(&lfs, &file, rbuffer, size) => size;
assert(memcmp(rbuffer, buffer, size) == 0);
}
lfs_file_close(&lfs, &file) => 0;
lfs_unmount(&lfs) => 0;
// get pair/rev count
lfs_mount(&lfs, cfg) => 0;
lfs_dir_t dir;
lfs_dir_open(&lfs, &dir, "notebook") => 0;
lfs_block_t pair[2] = {dir.m.pair[0], dir.m.pair[1]};
uint32_t rev = dir.m.rev;
lfs_dir_close(&lfs, &dir) => 0;
lfs_unmount(&lfs) => 0;
// write just the revision count
uint8_t bbuffer[BLOCK_SIZE];
cfg->read(cfg, pair[1], 0, bbuffer, BLOCK_SIZE) => 0;
memcpy(bbuffer, &(uint32_t){lfs_tole32(rev+1)}, sizeof(uint32_t));
cfg->erase(cfg, pair[1]) => 0;
cfg->prog(cfg, pair[1], 0, bbuffer, BLOCK_SIZE) => 0;
lfs_mount(&lfs, cfg) => 0;
// can read?
lfs_file_open(&lfs, &file, "notebook/paper", LFS_O_RDONLY) => 0;
for (int i = 0; i < 5; i++) {
lfs_file_read(&lfs, &file, rbuffer, size) => size;
assert(memcmp(rbuffer, buffer, size) == 0);
}
lfs_file_close(&lfs, &file) => 0;
// can write?
lfs_file_open(&lfs, &file, "notebook/paper",
LFS_O_WRONLY | LFS_O_APPEND) => 0;
strcpy(buffer, "goodbye");
size = strlen("goodbye");
for (int i = 0; i < 5; i++) {
lfs_file_write(&lfs, &file, buffer, size) => size;
lfs_file_sync(&lfs, &file) => 0;
}
lfs_file_close(&lfs, &file) => 0;
lfs_file_open(&lfs, &file, "notebook/paper", LFS_O_RDONLY) => 0;
strcpy(buffer, "hello");
size = strlen("hello");
for (int i = 0; i < 5; i++) {
lfs_file_read(&lfs, &file, rbuffer, size) => size;
assert(memcmp(rbuffer, buffer, size) == 0);
}
strcpy(buffer, "goodbye");
size = strlen("goodbye");
for (int i = 0; i < 5; i++) {
lfs_file_read(&lfs, &file, rbuffer, size) => size;
assert(memcmp(rbuffer, buffer, size) == 0);
}
lfs_file_close(&lfs, &file) => 0;
lfs_unmount(&lfs) => 0;
'''
# partial prog, may not be byte in order!
[cases.test_powerloss_partial_prog]
if = '''
PROG_SIZE < BLOCK_SIZE
&& (DISK_VERSION == 0 || DISK_VERSION >= 0x00020001)
'''
defines.BYTE_OFF = ["0", "PROG_SIZE-1", "PROG_SIZE/2"]
defines.BYTE_VALUE = [0x33, 0xcc]
in = "lfs.c"
code = '''
lfs_t lfs;
lfs_format(&lfs, cfg) => 0;
lfs_mount(&lfs, cfg) => 0;
lfs_mkdir(&lfs, "notebook") => 0;
lfs_file_t file;
lfs_file_open(&lfs, &file, "notebook/paper",
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_APPEND) => 0;
char buffer[256];
strcpy(buffer, "hello");
lfs_size_t size = strlen("hello");
for (int i = 0; i < 5; i++) {
lfs_file_write(&lfs, &file, buffer, size) => size;
lfs_file_sync(&lfs, &file) => 0;
}
lfs_file_close(&lfs, &file) => 0;
char rbuffer[256];
lfs_file_open(&lfs, &file, "notebook/paper", LFS_O_RDONLY) => 0;
for (int i = 0; i < 5; i++) {
lfs_file_read(&lfs, &file, rbuffer, size) => size;
assert(memcmp(rbuffer, buffer, size) == 0);
}
lfs_file_close(&lfs, &file) => 0;
lfs_unmount(&lfs) => 0;
// imitate a partial prog, value should not matter, if littlefs
// doesn't notice the partial prog testbd will assert
// get offset to next prog
lfs_mount(&lfs, cfg) => 0;
lfs_dir_t dir;
lfs_dir_open(&lfs, &dir, "notebook") => 0;
lfs_block_t block = dir.m.pair[0];
lfs_off_t off = dir.m.off;
lfs_dir_close(&lfs, &dir) => 0;
lfs_unmount(&lfs) => 0;
// tweak byte
uint8_t bbuffer[BLOCK_SIZE];
cfg->read(cfg, block, 0, bbuffer, BLOCK_SIZE) => 0;
bbuffer[off + BYTE_OFF] = BYTE_VALUE;
cfg->erase(cfg, block) => 0;
cfg->prog(cfg, block, 0, bbuffer, BLOCK_SIZE) => 0;
lfs_mount(&lfs, cfg) => 0;
// can read?
lfs_file_open(&lfs, &file, "notebook/paper", LFS_O_RDONLY) => 0;
for (int i = 0; i < 5; i++) {
lfs_file_read(&lfs, &file, rbuffer, size) => size;
assert(memcmp(rbuffer, buffer, size) == 0);
}
lfs_file_close(&lfs, &file) => 0;
// can write?
lfs_file_open(&lfs, &file, "notebook/paper",
LFS_O_WRONLY | LFS_O_APPEND) => 0;
strcpy(buffer, "goodbye");
size = strlen("goodbye");
for (int i = 0; i < 5; i++) {
lfs_file_write(&lfs, &file, buffer, size) => size;
lfs_file_sync(&lfs, &file) => 0;
}
lfs_file_close(&lfs, &file) => 0;
lfs_file_open(&lfs, &file, "notebook/paper", LFS_O_RDONLY) => 0;
strcpy(buffer, "hello");
size = strlen("hello");
for (int i = 0; i < 5; i++) {
lfs_file_read(&lfs, &file, rbuffer, size) => size;
assert(memcmp(rbuffer, buffer, size) == 0);
}
strcpy(buffer, "goodbye");
size = strlen("goodbye");
for (int i = 0; i < 5; i++) {
lfs_file_read(&lfs, &file, rbuffer, size) => size;
assert(memcmp(rbuffer, buffer, size) == 0);
}
lfs_file_close(&lfs, &file) => 0;
lfs_unmount(&lfs) => 0;
'''

View File

@@ -14,21 +14,6 @@ code = '''
lfs_unmount(&lfs) => 0;
'''
# mount/unmount from interpretting a previous superblock block_count
[cases.test_superblocks_mount_unknown_block_count]
code = '''
lfs_t lfs;
lfs_format(&lfs, cfg) => 0;
memset(&lfs, 0, sizeof(lfs));
struct lfs_config tweaked_cfg = *cfg;
tweaked_cfg.block_count = 0;
lfs_mount(&lfs, &tweaked_cfg) => 0;
assert(lfs.block_count == cfg->block_count);
lfs_unmount(&lfs) => 0;
'''
# reentrant format
[cases.test_superblocks_reentrant_format]
reentrant = true
@@ -46,55 +31,7 @@ code = '''
[cases.test_superblocks_invalid_mount]
code = '''
lfs_t lfs;
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;
lfs_mount(&lfs, cfg) => LFS_ERR_INVAL;
'''
# expanding superblock
@@ -213,8 +150,8 @@ code = '''
lfs_unmount(&lfs) => 0;
'''
# mount with unknown block_count
[cases.test_superblocks_unknown_blocks]
# mount with unknown block_size/block_count
[cases.test_superblocks_unknowns]
code = '''
lfs_t lfs;
lfs_format(&lfs, cfg) => 0;
@@ -238,6 +175,24 @@ code = '''
assert(fsinfo.block_count == BLOCK_COUNT);
lfs_unmount(&lfs) => 0;
// unknown block_size
cfg->block_size = 0;
cfg->block_count = ERASE_COUNT;
lfs_mount(&lfs, cfg) => 0;
lfs_fs_stat(&lfs, &fsinfo) => 0;
assert(fsinfo.block_size == BLOCK_SIZE);
assert(fsinfo.block_count == BLOCK_COUNT);
lfs_unmount(&lfs) => 0;
// unknown block_count/block_size
cfg->block_size = 0;
cfg->block_count = 0;
lfs_mount(&lfs, cfg) => 0;
lfs_fs_stat(&lfs, &fsinfo) => 0;
assert(fsinfo.block_size == BLOCK_SIZE);
assert(fsinfo.block_count == BLOCK_COUNT);
lfs_unmount(&lfs) => 0;
// do some work
lfs_mount(&lfs, cfg) => 0;
lfs_fs_stat(&lfs, &fsinfo) => 0;
@@ -262,6 +217,142 @@ code = '''
lfs_unmount(&lfs) => 0;
'''
# mount with blocks larger than the erase_size
[cases.test_superblocks_larger_blocks]
defines.BLOCK_SIZE = [
'2*ERASE_SIZE',
'4*ERASE_SIZE',
'(ERASE_COUNT/2)*ERASE_SIZE']
code = '''
lfs_t lfs;
lfs_format(&lfs, cfg) => 0;
// known block_size/block_count
cfg->block_size = BLOCK_SIZE;
cfg->block_count = BLOCK_COUNT;
lfs_mount(&lfs, cfg) => 0;
struct lfs_fsinfo fsinfo;
lfs_fs_stat(&lfs, &fsinfo) => 0;
assert(fsinfo.block_size == BLOCK_SIZE);
assert(fsinfo.block_count == BLOCK_COUNT);
lfs_unmount(&lfs) => 0;
// incorrect block_size
cfg->block_size = ERASE_SIZE;
cfg->block_count = 0;
lfs_mount(&lfs, cfg) => LFS_ERR_INVAL;
// unknown block_count
cfg->block_size = BLOCK_SIZE;
cfg->block_count = 0;
lfs_mount(&lfs, cfg) => 0;
lfs_fs_stat(&lfs, &fsinfo) => 0;
assert(fsinfo.block_size == BLOCK_SIZE);
assert(fsinfo.block_count == BLOCK_COUNT);
lfs_unmount(&lfs) => 0;
// unknown block_size
cfg->block_size = 0;
cfg->block_count = ERASE_COUNT;
lfs_mount(&lfs, cfg) => 0;
lfs_fs_stat(&lfs, &fsinfo) => 0;
assert(fsinfo.block_size == BLOCK_SIZE);
assert(fsinfo.block_count == BLOCK_COUNT);
lfs_unmount(&lfs) => 0;
// unknown block_count/block_size
cfg->block_size = 0;
cfg->block_count = 0;
lfs_mount(&lfs, cfg) => 0;
lfs_fs_stat(&lfs, &fsinfo) => 0;
assert(fsinfo.block_size == BLOCK_SIZE);
assert(fsinfo.block_count == BLOCK_COUNT);
lfs_unmount(&lfs) => 0;
// do some work
lfs_mount(&lfs, cfg) => 0;
lfs_fs_stat(&lfs, &fsinfo) => 0;
assert(fsinfo.block_size == BLOCK_SIZE);
assert(fsinfo.block_count == BLOCK_COUNT);
lfs_file_t file;
lfs_file_open(&lfs, &file, "test",
LFS_O_CREAT | LFS_O_EXCL | LFS_O_WRONLY) => 0;
lfs_file_write(&lfs, &file, "hello!", 6) => 6;
lfs_file_close(&lfs, &file) => 0;
lfs_unmount(&lfs) => 0;
lfs_mount(&lfs, cfg) => 0;
lfs_fs_stat(&lfs, &fsinfo) => 0;
assert(fsinfo.block_size == BLOCK_SIZE);
assert(fsinfo.block_count == BLOCK_COUNT);
lfs_file_open(&lfs, &file, "test", LFS_O_RDONLY) => 0;
uint8_t buffer[256];
lfs_file_read(&lfs, &file, buffer, sizeof(buffer)) => 6;
lfs_file_close(&lfs, &file) => 0;
assert(memcmp(buffer, "hello!", 6) == 0);
lfs_unmount(&lfs) => 0;
'''
# mount with blocks smaller than the erase_size
[cases.test_superblocks_smaller_blocks]
defines.FORMAT_BLOCK_SIZE = 'ERASE_SIZE/2'
defines.FORMAT_BLOCK_COUNT = 'BLOCK_COUNT * (BLOCK_SIZE/FORMAT_BLOCK_SIZE)'
in = 'lfs.c'
code = '''
lfs_t lfs;
lfs_init(&lfs, cfg) => 0;
lfs.block_size = BLOCK_SIZE;
lfs.block_count = BLOCK_COUNT;
lfs_mdir_t root = {
.pair = {0, 0}, // make sure this goes into block 0
.rev = 0,
.off = sizeof(uint32_t),
.etag = 0xffffffff,
.count = 0,
.tail = {LFS_BLOCK_NULL, LFS_BLOCK_NULL},
.erased = false,
.split = false,
};
lfs_superblock_t superblock = {
.version = LFS_DISK_VERSION,
.block_size = FORMAT_BLOCK_SIZE,
.block_count = FORMAT_BLOCK_COUNT,
.name_max = LFS_NAME_MAX,
.file_max = LFS_FILE_MAX,
.attr_max = LFS_ATTR_MAX,
};
lfs_superblock_tole32(&superblock);
lfs_dir_commit(&lfs, &root, LFS_MKATTRS(
{LFS_MKTAG(LFS_TYPE_CREATE, 0, 0), NULL},
{LFS_MKTAG(LFS_TYPE_SUPERBLOCK, 0, 8), "littlefs"},
{LFS_MKTAG(LFS_TYPE_INLINESTRUCT, 0, sizeof(superblock)),
&superblock})) => 0;
lfs_deinit(&lfs) => 0;
// known block_size/block_count
cfg->block_size = BLOCK_SIZE;
cfg->block_count = BLOCK_COUNT;
lfs_mount(&lfs, cfg) => LFS_ERR_INVAL;
// unknown block_count
cfg->block_size = BLOCK_SIZE;
cfg->block_count = 0;
lfs_mount(&lfs, cfg) => LFS_ERR_INVAL;
// unknown block_size
cfg->block_size = 0;
cfg->block_count = ERASE_COUNT;
lfs_mount(&lfs, cfg) => LFS_ERR_INVAL;
// unknown block_count/block_size
cfg->block_size = 0;
cfg->block_count = 0;
lfs_mount(&lfs, cfg) => LFS_ERR_INVAL;
'''
# mount with blocks fewer than the erase_count
[cases.test_superblocks_fewer_blocks]
defines.BLOCK_COUNT = ['ERASE_COUNT/2', 'ERASE_COUNT/4', '2']
@@ -293,6 +384,24 @@ code = '''
assert(fsinfo.block_count == BLOCK_COUNT);
lfs_unmount(&lfs) => 0;
// unknown block_size
cfg->block_size = 0;
cfg->block_count = BLOCK_COUNT;
lfs_mount(&lfs, cfg) => 0;
lfs_fs_stat(&lfs, &fsinfo) => 0;
assert(fsinfo.block_size == BLOCK_SIZE);
assert(fsinfo.block_count == BLOCK_COUNT);
lfs_unmount(&lfs) => 0;
// unknown block_count/block_size
cfg->block_size = 0;
cfg->block_count = 0;
lfs_mount(&lfs, cfg) => 0;
lfs_fs_stat(&lfs, &fsinfo) => 0;
assert(fsinfo.block_size == BLOCK_SIZE);
assert(fsinfo.block_count == BLOCK_COUNT);
lfs_unmount(&lfs) => 0;
// do some work
lfs_mount(&lfs, cfg) => 0;
lfs_fs_stat(&lfs, &fsinfo) => 0;
@@ -324,6 +433,7 @@ in = 'lfs.c'
code = '''
lfs_t lfs;
lfs_init(&lfs, cfg) => 0;
lfs.block_size = BLOCK_SIZE;
lfs.block_count = BLOCK_COUNT;
lfs_mdir_t root = {
@@ -358,6 +468,99 @@ code = '''
cfg->block_size = BLOCK_SIZE;
cfg->block_count = BLOCK_COUNT;
lfs_mount(&lfs, cfg) => LFS_ERR_INVAL;
// unknown block_size
cfg->block_size = 0;
cfg->block_count = ERASE_COUNT;
lfs_mount(&lfs, cfg) => LFS_ERR_INVAL;
'''
# the real challenge is finding superblock 1 when block_size is unknown,
# test for this explicitly
[cases.test_superblocks_unknown_superblock1]
in = 'lfs.c'
defines.BLOCK_SIZE = [
'ERASE_SIZE',
'2*ERASE_SIZE',
'4*ERASE_SIZE',
'(ERASE_COUNT/2)*ERASE_SIZE']
code = '''
lfs_t lfs;
lfs_init(&lfs, cfg) => 0;
lfs.block_size = BLOCK_SIZE;
lfs.block_count = BLOCK_COUNT;
uint8_t buffer[lfs_max(READ_SIZE, sizeof(uint32_t))];
memset(buffer, 0, lfs_max(READ_SIZE, sizeof(uint32_t)));
cfg->read(cfg, 0, 0, buffer, lfs_max(READ_SIZE, sizeof(uint32_t))) => 0;
uint32_t rev;
memcpy(&rev, buffer, sizeof(uint32_t));
rev = lfs_fromle32(rev);
lfs_mdir_t root = {
.pair = {1, 1}, // make sure this goes into block 1
.rev = rev,
.off = sizeof(uint32_t),
.etag = 0xffffffff,
.count = 0,
.tail = {LFS_BLOCK_NULL, LFS_BLOCK_NULL},
.erased = false,
.split = false,
};
lfs_superblock_t superblock = {
.version = LFS_DISK_VERSION,
.block_size = BLOCK_SIZE,
.block_count = BLOCK_COUNT,
.name_max = LFS_NAME_MAX,
.file_max = LFS_FILE_MAX,
.attr_max = LFS_ATTR_MAX,
};
lfs_superblock_tole32(&superblock);
lfs_dir_commit(&lfs, &root, LFS_MKATTRS(
{LFS_MKTAG(LFS_TYPE_CREATE, 0, 0), NULL},
{LFS_MKTAG(LFS_TYPE_SUPERBLOCK, 0, 8), "littlefs"},
{LFS_MKTAG(LFS_TYPE_INLINESTRUCT, 0, sizeof(superblock)),
&superblock})) => 0;
lfs_deinit(&lfs) => 0;
// known block_size/block_count
cfg->block_size = BLOCK_SIZE;
cfg->block_count = BLOCK_COUNT;
lfs_mount(&lfs, cfg) => 0;
struct lfs_fsinfo fsinfo;
lfs_fs_stat(&lfs, &fsinfo) => 0;
assert(fsinfo.block_size == BLOCK_SIZE);
assert(fsinfo.block_count == BLOCK_COUNT);
lfs_unmount(&lfs) => 0;
// unknown block_count
cfg->block_size = BLOCK_SIZE;
cfg->block_count = 0;
lfs_mount(&lfs, cfg) => 0;
lfs_fs_stat(&lfs, &fsinfo) => 0;
assert(fsinfo.block_size == BLOCK_SIZE);
assert(fsinfo.block_count == BLOCK_COUNT);
lfs_unmount(&lfs) => 0;
// unknown block_size
cfg->block_size = 0;
cfg->block_count = ERASE_COUNT;
lfs_mount(&lfs, cfg) => 0;
lfs_fs_stat(&lfs, &fsinfo) => 0;
assert(fsinfo.block_size == BLOCK_SIZE);
assert(fsinfo.block_count == BLOCK_COUNT);
lfs_unmount(&lfs) => 0;
// unknown block_count/block_size
cfg->block_size = 0;
cfg->block_count = 0;
lfs_mount(&lfs, cfg) => 0;
lfs_fs_stat(&lfs, &fsinfo) => 0;
assert(fsinfo.block_size == BLOCK_SIZE);
assert(fsinfo.block_count == BLOCK_COUNT);
lfs_unmount(&lfs) => 0;
'''
# mount and grow the filesystem

View File

@@ -1,8 +1,7 @@
# simple truncate
[cases.test_truncate_simple]
defines.MEDIUMSIZE = [31, 32, 33, 511, 512, 513, 2047, 2048, 2049]
defines.LARGESIZE = [32, 33, 512, 513, 2048, 2049, 8192, 8193]
if = 'MEDIUMSIZE < LARGESIZE'
defines.MEDIUMSIZE = [32, 2048]
defines.LARGESIZE = 8192
code = '''
lfs_t lfs;
lfs_format(&lfs, cfg) => 0;
@@ -15,8 +14,7 @@ code = '''
strcpy((char*)buffer, "hair");
size_t size = strlen((char*)buffer);
for (lfs_off_t j = 0; j < LARGESIZE; j += size) {
lfs_file_write(&lfs, &file, buffer, lfs_min(size, LARGESIZE-j))
=> lfs_min(size, LARGESIZE-j);
lfs_file_write(&lfs, &file, buffer, size) => size;
}
lfs_file_size(&lfs, &file) => LARGESIZE;
@@ -39,9 +37,8 @@ code = '''
size = strlen("hair");
for (lfs_off_t j = 0; j < MEDIUMSIZE; j += size) {
lfs_file_read(&lfs, &file, buffer, lfs_min(size, MEDIUMSIZE-j))
=> lfs_min(size, MEDIUMSIZE-j);
memcmp(buffer, "hair", lfs_min(size, MEDIUMSIZE-j)) => 0;
lfs_file_read(&lfs, &file, buffer, size) => size;
memcmp(buffer, "hair", size) => 0;
}
lfs_file_read(&lfs, &file, buffer, size) => 0;
@@ -51,9 +48,8 @@ code = '''
# truncate and read
[cases.test_truncate_read]
defines.MEDIUMSIZE = [31, 32, 33, 511, 512, 513, 2047, 2048, 2049]
defines.LARGESIZE = [32, 33, 512, 513, 2048, 2049, 8192, 8193]
if = 'MEDIUMSIZE < LARGESIZE'
defines.MEDIUMSIZE = [32, 2048]
defines.LARGESIZE = 8192
code = '''
lfs_t lfs;
lfs_format(&lfs, cfg) => 0;
@@ -66,8 +62,7 @@ code = '''
strcpy((char*)buffer, "hair");
size_t size = strlen((char*)buffer);
for (lfs_off_t j = 0; j < LARGESIZE; j += size) {
lfs_file_write(&lfs, &file, buffer, lfs_min(size, LARGESIZE-j))
=> lfs_min(size, LARGESIZE-j);
lfs_file_write(&lfs, &file, buffer, size) => size;
}
lfs_file_size(&lfs, &file) => LARGESIZE;
@@ -83,9 +78,8 @@ code = '''
size = strlen("hair");
for (lfs_off_t j = 0; j < MEDIUMSIZE; j += size) {
lfs_file_read(&lfs, &file, buffer, lfs_min(size, MEDIUMSIZE-j))
=> lfs_min(size, MEDIUMSIZE-j);
memcmp(buffer, "hair", lfs_min(size, MEDIUMSIZE-j)) => 0;
lfs_file_read(&lfs, &file, buffer, size) => size;
memcmp(buffer, "hair", size) => 0;
}
lfs_file_read(&lfs, &file, buffer, size) => 0;
@@ -98,9 +92,8 @@ code = '''
size = strlen("hair");
for (lfs_off_t j = 0; j < MEDIUMSIZE; j += size) {
lfs_file_read(&lfs, &file, buffer, lfs_min(size, MEDIUMSIZE-j))
=> lfs_min(size, MEDIUMSIZE-j);
memcmp(buffer, "hair", lfs_min(size, MEDIUMSIZE-j)) => 0;
lfs_file_read(&lfs, &file, buffer, size) => size;
memcmp(buffer, "hair", size) => 0;
}
lfs_file_read(&lfs, &file, buffer, size) => 0;
@@ -155,7 +148,7 @@ code = '''
lfs_file_truncate(&lfs, &file, trunc) => 0;
lfs_file_tell(&lfs, &file) => qsize;
lfs_file_size(&lfs, &file) => trunc;
/* Read should produce second quarter */
lfs_file_read(&lfs, &file, rb, size) => trunc - qsize;
memcmp(rb, wb + qsize, trunc - qsize) => 0;
@@ -166,9 +159,8 @@ code = '''
# truncate and write
[cases.test_truncate_write]
defines.MEDIUMSIZE = [31, 32, 33, 511, 512, 513, 2047, 2048, 2049]
defines.LARGESIZE = [32, 33, 512, 513, 2048, 2049, 8192, 8193]
if = 'MEDIUMSIZE < LARGESIZE'
defines.MEDIUMSIZE = [32, 2048]
defines.LARGESIZE = 8192
code = '''
lfs_t lfs;
lfs_format(&lfs, cfg) => 0;
@@ -181,8 +173,7 @@ code = '''
strcpy((char*)buffer, "hair");
size_t size = strlen((char*)buffer);
for (lfs_off_t j = 0; j < LARGESIZE; j += size) {
lfs_file_write(&lfs, &file, buffer, lfs_min(size, LARGESIZE-j))
=> lfs_min(size, LARGESIZE-j);
lfs_file_write(&lfs, &file, buffer, size) => size;
}
lfs_file_size(&lfs, &file) => LARGESIZE;
@@ -193,16 +184,13 @@ code = '''
lfs_file_open(&lfs, &file, "baldywrite", LFS_O_RDWR) => 0;
lfs_file_size(&lfs, &file) => LARGESIZE;
/* truncate */
lfs_file_truncate(&lfs, &file, MEDIUMSIZE) => 0;
lfs_file_size(&lfs, &file) => MEDIUMSIZE;
/* and write */
strcpy((char*)buffer, "bald");
size = strlen((char*)buffer);
for (lfs_off_t j = 0; j < MEDIUMSIZE; j += size) {
lfs_file_write(&lfs, &file, buffer, lfs_min(size, MEDIUMSIZE-j))
=> lfs_min(size, MEDIUMSIZE-j);
lfs_file_write(&lfs, &file, buffer, size) => size;
}
lfs_file_size(&lfs, &file) => MEDIUMSIZE;
@@ -215,9 +203,8 @@ code = '''
size = strlen("bald");
for (lfs_off_t j = 0; j < MEDIUMSIZE; j += size) {
lfs_file_read(&lfs, &file, buffer, lfs_min(size, MEDIUMSIZE-j))
=> lfs_min(size, MEDIUMSIZE-j);
memcmp(buffer, "bald", lfs_min(size, MEDIUMSIZE-j)) => 0;
lfs_file_read(&lfs, &file, buffer, size) => size;
memcmp(buffer, "bald", size) => 0;
}
lfs_file_read(&lfs, &file, buffer, size) => 0;
@@ -228,7 +215,7 @@ code = '''
# truncate write under powerloss
[cases.test_truncate_reentrant_write]
defines.SMALLSIZE = [4, 512]
defines.MEDIUMSIZE = [0, 3, 4, 5, 31, 32, 33, 511, 512, 513, 1023, 1024, 1025]
defines.MEDIUMSIZE = [32, 1024]
defines.LARGESIZE = 2048
reentrant = true
code = '''
@@ -249,11 +236,10 @@ code = '''
size == (size_t)SMALLSIZE);
for (lfs_off_t j = 0; j < size; j += 4) {
uint8_t buffer[1024];
lfs_file_read(&lfs, &file, buffer, lfs_min(4, size-j))
=> lfs_min(4, size-j);
assert(memcmp(buffer, "hair", lfs_min(4, size-j)) == 0 ||
memcmp(buffer, "bald", lfs_min(4, size-j)) == 0 ||
memcmp(buffer, "comb", lfs_min(4, size-j)) == 0);
lfs_file_read(&lfs, &file, buffer, 4) => 4;
assert(memcmp(buffer, "hair", 4) == 0 ||
memcmp(buffer, "bald", 4) == 0 ||
memcmp(buffer, "comb", 4) == 0);
}
lfs_file_close(&lfs, &file) => 0;
}
@@ -265,23 +251,19 @@ code = '''
strcpy((char*)buffer, "hair");
size_t size = strlen((char*)buffer);
for (lfs_off_t j = 0; j < LARGESIZE; j += size) {
lfs_file_write(&lfs, &file, buffer, lfs_min(size, LARGESIZE-j))
=> lfs_min(size, LARGESIZE-j);
lfs_file_write(&lfs, &file, buffer, size) => size;
}
lfs_file_size(&lfs, &file) => LARGESIZE;
lfs_file_close(&lfs, &file) => 0;
lfs_file_open(&lfs, &file, "baldy", LFS_O_RDWR) => 0;
lfs_file_size(&lfs, &file) => LARGESIZE;
/* truncate */
lfs_file_truncate(&lfs, &file, MEDIUMSIZE) => 0;
lfs_file_size(&lfs, &file) => MEDIUMSIZE;
/* and write */
strcpy((char*)buffer, "bald");
size = strlen((char*)buffer);
for (lfs_off_t j = 0; j < MEDIUMSIZE; j += size) {
lfs_file_write(&lfs, &file, buffer, lfs_min(size, MEDIUMSIZE-j))
=> lfs_min(size, MEDIUMSIZE-j);
lfs_file_write(&lfs, &file, buffer, size) => size;
}
lfs_file_size(&lfs, &file) => MEDIUMSIZE;
lfs_file_close(&lfs, &file) => 0;
@@ -293,8 +275,7 @@ code = '''
strcpy((char*)buffer, "comb");
size = strlen((char*)buffer);
for (lfs_off_t j = 0; j < SMALLSIZE; j += size) {
lfs_file_write(&lfs, &file, buffer, lfs_min(size, SMALLSIZE-j))
=> lfs_min(size, SMALLSIZE-j);
lfs_file_write(&lfs, &file, buffer, size) => size;
}
lfs_file_size(&lfs, &file) => SMALLSIZE;
lfs_file_close(&lfs, &file) => 0;
@@ -448,7 +429,7 @@ code = '''
# noop truncate
[cases.test_truncate_nop]
defines.MEDIUMSIZE = [32, 33, 512, 513, 2048, 2049, 8192, 8193]
defines.MEDIUMSIZE = [32, 2048]
code = '''
lfs_t lfs;
lfs_format(&lfs, cfg) => 0;
@@ -461,11 +442,10 @@ code = '''
strcpy((char*)buffer, "hair");
size_t size = strlen((char*)buffer);
for (lfs_off_t j = 0; j < MEDIUMSIZE; j += size) {
lfs_file_write(&lfs, &file, buffer, lfs_min(size, MEDIUMSIZE-j))
=> lfs_min(size, MEDIUMSIZE-j);
lfs_file_write(&lfs, &file, buffer, size) => size;
// this truncate should do nothing
lfs_file_truncate(&lfs, &file, j+lfs_min(size, MEDIUMSIZE-j)) => 0;
lfs_file_truncate(&lfs, &file, j+size) => 0;
}
lfs_file_size(&lfs, &file) => MEDIUMSIZE;
@@ -475,9 +455,8 @@ code = '''
lfs_file_size(&lfs, &file) => MEDIUMSIZE;
for (lfs_off_t j = 0; j < MEDIUMSIZE; j += size) {
lfs_file_read(&lfs, &file, buffer, lfs_min(size, MEDIUMSIZE-j))
=> lfs_min(size, MEDIUMSIZE-j);
memcmp(buffer, "hair", lfs_min(size, MEDIUMSIZE-j)) => 0;
lfs_file_read(&lfs, &file, buffer, size) => size;
memcmp(buffer, "hair", size) => 0;
}
lfs_file_read(&lfs, &file, buffer, size) => 0;
@@ -489,9 +468,8 @@ code = '''
lfs_file_open(&lfs, &file, "baldynoop", LFS_O_RDWR) => 0;
lfs_file_size(&lfs, &file) => MEDIUMSIZE;
for (lfs_off_t j = 0; j < MEDIUMSIZE; j += size) {
lfs_file_read(&lfs, &file, buffer, lfs_min(size, MEDIUMSIZE-j))
=> lfs_min(size, MEDIUMSIZE-j);
memcmp(buffer, "hair", lfs_min(size, MEDIUMSIZE-j)) => 0;
lfs_file_read(&lfs, &file, buffer, size) => size;
memcmp(buffer, "hair", size) => 0;
}
lfs_file_read(&lfs, &file, buffer, size) => 0;
lfs_file_close(&lfs, &file) => 0;