Merge pull request #1050 from littlefs-project/devel

Minor release: v2.10
This commit is contained in:
Christopher Haster
2024-12-11 16:56:45 -06:00
committed by GitHub
15 changed files with 7693 additions and 361 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,16 @@ jobs:
}' | tee status/$(basename $f .csv).json
done
- name: upload-status-sizes
uses: actions/upload-artifact@v2
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 +317,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 +336,7 @@ jobs:
pls: [1, 2]
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: install
run: |
# need a few things
@@ -361,7 +361,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 +378,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 +395,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 +414,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 +436,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 +459,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 +491,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 +525,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 +535,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 +572,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 +582,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 +622,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 +632,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 +694,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 +704,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 +865,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

@@ -251,6 +251,12 @@ License Identifiers that are here available: http://spdx.org/licenses/
filesystem over USB. Allows mounting littlefs on a host PC without additional
drivers.
- [ramcrc32bd] - An example block device using littlefs's 32-bit CRC for
error-correction.
- [ramrsbd] - An example block device using Reed-Solomon codes for
error-correction.
- [Mbed OS] - The easiest way to get started with littlefs is to jump into Mbed
which already has block device drivers for most forms of embedded storage.
littlefs is available in Mbed OS as the [LittleFileSystem] class.
@@ -281,6 +287,8 @@ License Identifiers that are here available: http://spdx.org/licenses/
[mklfs]: https://github.com/whitecatboard/Lua-RTOS-ESP32/tree/master/components/mklfs/src
[mklittlefs]: https://github.com/earlephilhower/mklittlefs
[pico-littlefs-usb]: https://github.com/oyama/pico-littlefs-usb
[ramcrc32bd]: https://github.com/geky/ramcrc32bd
[ramrsbd]: https://github.com/geky/ramrsbd
[Mbed OS]: https://github.com/armmbed/mbed-os
[LittleFileSystem]: https://os.mbed.com/docs/mbed-os/latest/apis/littlefilesystem.html
[SPIFFS]: https://github.com/pellepl/spiffs

249
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
// 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) {
@@ -3842,6 +3873,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 +3981,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 +3992,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 +4059,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 +4217,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 +4261,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 +4457,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 +4489,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 +4711,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 +4800,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 +4860,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 +5953,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 +5983,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 +6120,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 +6140,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 +6502,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

@@ -405,3 +405,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;
'''