Compare commits

..

20 Commits

Author SHA1 Message Date
Christopher Haster
999ef6656f paths: Changed CREAT with a trailing slash to return NOTDIR
- before: lfs_file_open("missing/") => LFS_ERR_ISDIR
- after:  lfs_file_open("missing/") => LFS_ERR_NOTDIR

As noted by bmcdonnell-fb, returning LFS_ERR_ISDIR here was inconsistent
with the case where the file exists:

  case                           before          after
  lfs_file_open("dir_a")      => LFS_ERR_ISDIR   LFS_ERR_ISDIR
  lfs_file_open("dir_a/")     => LFS_ERR_ISDIR   LFS_ERR_ISDIR
  lfs_file_open("reg_a/")     => LFS_ERR_NOTDIR  LFS_ERR_NOTDIR
  lfs_file_open("missing_a/") => LFS_ERR_ISDIR   LFS_ERR_NOTDIR

Note this is consistent with the behavior of lfs_stat:

  lfs_file_open("reg_a/") => LFS_ERR_NOTDIR
  lfs_stat("reg_a/")      => LFS_ERR_NOTDIR

And the only other function that can "create" files, lfs_rename:

  lfs_file_open("missing_a/")       => LFS_ERR_NOTDIR
  lfs_rename("reg_a", "missing_a/") => LFS_ERR_NOTDIR

There is some ongoing discussion about if these should return NOTDIR,
ISDIR, or INVAL, but this is at least an improvement over the
rename/open mismatch.
2024-11-25 15:40:44 -06:00
Christopher Haster
b735c8fd7f paths: Added tests over NOENT + trailing slash/dot
- test_paths_noent_trailing_slashes
- test_paths_noent_trailing_dots
- test_paths_noent_trailing_dotdots

These managed to slip through our path testing but should be tested, if
anything just to know exactly what errors these return.
2024-11-25 15:40:15 -06:00
Christopher Haster
30947054d4 paths: Extended tests to cover open with CREAT/EXCL
These flags change the behavior of open quite significantly. It's useful
to cover these in our path tests so the behavior is locked down.
2024-11-25 15:40:15 -06:00
Christopher Haster
80ca1ea300 paths: Reject empty paths
Before this, the empty path ("") was treated as an alias for the root.
This was unintentional and just a side-effect of how the path parser
worked.

Now, the empty path should always result in LFS_ERR_INVAL:

- before: lfs_stat("") => 0
- after:  lfs_stat("") => LFS_ERR_INVAL
2024-11-25 15:40:15 -06:00
Christopher Haster
815f0d85a5 paths: Fixed dots followed by dotdots
Unlike normal files, dots (".") should not change the depth when
attempting to skip dotdot ("..") entries.

A weird nuance in the path parser, but at least it had a relatively easy
fix.

Added test_paths_dot_dotdots to prevent a regression.
2024-11-25 15:40:15 -06:00
Christopher Haster
dc92dec6d3 paths: Reject dotdots above root
This changes the behavior of paths that attempt to navigate above root
to now return LFS_ERR_INVAL:

- before: lfs_stat("/../a") => 0
- after:  lfs_stat("/../a") => LFS_ERR_INVAL

This is a bit of an opinionated change while making other path
resolution tweaks.

In terms of POSIX-compatibility, it's a bit unclear exactly what dotdots
above the root should do.

POSIX notes:

> As a special case, in the root directory, dot-dot may refer to the
> root directory itself.

But the word choice of "may" implies it is up to the implementation.

I originally implement this as a root-loop simply because that is what
my Linux machine does, but I now think that's not the best option. Since
we're making other path-related tweaks, we might as well try to adopt
behavior that is, in my opinion, safer and less... weird...

This should also help make paths more consistent with future theoretical
openat-list APIs, where saturating at the current directory is sort of
the least expected behavior.
2024-11-25 15:40:07 -06:00
Christopher Haster
a6035071be paths: Fixed/doc trailing slash/dot POSIX incompatibilities
- lfs_mkdir now accepts trailing slashes:
  - before: lfs_mkdir("a/") => LFS_ERR_NOENT
  - after:  lfs_mkdir("a/") => 0

- lfs_stat, lfs_getattr, etc, now reject trailing slashes if the file is
  not a directory:
  - before: lfs_stat("reg_a/") => 0
  - after:  lfs_stat("reg_a/") => LFS_ERR_NOTDIR

  Note trailing slashes are accepted if the file is a directory:
  - before: lfs_stat("dir_a/") => 0
  - after:  lfs_stat("dir_a/") => 0

- lfs_file_open now returns LFS_ERR_NOTDIR if the file exists but the
  path contains trailing slashes:
  - before: lfs_file_open("reg_a/") => LFS_ERR_NOENT
  - after:  lfs_file_open("reg_a/") => LFS_ERR_NOTDIR

To make these work, the internal lfs_dir_find API required some
interesting changes:

- lfs_dir_find no longer sets id=0x3ff on not finding a parent entry in
  the path. Instead, lfs_path_islast can be used to determine if the
  modified path references a parent entry or child entry based on the
  remainder of the path string.

  Note this is only necessary for functions that create new entries
  (lfs_mkdir, lfs_rename, lfs_file_open).

- Trailing slashes mean we can no longer rely on the modified path being
  NULL-terminated. lfs_path_namelen provides an alternative to strlen
  that stops at slash or NULL.

- lfs_path_isdir also tells you if the modified path must reference a
  dir (contains trailing slashes). I considered handling this entirely
  in lfs_dir_find, but the behavior of entry-creating functions is too
  nuanced.

  At least lfs_dir_find returns LFS_ERR_NOTDIR if the file exists on
  disk.

Like strlen, lfs_path_namelen/islast/isdir are all O(n) where n is the
name length. This isn't great, but if you're using filenames large
enough for this to actually matter... uh... open an issue on GitHub and
we might improve this in the future.

---

There are a couple POSIX incompatibilities that I think are not
worth fixing:

- Root modifications return EINVAL instead of EBUSY:
  - littlefs: remove("/") => EINVAL
  - POSIX:    remove("/") => EBUSY
  Reason: This would be the only use of EBUSY in the system.

- We accept modifications of directories with trailing dots:
  - littlefs: remove("a/.") => 0
  - POSIX:    remove("a/.") => EBUSY
  Reason: Not worth implementing.

- We do not check for existence of directories followed by dotdots:
  - littlefs: stat("a/missing/..") => 0
  - POSIX:    stat("a/missing/..") => ENOENT
  Reason: Difficult to implement non-recursively.

- We accept modifications of directories with trailing dotdots:
  - littlefs: rename("a/b/..", "c") => 0
  - POSIX:    rename("a/b/..", "c") => EBUSY
  Reason: Not worth implementing.

These are at least now documented in tests/test_paths.toml, which isn't
the greatest location, but it's at least something until a better
document is created.

Note that these don't really belong in SPEC.md because path parsing is
a function of the driver and has no impact on disk.
2024-11-25 15:39:29 -06:00
Christopher Haster
232e736aae paths: Added trailing slashes and dots tests
As expected these are failing and will need some work to pass.

The issue with lfs_file_open allowing trailing slashes was found by
rob-zeno, and the issue with lfs_mkdir disallowing trailing slashes was
found by XinStellaris, PoppaChubby, pavel-kirienko, inf265, Xywzel,
steverpalmer, and likely others.
2024-11-23 19:03:36 -06:00
Christopher Haster
0de0389c6f paths: Reworked test_paths to cover more corner cases
This should be a superset of the previous test_paths test suite, while
covering a couple more things (more APIs, more path synonyms, utf8,
non-printable ascii, non-utf8, etc).

Not yet tested are some corner cases with known bugs, mainly around
trailing slashes.
2024-11-23 18:20:06 -06:00
Christopher Haster
b78afe2518 Merge pull request #1026 from yamt/update-gh-actions
Update github actions to the latest versions
2024-09-24 12:25:04 -05:00
Christopher Haster
798073c2a7 gha: Dropped minor/patch version pinning of actions
With GitHub forcibly deprecating old versions of actions, pinning the
minor/patch version is more likely to cause breakage than not.
2024-09-20 16:05:15 -05:00
Christopher Haster
7db9e1663a gha: Switched to standard da for cross-workflow downloads
Looks like cross-workflow downloads has finally been added to the
standard download-artifact action, so we might as well switch to it to
reduce dependencies.

dawidd6's version was also missing the merge-multiple feature which is
necessary to work around breaking changes in download-artifact's v4
bump.

Weirdly it needs GITHUB_TOKEN for some reason? Not sure why this
couldn't be implicit.
2024-09-20 16:05:12 -05:00
Christopher Haster
2c4b262c35 gha: Merge artifacts on download
Turns out major versions break things.

Old behavior: Artifacts with same name are merged
New behavior: Artifacts with same name error

Using a pattern and merging on download should fix this at least on the
job-side. Though I do wonder if we'll start running into artifact limit
issues with the new way artifacts are handled...
2024-09-20 16:04:35 -05:00
YAMAMOTO Takashi
72a4b57f4e gha: Make the artifact names unique 2024-09-19 17:26:49 -05:00
YAMAMOTO Takashi
6e7269890a gha: Update github actions to the latest versions 2024-09-19 17:18:15 -05:00
Christopher Haster
d01280e649 Merge pull request #968 from littlefs-project/link-pico-littlefs-usb
Add links to pico-littlefs-usb (FAT12 emulation) and mklittlefs
2024-04-29 16:21:49 -05:00
Christopher Haster
6e52140d51 Merge pull request #959 from littlefs-project/fix-expanded-magic
Duplicate the superblock entry during superblock expansion, fix missing magic
2024-04-29 14:26:38 -05:00
Christopher Haster
11b036cc6c Prevented unnecessary superblock rewrites if old version in superblock chain
Because multiple, out-of-date superblocks can exist in our superblock
chain, we need to be careful to make sure newer superblock entries
override older superblock entries.

If we see an older on-disk minor version in the superblock chain, we
were correctly overriding the on-disk minor version, but we were also
leaving the "needs superblock" bit set in our consistency state.

This isn't a hard-error, but would lead to a superblock rewrite every
mount. The rewrite would make no progress, as the out-of-date version is
effectively immutable at this point, and just waste prog cycles.

This should fix that by clearing the "needs superblock" bit if we see a
newer on-disk minor version.
2024-03-19 00:49:28 -05:00
Christopher Haster
25ee90fdf1 Clarified what is accessible at specific superblock offsets in SPEC.md
It used to be the case that the entire superblock entry could be found
at specific offsets, but this was only possible while the superblock
entry was immutable. Now that the superblock entry is very mutable
(block-count changes, lfs2.0 -> lfs2.1 version bumps, etc), the correct
superblock entry may end up later in the metadata log.

At the very least, the "littlefs" magic string is still immutable and at
the specific offset offset=8. This is arguably the most useful
fixed-offset item.
2024-03-19 00:49:28 -05:00
Christopher Haster
a60a986c9c Duplicate the superblock entry during superblock expansion
The documentation does not match the implementation here. The intended
behavior of superblock expansion was to duplicate the current superblock
entry into the new superblock:

   .--------.  .--------.
  .|littlefs|->|littlefs|
  ||bs=4096 | ||bs=4096 |
  ||bc=256  | ||bc=256  |
  ||crc32   | ||root dir|
  ||        | ||crc32   |
  |'--------' |'--------'
  '--------'  '--------'

The main benefit is that we can rely on the magic string "littlefs"
always residing in blocks 0x{0,1}, even if the superblock chain has
multiple superblocks.

The downside is that earlier superblocks in the superblock chain may
contain out-of-date configuration. This is a bit annoying, and risks
hard-to-reach bugs, but in theory shouldn't break anything as long as
the filesystem is aware of this.

Unfortunately this was lost at some point during refactoring in the
early v2-alpha work. A lot of code was moving around in this stage, so
it's a bit hard to track down the change and if it was intentional. The
result is superblock expansion creates a valid linked-list of
superblocks, but only the last superblock contains a valid superblock
entry:

   .--------.  .--------.
  .|crc32   |->|littlefs|
  ||        | ||bs=4096 |
  ||        | ||bc=256  |
  ||        | ||root dir|
  ||        | ||crc32   |
  |'--------' |'--------'
  '--------'  '--------'

What's interesting is this isn't invalid as far as lfs_mount is
concerned. lfs_mount is happy as long as a superblock entry exists
anywhere in the superblock chain. This is good for compat flexibility,
but is the main reason this has gone unnoticed for so long.

---

With the benefit of more time to think about the problem, it may have
been more preferable to copy only the "littlefs" magic string and NOT
the superblock entry:

   .--------.  .--------.
  .|littlefs|->|littlefs|
  ||crc32c  | ||bs=4096 |
  ||        | ||bc=256  |
  ||        | ||root dir|
  ||        | ||crc32   |
  |'--------' |'--------'
  '--------'  '--------'

This would allow for simple "littlefs" magic string checks without the
risks associated with out-of-date superblock entries.

Unfortunately the current implementation errors if it finds a "littlefs"
magic string without an associated superblock entry, so such a change
would not be compatible with old drivers.

---

This commit tweaks superblock expansion to duplicate the superblock
entry instead of simply moving it to the new superblock. And adds tests
over the magic string "littlefs" both before and after superblock
expansion.

Found by rojer and Nikola Kosturski
2024-03-19 00:48:56 -05:00
7 changed files with 7455 additions and 278 deletions

View File

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

View File

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

View File

@@ -21,7 +21,7 @@ jobs:
arch: [x86_64, thumb, mips, powerpc]
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: install
run: |
# need a few things
@@ -235,9 +235,9 @@ jobs:
# create size statuses
- name: upload-sizes
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v4
with:
name: sizes
name: sizes-${{matrix.arch}}
path: sizes
- name: status-sizes
run: |
@@ -273,16 +273,17 @@ jobs:
}' | tee status/$(basename $f .csv).json
done
- name: upload-status-sizes
uses: actions/upload-artifact@v2
if: ${{matrix.arch == 'x86_64'}}
uses: actions/upload-artifact@v4
with:
name: status
name: status-sizes-${{matrix.arch}}
path: status
retention-days: 1
# create cov statuses
- name: upload-cov
if: ${{matrix.arch == 'x86_64'}}
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v4
with:
name: cov
path: cov
@@ -317,11 +318,11 @@ jobs:
target_step: env.STEP,
}' | tee status/$(basename $f .csv)-$s.json
done
- name: upload-status-sizes
- name: upload-status-cov
if: ${{matrix.arch == 'x86_64'}}
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v4
with:
name: status
name: status-cov
path: status
retention-days: 1
@@ -336,7 +337,7 @@ jobs:
pls: [1, 2]
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: install
run: |
# need a few things
@@ -361,7 +362,7 @@ jobs:
test-no-intrinsics:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: install
run: |
# need a few things
@@ -378,7 +379,7 @@ jobs:
test-multiversion:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: install
run: |
# need a few things
@@ -395,7 +396,7 @@ jobs:
test-lfs2_0:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: install
run: |
# need a few things
@@ -414,7 +415,7 @@ jobs:
test-valgrind:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: install
run: |
# need a few things
@@ -436,7 +437,7 @@ jobs:
test-clang:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: install
run: |
# need a few things
@@ -459,7 +460,7 @@ jobs:
bench:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: install
run: |
# need a few things
@@ -491,7 +492,7 @@ jobs:
# create bench statuses
- name: upload-bench
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v4
with:
name: bench
path: bench
@@ -525,9 +526,9 @@ jobs:
}' | tee status/$(basename $f .csv)-$s.json
done
- name: upload-status-bench
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v4
with:
name: status
name: status-bench
path: status
retention-days: 1
@@ -535,10 +536,10 @@ jobs:
test-compat:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
if: ${{github.event_name == 'pull_request'}}
# checkout the current pr target into lfsp
- uses: actions/checkout@v2
- uses: actions/checkout@v4
if: ${{github.event_name == 'pull_request'}}
with:
ref: ${{github.event.pull_request.base.ref}}
@@ -572,7 +573,7 @@ jobs:
runs-on: ubuntu-latest
if: ${{!endsWith(github.ref, '-prefix')}}
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: install
run: |
# need a few things
@@ -582,7 +583,7 @@ jobs:
gcc --version
python3 --version
fusermount -V
- uses: actions/checkout@v2
- uses: actions/checkout@v4
with:
repository: littlefs-project/littlefs-fuse
ref: v2
@@ -622,7 +623,7 @@ jobs:
runs-on: ubuntu-latest
if: ${{!endsWith(github.ref, '-prefix')}}
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: install
run: |
# need a few things
@@ -632,12 +633,12 @@ jobs:
gcc --version
python3 --version
fusermount -V
- uses: actions/checkout@v2
- uses: actions/checkout@v4
with:
repository: littlefs-project/littlefs-fuse
ref: v2
path: v2
- uses: actions/checkout@v2
- uses: actions/checkout@v4
with:
repository: littlefs-project/littlefs-fuse
ref: v1
@@ -694,7 +695,7 @@ jobs:
runs-on: ubuntu-latest
needs: [test, bench]
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
if: ${{github.event_name == 'pull_request'}}
- name: install
if: ${{github.event_name == 'pull_request'}}
@@ -704,23 +705,26 @@ jobs:
pip3 install toml
gcc --version
python3 --version
- uses: actions/download-artifact@v2
- uses: actions/download-artifact@v4
if: ${{github.event_name == 'pull_request'}}
continue-on-error: true
with:
name: sizes
pattern: '{sizes,sizes-*}'
merge-multiple: true
path: sizes
- uses: actions/download-artifact@v2
- uses: actions/download-artifact@v4
if: ${{github.event_name == 'pull_request'}}
continue-on-error: true
with:
name: cov
pattern: '{cov,cov-*}'
merge-multiple: true
path: cov
- uses: actions/download-artifact@v2
- uses: actions/download-artifact@v4
if: ${{github.event_name == 'pull_request'}}
continue-on-error: true
with:
name: bench
pattern: '{bench,bench-*}'
merge-multiple: true
path: bench
# try to find results from tests
@@ -862,7 +866,7 @@ jobs:
body: $comment,
}' | tee comment/comment.json
- name: upload-comment
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v4
with:
name: comment
path: comment

View File

@@ -441,9 +441,10 @@ Superblock fields:
7. **Attr max (32-bits)** - Maximum size of file attributes in bytes.
The superblock must always be the first entry (id 0) in a metadata pair as well
as be the first entry written to the block. This means that the superblock
entry can be read from a device using offsets alone.
The superblock must always be the first entry (id 0) in the metadata pair, and
the name tag must always be the first tag in the metadata pair. This makes it
so that the magic string "littlefs" will always reside at offset=8 in a valid
littlefs superblock.
---
#### `0x2xx` LFS_TYPE_STRUCT

104
lfs.c
View File

@@ -282,6 +282,21 @@ static int lfs_bd_erase(lfs_t *lfs, lfs_block_t block) {
/// Small type-level utilities ///
// some operations on paths
static inline lfs_size_t lfs_path_namelen(const char *path) {
return strcspn(path, "/");
}
static inline bool lfs_path_islast(const char *path) {
lfs_size_t namelen = lfs_path_namelen(path);
return path[namelen + strspn(path + namelen, "/")] == '\0';
}
static inline bool lfs_path_isdir(const char *path) {
return path[lfs_path_namelen(path)] != '\0';
}
// operations on block pairs
static inline void lfs_pair_swap(lfs_block_t pair[2]) {
lfs_block_t t = pair[0];
@@ -1461,32 +1476,46 @@ static int lfs_dir_find_match(void *data,
return LFS_CMP_EQ;
}
// lfs_dir_find tries to set path and id even if file is not found
//
// returns:
// - 0 if file is found
// - LFS_ERR_NOENT if file or parent is not found
// - LFS_ERR_NOTDIR if parent is not a dir
static lfs_stag_t lfs_dir_find(lfs_t *lfs, lfs_mdir_t *dir,
const char **path, uint16_t *id) {
// we reduce path to a single name if we can find it
const char *name = *path;
if (id) {
*id = 0x3ff;
}
// default to root dir
lfs_stag_t tag = LFS_MKTAG(LFS_TYPE_DIR, 0x3ff, 0);
dir->tail[0] = lfs->root[0];
dir->tail[1] = lfs->root[1];
// empty paths are not allowed
if (*name == '\0') {
return LFS_ERR_INVAL;
}
while (true) {
nextname:
// skip slashes
name += strspn(name, "/");
// skip slashes if we're a directory
if (lfs_tag_type3(tag) == LFS_TYPE_DIR) {
name += strspn(name, "/");
}
lfs_size_t namelen = strcspn(name, "/");
// skip '.' and root '..'
if ((namelen == 1 && memcmp(name, ".", 1) == 0) ||
(namelen == 2 && memcmp(name, "..", 2) == 0)) {
// skip '.'
if (namelen == 1 && memcmp(name, ".", 1) == 0) {
name += namelen;
goto nextname;
}
// error on unmatched '..', trying to go above root?
if (namelen == 2 && memcmp(name, "..", 2) == 0) {
return LFS_ERR_INVAL;
}
// skip if matched by '..' in name
const char *suffix = name + namelen;
lfs_size_t sufflen;
@@ -1498,7 +1527,9 @@ nextname:
break;
}
if (sufflen == 2 && memcmp(suffix, "..", 2) == 0) {
if (sufflen == 1 && memcmp(suffix, ".", 1) == 0) {
// noop
} else if (sufflen == 2 && memcmp(suffix, "..", 2) == 0) {
depth -= 1;
if (depth == 0) {
name = suffix + sufflen;
@@ -1512,14 +1543,14 @@ nextname:
}
// found path
if (name[0] == '\0') {
if (*name == '\0') {
return tag;
}
// update what we've found so far
*path = name;
// only continue if we hit a directory
// only continue if we're a directory
if (lfs_tag_type3(tag) != LFS_TYPE_DIR) {
return LFS_ERR_NOTDIR;
}
@@ -1539,8 +1570,7 @@ nextname:
tag = lfs_dir_fetchmatch(lfs, dir, dir->tail,
LFS_MKTAG(0x780, 0, 0),
LFS_MKTAG(LFS_TYPE_NAME, 0, namelen),
// are we last name?
(strchr(name, '/') == NULL) ? id : NULL,
id,
lfs_dir_find_match, &(struct lfs_dir_find_match){
lfs, name, namelen});
if (tag < 0) {
@@ -2191,7 +2221,8 @@ static int lfs_dir_splittingcompact(lfs_t *lfs, lfs_mdir_t *dir,
// we can do, we'll error later if we've become frozen
LFS_WARN("Unable to expand superblock");
} else {
end = begin;
// duplicate the superblock entry into the new superblock
end = 1;
}
}
}
@@ -2358,7 +2389,9 @@ fixmlist:;
while (d->id >= d->m.count && d->m.split) {
// we split and id is on tail now
d->id -= d->m.count;
if (lfs_pair_cmp(d->m.tail, lfs->root) != 0) {
d->id -= d->m.count;
}
int err = lfs_dir_fetch(lfs, &d->m, d->m.tail);
if (err) {
return err;
@@ -2600,12 +2633,12 @@ static int lfs_mkdir_(lfs_t *lfs, const char *path) {
cwd.next = lfs->mlist;
uint16_t id;
err = lfs_dir_find(lfs, &cwd.m, &path, &id);
if (!(err == LFS_ERR_NOENT && id != 0x3ff)) {
if (!(err == LFS_ERR_NOENT && lfs_path_islast(path))) {
return (err < 0) ? err : LFS_ERR_EXIST;
}
// check that name fits
lfs_size_t nlen = strlen(path);
lfs_size_t nlen = lfs_path_namelen(path);
if (nlen > lfs->name_max) {
return LFS_ERR_NAMETOOLONG;
}
@@ -3054,7 +3087,7 @@ static int lfs_file_opencfg_(lfs_t *lfs, lfs_file_t *file,
// allocate entry for file if it doesn't exist
lfs_stag_t tag = lfs_dir_find(lfs, &file->m, &path, &file->id);
if (tag < 0 && !(tag == LFS_ERR_NOENT && file->id != 0x3ff)) {
if (tag < 0 && !(tag == LFS_ERR_NOENT && lfs_path_islast(path))) {
err = tag;
goto cleanup;
}
@@ -3074,8 +3107,14 @@ static int lfs_file_opencfg_(lfs_t *lfs, lfs_file_t *file,
goto cleanup;
}
// don't allow trailing slashes
if (lfs_path_isdir(path)) {
err = LFS_ERR_NOTDIR;
goto cleanup;
}
// check that name fits
lfs_size_t nlen = strlen(path);
lfs_size_t nlen = lfs_path_namelen(path);
if (nlen > lfs->name_max) {
err = LFS_ERR_NAMETOOLONG;
goto cleanup;
@@ -3839,6 +3878,12 @@ static int lfs_stat_(lfs_t *lfs, const char *path, struct lfs_info *info) {
return (int)tag;
}
// only allow trailing slashes on dirs
if (strchr(path, '/') != NULL
&& lfs_tag_type3(tag) != LFS_TYPE_DIR) {
return LFS_ERR_NOTDIR;
}
return lfs_dir_getinfo(lfs, &cwd, lfs_tag_id(tag), info);
}
@@ -3941,7 +3986,7 @@ static int lfs_rename_(lfs_t *lfs, const char *oldpath, const char *newpath) {
uint16_t newid;
lfs_stag_t prevtag = lfs_dir_find(lfs, &newcwd, &newpath, &newid);
if ((prevtag < 0 || lfs_tag_id(prevtag) == 0x3ff) &&
!(prevtag == LFS_ERR_NOENT && newid != 0x3ff)) {
!(prevtag == LFS_ERR_NOENT && lfs_path_islast(newpath))) {
return (prevtag < 0) ? (int)prevtag : LFS_ERR_INVAL;
}
@@ -3952,8 +3997,14 @@ static int lfs_rename_(lfs_t *lfs, const char *oldpath, const char *newpath) {
struct lfs_mlist prevdir;
prevdir.next = lfs->mlist;
if (prevtag == LFS_ERR_NOENT) {
// if we're a file, don't allow trailing slashes
if (lfs_path_isdir(newpath)
&& lfs_tag_type3(oldtag) != LFS_TYPE_DIR) {
return LFS_ERR_NOTDIR;
}
// check that name fits
lfs_size_t nlen = strlen(newpath);
lfs_size_t nlen = lfs_path_namelen(newpath);
if (nlen > lfs->name_max) {
return LFS_ERR_NAMETOOLONG;
}
@@ -4013,7 +4064,8 @@ static int lfs_rename_(lfs_t *lfs, const char *oldpath, const char *newpath) {
{LFS_MKTAG_IF(prevtag != LFS_ERR_NOENT,
LFS_TYPE_DELETE, newid, 0), NULL},
{LFS_MKTAG(LFS_TYPE_CREATE, newid, 0), NULL},
{LFS_MKTAG(lfs_tag_type3(oldtag), newid, strlen(newpath)), newpath},
{LFS_MKTAG(lfs_tag_type3(oldtag),
newid, lfs_path_namelen(newpath)), newpath},
{LFS_MKTAG(LFS_FROM_MOVE, newid, lfs_tag_id(oldtag)), &oldcwd},
{LFS_MKTAG_IF(samepair,
LFS_TYPE_DELETE, newoldid, 0), NULL}));
@@ -4466,6 +4518,7 @@ static int lfs_mount_(lfs_t *lfs, const struct lfs_config *cfg) {
// 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
bool needssuperblock = false;
if (minor_version < lfs_fs_disk_version_minor(lfs)) {
LFS_DEBUG("Found older minor version "
"v%"PRIu16".%"PRIu16" < v%"PRIu16".%"PRIu16,
@@ -4473,10 +4526,11 @@ static int lfs_mount_(lfs_t *lfs, const struct lfs_config *cfg) {
minor_version,
lfs_fs_disk_version_major(lfs),
lfs_fs_disk_version_minor(lfs));
// note this bit is reserved on disk, so fetching more gstate
// will not interfere here
lfs_fs_prepsuperblock(lfs, true);
needssuperblock = true;
}
// note this bit is reserved on disk, so fetching more gstate
// will not interfere here
lfs_fs_prepsuperblock(lfs, needssuperblock);
// check superblock configuration
if (superblock.name_max) {

File diff suppressed because it is too large Load Diff

View File

@@ -14,6 +14,24 @@ code = '''
lfs_unmount(&lfs) => 0;
'''
# make sure the magic string "littlefs" is always at offset=8
[cases.test_superblocks_magic]
code = '''
lfs_t lfs;
lfs_format(&lfs, cfg) => 0;
// check our magic string
//
// note if we lose power we may not have the magic string in both blocks!
// but we don't lose power in this test so we can assert the magic string
// is present in both
uint8_t magic[lfs_max(16, READ_SIZE)];
cfg->read(cfg, 0, 0, magic, lfs_max(16, READ_SIZE)) => 0;
assert(memcmp(&magic[8], "littlefs", 8) == 0);
cfg->read(cfg, 1, 0, magic, lfs_max(16, READ_SIZE)) => 0;
assert(memcmp(&magic[8], "littlefs", 8) == 0);
'''
# mount/unmount from interpretting a previous superblock block_count
[cases.test_superblocks_mount_unknown_block_count]
code = '''
@@ -28,7 +46,6 @@ code = '''
lfs_unmount(&lfs) => 0;
'''
# reentrant format
[cases.test_superblocks_reentrant_format]
reentrant = true
@@ -135,6 +152,39 @@ code = '''
lfs_unmount(&lfs) => 0;
'''
# make sure the magic string "littlefs" is always at offset=8
[cases.test_superblocks_magic_expand]
defines.BLOCK_CYCLES = [32, 33, 1]
defines.N = [10, 100, 1000]
code = '''
lfs_t lfs;
lfs_format(&lfs, cfg) => 0;
lfs_mount(&lfs, cfg) => 0;
for (int i = 0; i < N; i++) {
lfs_file_t file;
lfs_file_open(&lfs, &file, "dummy",
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
lfs_file_close(&lfs, &file) => 0;
struct lfs_info info;
lfs_stat(&lfs, "dummy", &info) => 0;
assert(strcmp(info.name, "dummy") == 0);
assert(info.type == LFS_TYPE_REG);
lfs_remove(&lfs, "dummy") => 0;
}
lfs_unmount(&lfs) => 0;
// check our magic string
//
// note if we lose power we may not have the magic string in both blocks!
// but we don't lose power in this test so we can assert the magic string
// is present in both
uint8_t magic[lfs_max(16, READ_SIZE)];
cfg->read(cfg, 0, 0, magic, lfs_max(16, READ_SIZE)) => 0;
assert(memcmp(&magic[8], "littlefs", 8) == 0);
cfg->read(cfg, 1, 0, magic, lfs_max(16, READ_SIZE)) => 0;
assert(memcmp(&magic[8], "littlefs", 8) == 0);
'''
# expanding superblock with power cycle
[cases.test_superblocks_expand_power_cycle]
defines.BLOCK_CYCLES = [32, 33, 1]
@@ -221,6 +271,7 @@ code = '''
lfs_unmount(&lfs) => 0;
'''
# mount with unknown block_count
[cases.test_superblocks_unknown_blocks]
code = '''