Compare commits

...

26 Commits

Author SHA1 Message Date
Christopher Haster
b6773e68bf Merge remote-tracking branch 'origin/devel' into fix-dir-seek-end 2023-04-26 13:47:58 -05:00
Christopher Haster
92298c749d Merge pull request #802 from littlefs-project/assert-minimum-block-size
Add explicit assert for minimum block size of 128 bytes
2023-04-26 02:41:44 -05:00
Christopher Haster
50b394ca36 Merge pull request #801 from littlefs-project/assert-bool-cast
Add an assert for truthy-preserving bool conversions
2023-04-26 02:41:30 -05:00
Christopher Haster
a99574cd5b Merge pull request #807 from littlefs-project/doc-link-littlefs2-rust
Add littlefs2 crate to README
2023-04-26 02:40:51 -05:00
Christopher Haster
363a8b56cf Tweaked wording of littlefs2-rust link in README.md 2023-04-26 02:02:23 -05:00
Lachezar Lechev
e43d381135 chore: add littlefs2 crate to README 2023-04-26 01:59:57 -05:00
Christopher Haster
ee6a51bbbe Merge pull request #718 from yomimono/mention-chamelon
Add "chamelon" to the related projects section.
2023-04-26 01:57:31 -05:00
Christopher Haster
01ac033d47 Merge pull request #572 from tniessen/add-littlefs-disk-img-viewer
Add littlefs-disk-img-viewer to README
2023-04-26 01:56:31 -05:00
Christopher Haster
2a18e03cb8 Merge pull request #809 from littlefs-project/brent-cycle-detection
Adopt Brent's algorithm for cycle detection
2023-04-26 01:55:50 -05:00
Christopher Haster
6f074ebe31 Merge pull request #497 from littlefs-project/crc-rework-2
Forward-looking erase-state CRCs
2023-04-26 01:15:59 -05:00
Christopher Haster
0a7eca0bd5 Merge pull request #752 from littlefs-project/test-and-bench-runners
Add test/bench runners, benchmarks, additional scripts
2023-04-26 01:09:01 -05:00
Christopher Haster
3e25dfc16c Added FCRC tags and an explanation of how FCRCs work to SPEC.md
See SPEC.md for more info.

Also considered adding an explanation to DESIGN.md, but there's not a
great place for it. Maybe FCRCs are too low-level for the high-level
design document. Though may be worth reconsidering if DESIGN.md gets
revisited.
2023-04-21 14:49:49 -05:00
Christopher Haster
9e28c75482 Bumped minor version to v2.6 and on-disk minor version to lfs2.1 2023-04-21 00:57:00 -05:00
Christopher Haster
4c9360020e Added ability to bump on-disk minor version
This just means a rewrite of the superblock entry with the new minor
version.

Though it's interesting to note, we don't need to rewrite the superblock
entry until the first write operation in the filesystem, an optimization
that is already in use for the fixing of orphans and in-flight moves.

To keep track of any outdated minor version found during lfs_mount, we
can carve out a bit from the reserved bits in our gstate. These are
currently used for a counter tracking the number of orphans in the
filesystem, but this is usually a very small number so this hopefully
won't be an issue.

In-device gstate tag:

  [--       32      --]
  [1|- 11 -| 10 |1| 9 ]
   ^----^-----^--^--^-- 1-bit has orphans
        '-----|--|--|-- 11-bit move type
              '--|--|-- 10-bit move id
                 '--|-- 1-bit needs superblock
                    '-- 9-bit orphan count
2023-04-21 00:56:55 -05:00
Christopher Haster
ca0da3d490 Added compatibility testing on pull-request to GitHub test action
This uses the "github.event.pull_request.base.ref" variable as the
"lfsp" target for compatibility testing.
2023-04-21 00:29:28 -05:00
Christopher Haster
116332d3f7 Added tests for forwards and backwards disk compatibility
This is a bit tricky since we need two different version of littlefs in
order to test for most compatibility concerns.

Fortunately we already have scripts/changeprefix.py for version-specific
symbols, so it's not that hard to link in the previous version of
littlefs in CI as a separate set of symbols, "lfsp_" in this case.

So that we can at least test the compatibility tests locally, I've added
an ifdef against the expected define "LFSP" to define a set of aliases
mapping "lfsp_" symbols to "lfs_" symbols. This is manual at the moment,
and a bit hacky, but gets the job done.

---

Also changed BUILDDIR creation to derive subdirectories from a few
Makefile variables. This makes the subdirectories less manual and more
flexible for things like LFSP. Note this wasn't possible until BUILDDIR
was changed to default to "." when omitted.
2023-04-21 00:28:55 -05:00
Christopher Haster
f0cc1db793 Tweaked changeprefix.py to not rename dir component in paths
This wasn't implemented correctly anyways, as it would need to recursively
rename directories that may not exist. Things would also get a bit
complicated if only some files in a directory were renamed.

Doable, but not needed for our use case.

For now just ignore any directory components. Though this may be worth
changing if the source directory structure becomes more complicated in
the future (maybe with a -r/--recursive flag?).
2023-04-19 18:33:47 -05:00
Christopher Haster
bf045dd13c Tweaked link to littlefs-disk-img-viewer to go to github repo 2023-04-19 11:48:06 -05:00
Christopher Haster
b33a5b3f85 Fixed issue where seeking to end-of-directory return LFS_ERR_INVAL
This was just an oversight. Seeking to the end of the directory should
not error, but instead restore the directory to the state where the next
read returns 0.

Note this matches the behavior of lfs_file_tell/lfs_file_seek.

Found by sosthene-nitrokey
2023-04-18 15:10:07 -05:00
Christopher Haster
384a498762 Extend dir seek tests to include seeking to end of directory 2023-04-18 14:55:43 -05:00
Christopher Haster
b0a4a44e5b Added explicit assert for minimum block size of 128 bytes
There was already an assert for this, but because it included the
underlying equation for the requirement it was too confusing for
users that had no prior knowledge for why the assert could trigger.

The math works out such that 128 bytes is a reasonable minimum
requirement, so I've added that number as an explicit assert.
Hopefully this makes this sort of situation easier to debug.

Note that this requirement would need to be increased to 512 bytes if
block addresses are ever increased to 64-bits. DESIGN.md goes into more
detail why this is.
2023-04-17 19:58:09 -05:00
Christopher Haster
aae897ffd0 Added an assert for truthy-preserving bool conversions
This has caught enough people that an explicit assert is warranted.
How littlefs, a c99 project, should be integrated with c89 projects
is still an open question, but no one deserves to debug this sort of
undetected casting issue.

Found by johnernberg and XinStellaris
2023-04-17 19:19:42 -05:00
Sosthène Guédon
24795e6b74 Add missing iterations in tests 2023-03-13 11:39:06 +01:00
Christopher Haster
1278ec1d08 Adopted Brent's algorithm for cycle detection
The previous cycle detection algorithm (a naive check against the largest
possible tail list) is simple and gets the job done, but has the potential to
take a very long time on disks with many blocks. Brent's algorithm, on
the other hand, takes at most 2x the number of blocks in the tail list.

Originally naive cycle detection was chosen over Floyd's algorithm to
avoid the extra complexity of managing two desynced traversals for every
traversal of the tail list, but Brent's algorithm is very well suited for our
use case, requiring only we keep track of an additional mdir pointer on the
stack as we traverse.
2022-12-17 12:41:39 -06:00
yomimono
d9333ecbd4 Add "chamelon" to the related projects section.
"chamelon" implements a subset of littlefs (no global move state or
singly-linked list threaded through the directory tree) for use in the
MirageOS library operating system project. It is written entirely in
OCaml and is interoperable (with the above caveats) with the reference
implementation via FUSE.
2022-08-02 11:53:22 -05:00
Tobias Nießen
3ae87f4e29 Add littlefs-disk-img-viewer to README 2021-06-21 18:52:00 +02:00
9 changed files with 1767 additions and 64 deletions

View File

@@ -473,6 +473,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

View File

@@ -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 $@

View File

@@ -227,6 +227,13 @@ License Identifiers that are here available: http://spdx.org/licenses/
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,12 @@ 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.
- [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
@@ -256,3 +267,5 @@ License Identifiers that are here available: http://spdx.org/licenses/
[SPIFFS]: https://github.com/pellepl/spiffs [SPIFFS]: https://github.com/pellepl/spiffs
[Dhara]: https://github.com/dlbeer/dhara [Dhara]: https://github.com/dlbeer/dhara
[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
View File

@@ -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`.
---

178
lfs.c
View File

@@ -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,12 +409,16 @@ 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) {
return lfs_tag_type1(a->tag); return lfs_tag_type1(a->tag);
} }
static inline bool lfs_gstate_needssuperblock(const lfs_gstate_t *a) {
return lfs_tag_size(a->tag) >> 9;
}
#endif #endif
static inline bool lfs_gstate_hasmovehere(const lfs_gstate_t *a, static inline bool lfs_gstate_hasmovehere(const lfs_gstate_t *a,
@@ -533,6 +535,7 @@ static int lfs_file_outline(lfs_t *lfs, lfs_file_t *file);
static int lfs_file_flush(lfs_t *lfs, lfs_file_t *file); static int lfs_file_flush(lfs_t *lfs, lfs_file_t *file);
static int lfs_fs_deorphan(lfs_t *lfs, bool powerloss); static int lfs_fs_deorphan(lfs_t *lfs, bool powerloss);
static void lfs_fs_prepsuperblock(lfs_t *lfs, bool needssuperblock);
static int lfs_fs_preporphans(lfs_t *lfs, int8_t orphans); static int lfs_fs_preporphans(lfs_t *lfs, int8_t orphans);
static void lfs_fs_prepmove(lfs_t *lfs, static void lfs_fs_prepmove(lfs_t *lfs,
uint16_t id, const lfs_block_t pair[2]); uint16_t id, const lfs_block_t pair[2]);
@@ -2709,11 +2712,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 +2724,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;
@@ -4019,6 +4022,12 @@ static int lfs_init(lfs_t *lfs, const struct lfs_config *cfg) {
lfs->cfg = cfg; lfs->cfg = cfg;
int err = 0; int err = 0;
// 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 +4040,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);
@@ -4215,14 +4227,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,
@@ -4258,12 +4279,29 @@ static int lfs_rawmount(lfs_t *lfs, const struct lfs_config *cfg) {
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_DISK_VERSION_MAJOR ||
minor_version > LFS_DISK_VERSION_MINOR)) { minor_version > LFS_DISK_VERSION_MINOR)) {
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_DISK_VERSION_MAJOR, LFS_DISK_VERSION_MINOR);
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_DISK_VERSION_MINOR) {
LFS_DEBUG("Found older minor version "
"v%"PRIu16".%"PRIu16" < v%"PRIu16".%"PRIu16,
major_version, minor_version,
LFS_DISK_VERSION_MAJOR, LFS_DISK_VERSION_MINOR);
#ifndef LFS_READONLY
// note this bit is reserved on disk, so fetching more gstate
// will not interfere here
lfs_fs_prepsuperblock(lfs, true);
#endif
}
// 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) {
@@ -4373,13 +4411,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 +4505,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 +4570,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 +4602,17 @@ static lfs_stag_t lfs_fs_parent(lfs_t *lfs, const lfs_block_t pair[2],
} }
#endif #endif
#ifndef LFS_READONLY
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;
}
#endif
#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 +4631,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_DISK_VERSION,
.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)) {
@@ -4643,7 +4754,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"} "
@@ -4736,7 +4847,12 @@ static int lfs_fs_deorphan(lfs_t *lfs, bool powerloss) {
#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;
} }

4
lfs.h
View File

@@ -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 0x00020006
#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))

View File

@@ -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,

1360
tests/test_compat.toml Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -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;
''' '''