Compare commits

...

33 Commits

Author SHA1 Message Date
Christopher Haster
0494ce7169 Merge pull request #1058 from littlefs-project/fix-seek-eob-cache
Fixed incorrect cache reuse when seeking from end-of-block
2024-12-20 09:02:13 -06:00
Christopher Haster
366100b140 Fixed incorrect cache reuse when seeking from end-of-block
In v2.5, we introduced an optimization to avoid rereading data when
seeking inside the file cache. Unfortunately this used a slightly
wrong condition to check if the cache was "live", which meant seeks from
end-of-blocks could end up with invalid caches and wrong data. Not
great.

The problem is the nuance of when a file's cache is "live":

1. The file is marked as LFS_F_READING or LFS_F_WRITING.

   But we can't reuse the cache when writing, so we only care about
   LFS_F_READING.

2. file->off != lfs->cfg->block_size (end-of-block).

   This is an optimization to avoid eagerly reading blocks we may not
   actually care about.

We weren't checking for the end-of-block case, which meant if you seeked
_from_ the end of a block to a seemingly valid location in the file
cache, you could end up with an invalid cache.

Note that end-of-block may not be powers-of-two due to CTZ skip-list
pointers.

---

The fix is to check for the end-of-block case in lfs_file_seek. Note
this now matches the need-new-block logic in lfs_file_flushedread.

This logic change may also make lfs_file_seek call lfs_file_flush more
often, but only in cases where lfs_file_flush is a noop.

I've also extended the test_seek tests to cover a few more boundary-read
cases and prevent a regression in the future.

Found by wjl and lrodorigo
2024-12-19 02:39:10 -06:00
Christopher Haster
630a0d87c2 Merge pull request #1050 from littlefs-project/devel
Minor release: v2.10
2024-12-11 16:56:45 -06:00
Christopher Haster
3d0386489b Bumped minor version to v2.10 2024-12-11 16:23:10 -06:00
Christopher Haster
b8e4433b34 Merge pull request #1052 from wangdongustc/assert_null_sync
Assert on NULL IO functions
2024-12-10 11:48:48 -06:00
Dong Wang
dae656aa53 Fix prettyasserts.py for pointer asserts 2024-12-10 22:54:58 +08:00
Dong Wang
469c863c18 Assert on NULL IO function 2024-12-10 22:54:54 +08:00
Christopher Haster
215613e41f gha: Fixed x86-only statuses
Looks like I missed a line during refactoring, resulted in only x86
sizes being reported in GitHub statuses.

If we wanted to limited these to one architecture, thumb would have
probably been a better pick.
2024-12-09 14:56:12 -06:00
Christopher Haster
2fcecc8894 Merge pull request #1046 from littlefs-project/fix-trailing-slashes
paths: Revisit path parsing, fix trailing slash behavior
2024-12-06 13:48:26 -06:00
Christopher Haster
78f9a5fcd3 Merge pull request #1038 from littlefs-project/link-ramcrc32bd-ramrsbd
Add links to ramcrc32bd and ramrsbd
2024-12-06 13:47:47 -06:00
Christopher Haster
83fe41b605 Merge pull request #1031 from littlefs-project/fix-enospc-issues
Fix metadata_max==prog_size commit->end calculation
2024-12-06 13:47:36 -06:00
Christopher Haster
d7a911923b Merge pull request #1027 from littlefs-project/fix-seek-overflow-ub
Fix seek undefined behavior on signed integer overflow
2024-12-06 13:47:20 -06:00
Christopher Haster
2ba4280a5e Merge pull request #997 from littlefs-project/fix-trace-format-again
Fix some more LFS_TRACE format specifiers
2024-12-06 13:47:06 -06:00
Christopher Haster
c961e1fe66 Merge pull request #1004 from yamt/user-define-header
Add an alternative way to override LFS_MALLOC etc
2024-12-06 13:45:56 -06:00
Christopher Haster
bd01a4c0ee Merge pull request #1013 from wdfk-prog/feature_2.9.3
Write the detect cycles function as a function to optimize code
2024-12-06 13:44:37 -06:00
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
ea431bd6ae Added some checks that metadata_max makes sense
Like the read/prog/block_size checks, these are just asserts. If these
invariants are broken the filesystem will break in surprising ways.
2024-10-04 13:45:57 -05:00
Christopher Haster
2d62d2f4c9 Fixed metadata_max==prog_size commit->end calculation
The inconsistency here between the use of block_size vs metadata_max was
suspicious. Turns out there's a bug when metadata_max == prog_size.

We correctly use metadata_max for the block_size/2 check, but we weren't
using it for the block_size-40 check. The second check seems unnecessary
after the first, but it protects against running out of space in a
commit for commit-related metadata (checksums, tail pointers, etc) when
we can't program half-blocks.

Turns out this is also needed when limiting metadata_max to a single
prog, otherwise we risk erroring with LFS_ERR_NOSPC early.

Found by ajheck, dpkristensen, NLLK, and likely others.
2024-10-04 13:45:43 -05:00
Christopher Haster
1f82c0f27f Added some metadata_max testing
- Added METADATA_MAX to test_runner.
- Added METADATA_MAX to bench_runner.
- Added a simple metadata_max test to test_superblocks, for lack of
  better location.

There have been several issues floating around related to metadata_max
and LFS_ERR_NOSPC which makes me think there's a bug in our metadata_max
logic.

metadata_max was a quick patch and is relatively untested, so an
undetected bug isn't too surprising. This commit adds at least some
testing over metadata_max.

Sure enough, the new test_superblocks_metadata_max test reveals a
curious LFS_ERR_NAMETOOLONG error that shouldn't be there.

More investigation needed.
2024-10-04 13:06:23 -05:00
wdfk-prog
a2c2e49e6b Write the detect cycles function as a function to optimize code 2024-10-04 10:37:25 +08:00
Christopher Haster
abaec45652 Fixed seek undefined behavior on signed integer overflow
In the previous implementation of lfs_file_seek, we calculated the new
offset using signed arithmetic before checking for possible
overflow/underflow conditions. This results in undefined behavior in C.

Fortunately for us, littlefs is now limited to 31-bit file sizes for API
reasons, so we don't have to be too clever here. Doing the arithmetic
with unsigned integers and just checking if we're in a valid range
afterwards should work.

Found by m-kostrzewa and lucic71
2024-09-24 14:01:20 -05:00
Christopher Haster
f1c430e779 Added some tests around seek integer overflow/underflow
Original tests provided by m-kostrzewa, these identify signed overflow
(undefined behavior) when compiled with -fsanitize=undefined.
2024-09-24 14:01:08 -05:00
YAMAMOTO Takashi
4a845be0be Rename LFS_USER_DEFINES to LFS_DEFINES 2024-09-24 12:29:13 -05:00
YAMAMOTO Takashi
e1636d05ab Add an alternative way to override LFS_MALLOC etc
With the existing method, (-DLFS_MALLOC=my_malloc)
users often had to use compiler options like -include, which
was not so portable.
This change introduces another way to provide partial overrides of
lfs_util.h using a user-provided header.
2024-09-24 12:29:13 -05:00
Christopher Haster
ac207586ba Fixed some more LFS_TRACE format specifiers
- block_cycles is signed and should use PRId32
- flags is signed (which is a bit weird) and should be cast for %x

Unfortunately exactly what PRI* expands to is dependant on both the
compiler and the underlying architecture, so I don't think it's possible
for us to catch these mistakes with CI...

Found by stefano-zanotti
2024-06-25 16:08:00 -05:00
12 changed files with 7772 additions and 315 deletions

View File

@@ -273,7 +273,6 @@ jobs:
}' | tee status/$(basename $f .csv).json
done
- name: upload-status-sizes
if: ${{matrix.arch == 'x86_64'}}
uses: actions/upload-artifact@v4
with:
name: status-sizes-${{matrix.arch}}

260
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) {
@@ -2128,13 +2158,14 @@ static int lfs_dir_splittingcompact(lfs_t *lfs, lfs_mdir_t *dir,
// And we cap at half a block to avoid degenerate cases with
// nearly-full metadata blocks.
//
lfs_size_t metadata_max = (lfs->cfg->metadata_max)
? lfs->cfg->metadata_max
: lfs->cfg->block_size;
if (end - split < 0xff
&& size <= lfs_min(
lfs->cfg->block_size - 40,
metadata_max - 40,
lfs_alignup(
(lfs->cfg->metadata_max
? lfs->cfg->metadata_max
: lfs->cfg->block_size)/2,
metadata_max/2,
lfs->cfg->prog_size))) {
break;
}
@@ -2603,12 +2634,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;
}
@@ -3057,7 +3088,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;
}
@@ -3077,8 +3108,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;
@@ -3664,22 +3701,16 @@ static lfs_ssize_t lfs_file_write_(lfs_t *lfs, lfs_file_t *file,
static lfs_soff_t lfs_file_seek_(lfs_t *lfs, lfs_file_t *file,
lfs_soff_t off, int whence) {
// find new pos
//
// fortunately for us, littlefs is limited to 31-bit file sizes, so we
// don't have to worry too much about integer overflow
lfs_off_t npos = file->pos;
if (whence == LFS_SEEK_SET) {
npos = off;
} else if (whence == LFS_SEEK_CUR) {
if ((lfs_soff_t)file->pos + off < 0) {
return LFS_ERR_INVAL;
} else {
npos = file->pos + off;
}
npos = file->pos + (lfs_off_t)off;
} else if (whence == LFS_SEEK_END) {
lfs_soff_t res = lfs_file_size_(lfs, file) + off;
if (res < 0) {
return LFS_ERR_INVAL;
} else {
npos = res;
}
npos = (lfs_off_t)lfs_file_size_(lfs, file) + (lfs_off_t)off;
}
if (npos > lfs->file_max) {
@@ -3694,13 +3725,8 @@ static lfs_soff_t lfs_file_seek_(lfs_t *lfs, lfs_file_t *file,
// if we're only reading and our new offset is still in the file's cache
// we can avoid flushing and needing to reread the data
if (
#ifndef LFS_READONLY
!(file->flags & LFS_F_WRITING)
#else
true
#endif
) {
if ((file->flags & LFS_F_READING)
&& file->off != lfs->cfg->block_size) {
int oindex = lfs_ctz_index(lfs, &(lfs_off_t){file->pos});
lfs_off_t noff = npos;
int nindex = lfs_ctz_index(lfs, &noff);
@@ -3842,6 +3868,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);
}
@@ -3944,7 +3976,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;
}
@@ -3955,8 +3987,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;
}
@@ -4016,7 +4054,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}));
@@ -4173,6 +4212,14 @@ static int lfs_init(lfs_t *lfs, const struct lfs_config *cfg) {
// which littlefs currently does not support
LFS_ASSERT((bool)0x80000000);
// check that the required io functions are provided
LFS_ASSERT(lfs->cfg->read != NULL);
#ifndef LFS_READONLY
LFS_ASSERT(lfs->cfg->prog != NULL);
LFS_ASSERT(lfs->cfg->erase != NULL);
LFS_ASSERT(lfs->cfg->sync != NULL);
#endif
// validate that the lfs-cfg sizes were initiated properly before
// performing any arithmetic logics with them
LFS_ASSERT(lfs->cfg->read_size != 0);
@@ -4209,6 +4256,15 @@ static int lfs_init(lfs_t *lfs, const struct lfs_config *cfg) {
LFS_ASSERT(lfs->cfg->compact_thresh == (lfs_size_t)-1
|| lfs->cfg->compact_thresh <= lfs->cfg->block_size);
// check that metadata_max is a multiple of read_size and prog_size,
// and a factor of the block_size
LFS_ASSERT(!lfs->cfg->metadata_max
|| lfs->cfg->metadata_max % lfs->cfg->read_size == 0);
LFS_ASSERT(!lfs->cfg->metadata_max
|| lfs->cfg->metadata_max % lfs->cfg->prog_size == 0);
LFS_ASSERT(!lfs->cfg->metadata_max
|| lfs->cfg->block_size % lfs->cfg->metadata_max == 0);
// setup read cache
if (lfs->cfg->read_buffer) {
lfs->rcache.buffer = lfs->cfg->read_buffer;
@@ -4396,6 +4452,30 @@ cleanup:
}
#endif
struct lfs_tortoise_t {
lfs_block_t pair[2];
lfs_size_t i;
lfs_size_t period;
};
static int lfs_tortoise_detectcycles(
const lfs_mdir_t *dir, struct lfs_tortoise_t *tortoise) {
// detect cycles with Brent's algorithm
if (lfs_pair_issync(dir->tail, tortoise->pair)) {
LFS_WARN("Cycle detected in tail list");
return LFS_ERR_CORRUPT;
}
if (tortoise->i == tortoise->period) {
tortoise->pair[0] = dir->tail[0];
tortoise->pair[1] = dir->tail[1];
tortoise->i = 0;
tortoise->period *= 2;
}
tortoise->i += 1;
return LFS_ERR_OK;
}
static int lfs_mount_(lfs_t *lfs, const struct lfs_config *cfg) {
int err = lfs_init(lfs, cfg);
if (err) {
@@ -4404,23 +4484,16 @@ static int lfs_mount_(lfs_t *lfs, const struct lfs_config *cfg) {
// scan directory blocks for superblock and any global updates
lfs_mdir_t dir = {.tail = {0, 1}};
lfs_block_t tortoise[2] = {LFS_BLOCK_NULL, LFS_BLOCK_NULL};
lfs_size_t tortoise_i = 1;
lfs_size_t tortoise_period = 1;
struct lfs_tortoise_t tortoise = {
.pair = {LFS_BLOCK_NULL, LFS_BLOCK_NULL},
.i = 1,
.period = 1,
};
while (!lfs_pair_isnull(dir.tail)) {
// detect cycles with Brent's algorithm
if (lfs_pair_issync(dir.tail, tortoise)) {
LFS_WARN("Cycle detected in tail list");
err = LFS_ERR_CORRUPT;
err = lfs_tortoise_detectcycles(&dir, &tortoise);
if (err < 0) {
goto cleanup;
}
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
lfs_stag_t tag = lfs_dir_fetchmatch(lfs, &dir, dir.tail,
@@ -4633,22 +4706,17 @@ int lfs_fs_traverse_(lfs_t *lfs,
}
#endif
lfs_block_t tortoise[2] = {LFS_BLOCK_NULL, LFS_BLOCK_NULL};
lfs_size_t tortoise_i = 1;
lfs_size_t tortoise_period = 1;
struct lfs_tortoise_t tortoise = {
.pair = {LFS_BLOCK_NULL, LFS_BLOCK_NULL},
.i = 1,
.period = 1,
};
int err = LFS_ERR_OK;
while (!lfs_pair_isnull(dir.tail)) {
// detect cycles with Brent's algorithm
if (lfs_pair_issync(dir.tail, tortoise)) {
LFS_WARN("Cycle detected in tail list");
err = lfs_tortoise_detectcycles(&dir, &tortoise);
if (err < 0) {
return LFS_ERR_CORRUPT;
}
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++) {
int err = cb(data, dir.tail[i]);
@@ -4727,22 +4795,17 @@ static int lfs_fs_pred(lfs_t *lfs,
// iterate over all directory directory entries
pdir->tail[0] = 0;
pdir->tail[1] = 1;
lfs_block_t tortoise[2] = {LFS_BLOCK_NULL, LFS_BLOCK_NULL};
lfs_size_t tortoise_i = 1;
lfs_size_t tortoise_period = 1;
struct lfs_tortoise_t tortoise = {
.pair = {LFS_BLOCK_NULL, LFS_BLOCK_NULL},
.i = 1,
.period = 1,
};
int err = LFS_ERR_OK;
while (!lfs_pair_isnull(pdir->tail)) {
// detect cycles with Brent's algorithm
if (lfs_pair_issync(pdir->tail, tortoise)) {
LFS_WARN("Cycle detected in tail list");
err = lfs_tortoise_detectcycles(pdir, &tortoise);
if (err < 0) {
return LFS_ERR_CORRUPT;
}
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) {
return 0;
@@ -4792,22 +4855,17 @@ static lfs_stag_t lfs_fs_parent(lfs_t *lfs, const lfs_block_t pair[2],
// use fetchmatch with callback to find pairs
parent->tail[0] = 0;
parent->tail[1] = 1;
lfs_block_t tortoise[2] = {LFS_BLOCK_NULL, LFS_BLOCK_NULL};
lfs_size_t tortoise_i = 1;
lfs_size_t tortoise_period = 1;
struct lfs_tortoise_t tortoise = {
.pair = {LFS_BLOCK_NULL, LFS_BLOCK_NULL},
.i = 1,
.period = 1,
};
int err = LFS_ERR_OK;
while (!lfs_pair_isnull(parent->tail)) {
// detect cycles with Brent's algorithm
if (lfs_pair_issync(parent->tail, tortoise)) {
LFS_WARN("Cycle detected in tail list");
return LFS_ERR_CORRUPT;
err = lfs_tortoise_detectcycles(parent, &tortoise);
if (err < 0) {
return err;
}
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_MKTAG(0x7ff, 0, 0x3ff),
@@ -5890,7 +5948,7 @@ int lfs_format(lfs_t *lfs, const struct lfs_config *cfg) {
".read=%p, .prog=%p, .erase=%p, .sync=%p, "
".read_size=%"PRIu32", .prog_size=%"PRIu32", "
".block_size=%"PRIu32", .block_count=%"PRIu32", "
".block_cycles=%"PRIu32", .cache_size=%"PRIu32", "
".block_cycles=%"PRId32", .cache_size=%"PRIu32", "
".lookahead_size=%"PRIu32", .read_buffer=%p, "
".prog_buffer=%p, .lookahead_buffer=%p, "
".name_max=%"PRIu32", .file_max=%"PRIu32", "
@@ -5920,7 +5978,7 @@ int lfs_mount(lfs_t *lfs, const struct lfs_config *cfg) {
".read=%p, .prog=%p, .erase=%p, .sync=%p, "
".read_size=%"PRIu32", .prog_size=%"PRIu32", "
".block_size=%"PRIu32", .block_count=%"PRIu32", "
".block_cycles=%"PRIu32", .cache_size=%"PRIu32", "
".block_cycles=%"PRId32", .cache_size=%"PRIu32", "
".lookahead_size=%"PRIu32", .read_buffer=%p, "
".prog_buffer=%p, .lookahead_buffer=%p, "
".name_max=%"PRIu32", .file_max=%"PRIu32", "
@@ -6057,7 +6115,7 @@ int lfs_file_open(lfs_t *lfs, lfs_file_t *file, const char *path, int flags) {
return err;
}
LFS_TRACE("lfs_file_open(%p, %p, \"%s\", %x)",
(void*)lfs, (void*)file, path, flags);
(void*)lfs, (void*)file, path, (unsigned)flags);
LFS_ASSERT(!lfs_mlist_isopen(lfs->mlist, (struct lfs_mlist*)file));
err = lfs_file_open_(lfs, file, path, flags);
@@ -6077,7 +6135,7 @@ int lfs_file_opencfg(lfs_t *lfs, lfs_file_t *file,
}
LFS_TRACE("lfs_file_opencfg(%p, %p, \"%s\", %x, %p {"
".buffer=%p, .attrs=%p, .attr_count=%"PRIu32"})",
(void*)lfs, (void*)file, path, flags,
(void*)lfs, (void*)file, path, (unsigned)flags,
(void*)cfg, cfg->buffer, (void*)cfg->attrs, cfg->attr_count);
LFS_ASSERT(!lfs_mlist_isopen(lfs->mlist, (struct lfs_mlist*)file));
@@ -6439,7 +6497,7 @@ int lfs_migrate(lfs_t *lfs, const struct lfs_config *cfg) {
".read=%p, .prog=%p, .erase=%p, .sync=%p, "
".read_size=%"PRIu32", .prog_size=%"PRIu32", "
".block_size=%"PRIu32", .block_count=%"PRIu32", "
".block_cycles=%"PRIu32", .cache_size=%"PRIu32", "
".block_cycles=%"PRId32", .cache_size=%"PRIu32", "
".lookahead_size=%"PRIu32", .read_buffer=%p, "
".prog_buffer=%p, .lookahead_buffer=%p, "
".name_max=%"PRIu32", .file_max=%"PRIu32", "

2
lfs.h
View File

@@ -21,7 +21,7 @@ extern "C"
// Software library version
// Major (top-nibble), incremented on backwards incompatible changes
// Minor (bottom-nibble), incremented on feature additions
#define LFS_VERSION 0x00020009
#define LFS_VERSION 0x0002000a
#define LFS_VERSION_MAJOR (0xffff & (LFS_VERSION >> 16))
#define LFS_VERSION_MINOR (0xffff & (LFS_VERSION >> 0))

View File

@@ -8,6 +8,9 @@
#ifndef LFS_UTIL_H
#define LFS_UTIL_H
#define LFS_STRINGIZE(x) LFS_STRINGIZE2(x)
#define LFS_STRINGIZE2(x) #x
// Users can override lfs_util.h with their own configuration by defining
// LFS_CONFIG as a header file to include (-DLFS_CONFIG=lfs_config.h).
//
@@ -15,11 +18,26 @@
// provided by the config file. To start, I would suggest copying lfs_util.h
// and modifying as needed.
#ifdef LFS_CONFIG
#define LFS_STRINGIZE(x) LFS_STRINGIZE2(x)
#define LFS_STRINGIZE2(x) #x
#include LFS_STRINGIZE(LFS_CONFIG)
#else
// Alternatively, users can provide a header file which defines
// macros and other things consumed by littlefs.
//
// For example, provide my_defines.h, which contains
// something like:
//
// #include <stddef.h>
// extern void *my_malloc(size_t sz);
// #define LFS_MALLOC(sz) my_malloc(sz)
//
// And build littlefs with the header by defining LFS_DEFINES.
// (-DLFS_DEFINES=my_defines.h)
#ifdef LFS_DEFINES
#include LFS_STRINGIZE(LFS_DEFINES)
#endif
// System includes
#include <stdint.h>
#include <stdbool.h>

View File

@@ -1322,6 +1322,7 @@ void perm_run(
.cache_size = CACHE_SIZE,
.lookahead_size = LOOKAHEAD_SIZE,
.compact_thresh = COMPACT_THRESH,
.metadata_max = METADATA_MAX,
.inline_max = INLINE_MAX,
};

View File

@@ -96,12 +96,13 @@ intmax_t bench_define(size_t define);
#define CACHE_SIZE_i 6
#define LOOKAHEAD_SIZE_i 7
#define COMPACT_THRESH_i 8
#define INLINE_MAX_i 9
#define BLOCK_CYCLES_i 10
#define ERASE_VALUE_i 11
#define ERASE_CYCLES_i 12
#define BADBLOCK_BEHAVIOR_i 13
#define POWERLOSS_BEHAVIOR_i 14
#define METADATA_MAX_i 9
#define INLINE_MAX_i 10
#define BLOCK_CYCLES_i 11
#define ERASE_VALUE_i 12
#define ERASE_CYCLES_i 13
#define BADBLOCK_BEHAVIOR_i 14
#define POWERLOSS_BEHAVIOR_i 15
#define READ_SIZE bench_define(READ_SIZE_i)
#define PROG_SIZE bench_define(PROG_SIZE_i)
@@ -112,6 +113,7 @@ intmax_t bench_define(size_t define);
#define CACHE_SIZE bench_define(CACHE_SIZE_i)
#define LOOKAHEAD_SIZE bench_define(LOOKAHEAD_SIZE_i)
#define COMPACT_THRESH bench_define(COMPACT_THRESH_i)
#define METADATA_MAX bench_define(METADATA_MAX_i)
#define INLINE_MAX bench_define(INLINE_MAX_i)
#define BLOCK_CYCLES bench_define(BLOCK_CYCLES_i)
#define ERASE_VALUE bench_define(ERASE_VALUE_i)
@@ -129,6 +131,7 @@ intmax_t bench_define(size_t define);
BENCH_DEF(CACHE_SIZE, lfs_max(64,lfs_max(READ_SIZE,PROG_SIZE))) \
BENCH_DEF(LOOKAHEAD_SIZE, 16) \
BENCH_DEF(COMPACT_THRESH, 0) \
BENCH_DEF(METADATA_MAX, 0) \
BENCH_DEF(INLINE_MAX, 0) \
BENCH_DEF(BLOCK_CYCLES, -1) \
BENCH_DEF(ERASE_VALUE, 0xff) \
@@ -137,7 +140,7 @@ intmax_t bench_define(size_t define);
BENCH_DEF(POWERLOSS_BEHAVIOR, LFS_EMUBD_POWERLOSS_NOOP)
#define BENCH_GEOMETRY_DEFINE_COUNT 4
#define BENCH_IMPLICIT_DEFINE_COUNT 15
#define BENCH_IMPLICIT_DEFINE_COUNT 16
#endif

View File

@@ -1347,6 +1347,7 @@ static void run_powerloss_none(
.cache_size = CACHE_SIZE,
.lookahead_size = LOOKAHEAD_SIZE,
.compact_thresh = COMPACT_THRESH,
.metadata_max = METADATA_MAX,
.inline_max = INLINE_MAX,
#ifdef LFS_MULTIVERSION
.disk_version = DISK_VERSION,
@@ -1425,6 +1426,7 @@ static void run_powerloss_linear(
.cache_size = CACHE_SIZE,
.lookahead_size = LOOKAHEAD_SIZE,
.compact_thresh = COMPACT_THRESH,
.metadata_max = METADATA_MAX,
.inline_max = INLINE_MAX,
#ifdef LFS_MULTIVERSION
.disk_version = DISK_VERSION,
@@ -1520,6 +1522,7 @@ static void run_powerloss_log(
.cache_size = CACHE_SIZE,
.lookahead_size = LOOKAHEAD_SIZE,
.compact_thresh = COMPACT_THRESH,
.metadata_max = METADATA_MAX,
.inline_max = INLINE_MAX,
#ifdef LFS_MULTIVERSION
.disk_version = DISK_VERSION,
@@ -1613,6 +1616,7 @@ static void run_powerloss_cycles(
.cache_size = CACHE_SIZE,
.lookahead_size = LOOKAHEAD_SIZE,
.compact_thresh = COMPACT_THRESH,
.metadata_max = METADATA_MAX,
.inline_max = INLINE_MAX,
#ifdef LFS_MULTIVERSION
.disk_version = DISK_VERSION,
@@ -1804,6 +1808,7 @@ static void run_powerloss_exhaustive(
.cache_size = CACHE_SIZE,
.lookahead_size = LOOKAHEAD_SIZE,
.compact_thresh = COMPACT_THRESH,
.metadata_max = METADATA_MAX,
.inline_max = INLINE_MAX,
#ifdef LFS_MULTIVERSION
.disk_version = DISK_VERSION,

View File

@@ -89,13 +89,14 @@ intmax_t test_define(size_t define);
#define CACHE_SIZE_i 6
#define LOOKAHEAD_SIZE_i 7
#define COMPACT_THRESH_i 8
#define INLINE_MAX_i 9
#define BLOCK_CYCLES_i 10
#define ERASE_VALUE_i 11
#define ERASE_CYCLES_i 12
#define BADBLOCK_BEHAVIOR_i 13
#define POWERLOSS_BEHAVIOR_i 14
#define DISK_VERSION_i 15
#define METADATA_MAX_i 9
#define INLINE_MAX_i 10
#define BLOCK_CYCLES_i 11
#define ERASE_VALUE_i 12
#define ERASE_CYCLES_i 13
#define BADBLOCK_BEHAVIOR_i 14
#define POWERLOSS_BEHAVIOR_i 15
#define DISK_VERSION_i 16
#define READ_SIZE TEST_DEFINE(READ_SIZE_i)
#define PROG_SIZE TEST_DEFINE(PROG_SIZE_i)
@@ -106,6 +107,7 @@ intmax_t test_define(size_t define);
#define CACHE_SIZE TEST_DEFINE(CACHE_SIZE_i)
#define LOOKAHEAD_SIZE TEST_DEFINE(LOOKAHEAD_SIZE_i)
#define COMPACT_THRESH TEST_DEFINE(COMPACT_THRESH_i)
#define METADATA_MAX TEST_DEFINE(METADATA_MAX_i)
#define INLINE_MAX TEST_DEFINE(INLINE_MAX_i)
#define BLOCK_CYCLES TEST_DEFINE(BLOCK_CYCLES_i)
#define ERASE_VALUE TEST_DEFINE(ERASE_VALUE_i)
@@ -124,6 +126,7 @@ intmax_t test_define(size_t define);
TEST_DEF(CACHE_SIZE, lfs_max(64,lfs_max(READ_SIZE,PROG_SIZE))) \
TEST_DEF(LOOKAHEAD_SIZE, 16) \
TEST_DEF(COMPACT_THRESH, 0) \
TEST_DEF(METADATA_MAX, 0) \
TEST_DEF(INLINE_MAX, 0) \
TEST_DEF(BLOCK_CYCLES, -1) \
TEST_DEF(ERASE_VALUE, 0xff) \
@@ -133,7 +136,7 @@ intmax_t test_define(size_t define);
TEST_DEF(DISK_VERSION, 0)
#define TEST_GEOMETRY_DEFINE_COUNT 4
#define TEST_IMPLICIT_DEFINE_COUNT 16
#define TEST_IMPLICIT_DEFINE_COUNT 17
#endif

View File

@@ -86,6 +86,13 @@ def write_header(f, limit=LIMIT):
f.writeln("}")
f.writeln()
f.writeln("__attribute__((unused))")
f.writeln("static void __pretty_assert_print_ptr(")
f.writeln(" const void *v, size_t size) {")
f.writeln(" (void)size;")
f.writeln(" printf(\"%p\", v);")
f.writeln("}")
f.writeln()
f.writeln("__attribute__((unused))")
f.writeln("static void __pretty_assert_print_mem(")
f.writeln(" const void *v, size_t size) {")
f.writeln(" const uint8_t *v_ = v;")
@@ -183,6 +190,23 @@ def write_header(f, limit=LIMIT):
f.writeln(" _rh, strlen(_rh)); \\")
f.writeln(" } \\")
f.writeln("} while (0)")
for op, cmp in sorted(CMP.items()):
# Only EQ and NE are supported when compared to NULL.
if cmp not in ['eq', 'ne']:
continue
f.writeln("#define __PRETTY_ASSERT_PTR_%s(lh, rh) do { \\"
% cmp.upper())
f.writeln(" const void *_lh = (const void*)(uintptr_t)lh; \\")
f.writeln(" const void *_rh = (const void*)(uintptr_t)rh; \\")
f.writeln(" if (!(_lh %s _rh)) { \\" % op)
f.writeln(" __pretty_assert_fail( \\")
f.writeln(" __FILE__, __LINE__, \\")
f.writeln(" __pretty_assert_print_ptr, \"%s\", \\"
% cmp)
f.writeln(" (const void*){_lh}, 0, \\")
f.writeln(" (const void*){_rh}, 0); \\")
f.writeln(" } \\")
f.writeln("} while (0)")
f.writeln()
f.writeln()
@@ -301,6 +325,8 @@ def p_assert(p):
cmp = p.expect('cmp') ; p.accept('ws')
rh = p_expr(p) ; p.accept('ws')
p.expect(')')
if rh == 'NULL' or lh == 'NULL':
return mkassert('ptr', CMP[cmp], lh, rh)
return mkassert('int', CMP[cmp], lh, rh)
except ParseFailure:
p.pop(state)

File diff suppressed because it is too large Load Diff

View File

@@ -137,6 +137,130 @@ code = '''
lfs_unmount(&lfs) => 0;
'''
# boundary seek and reads
[cases.test_seek_boundary_read]
defines.COUNT = 132
code = '''
lfs_t lfs;
lfs_format(&lfs, cfg) => 0;
lfs_mount(&lfs, cfg) => 0;
lfs_file_t file;
lfs_file_open(&lfs, &file, "kitty",
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_APPEND) => 0;
size_t size = strlen("kittycatcat");
uint8_t buffer[1024];
memcpy(buffer, "kittycatcat", size);
for (int j = 0; j < COUNT; j++) {
lfs_file_write(&lfs, &file, buffer, size);
}
lfs_file_close(&lfs, &file) => 0;
lfs_unmount(&lfs) => 0;
lfs_mount(&lfs, cfg) => 0;
lfs_file_open(&lfs, &file, "kitty", LFS_O_RDONLY) => 0;
size = strlen("kittycatcat");
const lfs_soff_t offsets[] = {
512,
1024-4,
512+1,
1024-4+1,
512-1,
1024-4-1,
512-strlen("kittycatcat"),
1024-4-strlen("kittycatcat"),
512-strlen("kittycatcat")+1,
1024-4-strlen("kittycatcat")+1,
512-strlen("kittycatcat")-1,
1024-4-strlen("kittycatcat")-1,
strlen("kittycatcat")*(COUNT-2)-1,
};
for (unsigned i = 0; i < sizeof(offsets) / sizeof(offsets[0]); i++) {
lfs_soff_t off = offsets[i];
// read @ offset
lfs_file_seek(&lfs, &file, off, LFS_SEEK_SET) => off;
lfs_file_read(&lfs, &file, buffer, size) => size;
memcmp(buffer,
&"kittycatcatkittycatcat"[off % strlen("kittycatcat")],
size) => 0;
// read after
lfs_file_seek(&lfs, &file, off+strlen("kittycatcat")+1, LFS_SEEK_SET)
=> off+strlen("kittycatcat")+1;
lfs_file_read(&lfs, &file, buffer, size) => size;
memcmp(buffer,
&"kittycatcatkittycatcat"[(off+1) % strlen("kittycatcat")],
size) => 0;
// read before
lfs_file_seek(&lfs, &file, off-strlen("kittycatcat")-1, LFS_SEEK_SET)
=> off-strlen("kittycatcat")-1;
lfs_file_read(&lfs, &file, buffer, size) => size;
memcmp(buffer,
&"kittycatcatkittycatcat"[(off-1) % strlen("kittycatcat")],
size) => 0;
// read @ 0
lfs_file_seek(&lfs, &file, 0, LFS_SEEK_SET) => 0;
lfs_file_read(&lfs, &file, buffer, size) => size;
memcmp(buffer, "kittycatcat", size) => 0;
// read @ offset
lfs_file_seek(&lfs, &file, off, LFS_SEEK_SET) => off;
lfs_file_read(&lfs, &file, buffer, size) => size;
memcmp(buffer,
&"kittycatcatkittycatcat"[off % strlen("kittycatcat")],
size) => 0;
// read after
lfs_file_seek(&lfs, &file, off+strlen("kittycatcat")+1, LFS_SEEK_SET)
=> off+strlen("kittycatcat")+1;
lfs_file_read(&lfs, &file, buffer, size) => size;
memcmp(buffer,
&"kittycatcatkittycatcat"[(off+1) % strlen("kittycatcat")],
size) => 0;
// read before
lfs_file_seek(&lfs, &file, off-strlen("kittycatcat")-1, LFS_SEEK_SET)
=> off-strlen("kittycatcat")-1;
lfs_file_read(&lfs, &file, buffer, size) => size;
memcmp(buffer,
&"kittycatcatkittycatcat"[(off-1) % strlen("kittycatcat")],
size) => 0;
// sync
lfs_file_sync(&lfs, &file) => 0;
// read @ 0
lfs_file_seek(&lfs, &file, 0, LFS_SEEK_SET) => 0;
lfs_file_read(&lfs, &file, buffer, size) => size;
memcmp(buffer, "kittycatcat", size) => 0;
// read @ offset
lfs_file_seek(&lfs, &file, off, LFS_SEEK_SET) => off;
lfs_file_read(&lfs, &file, buffer, size) => size;
memcmp(buffer,
&"kittycatcatkittycatcat"[off % strlen("kittycatcat")],
size) => 0;
// read after
lfs_file_seek(&lfs, &file, off+strlen("kittycatcat")+1, LFS_SEEK_SET)
=> off+strlen("kittycatcat")+1;
lfs_file_read(&lfs, &file, buffer, size) => size;
memcmp(buffer,
&"kittycatcatkittycatcat"[(off+1) % strlen("kittycatcat")],
size) => 0;
// read before
lfs_file_seek(&lfs, &file, off-strlen("kittycatcat")-1, LFS_SEEK_SET)
=> off-strlen("kittycatcat")-1;
lfs_file_read(&lfs, &file, buffer, size) => size;
memcmp(buffer,
&"kittycatcatkittycatcat"[(off-1) % strlen("kittycatcat")],
size) => 0;
}
lfs_file_close(&lfs, &file) => 0;
lfs_unmount(&lfs) => 0;
'''
# boundary seek and writes
[cases.test_seek_boundary_write]
defines.COUNT = 132
@@ -160,31 +284,54 @@ code = '''
lfs_file_open(&lfs, &file, "kitty", LFS_O_RDWR) => 0;
size = strlen("hedgehoghog");
const lfs_soff_t offsets[] = {512, 1020, 513, 1021, 511, 1019, 1441};
const lfs_soff_t offsets[] = {
512,
1024-4,
512+1,
1024-4+1,
512-1,
1024-4-1,
512-strlen("kittycatcat"),
1024-4-strlen("kittycatcat"),
512-strlen("kittycatcat")+1,
1024-4-strlen("kittycatcat")+1,
512-strlen("kittycatcat")-1,
1024-4-strlen("kittycatcat")-1,
strlen("kittycatcat")*(COUNT-2)-1,
};
for (unsigned i = 0; i < sizeof(offsets) / sizeof(offsets[0]); i++) {
lfs_soff_t off = offsets[i];
// write @ offset
memcpy(buffer, "hedgehoghog", size);
lfs_file_seek(&lfs, &file, off, LFS_SEEK_SET) => off;
lfs_file_write(&lfs, &file, buffer, size) => size;
// read @ offset
lfs_file_seek(&lfs, &file, off, LFS_SEEK_SET) => off;
lfs_file_read(&lfs, &file, buffer, size) => size;
memcmp(buffer, "hedgehoghog", size) => 0;
// read @ 0
lfs_file_seek(&lfs, &file, 0, LFS_SEEK_SET) => 0;
lfs_file_read(&lfs, &file, buffer, size) => size;
memcmp(buffer, "kittycatcat", size) => 0;
// read @ offset
lfs_file_seek(&lfs, &file, off, LFS_SEEK_SET) => off;
lfs_file_read(&lfs, &file, buffer, size) => size;
memcmp(buffer, "hedgehoghog", size) => 0;
lfs_file_sync(&lfs, &file) => 0;
// read @ 0
lfs_file_seek(&lfs, &file, 0, LFS_SEEK_SET) => 0;
lfs_file_read(&lfs, &file, buffer, size) => size;
memcmp(buffer, "kittycatcat", size) => 0;
// read @ offset
lfs_file_seek(&lfs, &file, off, LFS_SEEK_SET) => off;
lfs_file_read(&lfs, &file, buffer, size) => size;
memcmp(buffer, "hedgehoghog", size) => 0;
@@ -405,3 +552,111 @@ code = '''
lfs_file_close(&lfs, &file) => 0;
lfs_unmount(&lfs) => 0;
'''
# test possible overflow/underflow conditions
#
# note these need -fsanitize=undefined to consistently detect
# overflow/underflow conditions
[cases.test_seek_filemax]
code = '''
lfs_t lfs;
lfs_format(&lfs, cfg) => 0;
lfs_mount(&lfs, cfg) => 0;
lfs_file_t file;
lfs_file_open(&lfs, &file, "kitty",
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_APPEND) => 0;
uint8_t buffer[1024];
strcpy((char*)buffer, "kittycatcat");
size_t size = strlen((char*)buffer);
lfs_file_write(&lfs, &file, buffer, size) => size;
// seek with LFS_SEEK_SET
lfs_file_seek(&lfs, &file, LFS_FILE_MAX, LFS_SEEK_SET) => LFS_FILE_MAX;
// seek with LFS_SEEK_CUR
lfs_file_seek(&lfs, &file, 0, LFS_SEEK_CUR) => LFS_FILE_MAX;
// the file hasn't changed size, so seek end takes us back to the offset=0
lfs_file_seek(&lfs, &file, +10, LFS_SEEK_END) => size+10;
lfs_file_close(&lfs, &file) => 0;
lfs_unmount(&lfs) => 0;
'''
[cases.test_seek_underflow]
code = '''
lfs_t lfs;
lfs_format(&lfs, cfg) => 0;
lfs_mount(&lfs, cfg) => 0;
lfs_file_t file;
lfs_file_open(&lfs, &file, "kitty",
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_APPEND) => 0;
uint8_t buffer[1024];
strcpy((char*)buffer, "kittycatcat");
size_t size = strlen((char*)buffer);
lfs_file_write(&lfs, &file, buffer, size) => size;
// underflow with LFS_SEEK_CUR, should error
lfs_file_seek(&lfs, &file, -(size+10), LFS_SEEK_CUR) => LFS_ERR_INVAL;
lfs_file_seek(&lfs, &file, -LFS_FILE_MAX, LFS_SEEK_CUR) => LFS_ERR_INVAL;
lfs_file_seek(&lfs, &file, -(size+LFS_FILE_MAX), LFS_SEEK_CUR)
=> LFS_ERR_INVAL;
// underflow with LFS_SEEK_END, should error
lfs_file_seek(&lfs, &file, -(size+10), LFS_SEEK_END) => LFS_ERR_INVAL;
lfs_file_seek(&lfs, &file, -LFS_FILE_MAX, LFS_SEEK_END) => LFS_ERR_INVAL;
lfs_file_seek(&lfs, &file, -(size+LFS_FILE_MAX), LFS_SEEK_END)
=> LFS_ERR_INVAL;
// file pointer should not have changed
lfs_file_tell(&lfs, &file) => size;
lfs_file_close(&lfs, &file) => 0;
lfs_unmount(&lfs) => 0;
'''
[cases.test_seek_overflow]
code = '''
lfs_t lfs;
lfs_format(&lfs, cfg) => 0;
lfs_mount(&lfs, cfg) => 0;
lfs_file_t file;
lfs_file_open(&lfs, &file, "kitty",
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_APPEND) => 0;
uint8_t buffer[1024];
strcpy((char*)buffer, "kittycatcat");
size_t size = strlen((char*)buffer);
lfs_file_write(&lfs, &file, buffer, size) => size;
// seek to LFS_FILE_MAX
lfs_file_seek(&lfs, &file, LFS_FILE_MAX, LFS_SEEK_SET) => LFS_FILE_MAX;
// overflow with LFS_SEEK_CUR, should error
lfs_file_seek(&lfs, &file, +10, LFS_SEEK_CUR) => LFS_ERR_INVAL;
lfs_file_seek(&lfs, &file, +LFS_FILE_MAX, LFS_SEEK_CUR) => LFS_ERR_INVAL;
// LFS_SEEK_SET/END don't care about the current file position, but we can
// still overflow with a large offset
// overflow with LFS_SEEK_SET, should error
lfs_file_seek(&lfs, &file,
+((uint32_t)LFS_FILE_MAX+10),
LFS_SEEK_SET) => LFS_ERR_INVAL;
lfs_file_seek(&lfs, &file,
+((uint32_t)LFS_FILE_MAX+(uint32_t)LFS_FILE_MAX),
LFS_SEEK_SET) => LFS_ERR_INVAL;
// overflow with LFS_SEEK_END, should error
lfs_file_seek(&lfs, &file, +(LFS_FILE_MAX-size+10), LFS_SEEK_END)
=> LFS_ERR_INVAL;
lfs_file_seek(&lfs, &file, +(LFS_FILE_MAX-size+LFS_FILE_MAX), LFS_SEEK_END)
=> LFS_ERR_INVAL;
// file pointer should not have changed
lfs_file_tell(&lfs, &file) => LFS_FILE_MAX;
lfs_file_close(&lfs, &file) => 0;
lfs_unmount(&lfs) => 0;
'''

View File

@@ -523,3 +523,30 @@ code = '''
assert(memcmp(buffer, "hello!", 6) == 0);
lfs_unmount(&lfs) => 0;
'''
# test that metadata_max does not cause problems for superblock compaction
[cases.test_superblocks_metadata_max]
defines.METADATA_MAX = [
'lfs_max(512, PROG_SIZE)',
'lfs_max(BLOCK_SIZE/2, PROG_SIZE)',
'BLOCK_SIZE'
]
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;
char name[256];
sprintf(name, "hello%03x", i);
lfs_file_open(&lfs, &file, name,
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
lfs_file_close(&lfs, &file) => 0;
struct lfs_info info;
lfs_stat(&lfs, name, &info) => 0;
assert(strcmp(info.name, name) == 0);
assert(info.type == LFS_TYPE_REG);
}
lfs_unmount(&lfs) => 0;
'''