Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/littlefs-project/littlefs.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.travis.yml7
-rw-r--r--Makefile3
-rw-r--r--lfs.c3152
-rw-r--r--lfs.h381
-rw-r--r--lfs_util.h35
-rwxr-xr-xtests/corrupt.py39
-rw-r--r--tests/template.fmt7
-rwxr-xr-xtests/test.py2
-rwxr-xr-xtests/test_alloc.sh12
-rwxr-xr-xtests/test_attrs.sh262
-rwxr-xr-xtests/test_corrupt.sh6
-rwxr-xr-xtests/test_entries.sh220
-rwxr-xr-xtests/test_format.sh12
-rwxr-xr-xtests/test_move.sh124
-rwxr-xr-xtests/test_orphan.sh28
-rwxr-xr-xtests/test_paths.sh18
16 files changed, 2978 insertions, 1330 deletions
diff --git a/.travis.yml b/.travis.yml
index 3714f7c..32c21d8 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -18,10 +18,11 @@ script:
- make test QUIET=1
# run tests with a few different configurations
- - make test QUIET=1 CFLAGS+="-DLFS_READ_SIZE=1 -DLFS_PROG_SIZE=1"
- - make test QUIET=1 CFLAGS+="-DLFS_READ_SIZE=512 -DLFS_PROG_SIZE=512"
+ - make test QUIET=1 CFLAGS+="-DLFS_READ_SIZE=1 -DLFS_CACHE_SIZE=4"
+ - make test QUIET=1 CFLAGS+="-DLFS_READ_SIZE=512 -DLFS_CACHE_SIZE=512"
- make test QUIET=1 CFLAGS+="-DLFS_BLOCK_COUNT=1023 -DLFS_LOOKAHEAD=2048"
+ - make clean test QUIET=1 CFLAGS+="-DLFS_INLINE_MAX=0"
- make clean test QUIET=1 CFLAGS+="-DLFS_NO_INTRINSICS"
# compile and find the code size with the smallest configuration
@@ -102,7 +103,7 @@ jobs:
- NAME=littlefs-fuse
install:
- sudo apt-get install libfuse-dev
- - git clone --depth 1 https://github.com/geky/littlefs-fuse
+ - git clone --depth 1 https://github.com/geky/littlefs-fuse -b v2-alpha
- fusermount -V
- gcc --version
before_script:
diff --git a/Makefile b/Makefile
index 99a3c0c..a8b8852 100644
--- a/Makefile
+++ b/Makefile
@@ -37,7 +37,8 @@ size: $(OBJ)
.SUFFIXES:
test: test_format test_dirs test_files test_seek test_truncate \
- test_interspersed test_alloc test_paths test_orphan test_move test_corrupt
+ test_entries test_interspersed test_alloc test_paths test_attrs \
+ test_move test_orphan test_corrupt
@rm test.c
test_%: tests/test_%.sh
diff --git a/lfs.c b/lfs.c
index c6b5870..4eb4821 100644
--- a/lfs.c
+++ b/lfs.c
@@ -1,28 +1,38 @@
/*
* The little filesystem
*
- * Copyright (c) 2017, Arm Limited. All rights reserved.
- * SPDX-License-Identifier: BSD-3-Clause
+ * Copyright (c) 2017 ARM Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
*/
#include "lfs.h"
#include "lfs_util.h"
-#include <inttypes.h>
-
/// Caching block device operations ///
-static int lfs_cache_read(lfs_t *lfs, lfs_cache_t *rcache,
- const lfs_cache_t *pcache, lfs_block_t block,
- lfs_off_t off, void *buffer, lfs_size_t size) {
+static int lfs_cache_read(lfs_t *lfs,
+ const lfs_cache_t *pcache, lfs_cache_t *rcache, bool store,
+ lfs_block_t block, lfs_off_t off,
+ void *buffer, lfs_size_t size) {
uint8_t *data = buffer;
- LFS_ASSERT(block < lfs->cfg->block_count);
+ LFS_ASSERT(block != 0xffffffff);
while (size > 0) {
- if (pcache && block == pcache->block && off >= pcache->off &&
- off < pcache->off + lfs->cfg->prog_size) {
+ if (pcache && block == pcache->block &&
+ off >= pcache->off &&
+ off < pcache->off + pcache->size) {
// is already in pcache?
- lfs_size_t diff = lfs_min(size,
- lfs->cfg->prog_size - (off-pcache->off));
+ lfs_size_t diff = lfs_min(size, pcache->size - (off-pcache->off));
memcpy(data, &pcache->buffer[off-pcache->off], diff);
data += diff;
@@ -31,11 +41,14 @@ static int lfs_cache_read(lfs_t *lfs, lfs_cache_t *rcache,
continue;
}
- if (block == rcache->block && off >= rcache->off &&
- off < rcache->off + lfs->cfg->read_size) {
+ if (block == rcache->block &&
+ off >= rcache->off &&
+ off < rcache->off + rcache->size) {
// is already in rcache?
- lfs_size_t diff = lfs_min(size,
- lfs->cfg->read_size - (off-rcache->off));
+ lfs_size_t diff = lfs_min(size, rcache->size - (off-rcache->off));
+ if (pcache && block == pcache->block) {
+ diff = lfs_min(diff, pcache->off - off);
+ }
memcpy(data, &rcache->buffer[off-rcache->off], diff);
data += diff;
@@ -44,7 +57,8 @@ static int lfs_cache_read(lfs_t *lfs, lfs_cache_t *rcache,
continue;
}
- if (off % lfs->cfg->read_size == 0 && size >= lfs->cfg->read_size) {
+ if (!store && off % lfs->cfg->read_size == 0 &&
+ size >= lfs->cfg->read_size) {
// bypass cache?
lfs_size_t diff = size - (size % lfs->cfg->read_size);
int err = lfs->cfg->read(lfs->cfg, block, off, data, diff);
@@ -59,10 +73,13 @@ static int lfs_cache_read(lfs_t *lfs, lfs_cache_t *rcache,
}
// load to cache, first condition can no longer fail
+ LFS_ASSERT(block < lfs->cfg->block_count);
+ lfs_size_t nsize = store ? lfs->cfg->cache_size : lfs->cfg->prog_size;
rcache->block = block;
- rcache->off = off - (off % lfs->cfg->read_size);
+ rcache->off = lfs_aligndown(off, nsize);
+ rcache->size = nsize;
int err = lfs->cfg->read(lfs->cfg, rcache->block,
- rcache->off, rcache->buffer, lfs->cfg->read_size);
+ rcache->off, rcache->buffer, nsize);
if (err) {
return err;
}
@@ -71,14 +88,15 @@ static int lfs_cache_read(lfs_t *lfs, lfs_cache_t *rcache,
return 0;
}
-static int lfs_cache_cmp(lfs_t *lfs, lfs_cache_t *rcache,
- const lfs_cache_t *pcache, lfs_block_t block,
- lfs_off_t off, const void *buffer, lfs_size_t size) {
+static int lfs_cache_cmp(lfs_t *lfs,
+ const lfs_cache_t *pcache, lfs_cache_t *rcache,
+ lfs_block_t block, lfs_off_t off,
+ const void *buffer, lfs_size_t size) {
const uint8_t *data = buffer;
for (lfs_off_t i = 0; i < size; i++) {
uint8_t c;
- int err = lfs_cache_read(lfs, rcache, pcache,
+ int err = lfs_cache_read(lfs, pcache, rcache, true,
block, off+i, &c, 1);
if (err) {
return err;
@@ -92,12 +110,12 @@ static int lfs_cache_cmp(lfs_t *lfs, lfs_cache_t *rcache,
return true;
}
-static int lfs_cache_crc(lfs_t *lfs, lfs_cache_t *rcache,
- const lfs_cache_t *pcache, lfs_block_t block,
- lfs_off_t off, lfs_size_t size, uint32_t *crc) {
+static int lfs_cache_crc(lfs_t *lfs,
+ const lfs_cache_t *pcache, lfs_cache_t *rcache,
+ lfs_block_t block, lfs_off_t off, lfs_size_t size, uint32_t *crc) {
for (lfs_off_t i = 0; i < size; i++) {
uint8_t c;
- int err = lfs_cache_read(lfs, rcache, pcache,
+ int err = lfs_cache_read(lfs, pcache, rcache, true,
block, off+i, &c, 1);
if (err) {
return err;
@@ -123,17 +141,21 @@ static inline void lfs_cache_zero(lfs_t *lfs, lfs_cache_t *pcache) {
}
static int lfs_cache_flush(lfs_t *lfs,
- lfs_cache_t *pcache, lfs_cache_t *rcache) {
+ lfs_cache_t *pcache, lfs_cache_t *rcache, bool validate) {
if (pcache->block != 0xffffffff) {
+ LFS_ASSERT(pcache->block < lfs->cfg->block_count);
+ lfs_size_t diff = lfs_alignup(pcache->size, lfs->cfg->prog_size);
int err = lfs->cfg->prog(lfs->cfg, pcache->block,
- pcache->off, pcache->buffer, lfs->cfg->prog_size);
+ pcache->off, pcache->buffer, diff);
if (err) {
return err;
}
- if (rcache) {
- int res = lfs_cache_cmp(lfs, rcache, NULL, pcache->block,
- pcache->off, pcache->buffer, lfs->cfg->prog_size);
+ if (validate) {
+ // check data on disk
+ lfs_cache_drop(lfs, rcache);
+ int res = lfs_cache_cmp(lfs, NULL, rcache, pcache->block,
+ pcache->off, pcache->buffer, diff);
if (res < 0) {
return res;
}
@@ -149,27 +171,31 @@ static int lfs_cache_flush(lfs_t *lfs,
return 0;
}
-static int lfs_cache_prog(lfs_t *lfs, lfs_cache_t *pcache,
- lfs_cache_t *rcache, lfs_block_t block,
- lfs_off_t off, const void *buffer, lfs_size_t size) {
+static int lfs_cache_prog(lfs_t *lfs,
+ lfs_cache_t *pcache, lfs_cache_t *rcache, bool validate,
+ lfs_block_t block, lfs_off_t off,
+ const void *buffer, lfs_size_t size) {
const uint8_t *data = buffer;
- LFS_ASSERT(block < lfs->cfg->block_count);
+ LFS_ASSERT(block != 0xffffffff);
+ LFS_ASSERT(off + size <= lfs->cfg->block_size);
while (size > 0) {
- if (block == pcache->block && off >= pcache->off &&
- off < pcache->off + lfs->cfg->prog_size) {
- // is already in pcache?
+ if (block == pcache->block &&
+ off >= pcache->off &&
+ off < pcache->off + lfs->cfg->cache_size) {
+ // already fits in pcache?
lfs_size_t diff = lfs_min(size,
- lfs->cfg->prog_size - (off-pcache->off));
+ lfs->cfg->cache_size - (off-pcache->off));
memcpy(&pcache->buffer[off-pcache->off], data, diff);
data += diff;
off += diff;
size -= diff;
- if (off % lfs->cfg->prog_size == 0) {
+ pcache->size = off - pcache->off;
+ if (pcache->size == lfs->cfg->cache_size) {
// eagerly flush out pcache if we fill up
- int err = lfs_cache_flush(lfs, pcache, rcache);
+ int err = lfs_cache_flush(lfs, pcache, rcache, validate);
if (err) {
return err;
}
@@ -182,36 +208,10 @@ static int lfs_cache_prog(lfs_t *lfs, lfs_cache_t *pcache,
// entire block or manually flushing the pcache
LFS_ASSERT(pcache->block == 0xffffffff);
- if (off % lfs->cfg->prog_size == 0 &&
- size >= lfs->cfg->prog_size) {
- // bypass pcache?
- lfs_size_t diff = size - (size % lfs->cfg->prog_size);
- int err = lfs->cfg->prog(lfs->cfg, block, off, data, diff);
- if (err) {
- return err;
- }
-
- if (rcache) {
- int res = lfs_cache_cmp(lfs, rcache, NULL,
- block, off, data, diff);
- if (res < 0) {
- return res;
- }
-
- if (!res) {
- return LFS_ERR_CORRUPT;
- }
- }
-
- data += diff;
- off += diff;
- size -= diff;
- continue;
- }
-
// prepare pcache, first condition can no longer fail
pcache->block = block;
- pcache->off = off - (off % lfs->cfg->prog_size);
+ pcache->off = lfs_aligndown(off, lfs->cfg->prog_size);
+ pcache->size = 0;
}
return 0;
@@ -221,36 +221,35 @@ static int lfs_cache_prog(lfs_t *lfs, lfs_cache_t *pcache,
/// General lfs block device operations ///
static int lfs_bd_read(lfs_t *lfs, lfs_block_t block,
lfs_off_t off, void *buffer, lfs_size_t size) {
- // if we ever do more than writes to alternating pairs,
- // this may need to consider pcache
- return lfs_cache_read(lfs, &lfs->rcache, NULL,
+ return lfs_cache_read(lfs, &lfs->pcache, &lfs->rcache, true,
block, off, buffer, size);
}
static int lfs_bd_prog(lfs_t *lfs, lfs_block_t block,
lfs_off_t off, const void *buffer, lfs_size_t size) {
- return lfs_cache_prog(lfs, &lfs->pcache, NULL,
+ return lfs_cache_prog(lfs, &lfs->pcache, &lfs->rcache, false,
block, off, buffer, size);
}
static int lfs_bd_cmp(lfs_t *lfs, lfs_block_t block,
lfs_off_t off, const void *buffer, lfs_size_t size) {
- return lfs_cache_cmp(lfs, &lfs->rcache, NULL, block, off, buffer, size);
+ return lfs_cache_cmp(lfs, NULL, &lfs->rcache, block, off, buffer, size);
}
static int lfs_bd_crc(lfs_t *lfs, lfs_block_t block,
lfs_off_t off, lfs_size_t size, uint32_t *crc) {
- return lfs_cache_crc(lfs, &lfs->rcache, NULL, block, off, size, crc);
+ return lfs_cache_crc(lfs, NULL, &lfs->rcache, block, off, size, crc);
}
static int lfs_bd_erase(lfs_t *lfs, lfs_block_t block) {
+ LFS_ASSERT(block < lfs->cfg->block_count);
return lfs->cfg->erase(lfs->cfg, block);
}
static int lfs_bd_sync(lfs_t *lfs) {
lfs_cache_drop(lfs, &lfs->rcache);
- int err = lfs_cache_flush(lfs, &lfs->pcache, NULL);
+ int err = lfs_cache_flush(lfs, &lfs->pcache, &lfs->rcache, false);
if (err) {
return err;
}
@@ -260,20 +259,19 @@ static int lfs_bd_sync(lfs_t *lfs) {
/// Internal operations predeclared here ///
-int lfs_traverse(lfs_t *lfs, int (*cb)(void*, lfs_block_t), void *data);
-static int lfs_pred(lfs_t *lfs, const lfs_block_t dir[2], lfs_dir_t *pdir);
-static int lfs_parent(lfs_t *lfs, const lfs_block_t dir[2],
- lfs_dir_t *parent, lfs_entry_t *entry);
-static int lfs_moved(lfs_t *lfs, const void *e);
-static int lfs_relocate(lfs_t *lfs,
- const lfs_block_t oldpair[2], const lfs_block_t newpair[2]);
-int lfs_deorphan(lfs_t *lfs);
+static int lfs_fs_pred(lfs_t *lfs, const lfs_block_t dir[2],
+ lfs_mdir_t *pdir);
+static int32_t lfs_fs_parent(lfs_t *lfs, const lfs_block_t dir[2],
+ lfs_mdir_t *parent);
+static int lfs_fs_relocate(lfs_t *lfs,
+ const lfs_block_t oldpair[2], lfs_block_t newpair[2]);
+static int lfs_fs_forceconsistency(lfs_t *lfs);
+static int lfs_deinit(lfs_t *lfs);
/// Block allocator ///
static int lfs_alloc_lookahead(void *p, lfs_block_t block) {
- lfs_t *lfs = p;
-
+ lfs_t *lfs = (lfs_t*)p;
lfs_block_t off = ((block - lfs->free.off)
+ lfs->cfg->block_count) % lfs->cfg->block_count;
@@ -310,7 +308,7 @@ static int lfs_alloc(lfs_t *lfs, lfs_block_t *block) {
// check if we have looked at all blocks since last ack
if (lfs->free.ack == 0) {
- LFS_WARN("No more free space %" PRIu32,
+ LFS_WARN("No more free space %"PRIu32,
lfs->free.i + lfs->free.off);
return LFS_ERR_NOSPC;
}
@@ -322,7 +320,7 @@ static int lfs_alloc(lfs_t *lfs, lfs_block_t *block) {
// find mask of free blocks from tree
memset(lfs->free.buffer, 0, lfs->cfg->lookahead/8);
- int err = lfs_traverse(lfs, lfs_alloc_lookahead, lfs);
+ int err = lfs_fs_traverse(lfs, lfs_alloc_lookahead, lfs);
if (err) {
return err;
}
@@ -334,48 +332,6 @@ static void lfs_alloc_ack(lfs_t *lfs) {
}
-/// Endian swapping functions ///
-static void lfs_dir_fromle32(struct lfs_disk_dir *d) {
- d->rev = lfs_fromle32(d->rev);
- d->size = lfs_fromle32(d->size);
- d->tail[0] = lfs_fromle32(d->tail[0]);
- d->tail[1] = lfs_fromle32(d->tail[1]);
-}
-
-static void lfs_dir_tole32(struct lfs_disk_dir *d) {
- d->rev = lfs_tole32(d->rev);
- d->size = lfs_tole32(d->size);
- d->tail[0] = lfs_tole32(d->tail[0]);
- d->tail[1] = lfs_tole32(d->tail[1]);
-}
-
-static void lfs_entry_fromle32(struct lfs_disk_entry *d) {
- d->u.dir[0] = lfs_fromle32(d->u.dir[0]);
- d->u.dir[1] = lfs_fromle32(d->u.dir[1]);
-}
-
-static void lfs_entry_tole32(struct lfs_disk_entry *d) {
- d->u.dir[0] = lfs_tole32(d->u.dir[0]);
- d->u.dir[1] = lfs_tole32(d->u.dir[1]);
-}
-
-static void lfs_superblock_fromle32(struct lfs_disk_superblock *d) {
- d->root[0] = lfs_fromle32(d->root[0]);
- d->root[1] = lfs_fromle32(d->root[1]);
- d->block_size = lfs_fromle32(d->block_size);
- d->block_count = lfs_fromle32(d->block_count);
- d->version = lfs_fromle32(d->version);
-}
-
-static void lfs_superblock_tole32(struct lfs_disk_superblock *d) {
- d->root[0] = lfs_tole32(d->root[0]);
- d->root[1] = lfs_tole32(d->root[1]);
- d->block_size = lfs_tole32(d->block_size);
- d->block_count = lfs_tole32(d->block_count);
- d->version = lfs_tole32(d->version);
-}
-
-
/// Metadata pair and directory operations ///
static inline void lfs_pairswap(lfs_block_t pair[2]) {
lfs_block_t t = pair[0];
@@ -401,14 +357,378 @@ static inline bool lfs_pairsync(
(paira[0] == pairb[1] && paira[1] == pairb[0]);
}
-static inline lfs_size_t lfs_entry_size(const lfs_entry_t *entry) {
- return 4 + entry->d.elen + entry->d.alen + entry->d.nlen;
+static inline void lfs_pairfromle32(lfs_block_t *pair) {
+ pair[0] = lfs_fromle32(pair[0]);
+ pair[1] = lfs_fromle32(pair[1]);
+}
+
+static inline void lfs_pairtole32(lfs_block_t *pair) {
+ pair[0] = lfs_tole32(pair[0]);
+ pair[1] = lfs_tole32(pair[1]);
+}
+
+static void lfs_ctzfromle32(struct lfs_ctz *ctz) {
+ ctz->head = lfs_fromle32(ctz->head);
+ ctz->size = lfs_fromle32(ctz->size);
+}
+
+static void lfs_ctztole32(struct lfs_ctz *ctz) {
+ ctz->head = lfs_tole32(ctz->head);
+ ctz->size = lfs_tole32(ctz->size);
+}
+
+
+/// Entry tag operations ///
+#define LFS_MKTAG(type, id, size) \
+ (((uint32_t)(type) << 22) | ((uint32_t)(id) << 12) | (uint32_t)(size))
+
+#define LFS_MKATTR(type, id, buffer, size, next) \
+ &(const lfs_mattr_t){LFS_MKTAG(type, id, size), (buffer), (next)}
+
+static inline bool lfs_tagisvalid(uint32_t tag) {
+ return !(tag & 0x80000000);
+}
+
+static inline bool lfs_tagisuser(uint32_t tag) {
+ return (tag & 0x40000000);
+}
+
+static inline uint16_t lfs_tagtype(uint32_t tag) {
+ return (tag & 0x7fc00000) >> 22;
+}
+
+static inline uint16_t lfs_tagsubtype(uint32_t tag) {
+ return (tag & 0x7c000000) >> 22;
+}
+
+static inline uint16_t lfs_tagid(uint32_t tag) {
+ return (tag & 0x003ff000) >> 12;
+}
+
+static inline lfs_size_t lfs_tagsize(uint32_t tag) {
+ return tag & 0x00000fff;
+}
+
+// operations on set of globals
+static inline void lfs_globalxor(lfs_global_t *a, const lfs_global_t *b) {
+ for (int i = 0; i < sizeof(lfs_global_t)/4; i++) {
+ a->u32[i] ^= b->u32[i];
+ }
+}
+
+static inline bool lfs_globaliszero(const lfs_global_t *a) {
+ for (int i = 0; i < sizeof(lfs_global_t)/4; i++) {
+ if (a->u32[i] != 0) {
+ return false;
+ }
+ }
+ return true;
+}
+
+static inline void lfs_globalzero(lfs_global_t *a) {
+ lfs_globalxor(a, a);
+}
+
+static inline void lfs_globalfromle32(lfs_global_t *a) {
+ lfs_pairfromle32(a->s.movepair);
+ a->s.moveid = lfs_fromle16(a->s.moveid);
+}
+
+static inline void lfs_globaltole32(lfs_global_t *a) {
+ lfs_pairtole32(a->s.movepair);
+ a->s.moveid = lfs_tole16(a->s.moveid);
+}
+
+static inline void lfs_globalmove(lfs_t *lfs,
+ const lfs_block_t pair[2], uint16_t id) {
+ lfs_global_t diff;
+ lfs_globalzero(&diff);
+ diff.s.movepair[0] ^= lfs->globals.s.movepair[0] ^ pair[0];
+ diff.s.movepair[1] ^= lfs->globals.s.movepair[1] ^ pair[1];
+ diff.s.moveid ^= lfs->globals.s.moveid ^ id;
+ lfs_globalfromle32(&lfs->locals);
+ lfs_globalxor(&lfs->locals, &diff);
+ lfs_globaltole32(&lfs->locals);
+ lfs_globalxor(&lfs->globals, &diff);
+}
+
+static inline void lfs_globaldeorphaned(lfs_t *lfs, bool deorphaned) {
+ lfs->locals.s.deorphaned ^= lfs->globals.s.deorphaned ^ deorphaned;
+ lfs->globals.s.deorphaned ^= lfs->globals.s.deorphaned ^ deorphaned;
+}
+
+
+// commit logic
+struct lfs_commit {
+ lfs_block_t block;
+ lfs_off_t off;
+ uint32_t ptag;
+ uint32_t crc;
+
+ lfs_off_t begin;
+ lfs_off_t end;
+};
+
+struct lfs_diskoff {
+ lfs_block_t block;
+ lfs_off_t off;
+};
+
+static int32_t lfs_commitget(lfs_t *lfs, lfs_block_t block, lfs_off_t off,
+ uint32_t tag, uint32_t getmask, uint32_t gettag, int32_t getdiff,
+ void *buffer, bool stopatcommit) {
+ // iterate over dir block backwards (for faster lookups)
+ while (off >= 2*sizeof(tag)+lfs_tagsize(tag)) {
+ off -= sizeof(tag)+lfs_tagsize(tag);
+
+ if (lfs_tagtype(tag) == LFS_TYPE_CRC && stopatcommit) {
+ break;
+ } else if (lfs_tagtype(tag) == LFS_TYPE_DELETE) {
+ if (lfs_tagid(tag) <= lfs_tagid(gettag + getdiff)) {
+ getdiff += LFS_MKTAG(0, 1, 0);
+ }
+ } else if ((tag & getmask) == ((gettag + getdiff) & getmask)) {
+ if (buffer) {
+ lfs_size_t diff = lfs_min(
+ lfs_tagsize(gettag), lfs_tagsize(tag));
+ int err = lfs_bd_read(lfs, block,
+ off+sizeof(tag), buffer, diff);
+ if (err) {
+ return err;
+ }
+
+ memset((uint8_t*)buffer + diff, 0,
+ lfs_tagsize(gettag) - diff);
+ }
+
+ return tag - getdiff;
+ }
+
+ uint32_t ntag;
+ int err = lfs_bd_read(lfs, block, off, &ntag, sizeof(ntag));
+ if (err) {
+ return err;
+ }
+ tag ^= lfs_fromle32(ntag);
+ }
+
+ return LFS_ERR_NOENT;
+}
+
+static int lfs_commitattrs(lfs_t *lfs, struct lfs_commit *commit,
+ uint16_t id, const struct lfs_attr *attrs);
+
+static int lfs_commitmove(lfs_t *lfs, struct lfs_commit *commit,
+ uint16_t fromid, uint16_t toid,
+ const lfs_mdir_t *dir, const lfs_mattr_t *attrs);
+
+static int lfs_commitattr(lfs_t *lfs, struct lfs_commit *commit,
+ uint32_t tag, const void *buffer) {
+ if (lfs_tagtype(tag) == LFS_FROM_ATTRS) {
+ // special case for custom attributes
+ return lfs_commitattrs(lfs, commit,
+ lfs_tagid(tag), buffer);
+ } else if (lfs_tagtype(tag) == LFS_FROM_MOVE) {
+ // special case for moves
+ return lfs_commitmove(lfs, commit,
+ lfs_tagsize(tag), lfs_tagid(tag),
+ buffer, NULL);
+ }
+
+ // check if we fit
+ lfs_size_t size = lfs_tagsize(tag);
+ if (commit->off + sizeof(tag)+size > commit->end) {
+ return LFS_ERR_NOSPC;
+ }
+
+ // write out tag
+ uint32_t ntag = lfs_tole32((tag & 0x7fffffff) ^ commit->ptag);
+ lfs_crc(&commit->crc, &ntag, sizeof(ntag));
+ int err = lfs_bd_prog(lfs, commit->block, commit->off,
+ &ntag, sizeof(ntag));
+ if (err) {
+ return err;
+ }
+ commit->off += sizeof(ntag);
+
+ if (!(tag & 0x80000000)) {
+ // from memory
+ lfs_crc(&commit->crc, buffer, size);
+ err = lfs_bd_prog(lfs, commit->block, commit->off, buffer, size);
+ if (err) {
+ return err;
+ }
+ } else {
+ // from disk
+ const struct lfs_diskoff *disk = buffer;
+ for (lfs_off_t i = 0; i < size; i++) {
+ // rely on caching to make this efficient
+ uint8_t dat;
+ err = lfs_bd_read(lfs, disk->block, disk->off+i, &dat, 1);
+ if (err) {
+ return err;
+ }
+
+ lfs_crc(&commit->crc, &dat, 1);
+ err = lfs_bd_prog(lfs, commit->block, commit->off+i, &dat, 1);
+ if (err) {
+ return err;
+ }
+ }
+ }
+
+ commit->off += size;
+ commit->ptag = tag & 0x7fffffff;
+ return 0;
+}
+
+static int lfs_commitattrs(lfs_t *lfs, struct lfs_commit *commit,
+ uint16_t id, const struct lfs_attr *attrs) {
+ for (const struct lfs_attr *a = attrs; a; a = a->next) {
+ int err = lfs_commitattr(lfs, commit,
+ LFS_MKTAG(0x100 | a->type, id, a->size), a->buffer);
+ if (err) {
+ return err;
+ }
+ }
+
+ return 0;
+}
+
+static int lfs_commitmove(lfs_t *lfs, struct lfs_commit *commit,
+ uint16_t fromid, uint16_t toid,
+ const lfs_mdir_t *dir, const lfs_mattr_t *attrs) {
+ // iterate through list and commits, only committing unique entries
+ lfs_off_t off = dir->off;
+ uint32_t ntag = dir->etag;
+ while (attrs || off > sizeof(uint32_t)) {
+ struct lfs_diskoff disk;
+ uint32_t tag;
+ const void *buffer;
+ if (attrs) {
+ tag = attrs->tag;
+ buffer = attrs->buffer;
+ attrs = attrs->next;
+ } else {
+ LFS_ASSERT(off > sizeof(ntag)+lfs_tagsize(ntag));
+ off -= sizeof(ntag)+lfs_tagsize(ntag);
+
+ tag = ntag;
+ buffer = &disk;
+ disk.block = dir->pair[0];
+ disk.off = off + sizeof(tag);
+
+ int err = lfs_bd_read(lfs, dir->pair[0], off, &ntag, sizeof(ntag));
+ if (err) {
+ return err;
+ }
+
+ ntag = lfs_fromle32(ntag);
+ ntag ^= tag;
+ tag |= 0x80000000;
+ }
+
+ if (lfs_tagtype(tag) == LFS_TYPE_DELETE && lfs_tagid(tag) <= fromid) {
+ // something was deleted, we need to move around it
+ fromid += 1;
+ } else if (lfs_tagid(tag) != fromid) {
+ // ignore non-matching ids
+ } else {
+ // check if type has already been committed
+ int32_t res = lfs_commitget(lfs, commit->block,
+ commit->off, commit->ptag,
+ lfs_tagisuser(tag) ? 0x7ffff000 : 0x7c3ff000,
+ LFS_MKTAG(lfs_tagtype(tag), toid, 0),
+ 0, NULL, true);
+ if (res < 0 && res != LFS_ERR_NOENT) {
+ return res;
+ }
+
+ if (res == LFS_ERR_NOENT) {
+ // update id and commit, as we are currently unique
+ int err = lfs_commitattr(lfs, commit,
+ (tag & 0xffc00fff) | LFS_MKTAG(0, toid, 0),
+ buffer);
+ if (err) {
+ return err;
+ }
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int lfs_commitglobals(lfs_t *lfs, struct lfs_commit *commit,
+ lfs_global_t *locals) {
+ if (lfs_globaliszero(&lfs->locals)) {
+ return 0;
+ }
+
+ lfs_globalxor(locals, &lfs->locals);
+ int err = lfs_commitattr(lfs, commit,
+ LFS_MKTAG(LFS_TYPE_GLOBALS + locals->s.deorphaned,
+ 0x3ff, sizeof(lfs_global_t)), locals);
+ lfs_globalxor(locals, &lfs->locals);
+ return err;
+}
+
+static int lfs_commitcrc(lfs_t *lfs, struct lfs_commit *commit) {
+ // align to program units
+ lfs_off_t off = lfs_alignup(commit->off + 2*sizeof(uint32_t),
+ lfs->cfg->prog_size);
+
+ // read erased state from next program unit
+ uint32_t tag;
+ int err = lfs_bd_read(lfs, commit->block, off, &tag, sizeof(tag));
+ if (err) {
+ return err;
+ }
+
+ // build crc tag
+ tag = lfs_fromle32(tag);
+ tag = (0x80000000 & ~tag) |
+ LFS_MKTAG(LFS_TYPE_CRC, 0x3ff,
+ off - (commit->off+sizeof(uint32_t)));
+
+ // write out crc
+ uint32_t footer[2];
+ footer[0] = lfs_tole32(tag ^ commit->ptag);
+ lfs_crc(&commit->crc, &footer[0], sizeof(footer[0]));
+ footer[1] = lfs_tole32(commit->crc);
+ err = lfs_bd_prog(lfs, commit->block, commit->off, footer, sizeof(footer));
+ if (err) {
+ return err;
+ }
+ commit->off += sizeof(tag)+lfs_tagsize(tag);
+ commit->ptag = tag;
+
+ // flush buffers
+ err = lfs_bd_sync(lfs);
+ if (err) {
+ return err;
+ }
+
+ // successful commit, check checksum to make sure
+ uint32_t crc = 0xffffffff;
+ err = lfs_bd_crc(lfs, commit->block, commit->begin,
+ commit->off-lfs_tagsize(tag)-commit->begin, &crc);
+ if (err) {
+ return err;
+ }
+
+ if (crc != commit->crc) {
+ return LFS_ERR_CORRUPT;
+ }
+
+ return 0;
}
-static int lfs_dir_alloc(lfs_t *lfs, lfs_dir_t *dir) {
- // allocate pair of dir blocks
+// internal dir operations
+static int lfs_dir_alloc(lfs_t *lfs, lfs_mdir_t *dir) {
+ // allocate pair of dir blocks (backwards, so we write to block 1 first)
for (int i = 0; i < 2; i++) {
- int err = lfs_alloc(lfs, &dir->pair[i]);
+ int err = lfs_alloc(lfs, &dir->pair[(i+1)%2]);
if (err) {
return err;
}
@@ -416,112 +736,267 @@ static int lfs_dir_alloc(lfs_t *lfs, lfs_dir_t *dir) {
// rather than clobbering one of the blocks we just pretend
// the revision may be valid
- int err = lfs_bd_read(lfs, dir->pair[0], 0, &dir->d.rev, 4);
+ int err = lfs_bd_read(lfs, dir->pair[0], 0, &dir->rev, 4);
+ dir->rev = lfs_fromle32(dir->rev);
if (err && err != LFS_ERR_CORRUPT) {
return err;
}
- if (err != LFS_ERR_CORRUPT) {
- dir->d.rev = lfs_fromle32(dir->d.rev);
- }
-
// set defaults
- dir->d.rev += 1;
- dir->d.size = sizeof(dir->d)+4;
- dir->d.tail[0] = 0xffffffff;
- dir->d.tail[1] = 0xffffffff;
- dir->off = sizeof(dir->d);
+ dir->off = sizeof(dir->rev);
+ dir->etag = 0;
+ dir->count = 0;
+ dir->tail[0] = 0xffffffff;
+ dir->tail[1] = 0xffffffff;
+ dir->erased = false;
+ dir->split = false;
+ lfs_globalzero(&dir->locals);
// don't write out yet, let caller take care of that
return 0;
}
-static int lfs_dir_fetch(lfs_t *lfs,
- lfs_dir_t *dir, const lfs_block_t pair[2]) {
- // copy out pair, otherwise may be aliasing dir
- const lfs_block_t tpair[2] = {pair[0], pair[1]};
- bool valid = false;
+static int32_t lfs_dir_find(lfs_t *lfs,
+ lfs_mdir_t *dir, const lfs_block_t pair[2],
+ uint32_t findmask, uint32_t findtag,
+ const void *findbuffer) {
+ dir->pair[0] = pair[0];
+ dir->pair[1] = pair[1];
+ int32_t foundtag = LFS_ERR_NOENT;
- // check both blocks for the most recent revision
+ // find the block with the most recent revision
+ uint32_t rev[2];
for (int i = 0; i < 2; i++) {
- struct lfs_disk_dir test;
- int err = lfs_bd_read(lfs, tpair[i], 0, &test, sizeof(test));
- lfs_dir_fromle32(&test);
- if (err) {
- if (err == LFS_ERR_CORRUPT) {
- continue;
- }
+ int err = lfs_cache_read(lfs, &lfs->pcache, &lfs->rcache, false,
+ dir->pair[i], 0, &rev[i], sizeof(rev[i]));
+ rev[i] = lfs_fromle32(rev[i]);
+ if (err && err != LFS_ERR_CORRUPT) {
return err;
}
- if (valid && lfs_scmp(test.rev, dir->d.rev) < 0) {
- continue;
+ if (err == LFS_ERR_CORRUPT) {
+ rev[i] = rev[(i+1)%2] - 1;
}
+ }
- if ((0x7fffffff & test.size) < sizeof(test)+4 ||
- (0x7fffffff & test.size) > lfs->cfg->block_size) {
- continue;
- }
+ if (lfs_scmp(rev[1], rev[0]) > 0) {
+ lfs_pairswap(dir->pair);
+ lfs_pairswap(rev);
+ }
+ // load blocks and check crc
+ for (int i = 0; i < 2; i++) {
+ lfs_off_t off = sizeof(dir->rev);
+ uint32_t ptag = 0;
uint32_t crc = 0xffffffff;
- lfs_dir_tole32(&test);
- lfs_crc(&crc, &test, sizeof(test));
- lfs_dir_fromle32(&test);
- err = lfs_bd_crc(lfs, tpair[i], sizeof(test),
- (0x7fffffff & test.size) - sizeof(test), &crc);
- if (err) {
- if (err == LFS_ERR_CORRUPT) {
- continue;
+
+ dir->rev = lfs_tole32(rev[0]);
+ lfs_crc(&crc, &dir->rev, sizeof(dir->rev));
+ dir->rev = lfs_fromle32(dir->rev);
+ dir->off = 0;
+
+ uint32_t tempfoundtag = foundtag;
+ uint16_t tempcount = 0;
+ lfs_block_t temptail[2] = {0xffffffff, 0xffffffff};
+ bool tempsplit = false;
+ lfs_global_t templocals;
+ lfs_globalzero(&templocals);
+
+ while (true) {
+ // extract next tag
+ uint32_t tag;
+ int err = lfs_bd_read(lfs, dir->pair[0],
+ off, &tag, sizeof(tag));
+ if (err) {
+ if (err == LFS_ERR_CORRUPT) {
+ // can't continue?
+ dir->erased = false;
+ break;
+ }
+ return err;
}
- return err;
- }
- if (crc != 0) {
- continue;
+ lfs_crc(&crc, &tag, sizeof(tag));
+ tag = lfs_fromle32(tag) ^ ptag;
+
+ // next commit not yet programmed
+ if (lfs_tagtype(ptag) == LFS_TYPE_CRC && !lfs_tagisvalid(tag)) {
+ dir->erased = true;
+ break;
+ }
+
+ // check we're in valid range
+ if (off + sizeof(tag)+lfs_tagsize(tag) > lfs->cfg->block_size) {
+ dir->erased = false;
+ break;
+ }
+
+ if (lfs_tagtype(tag) == LFS_TYPE_CRC) {
+ // check the crc attr
+ uint32_t dcrc;
+ err = lfs_bd_read(lfs, dir->pair[0],
+ off+sizeof(tag), &dcrc, sizeof(dcrc));
+ if (err) {
+ if (err == LFS_ERR_CORRUPT) {
+ dir->erased = false;
+ break;
+ }
+ return err;
+ }
+ dcrc = lfs_fromle32(dcrc);
+
+ if (crc != dcrc) {
+ dir->erased = false;
+ break;
+ }
+
+ foundtag = tempfoundtag;
+ dir->off = off + sizeof(tag)+lfs_tagsize(tag);
+ dir->etag = tag;
+ dir->count = tempcount;
+ dir->tail[0] = temptail[0];
+ dir->tail[1] = temptail[1];
+ dir->split = tempsplit;
+ dir->locals = templocals;
+ crc = 0xffffffff;
+ } else {
+ err = lfs_bd_crc(lfs, dir->pair[0],
+ off+sizeof(tag), lfs_tagsize(tag), &crc);
+ if (err) {
+ if (err == LFS_ERR_CORRUPT) {
+ dir->erased = false;
+ break;
+ }
+ }
+
+ if (lfs_tagid(tag) < 0x3ff && lfs_tagid(tag) >= tempcount) {
+ tempcount = lfs_tagid(tag)+1;
+ }
+
+ if (lfs_tagsubtype(tag) == LFS_TYPE_TAIL) {
+ tempsplit = (lfs_tagtype(tag) & 1);
+ err = lfs_bd_read(lfs, dir->pair[0], off+sizeof(tag),
+ temptail, sizeof(temptail));
+ if (err) {
+ if (err == LFS_ERR_CORRUPT) {
+ dir->erased = false;
+ break;
+ }
+ }
+ lfs_pairfromle32(temptail);
+ } else if (lfs_tagsubtype(tag) == LFS_TYPE_GLOBALS) {
+ templocals.s.deorphaned = (lfs_tagtype(tag) & 1);
+ err = lfs_bd_read(lfs, dir->pair[0], off+sizeof(tag),
+ &templocals, sizeof(templocals));
+ if (err) {
+ if (err == LFS_ERR_CORRUPT) {
+ dir->erased = false;
+ break;
+ }
+ }
+ } else if (lfs_tagsubtype(tag) == LFS_TYPE_DELETE) {
+ LFS_ASSERT(tempcount > 0);
+ tempcount -= 1;
+
+ if (lfs_tagid(tag) == lfs_tagid(tempfoundtag)) {
+ tempfoundtag = LFS_ERR_NOENT;
+ } else if (lfs_tagisvalid(tempfoundtag) &&
+ lfs_tagid(tag) < lfs_tagid(tempfoundtag)) {
+ tempfoundtag -= LFS_MKTAG(0, 1, 0);
+ }
+ } else if ((tag & findmask) == (findtag & findmask)) {
+ int res = lfs_bd_cmp(lfs, dir->pair[0], off+sizeof(tag),
+ findbuffer, lfs_tagsize(tag));
+ if (res < 0) {
+ if (res == LFS_ERR_CORRUPT) {
+ dir->erased = false;
+ break;
+ }
+ return res;
+ }
+
+ if (res) {
+ // found a match
+ tempfoundtag = tag;
+ }
+ }
+ }
+
+ ptag = tag;
+ off += sizeof(tag)+lfs_tagsize(tag);
}
- valid = true;
+ // consider what we have good enough
+ if (dir->off > 0) {
+ // synthetic move
+ if (lfs_paircmp(dir->pair, lfs->globals.s.movepair) == 0) {
+ if (lfs->globals.s.moveid == lfs_tagid(foundtag)) {
+ foundtag = LFS_ERR_NOENT;
+ } else if (lfs_tagisvalid(foundtag) &&
+ lfs->globals.s.moveid < lfs_tagid(foundtag)) {
+ foundtag -= LFS_MKTAG(0, 1, 0);
+ }
+ }
- // setup dir in case it's valid
- dir->pair[0] = tpair[(i+0) % 2];
- dir->pair[1] = tpair[(i+1) % 2];
- dir->off = sizeof(dir->d);
- dir->d = test;
- }
+ return foundtag;
+ }
- if (!valid) {
- LFS_ERROR("Corrupted dir pair at %" PRIu32 " %" PRIu32 ,
- tpair[0], tpair[1]);
- return LFS_ERR_CORRUPT;
+ // failed, try the other crc?
+ lfs_pairswap(dir->pair);
+ lfs_pairswap(rev);
}
- return 0;
+ LFS_ERROR("Corrupted dir pair at %"PRIu32" %"PRIu32,
+ dir->pair[0], dir->pair[1]);
+ return LFS_ERR_CORRUPT;
}
-struct lfs_region {
- lfs_off_t oldoff;
- lfs_size_t oldlen;
- const void *newdata;
- lfs_size_t newlen;
-};
+static int lfs_dir_fetch(lfs_t *lfs,
+ lfs_mdir_t *dir, const lfs_block_t pair[2]) {
+ int32_t res = lfs_dir_find(lfs, dir, pair, 0xffffffff, 0xffffffff, NULL);
+ if (res < 0 && res != LFS_ERR_NOENT) {
+ return res;
+ }
-static int lfs_dir_commit(lfs_t *lfs, lfs_dir_t *dir,
- const struct lfs_region *regions, int count) {
- // increment revision count
- dir->d.rev += 1;
+ return 0;
+}
- // keep pairs in order such that pair[0] is most recent
- lfs_pairswap(dir->pair);
- for (int i = 0; i < count; i++) {
- dir->d.size += regions[i].newlen - regions[i].oldlen;
+static int32_t lfs_dir_get(lfs_t *lfs, lfs_mdir_t *dir,
+ uint32_t getmask, uint32_t gettag, void *buffer) {
+ int32_t getdiff = 0;
+ if (lfs_paircmp(dir->pair, lfs->globals.s.movepair) == 0 &&
+ lfs_tagid(gettag) <= lfs->globals.s.moveid) {
+ // synthetic moves
+ getdiff = LFS_MKTAG(0, 1, 0);
}
- const lfs_block_t oldpair[2] = {dir->pair[0], dir->pair[1]};
+ return lfs_commitget(lfs, dir->pair[0], dir->off, dir->etag,
+ getmask, gettag, getdiff, buffer, false);
+}
+
+static int lfs_dir_compact(lfs_t *lfs,
+ lfs_mdir_t *dir, const lfs_mattr_t *attrs,
+ lfs_mdir_t *source, uint16_t begin, uint16_t end) {
+ // save some state in case block is bad
+ const lfs_block_t oldpair[2] = {dir->pair[1], dir->pair[0]};
bool relocated = false;
+ // There's nothing special about our global delta, so feed it back
+ // into the global global delta
+ lfs_globalxor(&lfs->locals, &dir->locals);
+ lfs_globalzero(&dir->locals);
+
+ // increment revision count
+ dir->rev += 1;
+
while (true) {
+ // last complete id
+ int16_t ack = -1;
+ dir->count = end - begin;
+
if (true) {
- int err = lfs_bd_erase(lfs, dir->pair[0]);
+ // erase block to write to
+ int err = lfs_bd_erase(lfs, dir->pair[1]);
if (err) {
if (err == LFS_ERR_CORRUPT) {
goto relocate;
@@ -529,11 +1004,11 @@ static int lfs_dir_commit(lfs_t *lfs, lfs_dir_t *dir,
return err;
}
+ // write out header
uint32_t crc = 0xffffffff;
- lfs_dir_tole32(&dir->d);
- lfs_crc(&crc, &dir->d, sizeof(dir->d));
- err = lfs_bd_prog(lfs, dir->pair[0], 0, &dir->d, sizeof(dir->d));
- lfs_dir_fromle32(&dir->d);
+ uint32_t rev = lfs_tole32(dir->rev);
+ lfs_crc(&crc, &rev, sizeof(rev));
+ err = lfs_bd_prog(lfs, dir->pair[1], 0, &rev, sizeof(rev));
if (err) {
if (err == LFS_ERR_CORRUPT) {
goto relocate;
@@ -541,56 +1016,66 @@ static int lfs_dir_commit(lfs_t *lfs, lfs_dir_t *dir,
return err;
}
- int i = 0;
- lfs_off_t oldoff = sizeof(dir->d);
- lfs_off_t newoff = sizeof(dir->d);
- while (newoff < (0x7fffffff & dir->d.size)-4) {
- if (i < count && regions[i].oldoff == oldoff) {
- lfs_crc(&crc, regions[i].newdata, regions[i].newlen);
- err = lfs_bd_prog(lfs, dir->pair[0],
- newoff, regions[i].newdata, regions[i].newlen);
- if (err) {
- if (err == LFS_ERR_CORRUPT) {
- goto relocate;
- }
- return err;
+ // setup compaction
+ struct lfs_commit commit = {
+ .block = dir->pair[1],
+ .off = sizeof(dir->rev),
+ .crc = crc,
+ .ptag = 0,
+
+ // space is complicated, we need room for tail, crc, globals,
+ // and we cap at half a block to give room for metadata updates
+ .begin = 0,
+ .end = lfs_min(
+ lfs_alignup(lfs->cfg->block_size/2, lfs->cfg->prog_size),
+ lfs->cfg->block_size - 34),
+ };
+
+ // commit with a move
+ for (uint16_t id = begin; id < end; id++) {
+ err = lfs_commitmove(lfs, &commit,
+ id, id - begin, source, attrs);
+ if (err) {
+ if (err == LFS_ERR_NOSPC) {
+ goto split;
+ } else if (err == LFS_ERR_CORRUPT) {
+ goto relocate;
}
+ return err;
+ }
- oldoff += regions[i].oldlen;
- newoff += regions[i].newlen;
- i += 1;
- } else {
- uint8_t data;
- err = lfs_bd_read(lfs, oldpair[1], oldoff, &data, 1);
- if (err) {
- return err;
- }
+ ack = id;
+ }
- lfs_crc(&crc, &data, 1);
- err = lfs_bd_prog(lfs, dir->pair[0], newoff, &data, 1);
- if (err) {
- if (err == LFS_ERR_CORRUPT) {
- goto relocate;
- }
- return err;
- }
+ // reopen reserved space at the end
+ commit.end = lfs->cfg->block_size - 8;
- oldoff += 1;
- newoff += 1;
+ if (!relocated) {
+ err = lfs_commitglobals(lfs, &commit, &dir->locals);
+ if (err) {
+ if (err == LFS_ERR_CORRUPT) {
+ goto relocate;
+ }
+ return err;
}
}
- crc = lfs_tole32(crc);
- err = lfs_bd_prog(lfs, dir->pair[0], newoff, &crc, 4);
- crc = lfs_fromle32(crc);
- if (err) {
- if (err == LFS_ERR_CORRUPT) {
- goto relocate;
+ if (!lfs_pairisnull(dir->tail)) {
+ // commit tail, which may be new after last size check
+ lfs_pairtole32(dir->tail);
+ err = lfs_commitattr(lfs, &commit,
+ LFS_MKTAG(LFS_TYPE_TAIL + dir->split,
+ 0x3ff, sizeof(dir->tail)), dir->tail);
+ lfs_pairfromle32(dir->tail);
+ if (err) {
+ if (err == LFS_ERR_CORRUPT) {
+ goto relocate;
+ }
+ return err;
}
- return err;
}
- err = lfs_bd_sync(lfs);
+ err = lfs_commitcrc(lfs, &commit);
if (err) {
if (err == LFS_ERR_CORRUPT) {
goto relocate;
@@ -598,23 +1083,49 @@ static int lfs_dir_commit(lfs_t *lfs, lfs_dir_t *dir,
return err;
}
- // successful commit, check checksum to make sure
- uint32_t ncrc = 0xffffffff;
- err = lfs_bd_crc(lfs, dir->pair[0], 0,
- (0x7fffffff & dir->d.size)-4, &ncrc);
- if (err) {
- return err;
- }
+ // successful compaction, swap dir pair to indicate most recent
+ lfs_pairswap(dir->pair);
+ dir->off = commit.off;
+ dir->etag = commit.ptag;
+ dir->erased = true;
+ }
+ break;
- if (ncrc != crc) {
- goto relocate;
- }
+split:
+ // commit no longer fits, need to split dir,
+ // drop caches and create tail
+ lfs_cache_drop(lfs, &lfs->pcache);
+
+ if (ack == -1) {
+ // If we can't fit in this block, we won't fit in next block
+ return LFS_ERR_NOSPC;
}
- break;
+ lfs_mdir_t tail;
+ int err = lfs_dir_alloc(lfs, &tail);
+ if (err) {
+ return err;
+ }
+
+ tail.split = dir->split;
+ tail.tail[0] = dir->tail[0];
+ tail.tail[1] = dir->tail[1];
+
+ err = lfs_dir_compact(lfs, &tail, attrs, dir, ack+1, end);
+ if (err) {
+ return err;
+ }
+
+ end = ack+1;
+ dir->tail[0] = tail.pair[0];
+ dir->tail[1] = tail.pair[1];
+ dir->split = true;
+ continue;
+
relocate:
//commit was corrupted
- LFS_DEBUG("Bad block at %" PRIu32, dir->pair[0]);
+ LFS_DEBUG("Bad block at %"PRIu32,
+ dir->pair[1]);
// drop caches and prepare to relocate block
relocated = true;
@@ -622,143 +1133,184 @@ relocate:
// can't relocate superblock, filesystem is now frozen
if (lfs_paircmp(oldpair, (const lfs_block_t[2]){0, 1}) == 0) {
- LFS_WARN("Superblock %" PRIu32 " has become unwritable",
- oldpair[0]);
+ LFS_WARN("Superblock %"PRIu32" has become unwritable", oldpair[1]);
return LFS_ERR_CORRUPT;
}
// relocate half of pair
- int err = lfs_alloc(lfs, &dir->pair[0]);
+ err = lfs_alloc(lfs, &dir->pair[1]);
if (err) {
return err;
}
+
+ continue;
}
- if (relocated) {
+ if (!relocated) {
+ // successful commit, update globals
+ lfs_globalxor(&dir->locals, &lfs->locals);
+ lfs_globalzero(&lfs->locals);
+ } else {
// update references if we relocated
- LFS_DEBUG("Relocating %" PRIu32 " %" PRIu32 " to %" PRIu32 " %" PRIu32,
+ LFS_DEBUG("Relocating %"PRIu32" %"PRIu32" to %"PRIu32" %"PRIu32,
oldpair[0], oldpair[1], dir->pair[0], dir->pair[1]);
- int err = lfs_relocate(lfs, oldpair, dir->pair);
+ int err = lfs_fs_relocate(lfs, oldpair, dir->pair);
if (err) {
return err;
}
}
- // shift over any directories that are affected
- for (lfs_dir_t *d = lfs->dirs; d; d = d->next) {
- if (lfs_paircmp(d->pair, dir->pair) == 0) {
- d->pair[0] = dir->pair[0];
- d->pair[1] = dir->pair[1];
- }
- }
-
return 0;
}
-static int lfs_dir_update(lfs_t *lfs, lfs_dir_t *dir,
- lfs_entry_t *entry, const void *data) {
- lfs_entry_tole32(&entry->d);
- int err = lfs_dir_commit(lfs, dir, (struct lfs_region[]){
- {entry->off, sizeof(entry->d), &entry->d, sizeof(entry->d)},
- {entry->off+sizeof(entry->d), entry->d.nlen, data, entry->d.nlen}
- }, data ? 2 : 1);
- lfs_entry_fromle32(&entry->d);
- return err;
-}
+static int lfs_dir_commit(lfs_t *lfs, lfs_mdir_t *dir,
+ const lfs_mattr_t *attrs) {
+ lfs_mattr_t cancelattr;
+ lfs_global_t canceldiff;
+ lfs_globalzero(&canceldiff);
+ if (lfs_paircmp(dir->pair, lfs->globals.s.movepair) == 0) {
+ // Wait, we have the move? Just cancel this out here
+ // We need to, or else the move can become outdated
+ canceldiff.s.movepair[0] ^= lfs->globals.s.movepair[0] ^ 0xffffffff;
+ canceldiff.s.movepair[1] ^= lfs->globals.s.movepair[1] ^ 0xffffffff;
+ canceldiff.s.moveid ^= lfs->globals.s.moveid ^ 0x3ff;
+ lfs_globalfromle32(&lfs->locals);
+ lfs_globalxor(&lfs->locals, &canceldiff);
+ lfs_globaltole32(&lfs->locals);
+
+ cancelattr.tag = LFS_MKTAG(LFS_TYPE_DELETE, lfs->globals.s.moveid, 0);
+ cancelattr.next = attrs;
+ attrs = &cancelattr;
+ }
+
+ // calculate new directory size
+ uint32_t deletetag = 0xffffffff;
+ for (const lfs_mattr_t *a = attrs; a; a = a->next) {
+ if (lfs_tagid(a->tag) < 0x3ff && lfs_tagid(a->tag) >= dir->count) {
+ dir->count = lfs_tagid(a->tag)+1;
+ }
+
+ if (lfs_tagtype(a->tag) == LFS_TYPE_DELETE) {
+ LFS_ASSERT(dir->count > 0);
+ dir->count -= 1;
+ deletetag = a->tag;
+
+ if (dir->count == 0) {
+ // should we actually drop the directory block?
+ lfs_mdir_t pdir;
+ int err = lfs_fs_pred(lfs, dir->pair, &pdir);
+ if (err && err != LFS_ERR_NOENT) {
+ return err;
+ }
-static int lfs_dir_append(lfs_t *lfs, lfs_dir_t *dir,
- lfs_entry_t *entry, const void *data) {
- // check if we fit, if top bit is set we do not and move on
- while (true) {
- if (dir->d.size + lfs_entry_size(entry) <= lfs->cfg->block_size) {
- entry->off = dir->d.size - 4;
-
- lfs_entry_tole32(&entry->d);
- int err = lfs_dir_commit(lfs, dir, (struct lfs_region[]){
- {entry->off, 0, &entry->d, sizeof(entry->d)},
- {entry->off, 0, data, entry->d.nlen}
- }, 2);
- lfs_entry_fromle32(&entry->d);
- return err;
+ if (err != LFS_ERR_NOENT && pdir.split) {
+ // steal tail and global state
+ pdir.split = dir->split;
+ pdir.tail[0] = dir->tail[0];
+ pdir.tail[1] = dir->tail[1];
+ lfs_globalxor(&lfs->locals, &dir->locals);
+ return lfs_dir_commit(lfs, &pdir,
+ LFS_MKATTR(LFS_TYPE_TAIL + pdir.split, 0x3ff,
+ pdir.tail, sizeof(pdir.tail),
+ NULL));
+ }
+ }
}
+ }
- // we need to allocate a new dir block
- if (!(0x80000000 & dir->d.size)) {
- lfs_dir_t olddir = *dir;
- int err = lfs_dir_alloc(lfs, dir);
- if (err) {
- return err;
+ if (dir->erased) {
+ // try to commit
+ struct lfs_commit commit = {
+ .block = dir->pair[0],
+ .off = dir->off,
+ .crc = 0xffffffff,
+ .ptag = dir->etag,
+
+ .begin = dir->off,
+ .end = lfs->cfg->block_size - 8,
+ };
+
+ for (const lfs_mattr_t *a = attrs; a; a = a->next) {
+ if (lfs_tagtype(a->tag) != LFS_TYPE_DELETE) {
+ lfs_pairtole32(dir->tail);
+ int err = lfs_commitattr(lfs, &commit, a->tag, a->buffer);
+ lfs_pairfromle32(dir->tail);
+ if (err) {
+ if (err == LFS_ERR_NOSPC || err == LFS_ERR_CORRUPT) {
+ goto compact;
+ }
+ return err;
+ }
}
+ }
- dir->d.tail[0] = olddir.d.tail[0];
- dir->d.tail[1] = olddir.d.tail[1];
- entry->off = dir->d.size - 4;
- lfs_entry_tole32(&entry->d);
- err = lfs_dir_commit(lfs, dir, (struct lfs_region[]){
- {entry->off, 0, &entry->d, sizeof(entry->d)},
- {entry->off, 0, data, entry->d.nlen}
- }, 2);
- lfs_entry_fromle32(&entry->d);
+ if (lfs_tagisvalid(deletetag)) {
+ // special case for deletes, since order matters
+ int err = lfs_commitattr(lfs, &commit, deletetag, NULL);
if (err) {
+ if (err == LFS_ERR_NOSPC || err == LFS_ERR_CORRUPT) {
+ goto compact;
+ }
return err;
}
-
- olddir.d.size |= 0x80000000;
- olddir.d.tail[0] = dir->pair[0];
- olddir.d.tail[1] = dir->pair[1];
- return lfs_dir_commit(lfs, &olddir, NULL, 0);
}
- int err = lfs_dir_fetch(lfs, dir, dir->d.tail);
+ int err = lfs_commitglobals(lfs, &commit, &dir->locals);
if (err) {
+ if (err == LFS_ERR_NOSPC || err == LFS_ERR_CORRUPT) {
+ goto compact;
+ }
return err;
}
- }
-}
-static int lfs_dir_remove(lfs_t *lfs, lfs_dir_t *dir, lfs_entry_t *entry) {
- // check if we should just drop the directory block
- if ((dir->d.size & 0x7fffffff) == sizeof(dir->d)+4
- + lfs_entry_size(entry)) {
- lfs_dir_t pdir;
- int res = lfs_pred(lfs, dir->pair, &pdir);
- if (res < 0) {
- return res;
+ err = lfs_commitcrc(lfs, &commit);
+ if (err) {
+ if (err == LFS_ERR_NOSPC || err == LFS_ERR_CORRUPT) {
+ goto compact;
+ }
+ return err;
}
- if (pdir.d.size & 0x80000000) {
- pdir.d.size &= dir->d.size | 0x7fffffff;
- pdir.d.tail[0] = dir->d.tail[0];
- pdir.d.tail[1] = dir->d.tail[1];
- return lfs_dir_commit(lfs, &pdir, NULL, 0);
+ // successful commit, update dir
+ dir->off = commit.off;
+ dir->etag = commit.ptag;
+ // successful commit, update globals
+ lfs_globalxor(&dir->locals, &lfs->locals);
+ lfs_globalzero(&lfs->locals);
+ } else {
+compact:
+ // fall back to compaction
+ lfs_cache_drop(lfs, &lfs->pcache);
+ int err = lfs_dir_compact(lfs, dir, attrs, dir, 0, dir->count);
+ if (err) {
+ return err;
}
}
- // shift out the entry
- int err = lfs_dir_commit(lfs, dir, (struct lfs_region[]){
- {entry->off, lfs_entry_size(entry), NULL, 0},
- }, 1);
- if (err) {
- return err;
- }
+ // update globals that are affected
+ lfs_globalxor(&lfs->globals, &canceldiff);
- // shift over any files/directories that are affected
- for (lfs_file_t *f = lfs->files; f; f = f->next) {
- if (lfs_paircmp(f->pair, dir->pair) == 0) {
- if (f->poff == entry->off) {
- f->pair[0] = 0xffffffff;
- f->pair[1] = 0xffffffff;
- } else if (f->poff > entry->off) {
- f->poff -= lfs_entry_size(entry);
+ // update any directories that are affected
+ for (lfs_mlist_t *d = lfs->mlist; d; d = d->next) {
+ if (lfs_paircmp(d->m.pair, dir->pair) == 0) {
+ d->m = *dir;
+ if (d->id == lfs_tagid(deletetag)) {
+ d->m.pair[0] = 0xffffffff;
+ d->m.pair[1] = 0xffffffff;
+ } else if (d->id > lfs_tagid(deletetag)) {
+ d->id -= 1;
+ if (d->type == LFS_TYPE_DIR) {
+ ((lfs_dir_t*)d)->pos -= 1;
+ }
}
- }
- }
- for (lfs_dir_t *d = lfs->dirs; d; d = d->next) {
- if (lfs_paircmp(d->pair, dir->pair) == 0) {
- if (d->off > entry->off) {
- d->off -= lfs_entry_size(entry);
- d->pos -= lfs_entry_size(entry);
+ while (d->id >= d->m.count && d->m.split) {
+ // we split and id is on tail now
+ d->id -= d->m.count;
+ int err = lfs_dir_fetch(lfs, &d->m, d->m.tail);
+ if (err) {
+ return err;
+ }
}
}
}
@@ -766,62 +1318,31 @@ static int lfs_dir_remove(lfs_t *lfs, lfs_dir_t *dir, lfs_entry_t *entry) {
return 0;
}
-static int lfs_dir_next(lfs_t *lfs, lfs_dir_t *dir, lfs_entry_t *entry) {
- while (dir->off + sizeof(entry->d) > (0x7fffffff & dir->d.size)-4) {
- if (!(0x80000000 & dir->d.size)) {
- entry->off = dir->off;
- return LFS_ERR_NOENT;
- }
-
- int err = lfs_dir_fetch(lfs, dir, dir->d.tail);
- if (err) {
- return err;
- }
-
- dir->off = sizeof(dir->d);
- dir->pos += sizeof(dir->d) + 4;
- }
-
- int err = lfs_bd_read(lfs, dir->pair[0], dir->off,
- &entry->d, sizeof(entry->d));
- lfs_entry_fromle32(&entry->d);
- if (err) {
- return err;
- }
-
- entry->off = dir->off;
- dir->off += lfs_entry_size(entry);
- dir->pos += lfs_entry_size(entry);
- return 0;
-}
+static int32_t lfs_dir_lookup(lfs_t *lfs, lfs_mdir_t *dir, const char **path) {
+ // we reduce path to a single name if we can find it
+ const char *name = *path;
+ *path = NULL;
-static int lfs_dir_find(lfs_t *lfs, lfs_dir_t *dir,
- lfs_entry_t *entry, const char **path) {
- const char *pathname = *path;
- size_t pathlen;
- entry->d.type = LFS_TYPE_DIR;
- entry->d.elen = sizeof(entry->d) - 4;
- entry->d.alen = 0;
- entry->d.nlen = 0;
- entry->d.u.dir[0] = lfs->root[0];
- entry->d.u.dir[1] = lfs->root[1];
+ // default to root dir
+ int32_t tag = LFS_MKTAG(LFS_TYPE_DIR, 0x3ff, 0);
+ lfs_block_t pair[2] = {lfs->root[0], lfs->root[1]};
while (true) {
nextname:
// skip slashes
- pathname += strspn(pathname, "/");
- pathlen = strcspn(pathname, "/");
+ name += strspn(name, "/");
+ lfs_size_t namelen = strcspn(name, "/");
// skip '.' and root '..'
- if ((pathlen == 1 && memcmp(pathname, ".", 1) == 0) ||
- (pathlen == 2 && memcmp(pathname, "..", 2) == 0)) {
- pathname += pathlen;
+ if ((namelen == 1 && memcmp(name, ".", 1) == 0) ||
+ (namelen == 2 && memcmp(name, "..", 2) == 0)) {
+ name += namelen;
goto nextname;
}
// skip if matched by '..' in name
- const char *suffix = pathname + pathlen;
- size_t sufflen;
+ const char *suffix = name + namelen;
+ lfs_size_t sufflen;
int depth = 1;
while (true) {
suffix += strspn(suffix, "/");
@@ -833,7 +1354,7 @@ nextname:
if (sufflen == 2 && memcmp(suffix, "..", 2) == 0) {
depth -= 1;
if (depth == 0) {
- pathname = suffix + sufflen;
+ name = suffix + sufflen;
goto nextname;
}
} else {
@@ -844,154 +1365,195 @@ nextname:
}
// found path
- if (pathname[0] == '\0') {
- return 0;
+ if (name[0] == '\0') {
+ return tag;
}
- // update what we've found
- *path = pathname;
+ // update what we've found if path is only a name
+ if (strchr(name, '/') == NULL) {
+ *path = name;
+ }
- // continue on if we hit a directory
- if (entry->d.type != LFS_TYPE_DIR) {
+ // only continue if we hit a directory
+ if (lfs_tagtype(tag) != LFS_TYPE_DIR) {
return LFS_ERR_NOTDIR;
}
- int err = lfs_dir_fetch(lfs, dir, entry->d.u.dir);
- if (err) {
- return err;
+ // grab the entry data
+ if (lfs_tagid(tag) != 0x3ff) {
+ int32_t res = lfs_dir_get(lfs, dir, 0x7c3ff000,
+ LFS_MKTAG(LFS_TYPE_STRUCT, lfs_tagid(tag), 8), pair);
+ if (res < 0) {
+ return res;
+ }
+ lfs_pairfromle32(pair);
}
// find entry matching name
while (true) {
- err = lfs_dir_next(lfs, dir, entry);
- if (err) {
- return err;
- }
-
- if (((0x7f & entry->d.type) != LFS_TYPE_REG &&
- (0x7f & entry->d.type) != LFS_TYPE_DIR) ||
- entry->d.nlen != pathlen) {
- continue;
- }
-
- int res = lfs_bd_cmp(lfs, dir->pair[0],
- entry->off + 4+entry->d.elen+entry->d.alen,
- pathname, pathlen);
- if (res < 0) {
- return res;
+ tag = lfs_dir_find(lfs, dir, pair, 0x7c000fff,
+ LFS_MKTAG(LFS_TYPE_NAME, 0, namelen), name);
+ if (tag < 0 && tag != LFS_ERR_NOENT) {
+ return tag;
}
- // found match
- if (res) {
+ if (tag != LFS_ERR_NOENT) {
+ // found it
break;
}
- }
- // check that entry has not been moved
- if (entry->d.type & 0x80) {
- int moved = lfs_moved(lfs, &entry->d.u);
- if (moved < 0 || moved) {
- return (moved < 0) ? moved : LFS_ERR_NOENT;
+ if (!dir->split) {
+ // couldn't find it
+ return LFS_ERR_NOENT;
}
- entry->d.type &= ~0x80;
+ pair[0] = dir->tail[0];
+ pair[1] = dir->tail[1];
}
// to next name
- pathname += pathlen;
+ name += namelen;
}
}
+static int lfs_dir_getinfo(lfs_t *lfs, lfs_mdir_t *dir,
+ uint16_t id, struct lfs_info *info) {
+ if (id == 0x3ff) {
+ // special case for root
+ strcpy(info->name, "/");
+ info->type = LFS_TYPE_DIR;
+ return 0;
+ }
+
+ int32_t tag = lfs_dir_get(lfs, dir, 0x7c3ff000,
+ LFS_MKTAG(LFS_TYPE_NAME, id, lfs->name_max+1), info->name);
+ if (tag < 0) {
+ return tag;
+ }
+
+ info->type = lfs_tagtype(tag);
+
+ struct lfs_ctz ctz;
+ tag = lfs_dir_get(lfs, dir, 0x7c3ff000,
+ LFS_MKTAG(LFS_TYPE_STRUCT, id, sizeof(ctz)), &ctz);
+ if (tag < 0) {
+ return tag;
+ }
+ lfs_ctzfromle32(&ctz);
+
+ if (lfs_tagtype(tag) == LFS_TYPE_CTZSTRUCT) {
+ info->size = ctz.size;
+ } else if (lfs_tagtype(tag) == LFS_TYPE_INLINESTRUCT) {
+ info->size = lfs_tagsize(tag);
+ }
+
+ return 0;
+}
/// Top level directory operations ///
int lfs_mkdir(lfs_t *lfs, const char *path) {
// deorphan if we haven't yet, needed at most once after poweron
- if (!lfs->deorphaned) {
- int err = lfs_deorphan(lfs);
- if (err) {
- return err;
- }
+ int err = lfs_fs_forceconsistency(lfs);
+ if (err) {
+ return err;
+ }
+
+ lfs_mdir_t cwd;
+ int32_t res = lfs_dir_lookup(lfs, &cwd, &path);
+ if (!(res == LFS_ERR_NOENT && path)) {
+ return (res < 0) ? res : LFS_ERR_EXIST;
}
- // fetch parent directory
- lfs_dir_t cwd;
- lfs_entry_t entry;
- int err = lfs_dir_find(lfs, &cwd, &entry, &path);
- if (err != LFS_ERR_NOENT || strchr(path, '/') != NULL) {
- return err ? err : LFS_ERR_EXIST;
+ // check that name fits
+ lfs_size_t nlen = strlen(path);
+ if (nlen > lfs->name_max) {
+ return LFS_ERR_NAMETOOLONG;
}
// build up new directory
lfs_alloc_ack(lfs);
- lfs_dir_t dir;
+ lfs_mdir_t dir;
err = lfs_dir_alloc(lfs, &dir);
if (err) {
return err;
}
- dir.d.tail[0] = cwd.d.tail[0];
- dir.d.tail[1] = cwd.d.tail[1];
- err = lfs_dir_commit(lfs, &dir, NULL, 0);
+ dir.tail[0] = cwd.tail[0];
+ dir.tail[1] = cwd.tail[1];
+ err = lfs_dir_commit(lfs, &dir, NULL);
if (err) {
return err;
}
- entry.d.type = LFS_TYPE_DIR;
- entry.d.elen = sizeof(entry.d) - 4;
- entry.d.alen = 0;
- entry.d.nlen = strlen(path);
- entry.d.u.dir[0] = dir.pair[0];
- entry.d.u.dir[1] = dir.pair[1];
-
- cwd.d.tail[0] = dir.pair[0];
- cwd.d.tail[1] = dir.pair[1];
-
- err = lfs_dir_append(lfs, &cwd, &entry, path);
+ // get next slot and commit
+ uint16_t id = cwd.count;
+ cwd.tail[0] = dir.pair[0];
+ cwd.tail[1] = dir.pair[1];
+ lfs_pairtole32(dir.pair);
+ err = lfs_dir_commit(lfs, &cwd,
+ LFS_MKATTR(LFS_TYPE_DIR, id, path, nlen,
+ LFS_MKATTR(LFS_TYPE_DIRSTRUCT, id, dir.pair, sizeof(dir.pair),
+ LFS_MKATTR(LFS_TYPE_SOFTTAIL, 0x3ff, cwd.tail, sizeof(cwd.tail),
+ NULL))));
+ lfs_pairfromle32(dir.pair);
if (err) {
return err;
}
- lfs_alloc_ack(lfs);
return 0;
}
int lfs_dir_open(lfs_t *lfs, lfs_dir_t *dir, const char *path) {
- dir->pair[0] = lfs->root[0];
- dir->pair[1] = lfs->root[1];
+ int32_t tag = lfs_dir_lookup(lfs, &dir->m, &path);
+ if (tag < 0) {
+ return tag;
+ }
- lfs_entry_t entry;
- int err = lfs_dir_find(lfs, dir, &entry, &path);
- if (err) {
- return err;
- } else if (entry.d.type != LFS_TYPE_DIR) {
+ if (lfs_tagtype(tag) != LFS_TYPE_DIR) {
return LFS_ERR_NOTDIR;
}
- err = lfs_dir_fetch(lfs, dir, entry.d.u.dir);
+ lfs_block_t pair[2];
+ if (lfs_tagid(tag) == 0x3ff) {
+ // handle root dir separately
+ pair[0] = lfs->root[0];
+ pair[1] = lfs->root[1];
+ } else {
+ // get dir pair from parent
+ int32_t res = lfs_dir_get(lfs, &dir->m, 0x7c3ff000,
+ LFS_MKTAG(LFS_TYPE_STRUCT, lfs_tagid(tag), 8), pair);
+ if (res < 0) {
+ return res;
+ }
+ lfs_pairfromle32(pair);
+ }
+
+ // fetch first pair
+ int err = lfs_dir_fetch(lfs, &dir->m, pair);
if (err) {
return err;
}
- // setup head dir
- // special offset for '.' and '..'
- dir->head[0] = dir->pair[0];
- dir->head[1] = dir->pair[1];
- dir->pos = sizeof(dir->d) - 2;
- dir->off = sizeof(dir->d);
+ // setup entry
+ dir->head[0] = dir->m.pair[0];
+ dir->head[1] = dir->m.pair[1];
+ dir->id = 0;
+ dir->pos = 0;
- // add to list of directories
- dir->next = lfs->dirs;
- lfs->dirs = dir;
+ // add to list of mdirs
+ dir->type = LFS_TYPE_DIR;
+ dir->next = (lfs_dir_t*)lfs->mlist;
+ lfs->mlist = (lfs_mlist_t*)dir;
return 0;
}
int lfs_dir_close(lfs_t *lfs, lfs_dir_t *dir) {
- // remove from list of directories
- for (lfs_dir_t **p = &lfs->dirs; *p; p = &(*p)->next) {
- if (*p == dir) {
- *p = dir->next;
+ // remove from list of mdirs
+ for (lfs_mlist_t **p = &lfs->mlist; *p; p = &(*p)->next) {
+ if (*p == (lfs_mlist_t*)dir) {
+ *p = (*p)->next;
break;
}
}
@@ -1003,60 +1565,45 @@ int lfs_dir_read(lfs_t *lfs, lfs_dir_t *dir, struct lfs_info *info) {
memset(info, 0, sizeof(*info));
// special offset for '.' and '..'
- if (dir->pos == sizeof(dir->d) - 2) {
+ if (dir->pos == 0) {
info->type = LFS_TYPE_DIR;
strcpy(info->name, ".");
dir->pos += 1;
return 1;
- } else if (dir->pos == sizeof(dir->d) - 1) {
+ } else if (dir->pos == 1) {
info->type = LFS_TYPE_DIR;
strcpy(info->name, "..");
dir->pos += 1;
return 1;
}
- lfs_entry_t entry;
while (true) {
- int err = lfs_dir_next(lfs, dir, &entry);
- if (err) {
- return (err == LFS_ERR_NOENT) ? 0 : err;
- }
-
- if ((0x7f & entry.d.type) != LFS_TYPE_REG &&
- (0x7f & entry.d.type) != LFS_TYPE_DIR) {
- continue;
- }
-
- // check that entry has not been moved
- if (entry.d.type & 0x80) {
- int moved = lfs_moved(lfs, &entry.d.u);
- if (moved < 0) {
- return moved;
+ if (dir->id == dir->m.count) {
+ if (!dir->m.split) {
+ return false;
}
- if (moved) {
- continue;
+ int err = lfs_dir_fetch(lfs, &dir->m, dir->m.tail);
+ if (err) {
+ return err;
}
- entry.d.type &= ~0x80;
+ dir->id = 0;
}
- break;
- }
-
- info->type = entry.d.type;
- if (info->type == LFS_TYPE_REG) {
- info->size = entry.d.u.file.size;
- }
+ int err = lfs_dir_getinfo(lfs, &dir->m, dir->id, info);
+ if (err && err != LFS_ERR_NOENT) {
+ return err;
+ }
- int err = lfs_bd_read(lfs, dir->pair[0],
- entry.off + 4+entry.d.elen+entry.d.alen,
- info->name, entry.d.nlen);
- if (err) {
- return err;
+ dir->id += 1;
+ if (err != LFS_ERR_NOENT) {
+ break;
+ }
}
- return 1;
+ dir->pos += 1;
+ return true;
}
int lfs_dir_seek(lfs_t *lfs, lfs_dir_t *dir, lfs_off_t off) {
@@ -1065,21 +1612,28 @@ int lfs_dir_seek(lfs_t *lfs, lfs_dir_t *dir, lfs_off_t off) {
if (err) {
return err;
}
- dir->pos = off;
- while (off > (0x7fffffff & dir->d.size)) {
- off -= 0x7fffffff & dir->d.size;
- if (!(0x80000000 & dir->d.size)) {
- return LFS_ERR_INVAL;
- }
+ // first two for ./..
+ dir->pos = lfs_min(2, off);
+ off -= dir->pos;
- err = lfs_dir_fetch(lfs, dir, dir->d.tail);
- if (err) {
- return err;
+ while (off != 0) {
+ dir->id = lfs_min(dir->m.count, off);
+ dir->pos += dir->id;
+ off -= dir->id;
+
+ if (dir->id == dir->m.count) {
+ if (!dir->m.split) {
+ return LFS_ERR_INVAL;
+ }
+
+ err = lfs_dir_fetch(lfs, &dir->m, dir->m.tail);
+ if (err) {
+ return err;
+ }
}
}
- dir->off = off;
return 0;
}
@@ -1090,21 +1644,21 @@ lfs_soff_t lfs_dir_tell(lfs_t *lfs, lfs_dir_t *dir) {
int lfs_dir_rewind(lfs_t *lfs, lfs_dir_t *dir) {
// reload the head dir
- int err = lfs_dir_fetch(lfs, dir, dir->head);
+ int err = lfs_dir_fetch(lfs, &dir->m, dir->head);
if (err) {
return err;
}
- dir->pair[0] = dir->head[0];
- dir->pair[1] = dir->head[1];
- dir->pos = sizeof(dir->d) - 2;
- dir->off = sizeof(dir->d);
+ dir->m.pair[0] = dir->head[0];
+ dir->m.pair[1] = dir->head[1];
+ dir->id = 0;
+ dir->pos = 0;
return 0;
}
/// File index list operations ///
-static int lfs_ctz_index(lfs_t *lfs, lfs_off_t *off) {
+static int lfs_ctzindex(lfs_t *lfs, lfs_off_t *off) {
lfs_off_t size = *off;
lfs_off_t b = lfs->cfg->block_size - 2*4;
lfs_off_t i = size / b;
@@ -1117,7 +1671,7 @@ static int lfs_ctz_index(lfs_t *lfs, lfs_off_t *off) {
return i;
}
-static int lfs_ctz_find(lfs_t *lfs,
+static int lfs_ctzfind(lfs_t *lfs,
lfs_cache_t *rcache, const lfs_cache_t *pcache,
lfs_block_t head, lfs_size_t size,
lfs_size_t pos, lfs_block_t *block, lfs_off_t *off) {
@@ -1127,15 +1681,16 @@ static int lfs_ctz_find(lfs_t *lfs,
return 0;
}
- lfs_off_t current = lfs_ctz_index(lfs, &(lfs_off_t){size-1});
- lfs_off_t target = lfs_ctz_index(lfs, &pos);
+ lfs_off_t current = lfs_ctzindex(lfs, &(lfs_off_t){size-1});
+ lfs_off_t target = lfs_ctzindex(lfs, &pos);
while (current > target) {
lfs_size_t skip = lfs_min(
lfs_npw2(current-target+1) - 1,
lfs_ctz(current));
- int err = lfs_cache_read(lfs, rcache, pcache, head, 4*skip, &head, 4);
+ int err = lfs_cache_read(lfs, pcache, rcache, false,
+ head, 4*skip, &head, 4);
head = lfs_fromle32(head);
if (err) {
return err;
@@ -1150,7 +1705,7 @@ static int lfs_ctz_find(lfs_t *lfs,
return 0;
}
-static int lfs_ctz_extend(lfs_t *lfs,
+static int lfs_ctzextend(lfs_t *lfs,
lfs_cache_t *rcache, lfs_cache_t *pcache,
lfs_block_t head, lfs_size_t size,
lfs_block_t *block, lfs_off_t *off) {
@@ -1179,20 +1734,20 @@ static int lfs_ctz_extend(lfs_t *lfs,
}
size -= 1;
- lfs_off_t index = lfs_ctz_index(lfs, &size);
+ lfs_off_t index = lfs_ctzindex(lfs, &size);
size += 1;
// just copy out the last block if it is incomplete
if (size != lfs->cfg->block_size) {
for (lfs_off_t i = 0; i < size; i++) {
uint8_t data;
- err = lfs_cache_read(lfs, rcache, NULL,
+ err = lfs_cache_read(lfs, NULL, rcache, true,
head, i, &data, 1);
if (err) {
return err;
}
- err = lfs_cache_prog(lfs, pcache, rcache,
+ err = lfs_cache_prog(lfs, pcache, rcache, true,
nblock, i, &data, 1);
if (err) {
if (err == LFS_ERR_CORRUPT) {
@@ -1213,7 +1768,7 @@ static int lfs_ctz_extend(lfs_t *lfs,
for (lfs_off_t i = 0; i < skips; i++) {
head = lfs_tole32(head);
- err = lfs_cache_prog(lfs, pcache, rcache,
+ err = lfs_cache_prog(lfs, pcache, rcache, true,
nblock, 4*i, &head, 4);
head = lfs_fromle32(head);
if (err) {
@@ -1224,7 +1779,7 @@ static int lfs_ctz_extend(lfs_t *lfs,
}
if (i != skips-1) {
- err = lfs_cache_read(lfs, rcache, NULL,
+ err = lfs_cache_read(lfs, NULL, rcache, false,
head, 4*i, &head, 4);
head = lfs_fromle32(head);
if (err) {
@@ -1241,14 +1796,14 @@ static int lfs_ctz_extend(lfs_t *lfs,
}
relocate:
- LFS_DEBUG("Bad block at %" PRIu32, nblock);
+ LFS_DEBUG("Bad block at %"PRIu32, nblock);
// just clear cache and try a new block
- lfs_cache_drop(lfs, &lfs->pcache);
+ lfs_cache_drop(lfs, pcache);
}
}
-static int lfs_ctz_traverse(lfs_t *lfs,
+static int lfs_ctztraverse(lfs_t *lfs,
lfs_cache_t *rcache, const lfs_cache_t *pcache,
lfs_block_t head, lfs_size_t size,
int (*cb)(void*, lfs_block_t), void *data) {
@@ -1256,7 +1811,7 @@ static int lfs_ctz_traverse(lfs_t *lfs,
return 0;
}
- lfs_off_t index = lfs_ctz_index(lfs, &(lfs_off_t){size-1});
+ lfs_off_t index = lfs_ctzindex(lfs, &(lfs_off_t){size-1});
while (true) {
int err = cb(data, head);
@@ -1270,7 +1825,8 @@ static int lfs_ctz_traverse(lfs_t *lfs,
lfs_block_t heads[2];
int count = 2 - (index & 1);
- err = lfs_cache_read(lfs, rcache, pcache, head, 0, &heads, count*4);
+ err = lfs_cache_read(lfs, pcache, rcache, false,
+ head, 0, &heads, count*4);
heads[0] = lfs_fromle32(heads[0]);
heads[1] = lfs_fromle32(heads[1]);
if (err) {
@@ -1295,111 +1851,163 @@ int lfs_file_opencfg(lfs_t *lfs, lfs_file_t *file,
const char *path, int flags,
const struct lfs_file_config *cfg) {
// deorphan if we haven't yet, needed at most once after poweron
- if ((flags & 3) != LFS_O_RDONLY && !lfs->deorphaned) {
- int err = lfs_deorphan(lfs);
+ if ((flags & 3) != LFS_O_RDONLY) {
+ int err = lfs_fs_forceconsistency(lfs);
if (err) {
return err;
}
}
+ // setup simple file details
+ int err = 0;
+ file->cfg = cfg;
+ file->flags = flags;
+ file->pos = 0;
+ file->cache.buffer = NULL;
+
// allocate entry for file if it doesn't exist
- lfs_dir_t cwd;
- lfs_entry_t entry;
- int err = lfs_dir_find(lfs, &cwd, &entry, &path);
- if (err && (err != LFS_ERR_NOENT || strchr(path, '/') != NULL)) {
- return err;
+ int32_t tag = lfs_dir_lookup(lfs, &file->m, &path);
+ if (tag < 0 && !(tag == LFS_ERR_NOENT && path)) {
+ err = tag;
+ goto cleanup;
}
- if (err == LFS_ERR_NOENT) {
+ // get id, add to list of mdirs to catch update changes
+ file->id = lfs_tagid(tag);
+ file->type = LFS_TYPE_REG;
+ file->next = (lfs_file_t*)lfs->mlist;
+ lfs->mlist = (lfs_mlist_t*)file;
+
+ if (tag == LFS_ERR_NOENT) {
if (!(flags & LFS_O_CREAT)) {
- return LFS_ERR_NOENT;
+ err = LFS_ERR_NOENT;
+ goto cleanup;
+ }
+
+ // check that name fits
+ lfs_size_t nlen = strlen(path);
+ if (nlen > lfs->name_max) {
+ err = LFS_ERR_NAMETOOLONG;
+ goto cleanup;
}
- // create entry to remember name
- entry.d.type = LFS_TYPE_REG;
- entry.d.elen = sizeof(entry.d) - 4;
- entry.d.alen = 0;
- entry.d.nlen = strlen(path);
- entry.d.u.file.head = 0xffffffff;
- entry.d.u.file.size = 0;
- err = lfs_dir_append(lfs, &cwd, &entry, path);
+ // get next slot and create entry to remember name
+ file->id = file->m.count;
+ err = lfs_dir_commit(lfs, &file->m,
+ LFS_MKATTR(LFS_TYPE_REG, file->id, path, nlen,
+ LFS_MKATTR(LFS_TYPE_INLINESTRUCT, file->id, NULL, 0,
+ NULL)));
if (err) {
- return err;
+ err = LFS_ERR_NAMETOOLONG;
+ goto cleanup;
}
- } else if (entry.d.type == LFS_TYPE_DIR) {
- return LFS_ERR_ISDIR;
+
+ tag = LFS_MKTAG(LFS_TYPE_INLINESTRUCT, 0, 0);
} else if (flags & LFS_O_EXCL) {
- return LFS_ERR_EXIST;
+ err = LFS_ERR_EXIST;
+ goto cleanup;
+ } else if (lfs_tagtype(tag) != LFS_TYPE_REG) {
+ err = LFS_ERR_ISDIR;
+ goto cleanup;
+ } else if (flags & LFS_O_TRUNC) {
+ // truncate if requested
+ tag = LFS_MKTAG(LFS_TYPE_INLINESTRUCT, file->id, 0);
+ file->flags |= LFS_F_DIRTY;
+ } else {
+ // try to load what's on disk, if it's inlined we'll fix it later
+ tag = lfs_dir_get(lfs, &file->m, 0x7c3ff000,
+ LFS_MKTAG(LFS_TYPE_STRUCT, file->id, 8), &file->ctz);
+ if (tag < 0) {
+ err = tag;
+ goto cleanup;
+ }
+ lfs_ctzfromle32(&file->ctz);
}
- // setup file struct
- file->cfg = cfg;
- file->pair[0] = cwd.pair[0];
- file->pair[1] = cwd.pair[1];
- file->poff = entry.off;
- file->head = entry.d.u.file.head;
- file->size = entry.d.u.file.size;
- file->flags = flags;
- file->pos = 0;
+ // fetch attrs
+ for (const struct lfs_attr *a = file->cfg->attrs; a; a = a->next) {
+ if ((file->flags & 3) != LFS_O_WRONLY) {
+ int32_t res = lfs_dir_get(lfs, &file->m, 0x7ffff000,
+ LFS_MKTAG(0x100 | a->type, file->id, a->size), a->buffer);
+ if (res < 0 && res != LFS_ERR_NOENT) {
+ err = res;
+ goto cleanup;
+ }
+ }
+
+ if ((file->flags & 3) != LFS_O_RDONLY) {
+ if (a->size > lfs->attr_max) {
+ err = LFS_ERR_NOSPC;
+ goto cleanup;
+ }
- if (flags & LFS_O_TRUNC) {
- if (file->size != 0) {
file->flags |= LFS_F_DIRTY;
}
- file->head = 0xffffffff;
- file->size = 0;
}
// allocate buffer if needed
- file->cache.block = 0xffffffff;
- if (file->cfg && file->cfg->buffer) {
+ if (file->cfg->buffer) {
file->cache.buffer = file->cfg->buffer;
- } else if (lfs->cfg->file_buffer) {
- if (lfs->files) {
- // already in use
- return LFS_ERR_NOMEM;
- }
- file->cache.buffer = lfs->cfg->file_buffer;
- } else if ((file->flags & 3) == LFS_O_RDONLY) {
- file->cache.buffer = lfs_malloc(lfs->cfg->read_size);
- if (!file->cache.buffer) {
- return LFS_ERR_NOMEM;
- }
} else {
- file->cache.buffer = lfs_malloc(lfs->cfg->prog_size);
+ file->cache.buffer = lfs_malloc(lfs->cfg->cache_size);
if (!file->cache.buffer) {
- return LFS_ERR_NOMEM;
+ err = LFS_ERR_NOMEM;
+ goto cleanup;
}
}
// zero to avoid information leak
lfs_cache_zero(lfs, &file->cache);
- // add to list of files
- file->next = lfs->files;
- lfs->files = file;
+ if (lfs_tagtype(tag) == LFS_TYPE_INLINESTRUCT) {
+ // load inline files
+ file->ctz.head = 0xfffffffe;
+ file->ctz.size = lfs_tagsize(tag);
+ file->flags |= LFS_F_INLINE;
+ file->cache.block = file->ctz.head;
+ file->cache.off = 0;
+ file->cache.size = lfs->cfg->cache_size;
+
+ // don't always read (may be new/trunc file)
+ if (file->ctz.size > 0) {
+ int32_t res = lfs_dir_get(lfs, &file->m, 0x7c3ff000,
+ LFS_MKTAG(LFS_TYPE_STRUCT, file->id, file->ctz.size),
+ file->cache.buffer);
+ if (res < 0) {
+ err = res;
+ goto cleanup;
+ }
+ }
+ }
return 0;
+
+cleanup:
+ // clean up lingering resources
+ file->flags |= LFS_F_ERRED;
+ lfs_file_close(lfs, file);
+ return err;
}
int lfs_file_open(lfs_t *lfs, lfs_file_t *file,
const char *path, int flags) {
- return lfs_file_opencfg(lfs, file, path, flags, NULL);
+ static const struct lfs_file_config defaults = {0};
+ return lfs_file_opencfg(lfs, file, path, flags, &defaults);
}
int lfs_file_close(lfs_t *lfs, lfs_file_t *file) {
int err = lfs_file_sync(lfs, file);
- // remove from list of files
- for (lfs_file_t **p = &lfs->files; *p; p = &(*p)->next) {
- if (*p == file) {
- *p = file->next;
+ // remove from list of mdirs
+ for (lfs_mlist_t **p = &lfs->mlist; *p; p = &(*p)->next) {
+ if (*p == (lfs_mlist_t*)file) {
+ *p = (*p)->next;
break;
}
}
// clean up memory
- if (!(file->cfg && file->cfg->buffer) && !lfs->cfg->file_buffer) {
+ if (file->cfg->buffer) {
lfs_free(file->cache.buffer);
}
@@ -1407,115 +2015,123 @@ int lfs_file_close(lfs_t *lfs, lfs_file_t *file) {
}
static int lfs_file_relocate(lfs_t *lfs, lfs_file_t *file) {
-relocate:
- LFS_DEBUG("Bad block at %" PRIu32, file->block);
-
- // just relocate what exists into new block
- lfs_block_t nblock;
- int err = lfs_alloc(lfs, &nblock);
- if (err) {
- return err;
- }
-
- err = lfs_bd_erase(lfs, nblock);
- if (err) {
- if (err == LFS_ERR_CORRUPT) {
- goto relocate;
- }
- return err;
- }
-
- // either read from dirty cache or disk
- for (lfs_off_t i = 0; i < file->off; i++) {
- uint8_t data;
- err = lfs_cache_read(lfs, &lfs->rcache, &file->cache,
- file->block, i, &data, 1);
+ while (true) {
+ // just relocate what exists into new block
+ lfs_block_t nblock;
+ int err = lfs_alloc(lfs, &nblock);
if (err) {
return err;
}
- err = lfs_cache_prog(lfs, &lfs->pcache, &lfs->rcache,
- nblock, i, &data, 1);
+ err = lfs_bd_erase(lfs, nblock);
if (err) {
if (err == LFS_ERR_CORRUPT) {
goto relocate;
}
return err;
}
- }
- // copy over new state of file
- memcpy(file->cache.buffer, lfs->pcache.buffer, lfs->cfg->prog_size);
- file->cache.block = lfs->pcache.block;
- file->cache.off = lfs->pcache.off;
- lfs_cache_zero(lfs, &lfs->pcache);
+ // either read from dirty cache or disk
+ for (lfs_off_t i = 0; i < file->off; i++) {
+ uint8_t data;
+ err = lfs_cache_read(lfs, &file->cache, &lfs->rcache, true,
+ file->block, i, &data, 1);
+ if (err) {
+ return err;
+ }
- file->block = nblock;
- return 0;
+ err = lfs_cache_prog(lfs, &lfs->pcache, &lfs->rcache, true,
+ nblock, i, &data, 1);
+ if (err) {
+ if (err == LFS_ERR_CORRUPT) {
+ goto relocate;
+ }
+ return err;
+ }
+ }
+
+ // copy over new state of file
+ memcpy(file->cache.buffer, lfs->pcache.buffer, lfs->cfg->cache_size);
+ file->cache.block = lfs->pcache.block;
+ file->cache.off = lfs->pcache.off;
+ file->cache.size = lfs->pcache.size;
+ lfs_cache_zero(lfs, &lfs->pcache);
+
+ file->block = nblock;
+ return 0;
+
+relocate:
+ continue;
+ }
}
static int lfs_file_flush(lfs_t *lfs, lfs_file_t *file) {
if (file->flags & LFS_F_READING) {
- // just drop read cache
- lfs_cache_drop(lfs, &file->cache);
file->flags &= ~LFS_F_READING;
}
if (file->flags & LFS_F_WRITING) {
lfs_off_t pos = file->pos;
- // copy over anything after current branch
- lfs_file_t orig = {
- .head = file->head,
- .size = file->size,
- .flags = LFS_O_RDONLY,
- .pos = file->pos,
- .cache = lfs->rcache,
- };
- lfs_cache_drop(lfs, &lfs->rcache);
-
- while (file->pos < file->size) {
- // copy over a byte at a time, leave it up to caching
- // to make this efficient
- uint8_t data;
- lfs_ssize_t res = lfs_file_read(lfs, &orig, &data, 1);
- if (res < 0) {
- return res;
- }
+ if (!(file->flags & LFS_F_INLINE)) {
+ // copy over anything after current branch
+ lfs_file_t orig = {
+ .ctz.head = file->ctz.head,
+ .ctz.size = file->ctz.size,
+ .flags = LFS_O_RDONLY,
+ .pos = file->pos,
+ .cache = lfs->rcache,
+ };
+ lfs_cache_drop(lfs, &lfs->rcache);
+
+ while (file->pos < file->ctz.size) {
+ // copy over a byte at a time, leave it up to caching
+ // to make this efficient
+ uint8_t data;
+ lfs_ssize_t res = lfs_file_read(lfs, &orig, &data, 1);
+ if (res < 0) {
+ return res;
+ }
- res = lfs_file_write(lfs, file, &data, 1);
- if (res < 0) {
- return res;
- }
+ res = lfs_file_write(lfs, file, &data, 1);
+ if (res < 0) {
+ return res;
+ }
- // keep our reference to the rcache in sync
- if (lfs->rcache.block != 0xffffffff) {
- lfs_cache_drop(lfs, &orig.cache);
- lfs_cache_drop(lfs, &lfs->rcache);
+ // keep our reference to the rcache in sync
+ if (lfs->rcache.block != 0xffffffff) {
+ lfs_cache_drop(lfs, &orig.cache);
+ lfs_cache_drop(lfs, &lfs->rcache);
+ }
}
- }
- // write out what we have
- while (true) {
- int err = lfs_cache_flush(lfs, &file->cache, &lfs->rcache);
- if (err) {
- if (err == LFS_ERR_CORRUPT) {
- goto relocate;
+ // write out what we have
+ while (true) {
+ int err = lfs_cache_flush(lfs,
+ &file->cache, &lfs->rcache, true);
+ if (err) {
+ if (err == LFS_ERR_CORRUPT) {
+ goto relocate;
+ }
+ return err;
}
- return err;
- }
- break;
+ break;
+
relocate:
- err = lfs_file_relocate(lfs, file);
- if (err) {
- return err;
+ LFS_DEBUG("Bad block at %"PRIu32, file->block);
+ err = lfs_file_relocate(lfs, file);
+ if (err) {
+ return err;
+ }
}
+ } else {
+ file->ctz.size = lfs_max(file->pos, file->ctz.size);
}
// actual file updates
- file->head = file->block;
- file->size = file->pos;
+ file->ctz.head = file->block;
+ file->ctz.size = file->pos;
file->flags &= ~LFS_F_WRITING;
file->flags |= LFS_F_DIRTY;
@@ -1526,42 +2142,64 @@ relocate:
}
int lfs_file_sync(lfs_t *lfs, lfs_file_t *file) {
- int err = lfs_file_flush(lfs, file);
- if (err) {
- return err;
- }
-
- if ((file->flags & LFS_F_DIRTY) &&
- !(file->flags & LFS_F_ERRED) &&
- !lfs_pairisnull(file->pair)) {
- // update dir entry
- lfs_dir_t cwd;
- err = lfs_dir_fetch(lfs, &cwd, file->pair);
+ while (true) {
+ int err = lfs_file_flush(lfs, file);
if (err) {
return err;
}
- lfs_entry_t entry = {.off = file->poff};
- err = lfs_bd_read(lfs, cwd.pair[0], entry.off,
- &entry.d, sizeof(entry.d));
- lfs_entry_fromle32(&entry.d);
- if (err) {
- return err;
+ if ((file->flags & LFS_F_DIRTY) &&
+ !(file->flags & LFS_F_ERRED) &&
+ !lfs_pairisnull(file->m.pair)) {
+ // update dir entry
+ uint16_t type;
+ const void *buffer;
+ lfs_size_t size;
+ if (file->flags & LFS_F_INLINE) {
+ // inline the whole file
+ type = LFS_TYPE_INLINESTRUCT;
+ buffer = file->cache.buffer;
+ size = file->ctz.size;
+ } else {
+ // update the ctz reference
+ type = LFS_TYPE_CTZSTRUCT;
+ buffer = &file->ctz;
+ size = sizeof(file->ctz);
+ }
+
+ // commit file data and attributes
+ lfs_ctztole32(&file->ctz);
+ err = lfs_dir_commit(lfs, &file->m,
+ LFS_MKATTR(type, file->id, buffer, size,
+ LFS_MKATTR(LFS_FROM_ATTRS, file->id, file->cfg->attrs, 0,
+ NULL)));
+ lfs_ctzfromle32(&file->ctz);
+ if (err) {
+ if (err == LFS_ERR_NOSPC && (file->flags & LFS_F_INLINE)) {
+ goto relocate;
+ }
+ return err;
+ }
+
+ file->flags &= ~LFS_F_DIRTY;
}
- LFS_ASSERT(entry.d.type == LFS_TYPE_REG);
- entry.d.u.file.head = file->head;
- entry.d.u.file.size = file->size;
+ return 0;
+
+relocate:
+ // inline file doesn't fit anymore
+ file->block = 0xfffffffe;
+ file->off = file->pos;
- err = lfs_dir_update(lfs, &cwd, &entry, NULL);
+ lfs_alloc_ack(lfs);
+ err = lfs_file_relocate(lfs, file);
if (err) {
return err;
}
- file->flags &= ~LFS_F_DIRTY;
+ file->flags &= ~LFS_F_INLINE;
+ file->flags |= LFS_F_WRITING;
}
-
- return 0;
}
lfs_ssize_t lfs_file_read(lfs_t *lfs, lfs_file_t *file,
@@ -1581,23 +2219,28 @@ lfs_ssize_t lfs_file_read(lfs_t *lfs, lfs_file_t *file,
}
}
- if (file->pos >= file->size) {
+ if (file->pos >= file->ctz.size) {
// eof if past end
return 0;
}
- size = lfs_min(size, file->size - file->pos);
+ size = lfs_min(size, file->ctz.size - file->pos);
nsize = size;
while (nsize > 0) {
// check if we need a new block
if (!(file->flags & LFS_F_READING) ||
file->off == lfs->cfg->block_size) {
- int err = lfs_ctz_find(lfs, &file->cache, NULL,
- file->head, file->size,
- file->pos, &file->block, &file->off);
- if (err) {
- return err;
+ if (!(file->flags & LFS_F_INLINE)) {
+ int err = lfs_ctzfind(lfs, &file->cache, NULL,
+ file->ctz.head, file->ctz.size,
+ file->pos, &file->block, &file->off);
+ if (err) {
+ return err;
+ }
+ } else {
+ file->block = 0xfffffffe;
+ file->off = file->pos;
}
file->flags |= LFS_F_READING;
@@ -1605,7 +2248,7 @@ lfs_ssize_t lfs_file_read(lfs_t *lfs, lfs_file_t *file,
// read as much as we can in current block
lfs_size_t diff = lfs_min(nsize, lfs->cfg->block_size - file->off);
- int err = lfs_cache_read(lfs, &file->cache, NULL,
+ int err = lfs_cache_read(lfs, NULL, &file->cache, true,
file->block, file->off, data, diff);
if (err) {
return err;
@@ -1637,14 +2280,14 @@ lfs_ssize_t lfs_file_write(lfs_t *lfs, lfs_file_t *file,
}
}
- if ((file->flags & LFS_O_APPEND) && file->pos < file->size) {
- file->pos = file->size;
+ if ((file->flags & LFS_O_APPEND) && file->pos < file->ctz.size) {
+ file->pos = file->ctz.size;
}
- if (!(file->flags & LFS_F_WRITING) && file->pos > file->size) {
+ if (!(file->flags & LFS_F_WRITING) && file->pos > file->ctz.size) {
// fill with zeros
lfs_off_t pos = file->pos;
- file->pos = file->size;
+ file->pos = file->ctz.size;
while (file->pos < pos) {
lfs_ssize_t res = lfs_file_write(lfs, file, &(uint8_t){0}, 1);
@@ -1654,32 +2297,54 @@ lfs_ssize_t lfs_file_write(lfs_t *lfs, lfs_file_t *file,
}
}
+ if ((file->flags & LFS_F_INLINE) &&
+ file->pos + nsize >= lfs->inline_max) {
+ // inline file doesn't fit anymore
+ file->block = 0xfffffffe;
+ file->off = file->pos;
+
+ lfs_alloc_ack(lfs);
+ int err = lfs_file_relocate(lfs, file);
+ if (err) {
+ file->flags |= LFS_F_ERRED;
+ return err;
+ }
+
+ file->flags &= ~LFS_F_INLINE;
+ file->flags |= LFS_F_WRITING;
+ }
+
while (nsize > 0) {
// check if we need a new block
if (!(file->flags & LFS_F_WRITING) ||
file->off == lfs->cfg->block_size) {
- if (!(file->flags & LFS_F_WRITING) && file->pos > 0) {
- // find out which block we're extending from
- int err = lfs_ctz_find(lfs, &file->cache, NULL,
- file->head, file->size,
- file->pos-1, &file->block, &file->off);
+ if (!(file->flags & LFS_F_INLINE)) {
+ if (!(file->flags & LFS_F_WRITING) && file->pos > 0) {
+ // find out which block we're extending from
+ int err = lfs_ctzfind(lfs, &file->cache, NULL,
+ file->ctz.head, file->ctz.size,
+ file->pos-1, &file->block, &file->off);
+ if (err) {
+ file->flags |= LFS_F_ERRED;
+ return err;
+ }
+
+ // mark cache as dirty since we may have read data into it
+ lfs_cache_zero(lfs, &file->cache);
+ }
+
+ // extend file with new blocks
+ lfs_alloc_ack(lfs);
+ int err = lfs_ctzextend(lfs, &lfs->rcache, &file->cache,
+ file->block, file->pos,
+ &file->block, &file->off);
if (err) {
file->flags |= LFS_F_ERRED;
return err;
}
-
- // mark cache as dirty since we may have read data into it
- lfs_cache_zero(lfs, &file->cache);
- }
-
- // extend file with new blocks
- lfs_alloc_ack(lfs);
- int err = lfs_ctz_extend(lfs, &lfs->rcache, &file->cache,
- file->block, file->pos,
- &file->block, &file->off);
- if (err) {
- file->flags |= LFS_F_ERRED;
- return err;
+ } else {
+ file->block = 0xfffffffe;
+ file->off = file->pos;
}
file->flags |= LFS_F_WRITING;
@@ -1688,7 +2353,7 @@ lfs_ssize_t lfs_file_write(lfs_t *lfs, lfs_file_t *file,
// program as much as we can in current block
lfs_size_t diff = lfs_min(nsize, lfs->cfg->block_size - file->off);
while (true) {
- int err = lfs_cache_prog(lfs, &file->cache, &lfs->rcache,
+ int err = lfs_cache_prog(lfs, &file->cache, &lfs->rcache, true,
file->block, file->off, data, diff);
if (err) {
if (err == LFS_ERR_CORRUPT) {
@@ -1737,11 +2402,11 @@ lfs_soff_t lfs_file_seek(lfs_t *lfs, lfs_file_t *file,
file->pos = file->pos + off;
} else if (whence == LFS_SEEK_END) {
- if (off < 0 && (lfs_off_t)-off > file->size) {
+ if (off < 0 && (lfs_off_t)-off > file->ctz.size) {
return LFS_ERR_INVAL;
}
- file->pos = file->size + off;
+ file->pos = file->ctz.size + off;
}
return file->pos;
@@ -1761,14 +2426,14 @@ int lfs_file_truncate(lfs_t *lfs, lfs_file_t *file, lfs_off_t size) {
}
// lookup new head in ctz skip list
- err = lfs_ctz_find(lfs, &file->cache, NULL,
- file->head, file->size,
- size, &file->head, &(lfs_off_t){0});
+ err = lfs_ctzfind(lfs, &file->cache, NULL,
+ file->ctz.head, file->ctz.size,
+ size, &file->ctz.head, &(lfs_off_t){0});
if (err) {
return err;
}
- file->size = size;
+ file->ctz.size = size;
file->flags |= LFS_F_DIRTY;
} else if (size > oldsize) {
lfs_off_t pos = file->pos;
@@ -1816,89 +2481,91 @@ int lfs_file_rewind(lfs_t *lfs, lfs_file_t *file) {
lfs_soff_t lfs_file_size(lfs_t *lfs, lfs_file_t *file) {
(void)lfs;
if (file->flags & LFS_F_WRITING) {
- return lfs_max(file->pos, file->size);
+ return lfs_max(file->pos, file->ctz.size);
} else {
- return file->size;
+ return file->ctz.size;
}
}
/// General fs operations ///
int lfs_stat(lfs_t *lfs, const char *path, struct lfs_info *info) {
- lfs_dir_t cwd;
- lfs_entry_t entry;
- int err = lfs_dir_find(lfs, &cwd, &entry, &path);
- if (err) {
- return err;
- }
-
- memset(info, 0, sizeof(*info));
- info->type = entry.d.type;
- if (info->type == LFS_TYPE_REG) {
- info->size = entry.d.u.file.size;
- }
-
- if (lfs_paircmp(entry.d.u.dir, lfs->root) == 0) {
- strcpy(info->name, "/");
- } else {
- err = lfs_bd_read(lfs, cwd.pair[0],
- entry.off + 4+entry.d.elen+entry.d.alen,
- info->name, entry.d.nlen);
- if (err) {
- return err;
- }
+ lfs_mdir_t cwd;
+ int32_t tag = lfs_dir_lookup(lfs, &cwd, &path);
+ if (tag < 0) {
+ return tag;
}
- return 0;
+ return lfs_dir_getinfo(lfs, &cwd, lfs_tagid(tag), info);
}
int lfs_remove(lfs_t *lfs, const char *path) {
// deorphan if we haven't yet, needed at most once after poweron
- if (!lfs->deorphaned) {
- int err = lfs_deorphan(lfs);
- if (err) {
- return err;
- }
+ int err = lfs_fs_forceconsistency(lfs);
+ if (err) {
+ return err;
}
- lfs_dir_t cwd;
- lfs_entry_t entry;
- int err = lfs_dir_find(lfs, &cwd, &entry, &path);
+ lfs_mdir_t cwd;
+ err = lfs_dir_fetch(lfs, &cwd, lfs->root);
if (err) {
return err;
}
- lfs_dir_t dir;
- if (entry.d.type == LFS_TYPE_DIR) {
- // must be empty before removal, checking size
- // without masking top bit checks for any case where
- // dir is not empty
- err = lfs_dir_fetch(lfs, &dir, entry.d.u.dir);
+ int32_t tag = lfs_dir_lookup(lfs, &cwd, &path);
+ if (tag < 0) {
+ return tag;
+ }
+
+ lfs_mdir_t dir;
+ if (lfs_tagtype(tag) == LFS_TYPE_DIR) {
+ // must be empty before removal
+ lfs_block_t pair[2];
+ int32_t res = lfs_dir_get(lfs, &cwd, 0x7c3ff000,
+ LFS_MKTAG(LFS_TYPE_STRUCT, lfs_tagid(tag), 8), pair);
+ if (res < 0) {
+ return res;
+ }
+ lfs_pairfromle32(pair);
+
+ err = lfs_dir_fetch(lfs, &dir, pair);
if (err) {
return err;
- } else if (dir.d.size != sizeof(dir.d)+4) {
+ }
+
+ if (dir.count > 0 || dir.split) {
return LFS_ERR_NOTEMPTY;
}
+
+ // mark fs as orphaned
+ lfs_globaldeorphaned(lfs, false);
}
- // remove the entry
- err = lfs_dir_remove(lfs, &cwd, &entry);
+ // delete the entry
+ err = lfs_dir_commit(lfs, &cwd,
+ LFS_MKATTR(LFS_TYPE_DELETE, lfs_tagid(tag), NULL, 0,
+ NULL));
if (err) {
return err;
}
- // if we were a directory, find pred, replace tail
- if (entry.d.type == LFS_TYPE_DIR) {
- int res = lfs_pred(lfs, dir.pair, &cwd);
- if (res < 0) {
- return res;
+ if (lfs_tagtype(tag) == LFS_TYPE_DIR) {
+ err = lfs_fs_pred(lfs, dir.pair, &cwd);
+ if (err) {
+ return err;
}
- LFS_ASSERT(res); // must have pred
- cwd.d.tail[0] = dir.d.tail[0];
- cwd.d.tail[1] = dir.d.tail[1];
+ // fix orphan
+ lfs_globaldeorphaned(lfs, true);
- err = lfs_dir_commit(lfs, &cwd, NULL, 0);
+ // steal state
+ cwd.tail[0] = dir.tail[0];
+ cwd.tail[1] = dir.tail[1];
+ lfs_globalxor(&lfs->locals, &dir.locals);
+ err = lfs_dir_commit(lfs, &cwd,
+ LFS_MKATTR(LFS_TYPE_SOFTTAIL, 0x3ff,
+ cwd.tail, sizeof(cwd.tail),
+ NULL));
if (err) {
return err;
}
@@ -1909,103 +2576,108 @@ int lfs_remove(lfs_t *lfs, const char *path) {
int lfs_rename(lfs_t *lfs, const char *oldpath, const char *newpath) {
// deorphan if we haven't yet, needed at most once after poweron
- if (!lfs->deorphaned) {
- int err = lfs_deorphan(lfs);
- if (err) {
- return err;
- }
- }
-
- // find old entry
- lfs_dir_t oldcwd;
- lfs_entry_t oldentry;
- int err = lfs_dir_find(lfs, &oldcwd, &oldentry, &oldpath);
+ int err = lfs_fs_forceconsistency(lfs);
if (err) {
return err;
}
- // allocate new entry
- lfs_dir_t newcwd;
- lfs_entry_t preventry;
- err = lfs_dir_find(lfs, &newcwd, &preventry, &newpath);
- if (err && (err != LFS_ERR_NOENT || strchr(newpath, '/') != NULL)) {
- return err;
+ // find old entry
+ lfs_mdir_t oldcwd;
+ int32_t oldtag = lfs_dir_lookup(lfs, &oldcwd, &oldpath);
+ if (oldtag < 0) {
+ return oldtag;
}
- bool prevexists = (err != LFS_ERR_NOENT);
- bool samepair = (lfs_paircmp(oldcwd.pair, newcwd.pair) == 0);
-
- // must have same type
- if (prevexists && preventry.d.type != oldentry.d.type) {
- return LFS_ERR_ISDIR;
+ // find new entry
+ lfs_mdir_t newcwd;
+ int32_t prevtag = lfs_dir_lookup(lfs, &newcwd, &newpath);
+ if (prevtag < 0 && prevtag != LFS_ERR_NOENT) {
+ return prevtag;
}
- lfs_dir_t dir;
- if (prevexists && preventry.d.type == LFS_TYPE_DIR) {
- // must be empty before removal, checking size
- // without masking top bit checks for any case where
- // dir is not empty
- err = lfs_dir_fetch(lfs, &dir, preventry.d.u.dir);
- if (err) {
- return err;
- } else if (dir.d.size != sizeof(dir.d)+4) {
- return LFS_ERR_NOTEMPTY;
+ uint16_t newid = lfs_tagid(prevtag);
+ //bool prevexists = (prevtag != LFS_ERR_NOENT);
+ //bool samepair = (lfs_paircmp(oldcwd.pair, newcwd.pair) == 0);
+
+ lfs_mdir_t prevdir;
+ if (prevtag != LFS_ERR_NOENT) {
+ // check that we have same type
+ if (lfs_tagtype(prevtag) != lfs_tagtype(oldtag)) {
+ return LFS_ERR_ISDIR;
}
- }
- // mark as moving
- oldentry.d.type |= 0x80;
- err = lfs_dir_update(lfs, &oldcwd, &oldentry, NULL);
- if (err) {
- return err;
- }
+ if (lfs_tagtype(prevtag) == LFS_TYPE_DIR) {
+ // must be empty before removal
+ lfs_block_t prevpair[2];
+ int32_t res = lfs_dir_get(lfs, &newcwd, 0x7c3ff000,
+ LFS_MKTAG(LFS_TYPE_STRUCT, newid, 8), prevpair);
+ if (res < 0) {
+ return res;
+ }
+ lfs_pairfromle32(prevpair);
- // update pair if newcwd == oldcwd
- if (samepair) {
- newcwd = oldcwd;
- }
+ // must be empty before removal
+ err = lfs_dir_fetch(lfs, &prevdir, prevpair);
+ if (err) {
+ return err;
+ }
- // move to new location
- lfs_entry_t newentry = preventry;
- newentry.d = oldentry.d;
- newentry.d.type &= ~0x80;
- newentry.d.nlen = strlen(newpath);
+ if (prevdir.count > 0 || prevdir.split) {
+ return LFS_ERR_NOTEMPTY;
+ }
- if (prevexists) {
- err = lfs_dir_update(lfs, &newcwd, &newentry, newpath);
- if (err) {
- return err;
+ // mark fs as orphaned
+ lfs_globaldeorphaned(lfs, false);
}
} else {
- err = lfs_dir_append(lfs, &newcwd, &newentry, newpath);
- if (err) {
- return err;
+ // check that name fits
+ lfs_size_t nlen = strlen(newpath);
+ if (nlen > lfs->name_max) {
+ return LFS_ERR_NAMETOOLONG;
}
- }
- // update pair if newcwd == oldcwd
- if (samepair) {
- oldcwd = newcwd;
+ // get next id
+ newid = newcwd.count;
}
- // remove old entry
- err = lfs_dir_remove(lfs, &oldcwd, &oldentry);
+ // create move to fix later
+ lfs_globalmove(lfs, oldcwd.pair, lfs_tagid(oldtag));
+
+ // move over all attributes
+ err = lfs_dir_commit(lfs, &newcwd,
+ LFS_MKATTR(lfs_tagtype(oldtag), newid, newpath, strlen(newpath),
+ LFS_MKATTR(LFS_FROM_MOVE, newid, &oldcwd, lfs_tagid(oldtag),
+ NULL)));
if (err) {
return err;
}
- // if we were a directory, find pred, replace tail
- if (prevexists && preventry.d.type == LFS_TYPE_DIR) {
- int res = lfs_pred(lfs, dir.pair, &newcwd);
- if (res < 0) {
- return res;
+ // let commit clean up after move (if we're different! otherwise move
+ // logic already fixed it for us)
+ if (lfs_paircmp(oldcwd.pair, newcwd.pair) != 0) {
+ err = lfs_dir_commit(lfs, &oldcwd, NULL);
+ if (err) {
+ return err;
}
+ }
- LFS_ASSERT(res); // must have pred
- newcwd.d.tail[0] = dir.d.tail[0];
- newcwd.d.tail[1] = dir.d.tail[1];
+ if (prevtag != LFS_ERR_NOENT && lfs_tagtype(prevtag) == LFS_TYPE_DIR) {
+ err = lfs_fs_pred(lfs, prevdir.pair, &newcwd);
+ if (err) {
+ return err;
+ }
+
+ // fix orphan
+ lfs_globaldeorphaned(lfs, true);
- err = lfs_dir_commit(lfs, &newcwd, NULL, 0);
+ // steal state
+ newcwd.tail[0] = prevdir.tail[0];
+ newcwd.tail[1] = prevdir.tail[1];
+ lfs_globalxor(&lfs->locals, &prevdir.locals);
+ err = lfs_dir_commit(lfs, &newcwd,
+ LFS_MKATTR(LFS_TYPE_SOFTTAIL, 0x3ff,
+ newcwd.tail, sizeof(newcwd.tail),
+ NULL));
if (err) {
return err;
}
@@ -2014,32 +2686,82 @@ int lfs_rename(lfs_t *lfs, const char *oldpath, const char *newpath) {
return 0;
}
+lfs_ssize_t lfs_getattr(lfs_t *lfs, const char *path,
+ uint8_t type, void *buffer, lfs_size_t size) {
+ lfs_mdir_t cwd;
+ int32_t res = lfs_dir_lookup(lfs, &cwd, &path);
+ if (res < 0) {
+ return res;
+ }
-/// Filesystem operations ///
-static void lfs_deinit(lfs_t *lfs) {
- // free allocated memory
- if (!lfs->cfg->read_buffer) {
- lfs_free(lfs->rcache.buffer);
+ res = lfs_dir_get(lfs, &cwd, 0x7ffff000,
+ LFS_MKTAG(0x100 | type, lfs_tagid(res),
+ lfs_min(size, lfs->attr_max)), buffer);
+ if (res < 0 && res != LFS_ERR_NOENT) {
+ return res;
}
- if (!lfs->cfg->prog_buffer) {
- lfs_free(lfs->pcache.buffer);
+ return (res == LFS_ERR_NOENT) ? 0 : lfs_tagsize(res);
+}
+
+int lfs_setattr(lfs_t *lfs, const char *path,
+ uint8_t type, const void *buffer, lfs_size_t size) {
+ if (size > lfs->attr_max) {
+ return LFS_ERR_NOSPC;
}
- if (!lfs->cfg->lookahead_buffer) {
- lfs_free(lfs->free.buffer);
+ lfs_mdir_t cwd;
+ int32_t res = lfs_dir_lookup(lfs, &cwd, &path);
+ if (res < 0) {
+ return res;
}
+
+ return lfs_dir_commit(lfs, &cwd,
+ LFS_MKATTR(0x100 | type, lfs_tagid(res), buffer, size,
+ NULL));
+}
+
+
+/// Filesystem operations ///
+static inline void lfs_superblockfromle32(lfs_superblock_t *superblock) {
+ superblock->version = lfs_fromle32(superblock->version);
+ superblock->block_size = lfs_fromle32(superblock->block_size);
+ superblock->block_count = lfs_fromle32(superblock->block_count);
+ superblock->inline_max = lfs_fromle32(superblock->inline_max);
+ superblock->attr_max = lfs_fromle32(superblock->attr_max);
+ superblock->name_max = lfs_fromle32(superblock->name_max);
+}
+
+static inline void lfs_superblocktole32(lfs_superblock_t *superblock) {
+ superblock->version = lfs_tole32(superblock->version);
+ superblock->block_size = lfs_tole32(superblock->block_size);
+ superblock->block_count = lfs_tole32(superblock->block_count);
+ superblock->inline_max = lfs_tole32(superblock->inline_max);
+ superblock->attr_max = lfs_tole32(superblock->attr_max);
+ superblock->name_max = lfs_tole32(superblock->name_max);
}
static int lfs_init(lfs_t *lfs, const struct lfs_config *cfg) {
lfs->cfg = cfg;
+ int err = 0;
+
+ // check that block size is a multiple of cache size is a multiple
+ // of prog and read sizes
+ LFS_ASSERT(lfs->cfg->cache_size % lfs->cfg->read_size == 0);
+ LFS_ASSERT(lfs->cfg->cache_size % lfs->cfg->prog_size == 0);
+ LFS_ASSERT(lfs->cfg->block_size % lfs->cfg->cache_size == 0);
+
+ // check that the block size is large enough to fit ctz pointers
+ LFS_ASSERT(4*lfs_npw2(0xffffffff / (lfs->cfg->block_size-2*4))
+ <= lfs->cfg->block_size);
// setup read cache
if (lfs->cfg->read_buffer) {
lfs->rcache.buffer = lfs->cfg->read_buffer;
} else {
- lfs->rcache.buffer = lfs_malloc(lfs->cfg->read_size);
+ lfs->rcache.buffer = lfs_malloc(lfs->cfg->cache_size);
if (!lfs->rcache.buffer) {
+ err = LFS_ERR_NOMEM;
goto cleanup;
}
}
@@ -2048,8 +2770,9 @@ static int lfs_init(lfs_t *lfs, const struct lfs_config *cfg) {
if (lfs->cfg->prog_buffer) {
lfs->pcache.buffer = lfs->cfg->prog_buffer;
} else {
- lfs->pcache.buffer = lfs_malloc(lfs->cfg->prog_size);
+ lfs->pcache.buffer = lfs_malloc(lfs->cfg->cache_size);
if (!lfs->pcache.buffer) {
+ err = LFS_ERR_NOMEM;
goto cleanup;
}
}
@@ -2058,7 +2781,7 @@ static int lfs_init(lfs_t *lfs, const struct lfs_config *cfg) {
lfs_cache_zero(lfs, &lfs->rcache);
lfs_cache_zero(lfs, &lfs->pcache);
- // setup lookahead, round down to nearest 32-bits
+ // setup lookahead, must be multiple of 32-bits
LFS_ASSERT(lfs->cfg->lookahead % 32 == 0);
LFS_ASSERT(lfs->cfg->lookahead > 0);
if (lfs->cfg->lookahead_buffer) {
@@ -2066,30 +2789,63 @@ static int lfs_init(lfs_t *lfs, const struct lfs_config *cfg) {
} else {
lfs->free.buffer = lfs_malloc(lfs->cfg->lookahead/8);
if (!lfs->free.buffer) {
+ err = LFS_ERR_NOMEM;
goto cleanup;
}
}
- // check that program and read sizes are multiples of the block size
- LFS_ASSERT(lfs->cfg->prog_size % lfs->cfg->read_size == 0);
- LFS_ASSERT(lfs->cfg->block_size % lfs->cfg->prog_size == 0);
+ // check that the size limits are sane
+ LFS_ASSERT(lfs->cfg->inline_max <= LFS_INLINE_MAX);
+ LFS_ASSERT(lfs->cfg->inline_max <= lfs->cfg->cache_size);
+ lfs->inline_max = lfs->cfg->inline_max;
+ if (!lfs->inline_max) {
+ lfs->inline_max = lfs_min(LFS_INLINE_MAX, lfs->cfg->cache_size);
+ }
- // check that the block size is large enough to fit ctz pointers
- LFS_ASSERT(4*lfs_npw2(0xffffffff / (lfs->cfg->block_size-2*4))
- <= lfs->cfg->block_size);
+ LFS_ASSERT(lfs->cfg->attr_max <= LFS_ATTR_MAX);
+ lfs->attr_max = lfs->cfg->attr_max;
+ if (!lfs->attr_max) {
+ lfs->attr_max = LFS_ATTR_MAX;
+ }
+
+ LFS_ASSERT(lfs->cfg->name_max <= LFS_NAME_MAX);
+ lfs->name_max = lfs->cfg->name_max;
+ if (!lfs->name_max) {
+ lfs->name_max = LFS_NAME_MAX;
+ }
// setup default state
lfs->root[0] = 0xffffffff;
lfs->root[1] = 0xffffffff;
- lfs->files = NULL;
- lfs->dirs = NULL;
- lfs->deorphaned = false;
+ lfs->mlist = NULL;
+ lfs->globals.s.movepair[0] = 0xffffffff;
+ lfs->globals.s.movepair[1] = 0xffffffff;
+ lfs->globals.s.moveid = 0x3ff;
+ lfs->globals.s.deorphaned = true;
+ lfs_globalzero(&lfs->locals);
return 0;
cleanup:
lfs_deinit(lfs);
- return LFS_ERR_NOMEM;
+ return err;
+}
+
+static int lfs_deinit(lfs_t *lfs) {
+ // free allocated memory
+ if (!lfs->cfg->read_buffer) {
+ lfs_free(lfs->rcache.buffer);
+ }
+
+ if (!lfs->cfg->prog_buffer) {
+ lfs_free(lfs->pcache.buffer);
+ }
+
+ if (!lfs->cfg->lookahead_buffer) {
+ lfs_free(lfs->free.buffer);
+ }
+
+ return 0;
}
int lfs_format(lfs_t *lfs, const struct lfs_config *cfg) {
@@ -2106,71 +2862,59 @@ int lfs_format(lfs_t *lfs, const struct lfs_config *cfg) {
lfs_alloc_ack(lfs);
// create superblock dir
- lfs_dir_t superdir;
- err = lfs_dir_alloc(lfs, &superdir);
+ lfs_mdir_t dir;
+ err = lfs_dir_alloc(lfs, &dir);
if (err) {
goto cleanup;
}
// write root directory
- lfs_dir_t root;
+ lfs_mdir_t root;
err = lfs_dir_alloc(lfs, &root);
if (err) {
goto cleanup;
}
- err = lfs_dir_commit(lfs, &root, NULL, 0);
+ err = lfs_dir_commit(lfs, &root, NULL);
if (err) {
goto cleanup;
}
lfs->root[0] = root.pair[0];
lfs->root[1] = root.pair[1];
+ dir.tail[0] = lfs->root[0];
+ dir.tail[1] = lfs->root[1];
- // write superblocks
+ // write one superblock
lfs_superblock_t superblock = {
- .off = sizeof(superdir.d),
- .d.type = LFS_TYPE_SUPERBLOCK,
- .d.elen = sizeof(superblock.d) - sizeof(superblock.d.magic) - 4,
- .d.nlen = sizeof(superblock.d.magic),
- .d.version = LFS_DISK_VERSION,
- .d.magic = {"littlefs"},
- .d.block_size = lfs->cfg->block_size,
- .d.block_count = lfs->cfg->block_count,
- .d.root = {lfs->root[0], lfs->root[1]},
+ .magic = {"littlefs"},
+ .version = LFS_DISK_VERSION,
+
+ .block_size = lfs->cfg->block_size,
+ .block_count = lfs->cfg->block_count,
+ .attr_max = lfs->attr_max,
+ .name_max = lfs->name_max,
+ .inline_max = lfs->inline_max,
};
- superdir.d.tail[0] = root.pair[0];
- superdir.d.tail[1] = root.pair[1];
- superdir.d.size = sizeof(superdir.d) + sizeof(superblock.d) + 4;
- // write both pairs to be safe
- lfs_superblock_tole32(&superblock.d);
- bool valid = false;
- for (int i = 0; i < 2; i++) {
- err = lfs_dir_commit(lfs, &superdir, (struct lfs_region[]){
- {sizeof(superdir.d), sizeof(superblock.d),
- &superblock.d, sizeof(superblock.d)}
- }, 1);
- if (err && err != LFS_ERR_CORRUPT) {
- goto cleanup;
- }
-
- valid = valid || !err;
- }
-
- if (!valid) {
- err = LFS_ERR_CORRUPT;
+ lfs_superblocktole32(&superblock);
+ lfs_pairtole32(lfs->root);
+ err = lfs_dir_commit(lfs, &dir,
+ LFS_MKATTR(LFS_TYPE_SUPERBLOCK, 0, &superblock, sizeof(superblock),
+ LFS_MKATTR(LFS_TYPE_DIRSTRUCT, 0, lfs->root, sizeof(lfs->root),
+ NULL)));
+ lfs_pairfromle32(lfs->root);
+ lfs_superblockfromle32(&superblock);
+ if (err) {
goto cleanup;
}
// sanity check that fetch works
- err = lfs_dir_fetch(lfs, &superdir, (const lfs_block_t[2]){0, 1});
+ err = lfs_dir_fetch(lfs, &dir, (const lfs_block_t[2]){0, 1});
if (err) {
goto cleanup;
}
- lfs_alloc_ack(lfs);
-
cleanup:
lfs_deinit(lfs);
return err;
@@ -2189,117 +2933,177 @@ int lfs_mount(lfs_t *lfs, const struct lfs_config *cfg) {
lfs_alloc_ack(lfs);
// load superblock
- lfs_dir_t dir;
- lfs_superblock_t superblock;
- err = lfs_dir_fetch(lfs, &dir, (const lfs_block_t[2]){0, 1});
- if (err && err != LFS_ERR_CORRUPT) {
+ lfs_mdir_t superdir;
+ err = lfs_dir_fetch(lfs, &superdir, (const lfs_block_t[2]){0, 1});
+ if (err) {
goto cleanup;
}
- if (!err) {
- err = lfs_bd_read(lfs, dir.pair[0], sizeof(dir.d),
- &superblock.d, sizeof(superblock.d));
- lfs_superblock_fromle32(&superblock.d);
- if (err) {
- goto cleanup;
- }
-
- lfs->root[0] = superblock.d.root[0];
- lfs->root[1] = superblock.d.root[1];
+ lfs_superblock_t superblock;
+ int32_t res = lfs_dir_get(lfs, &superdir, 0x7ffff000,
+ LFS_MKTAG(LFS_TYPE_SUPERBLOCK, 0, sizeof(superblock)),
+ &superblock);
+ if (res < 0) {
+ err = res;
+ goto cleanup;
}
+ lfs_superblockfromle32(&superblock);
- if (err || memcmp(superblock.d.magic, "littlefs", 8) != 0) {
- LFS_ERROR("Invalid superblock at %d %d", 0, 1);
- err = LFS_ERR_CORRUPT;
- goto cleanup;
+ if (memcmp(superblock.magic, "littlefs", 8) != 0) {
+ LFS_ERROR("Invalid superblock \"%.8s\"", superblock.magic);
+ return LFS_ERR_INVAL;
}
- uint16_t major_version = (0xffff & (superblock.d.version >> 16));
- uint16_t minor_version = (0xffff & (superblock.d.version >> 0));
+ uint16_t major_version = (0xffff & (superblock.version >> 16));
+ uint16_t minor_version = (0xffff & (superblock.version >> 0));
if ((major_version != LFS_DISK_VERSION_MAJOR ||
minor_version > LFS_DISK_VERSION_MINOR)) {
- LFS_ERROR("Invalid version %d.%d", major_version, minor_version);
+ LFS_ERROR("Invalid version %"PRIu32".%"PRIu32,
+ major_version, minor_version);
err = LFS_ERR_INVAL;
goto cleanup;
}
+ res = lfs_dir_get(lfs, &superdir, 0x7ffff000,
+ LFS_MKTAG(LFS_TYPE_DIRSTRUCT, 0, sizeof(lfs->root)),
+ &lfs->root);
+ if (res < 0) {
+ err = res;
+ goto cleanup;
+ }
+ lfs_pairfromle32(lfs->root);
+
+ // check superblock configuration
+ if (superblock.attr_max) {
+ if (superblock.attr_max > lfs->attr_max) {
+ LFS_ERROR("Unsupported attr_max (%"PRIu32" > %"PRIu32")",
+ superblock.attr_max, lfs->attr_max);
+ err = LFS_ERR_INVAL;
+ goto cleanup;
+ }
+
+ lfs->attr_max = superblock.attr_max;
+ }
+
+ if (superblock.name_max) {
+ if (superblock.name_max > lfs->name_max) {
+ LFS_ERROR("Unsupported name_max (%"PRIu32" > %"PRIu32")",
+ superblock.name_max, lfs->name_max);
+ err = LFS_ERR_INVAL;
+ goto cleanup;
+ }
+
+ lfs->name_max = superblock.name_max;
+ }
+
+ if (superblock.inline_max) {
+ if (superblock.inline_max > lfs->inline_max) {
+ LFS_ERROR("Unsupported inline_max (%"PRIu32" > %"PRIu32")",
+ superblock.inline_max, lfs->inline_max);
+ err = LFS_ERR_INVAL;
+ goto cleanup;
+ }
+
+ lfs->inline_max = superblock.inline_max;
+ }
+
+ // scan for any global updates
+ lfs_mdir_t dir = {.tail = {0, 1}};
+ while (!lfs_pairisnull(dir.tail)) {
+ err = lfs_dir_fetch(lfs, &dir, dir.tail);
+ if (err) {
+ err = LFS_ERR_INVAL;
+ goto cleanup;
+ }
+
+ // xor together indirect deletes
+ lfs_globalxor(&lfs->locals, &dir.locals);
+ }
+
+ // update littlefs with globals
+ lfs_globalfromle32(&lfs->locals);
+ lfs_globalxor(&lfs->globals, &lfs->locals);
+ lfs_globalzero(&lfs->locals);
+ if (!lfs_pairisnull(lfs->globals.s.movepair)) {
+ LFS_DEBUG("Found move %"PRIu32" %"PRIu32" %"PRIu32,
+ lfs->globals.s.movepair[0],
+ lfs->globals.s.movepair[1],
+ lfs->globals.s.moveid);
+ }
+
return 0;
cleanup:
-
- lfs_deinit(lfs);
+ lfs_unmount(lfs);
return err;
}
int lfs_unmount(lfs_t *lfs) {
- lfs_deinit(lfs);
- return 0;
+ return lfs_deinit(lfs);
}
-/// Littlefs specific operations ///
-int lfs_traverse(lfs_t *lfs, int (*cb)(void*, lfs_block_t), void *data) {
+/// Filesystem filesystem operations ///
+int lfs_fs_traverse(lfs_t *lfs,
+ int (*cb)(void *data, lfs_block_t block), void *data) {
if (lfs_pairisnull(lfs->root)) {
return 0;
}
// iterate over metadata pairs
- lfs_dir_t dir;
- lfs_entry_t entry;
- lfs_block_t cwd[2] = {0, 1};
-
- while (true) {
+ lfs_mdir_t dir = {.tail = {0, 1}};
+ while (!lfs_pairisnull(dir.tail)) {
for (int i = 0; i < 2; i++) {
- int err = cb(data, cwd[i]);
+ int err = cb(data, dir.tail[i]);
if (err) {
return err;
}
}
- int err = lfs_dir_fetch(lfs, &dir, cwd);
+ // iterate through ids in directory
+ int err = lfs_dir_fetch(lfs, &dir, dir.tail);
if (err) {
return err;
}
- // iterate over contents
- while (dir.off + sizeof(entry.d) <= (0x7fffffff & dir.d.size)-4) {
- err = lfs_bd_read(lfs, dir.pair[0], dir.off,
- &entry.d, sizeof(entry.d));
- lfs_entry_fromle32(&entry.d);
- if (err) {
- return err;
+ for (uint16_t id = 0; id < dir.count; id++) {
+ struct lfs_ctz ctz;
+ int32_t tag = lfs_dir_get(lfs, &dir, 0x7c3ff000,
+ LFS_MKTAG(LFS_TYPE_STRUCT, id, sizeof(ctz)), &ctz);
+ if (tag < 0) {
+ if (tag == LFS_ERR_NOENT) {
+ continue;
+ }
+ return tag;
}
+ lfs_ctzfromle32(&ctz);
- dir.off += lfs_entry_size(&entry);
- if ((0x70 & entry.d.type) == (0x70 & LFS_TYPE_REG)) {
- err = lfs_ctz_traverse(lfs, &lfs->rcache, NULL,
- entry.d.u.file.head, entry.d.u.file.size, cb, data);
+ if (lfs_tagtype(tag) == LFS_TYPE_CTZSTRUCT) {
+ err = lfs_ctztraverse(lfs, &lfs->rcache, NULL,
+ ctz.head, ctz.size, cb, data);
if (err) {
return err;
}
}
}
-
- cwd[0] = dir.d.tail[0];
- cwd[1] = dir.d.tail[1];
-
- if (lfs_pairisnull(cwd)) {
- break;
- }
}
// iterate over any open files
- for (lfs_file_t *f = lfs->files; f; f = f->next) {
- if (f->flags & LFS_F_DIRTY) {
- int err = lfs_ctz_traverse(lfs, &lfs->rcache, &f->cache,
- f->head, f->size, cb, data);
+ for (lfs_file_t *f = (lfs_file_t*)lfs->mlist; f; f = f->next) {
+ if (f->type != LFS_TYPE_REG) {
+ continue;
+ }
+
+ if ((f->flags & LFS_F_DIRTY) && !(f->flags & LFS_F_INLINE)) {
+ int err = lfs_ctztraverse(lfs, &lfs->rcache, &f->cache,
+ f->ctz.head, f->ctz.size, cb, data);
if (err) {
return err;
}
}
- if (f->flags & LFS_F_WRITING) {
- int err = lfs_ctz_traverse(lfs, &lfs->rcache, &f->cache,
+ if ((f->flags & LFS_F_WRITING) && !(f->flags & LFS_F_INLINE)) {
+ int err = lfs_ctztraverse(lfs, &lfs->rcache, &f->cache,
f->block, f->pos, cb, data);
if (err) {
return err;
@@ -2310,257 +3114,267 @@ int lfs_traverse(lfs_t *lfs, int (*cb)(void*, lfs_block_t), void *data) {
return 0;
}
-static int lfs_pred(lfs_t *lfs, const lfs_block_t dir[2], lfs_dir_t *pdir) {
+static int lfs_fs_pred(lfs_t *lfs,
+ const lfs_block_t pair[2], lfs_mdir_t *pdir) {
if (lfs_pairisnull(lfs->root)) {
- return 0;
+ return LFS_ERR_NOENT;
}
// iterate over all directory directory entries
- int err = lfs_dir_fetch(lfs, pdir, (const lfs_block_t[2]){0, 1});
- if (err) {
- return err;
- }
-
- while (!lfs_pairisnull(pdir->d.tail)) {
- if (lfs_paircmp(pdir->d.tail, dir) == 0) {
- return true;
+ pdir->tail[0] = 0;
+ pdir->tail[1] = 1;
+ while (!lfs_pairisnull(pdir->tail)) {
+ if (lfs_paircmp(pdir->tail, pair) == 0) {
+ return 0;
}
- err = lfs_dir_fetch(lfs, pdir, pdir->d.tail);
+ int err = lfs_dir_fetch(lfs, pdir, pdir->tail);
if (err) {
return err;
}
}
- return false;
+ return LFS_ERR_NOENT;
}
-static int lfs_parent(lfs_t *lfs, const lfs_block_t dir[2],
- lfs_dir_t *parent, lfs_entry_t *entry) {
+static int32_t lfs_fs_parent(lfs_t *lfs, const lfs_block_t pair[2],
+ lfs_mdir_t *parent) {
if (lfs_pairisnull(lfs->root)) {
- return 0;
+ return LFS_ERR_NOENT;
}
- parent->d.tail[0] = 0;
- parent->d.tail[1] = 1;
-
- // iterate over all directory directory entries
- while (!lfs_pairisnull(parent->d.tail)) {
- int err = lfs_dir_fetch(lfs, parent, parent->d.tail);
- if (err) {
- return err;
- }
-
- while (true) {
- err = lfs_dir_next(lfs, parent, entry);
- if (err && err != LFS_ERR_NOENT) {
- return err;
- }
-
- if (err == LFS_ERR_NOENT) {
- break;
- }
-
- if (((0x70 & entry->d.type) == (0x70 & LFS_TYPE_DIR)) &&
- lfs_paircmp(entry->d.u.dir, dir) == 0) {
- return true;
+ // search for both orderings so we can reuse the find function
+ lfs_block_t child[2] = {pair[0], pair[1]};
+ lfs_pairtole32(child);
+ for (int i = 0; i < 2; i++) {
+ // iterate over all directory directory entries
+ parent->tail[0] = 0;
+ parent->tail[1] = 1;
+ while (!lfs_pairisnull(parent->tail)) {
+ int32_t tag = lfs_dir_find(lfs, parent, parent->tail, 0x7fc00fff,
+ LFS_MKTAG(LFS_TYPE_DIRSTRUCT, 0, sizeof(child)),
+ child);
+ if (tag != LFS_ERR_NOENT) {
+ return tag;
}
}
+
+ lfs_pairswap(child);
}
- return false;
+ return LFS_ERR_NOENT;
}
-static int lfs_moved(lfs_t *lfs, const void *e) {
- if (lfs_pairisnull(lfs->root)) {
- return 0;
+static int lfs_fs_relocate(lfs_t *lfs,
+ const lfs_block_t oldpair[2], lfs_block_t newpair[2]) {
+ // update internal root
+ if (lfs_paircmp(oldpair, lfs->root) == 0) {
+ LFS_DEBUG("Relocating root %"PRIu32" %"PRIu32,
+ newpair[0], newpair[1]);
+ lfs->root[0] = newpair[0];
+ lfs->root[1] = newpair[1];
}
- // skip superblock
- lfs_dir_t cwd;
- int err = lfs_dir_fetch(lfs, &cwd, (const lfs_block_t[2]){0, 1});
- if (err) {
- return err;
- }
-
- // iterate over all directory directory entries
- lfs_entry_t entry;
- while (!lfs_pairisnull(cwd.d.tail)) {
- err = lfs_dir_fetch(lfs, &cwd, cwd.d.tail);
- if (err) {
- return err;
- }
-
- while (true) {
- err = lfs_dir_next(lfs, &cwd, &entry);
- if (err && err != LFS_ERR_NOENT) {
- return err;
- }
-
- if (err == LFS_ERR_NOENT) {
- break;
- }
-
- if (!(0x80 & entry.d.type) &&
- memcmp(&entry.d.u, e, sizeof(entry.d.u)) == 0) {
- return true;
- }
+ // update internally tracked dirs
+ for (lfs_mlist_t *d = lfs->mlist; d; d = d->next) {
+ if (lfs_paircmp(oldpair, d->m.pair) == 0) {
+ d->m.pair[0] = newpair[0];
+ d->m.pair[1] = newpair[1];
}
}
- return false;
-}
-
-static int lfs_relocate(lfs_t *lfs,
- const lfs_block_t oldpair[2], const lfs_block_t newpair[2]) {
// find parent
- lfs_dir_t parent;
- lfs_entry_t entry;
- int res = lfs_parent(lfs, oldpair, &parent, &entry);
- if (res < 0) {
- return res;
+ lfs_mdir_t parent;
+ int32_t tag = lfs_fs_parent(lfs, oldpair, &parent);
+ if (tag < 0 && tag != LFS_ERR_NOENT) {
+ return tag;
}
- if (res) {
+ if (tag != LFS_ERR_NOENT) {
// update disk, this creates a desync
- entry.d.u.dir[0] = newpair[0];
- entry.d.u.dir[1] = newpair[1];
-
- int err = lfs_dir_update(lfs, &parent, &entry, NULL);
+ lfs_pairtole32(newpair);
+ int err = lfs_dir_commit(lfs, &parent,
+ &(lfs_mattr_t){.tag=tag, .buffer=newpair});
+ lfs_pairfromle32(newpair);
if (err) {
return err;
}
- // update internal root
- if (lfs_paircmp(oldpair, lfs->root) == 0) {
- LFS_DEBUG("Relocating root %" PRIu32 " %" PRIu32,
- newpair[0], newpair[1]);
- lfs->root[0] = newpair[0];
- lfs->root[1] = newpair[1];
- }
-
// clean up bad block, which should now be a desync
- return lfs_deorphan(lfs);
+ return lfs_fs_forceconsistency(lfs);
}
// find pred
- res = lfs_pred(lfs, oldpair, &parent);
- if (res < 0) {
- return res;
+ int err = lfs_fs_pred(lfs, oldpair, &parent);
+ if (err && err != LFS_ERR_NOENT) {
+ return err;
}
- if (res) {
+ // if we can't find dir, it must be new
+ if (err != LFS_ERR_NOENT) {
// just replace bad pair, no desync can occur
- parent.d.tail[0] = newpair[0];
- parent.d.tail[1] = newpair[1];
-
- return lfs_dir_commit(lfs, &parent, NULL, 0);
+ parent.tail[0] = newpair[0];
+ parent.tail[1] = newpair[1];
+ err = lfs_dir_commit(lfs, &parent,
+ LFS_MKATTR(LFS_TYPE_TAIL + parent.split, 0x3ff,
+ parent.tail, sizeof(parent.tail),
+ NULL));
+ if (err) {
+ return err;
+ }
}
- // couldn't find dir, must be new
return 0;
}
-int lfs_deorphan(lfs_t *lfs) {
- lfs->deorphaned = true;
-
- if (lfs_pairisnull(lfs->root)) {
- return 0;
- }
-
- lfs_dir_t pdir = {.d.size = 0x80000000};
- lfs_dir_t cwd = {.d.tail[0] = 0, .d.tail[1] = 1};
+static int lfs_fs_forceconsistency(lfs_t *lfs) {
+ if (!lfs->globals.s.deorphaned) {
+ LFS_DEBUG("Found orphans %"PRIu32,
+ lfs->globals.s.deorphaned);
- // iterate over all directory directory entries
- while (!lfs_pairisnull(cwd.d.tail)) {
- int err = lfs_dir_fetch(lfs, &cwd, cwd.d.tail);
- if (err) {
- return err;
- }
+ // Fix any orphans
+ lfs_mdir_t pdir = {.split = true};
+ lfs_mdir_t dir = {.tail = {0, 1}};
- // check head blocks for orphans
- if (!(0x80000000 & pdir.d.size)) {
- // check if we have a parent
- lfs_dir_t parent;
- lfs_entry_t entry;
- int res = lfs_parent(lfs, pdir.d.tail, &parent, &entry);
- if (res < 0) {
- return res;
+ // iterate over all directory directory entries
+ while (!lfs_pairisnull(dir.tail)) {
+ int err = lfs_dir_fetch(lfs, &dir, dir.tail);
+ if (err) {
+ return err;
}
- if (!res) {
- // we are an orphan
- LFS_DEBUG("Found orphan %" PRIu32 " %" PRIu32,
- pdir.d.tail[0], pdir.d.tail[1]);
+ // check head blocks for orphans
+ if (!pdir.split) {
+ // check if we have a parent
+ lfs_mdir_t parent;
+ int32_t tag = lfs_fs_parent(lfs, pdir.tail, &parent);
+ if (tag < 0 && tag != LFS_ERR_NOENT) {
+ return tag;
+ }
- pdir.d.tail[0] = cwd.d.tail[0];
- pdir.d.tail[1] = cwd.d.tail[1];
+ if (tag == LFS_ERR_NOENT) {
+ // we are an orphan
+ LFS_DEBUG("Fixing orphan %"PRIu32" %"PRIu32,
+ pdir.tail[0], pdir.tail[1]);
+
+ pdir.tail[0] = dir.tail[0];
+ pdir.tail[1] = dir.tail[1];
+ err = lfs_dir_commit(lfs, &pdir,
+ LFS_MKATTR(LFS_TYPE_SOFTTAIL, 0x3ff,
+ pdir.tail, sizeof(pdir.tail),
+ NULL));
+ if (err) {
+ return err;
+ }
- err = lfs_dir_commit(lfs, &pdir, NULL, 0);
- if (err) {
- return err;
+ break;
}
- break;
+ lfs_block_t pair[2];
+ int32_t res = lfs_dir_get(lfs, &parent, 0x7ffff000, tag, pair);
+ if (res < 0) {
+ return res;
+ }
+ lfs_pairfromle32(pair);
+
+ if (!lfs_pairsync(pair, pdir.tail)) {
+ // we have desynced
+ LFS_DEBUG("Fixing half-orphan %"PRIu32" %"PRIu32,
+ pair[0], pair[1]);
+
+ pdir.tail[0] = pair[0];
+ pdir.tail[1] = pair[1];
+ err = lfs_dir_commit(lfs, &pdir,
+ LFS_MKATTR(LFS_TYPE_SOFTTAIL, 0x3ff,
+ pdir.tail, sizeof(pdir.tail),
+ NULL));
+ if (err) {
+ return err;
+ }
+
+ break;
+ }
}
- if (!lfs_pairsync(entry.d.u.dir, pdir.d.tail)) {
- // we have desynced
- LFS_DEBUG("Found desync %" PRIu32 " %" PRIu32,
- entry.d.u.dir[0], entry.d.u.dir[1]);
+ memcpy(&pdir, &dir, sizeof(pdir));
+ }
- pdir.d.tail[0] = entry.d.u.dir[0];
- pdir.d.tail[1] = entry.d.u.dir[1];
+ // mark orphan as fixed
+ lfs_globaldeorphaned(lfs, false);
+ }
- err = lfs_dir_commit(lfs, &pdir, NULL, 0);
- if (err) {
- return err;
- }
+ if (lfs->globals.s.moveid != 0x3ff) {
+ // Fix bad moves
+ LFS_DEBUG("Fixing move %"PRIu32" %"PRIu32" %"PRIu32,
+ lfs->globals.s.movepair[0],
+ lfs->globals.s.movepair[1],
+ lfs->globals.s.moveid);
- break;
- }
+ // fetch and delete the moved entry
+ lfs_mdir_t movedir;
+ int err = lfs_dir_fetch(lfs, &movedir, lfs->globals.s.movepair);
+ if (err) {
+ return err;
}
- // check entries for moves
- lfs_entry_t entry;
- while (true) {
- err = lfs_dir_next(lfs, &cwd, &entry);
- if (err && err != LFS_ERR_NOENT) {
- return err;
- }
+ // rely on cancel logic inside commit
+ err = lfs_dir_commit(lfs, &movedir, NULL);
+ if (err) {
+ return err;
+ }
+ }
- if (err == LFS_ERR_NOENT) {
- break;
- }
+ return 0;
+}
- // found moved entry
- if (entry.d.type & 0x80) {
- int moved = lfs_moved(lfs, &entry.d.u);
- if (moved < 0) {
- return moved;
- }
+lfs_ssize_t lfs_fs_getattr(lfs_t *lfs,
+ uint8_t type, void *buffer, lfs_size_t size) {
+ lfs_mdir_t superdir;
+ int err = lfs_dir_fetch(lfs, &superdir, (const lfs_block_t[2]){0, 1});
+ if (err) {
+ return err;
+ }
- if (moved) {
- LFS_DEBUG("Found move %" PRIu32 " %" PRIu32,
- entry.d.u.dir[0], entry.d.u.dir[1]);
- err = lfs_dir_remove(lfs, &cwd, &entry);
- if (err) {
- return err;
- }
- } else {
- LFS_DEBUG("Found partial move %" PRIu32 " %" PRIu32,
- entry.d.u.dir[0], entry.d.u.dir[1]);
- entry.d.type &= ~0x80;
- err = lfs_dir_update(lfs, &cwd, &entry, NULL);
- if (err) {
- return err;
- }
- }
- }
- }
+ int32_t res = lfs_dir_get(lfs, &superdir, 0x7ffff000,
+ LFS_MKTAG(0x100 | type, 0,
+ lfs_min(size, lfs->attr_max)), buffer);
+ if (res < 0) {
+ return res;
+ }
- memcpy(&pdir, &cwd, sizeof(pdir));
+ return (res == LFS_ERR_NOENT) ? 0 : lfs_tagsize(res);
+}
+
+int lfs_fs_setattr(lfs_t *lfs,
+ uint8_t type, const void *buffer, lfs_size_t size) {
+ if (size > lfs->attr_max) {
+ return LFS_ERR_NOSPC;
+ }
+
+ lfs_mdir_t superdir;
+ int err = lfs_dir_fetch(lfs, &superdir, (const lfs_block_t[2]){0, 1});
+ if (err) {
+ return err;
}
+ return lfs_dir_commit(lfs, &superdir,
+ LFS_MKATTR(0x100 | type, 0, buffer, size,
+ NULL));
+}
+
+static int lfs_fs_size_count(void *p, lfs_block_t block) {
+ (void)block;
+ lfs_size_t *size = p;
+ *size += 1;
return 0;
}
+
+lfs_ssize_t lfs_fs_size(lfs_t *lfs) {
+ lfs_size_t size = 0;
+ int err = lfs_fs_traverse(lfs, lfs_fs_size_count, &size);
+ if (err) {
+ return err;
+ }
+
+ return size;
+}
diff --git a/lfs.h b/lfs.h
index 7dd3604..4ced50a 100644
--- a/lfs.h
+++ b/lfs.h
@@ -21,14 +21,14 @@ extern "C"
// Software library version
// Major (top-nibble), incremented on backwards incompatible changes
// Minor (bottom-nibble), incremented on feature additions
-#define LFS_VERSION 0x00010006
+#define LFS_VERSION 0x00020000
#define LFS_VERSION_MAJOR (0xffff & (LFS_VERSION >> 16))
#define LFS_VERSION_MINOR (0xffff & (LFS_VERSION >> 0))
// Version of On-disk data structures
// Major (top-nibble), incremented on backwards incompatible changes
// Minor (bottom-nibble), incremented on feature additions
-#define LFS_DISK_VERSION 0x00010001
+#define LFS_DISK_VERSION 0x00020000
#define LFS_DISK_VERSION_MAJOR (0xffff & (LFS_DISK_VERSION >> 16))
#define LFS_DISK_VERSION_MINOR (0xffff & (LFS_DISK_VERSION >> 0))
@@ -44,51 +44,93 @@ typedef int32_t lfs_soff_t;
typedef uint32_t lfs_block_t;
-// Max name size in bytes
+// Maximum size of all attributes per file in bytes, may be redefined but a
+// a smaller LFS_ATTR_MAX has no benefit. Stored in 12-bits and limited
+// to <= 0xfff. Stored in superblock and must be respected by other
+// littlefs drivers.
+#ifndef LFS_ATTR_MAX
+#define LFS_ATTR_MAX 0xfff
+#endif
+
+// Maximum name size in bytes, may be redefined to reduce the size of the
+// info struct. Limited to <= LFS_ATTR_MAX. Stored in superblock and must
+// be respected by other littlefs drivers.
#ifndef LFS_NAME_MAX
-#define LFS_NAME_MAX 255
+#define LFS_NAME_MAX 0xff
+#endif
+
+// Maximum inline file size in bytes. Large inline files require a larger
+// cache size, but if a file can be inline it does not need its own data
+// block. Limited to <= LFS_ATTR_MAX and <= cache_size. Stored in superblock
+// and must be respected by other littlefs drivers.
+#ifndef LFS_INLINE_MAX
+#define LFS_INLINE_MAX 0xfff
#endif
// Possible error codes, these are negative to allow
// valid positive return values
enum lfs_error {
- LFS_ERR_OK = 0, // No error
- LFS_ERR_IO = -5, // Error during device operation
- LFS_ERR_CORRUPT = -52, // Corrupted
- LFS_ERR_NOENT = -2, // No directory entry
- LFS_ERR_EXIST = -17, // Entry already exists
- LFS_ERR_NOTDIR = -20, // Entry is not a dir
- LFS_ERR_ISDIR = -21, // Entry is a dir
- LFS_ERR_NOTEMPTY = -39, // Dir is not empty
- LFS_ERR_BADF = -9, // Bad file number
- LFS_ERR_INVAL = -22, // Invalid parameter
- LFS_ERR_NOSPC = -28, // No space left on device
- LFS_ERR_NOMEM = -12, // No more memory available
+ LFS_ERR_OK = 0, // No error
+ LFS_ERR_IO = -5, // Error during device operation
+ LFS_ERR_CORRUPT = -84, // Corrupted
+ LFS_ERR_NOENT = -2, // No directory entry
+ LFS_ERR_EXIST = -17, // Entry already exists
+ LFS_ERR_NOTDIR = -20, // Entry is not a dir
+ LFS_ERR_ISDIR = -21, // Entry is a dir
+ LFS_ERR_NOTEMPTY = -39, // Dir is not empty
+ LFS_ERR_BADF = -9, // Bad file number
+ LFS_ERR_INVAL = -22, // Invalid parameter
+ LFS_ERR_NOSPC = -28, // No space left on device
+ LFS_ERR_NOMEM = -12, // No more memory available
+ LFS_ERR_NAMETOOLONG = -36, // File name too long
};
// File types
enum lfs_type {
- LFS_TYPE_REG = 0x11,
- LFS_TYPE_DIR = 0x22,
- LFS_TYPE_SUPERBLOCK = 0x2e,
+ // file types
+ LFS_TYPE_REG = 0x001,
+ LFS_TYPE_DIR = 0x002,
+
+ // internally used types
+ LFS_TYPE_USER = 0x100,
+ LFS_TYPE_SUPERBLOCK = 0x010,
+ LFS_TYPE_NAME = 0x000,
+ LFS_TYPE_DELETE = 0x030,
+ LFS_TYPE_STRUCT = 0x040,
+ LFS_TYPE_GLOBALS = 0x080,
+ LFS_TYPE_TAIL = 0x0c0,
+ LFS_TYPE_SOFTTAIL = 0x0c0,
+ LFS_TYPE_HARDTAIL = 0x0c1,
+ LFS_TYPE_CRC = 0x0f0,
+
+ LFS_TYPE_INLINESTRUCT = 0x040,
+ LFS_TYPE_CTZSTRUCT = 0x041,
+ LFS_TYPE_DIRSTRUCT = 0x042,
+
+ // internal chip sources
+ LFS_FROM_REGION = 0x000,
+ LFS_FROM_DISK = 0x200,
+ LFS_FROM_MOVE = 0x021,
+ LFS_FROM_ATTRS = 0x022,
};
// File open flags
enum lfs_open_flags {
// open flags
- LFS_O_RDONLY = 1, // Open a file as read only
- LFS_O_WRONLY = 2, // Open a file as write only
- LFS_O_RDWR = 3, // Open a file as read and write
- LFS_O_CREAT = 0x0100, // Create a file if it does not exist
- LFS_O_EXCL = 0x0200, // Fail if a file already exists
- LFS_O_TRUNC = 0x0400, // Truncate the existing file to zero size
- LFS_O_APPEND = 0x0800, // Move to end of file on every write
+ LFS_O_RDONLY = 1, // Open a file as read only
+ LFS_O_WRONLY = 2, // Open a file as write only
+ LFS_O_RDWR = 3, // Open a file as read and write
+ LFS_O_CREAT = 0x0100, // Create a file if it does not exist
+ LFS_O_EXCL = 0x0200, // Fail if a file already exists
+ LFS_O_TRUNC = 0x0400, // Truncate the existing file to zero size
+ LFS_O_APPEND = 0x0800, // Move to end of file on every write
// internally used flags
- LFS_F_DIRTY = 0x10000, // File does not match storage
- LFS_F_WRITING = 0x20000, // File has been written since last flush
- LFS_F_READING = 0x40000, // File has been read since last flush
- LFS_F_ERRED = 0x80000, // An error occured during write
+ LFS_F_DIRTY = 0x010000, // File does not match storage
+ LFS_F_WRITING = 0x020000, // File has been written since last flush
+ LFS_F_READING = 0x040000, // File has been read since last flush
+ LFS_F_ERRED = 0x080000, // An error occured during write
+ LFS_F_INLINE = 0x100000, // Currently inlined in directory entry
};
// File seek flags
@@ -126,21 +168,24 @@ struct lfs_config {
// are propogated to the user.
int (*sync)(const struct lfs_config *c);
- // Minimum size of a block read. This determines the size of read buffers.
- // This may be larger than the physical read size to improve performance
- // by caching more of the block device.
+ // Minimum size of a block read. All read operations will be a
+ // multiple of this value.
lfs_size_t read_size;
- // Minimum size of a block program. This determines the size of program
- // buffers. This may be larger than the physical program size to improve
- // performance by caching more of the block device.
- // Must be a multiple of the read size.
+ // Minimum size of a block program. All program operations will be a
+ // multiple of this value.
lfs_size_t prog_size;
+ // Size of block caches. Each cache buffers a portion of a block in RAM.
+ // This determines the size of the read cache, the program cache, and a
+ // cache per file. Larger caches can improve performance by storing more
+ // data. Must be a multiple of the read and program sizes.
+ lfs_size_t cache_size;
+
// Size of an erasable block. This does not impact ram consumption and
// may be larger than the physical erase size. However, this should be
// kept small as each file currently takes up an entire block.
- // Must be a multiple of the program size.
+ // Must be a multiple of the read, program, and cache sizes.
lfs_size_t block_size;
// Number of erasable blocks on the device.
@@ -162,16 +207,24 @@ struct lfs_config {
// lookahead block.
void *lookahead_buffer;
- // Optional, statically allocated buffer for files. Must be program sized.
- // If enabled, only one file may be opened at a time.
- void *file_buffer;
-};
-
-// Optional configuration provided during lfs_file_opencfg
-struct lfs_file_config {
- // Optional, statically allocated buffer for files. Must be program sized.
- // If NULL, malloc will be used by default.
- void *buffer;
+ // Optional upper limit on file attributes in bytes. No downside for larger
+ // attributes size but must be less than LFS_ATTR_MAX. Defaults to
+ // LFS_ATTR_MAX when zero.Stored in superblock and must be respected by
+ // other littlefs drivers.
+ lfs_size_t attr_max;
+
+ // Optional upper limit on length of file names in bytes. No downside for
+ // larger names except the size of the info struct which is controlled by
+ // the LFS_NAME_MAX define. Defaults to LFS_NAME_MAX when zero. Stored in
+ // superblock and must be respected by other littlefs drivers.
+ lfs_size_t name_max;
+
+ // Optional upper limit on inlined files in bytes. Large inline files
+ // require a larger cache size, but if a file can be inlined it does not
+ // need its own data block. Must be smaller than cache_size and less than
+ // LFS_INLINE_MAX. Defaults to min(LFS_INLINE_MAX, read_size) when zero.
+ // Stored in superblock and must be respected by other littlefs drivers.
+ lfs_size_t inline_max;
};
// File info structure
@@ -186,77 +239,125 @@ struct lfs_info {
char name[LFS_NAME_MAX+1];
};
+// Custom attribute structure
+struct lfs_attr {
+ // 8-bit type of attribute, provided by user and used to
+ // identify the attribute
+ uint8_t type;
-/// littlefs data structures ///
-typedef struct lfs_entry {
- lfs_off_t off;
+ // Pointer to buffer containing the attribute
+ void *buffer;
+
+ // Size of attribute in bytes, limited to LFS_ATTR_MAX
+ lfs_size_t size;
+
+ // Pointer to next attribute in linked list
+ struct lfs_attr *next;
+};
+
+// Optional configuration provided during lfs_file_opencfg
+struct lfs_file_config {
+ // Optional, statically allocated buffer for files. Must be program sized.
+ // If NULL, malloc will be used by default.
+ void *buffer;
+
+ // Optional, linked list of custom attributes related to the file. If the
+ // file is opened with read access, the attributes will be read from
+ // during the open call. If the file is opened with write access, the
+ // attributes will be written to disk every file sync or close. This
+ // write occurs atomically with update to the file's contents.
+ //
+ // Custom attributes are uniquely identified by an 8-bit type and limited
+ // to LFS_ATTR_MAX bytes. When read, if the stored attribute is smaller
+ // than the buffer, it will be padded with zeros. If the stored attribute
+ // is larger, then it will be silently truncated. If the attribute is not
+ // found, it will be created implicitly.
+ struct lfs_attr *attrs;
+};
- struct lfs_disk_entry {
- uint8_t type;
- uint8_t elen;
- uint8_t alen;
- uint8_t nlen;
- union {
- struct {
- lfs_block_t head;
- lfs_size_t size;
- } file;
- lfs_block_t dir[2];
- } u;
- } d;
-} lfs_entry_t;
+
+/// littlefs data structures ///
+typedef struct lfs_mattr {
+ int32_t tag;
+ const void *buffer;
+ const struct lfs_mattr *next;
+} lfs_mattr_t;
typedef struct lfs_cache {
lfs_block_t block;
lfs_off_t off;
+ lfs_size_t size;
uint8_t *buffer;
} lfs_cache_t;
+typedef union lfs_global {
+ uint32_t u32[3];
+ struct {
+ lfs_block_t movepair[2];
+ uint16_t moveid;
+ bool deorphaned;
+ } s;
+} lfs_global_t;
+
+typedef struct lfs_mdir {
+ lfs_block_t pair[2];
+ uint32_t rev;
+ uint32_t etag;
+ lfs_off_t off;
+ uint16_t count;
+ bool erased;
+ bool split;
+ lfs_block_t tail[2];
+ lfs_global_t locals;
+} lfs_mdir_t;
+
+typedef struct lfs_mlist {
+ struct lfs_mlist *next;
+ uint16_t id;
+ uint8_t type;
+ lfs_mdir_t m;
+} lfs_mlist_t;
+
+typedef struct lfs_dir {
+ struct lfs_dir *next;
+ uint16_t id;
+ uint8_t type;
+ lfs_mdir_t m;
+
+ lfs_off_t pos;
+ lfs_block_t head[2];
+} lfs_dir_t;
+
typedef struct lfs_file {
struct lfs_file *next;
- lfs_block_t pair[2];
- lfs_off_t poff;
+ uint16_t id;
+ uint8_t type;
+ lfs_mdir_t m;
- lfs_block_t head;
- lfs_size_t size;
+ struct lfs_ctz {
+ lfs_block_t head;
+ lfs_size_t size;
+ } ctz;
- const struct lfs_file_config *cfg;
uint32_t flags;
lfs_off_t pos;
lfs_block_t block;
lfs_off_t off;
lfs_cache_t cache;
-} lfs_file_t;
-
-typedef struct lfs_dir {
- struct lfs_dir *next;
- lfs_block_t pair[2];
- lfs_off_t off;
- lfs_block_t head[2];
- lfs_off_t pos;
-
- struct lfs_disk_dir {
- uint32_t rev;
- lfs_size_t size;
- lfs_block_t tail[2];
- } d;
-} lfs_dir_t;
+ const struct lfs_file_config *cfg;
+} lfs_file_t;
typedef struct lfs_superblock {
- lfs_off_t off;
+ char magic[8];
+ uint32_t version;
- struct lfs_disk_superblock {
- uint8_t type;
- uint8_t elen;
- uint8_t alen;
- uint8_t nlen;
- lfs_block_t root[2];
- uint32_t block_size;
- uint32_t block_count;
- uint32_t version;
- char magic[8];
- } d;
+ lfs_size_t block_size;
+ lfs_size_t block_count;
+
+ lfs_size_t attr_max;
+ lfs_size_t name_max;
+ lfs_size_t inline_max;
} lfs_superblock_t;
typedef struct lfs_free {
@@ -269,17 +370,22 @@ typedef struct lfs_free {
// The littlefs type
typedef struct lfs {
- const struct lfs_config *cfg;
-
- lfs_block_t root[2];
- lfs_file_t *files;
- lfs_dir_t *dirs;
-
lfs_cache_t rcache;
lfs_cache_t pcache;
+ lfs_block_t root[2];
+ lfs_mlist_t *mlist;
+
+ lfs_global_t globals;
+ lfs_global_t locals;
lfs_free_t free;
- bool deorphaned;
+
+ const struct lfs_config *cfg;
+ lfs_size_t block_size;
+ lfs_size_t block_count;
+ lfs_size_t attr_max;
+ lfs_size_t name_max;
+ lfs_size_t inline_max;
} lfs_t;
@@ -332,6 +438,31 @@ int lfs_rename(lfs_t *lfs, const char *oldpath, const char *newpath);
// Returns a negative error code on failure.
int lfs_stat(lfs_t *lfs, const char *path, struct lfs_info *info);
+// Get a custom attribute
+//
+// Custom attributes are uniquely identified by an 8-bit type and limited
+// to LFS_ATTR_MAX bytes. When read, if the stored attribute is smaller than
+// the buffer, it will be padded with zeros. If the stored attribute is larger,
+// then it will be silently truncated.
+//
+// Returns the size of the attribute, or a negative error code on failure.
+// Note, the returned size is the size of the attribute on disk, irrespective
+// of the size of the buffer. This can be used to dynamically allocate a buffer
+// or check for existance.
+lfs_ssize_t lfs_getattr(lfs_t *lfs, const char *path,
+ uint8_t type, void *buffer, lfs_size_t size);
+
+// Set custom attributes
+//
+// Custom attributes are uniquely identified by an 8-bit type and limited
+// to LFS_ATTR_MAX bytes. If an attribute is not found, it will be
+// implicitly created, and setting the size of an attribute to zero deletes
+// the attribute.
+//
+// Returns a negative error code on failure.
+int lfs_setattr(lfs_t *lfs, const char *path,
+ uint8_t type, const void *buffer, lfs_size_t size);
+
/// File operations ///
@@ -466,7 +597,15 @@ lfs_soff_t lfs_dir_tell(lfs_t *lfs, lfs_dir_t *dir);
int lfs_dir_rewind(lfs_t *lfs, lfs_dir_t *dir);
-/// Miscellaneous littlefs specific operations ///
+/// Filesystem-level filesystem operations
+
+// Finds the current size of the filesystem
+//
+// Note: Result is best effort. If files share COW structures, the returned
+// size may be larger than the filesystem actually is.
+//
+// Returns the number of allocated blocks, or a negative error code on failure.
+lfs_ssize_t lfs_fs_size(lfs_t *lfs);
// Traverse through all blocks in use by the filesystem
//
@@ -475,16 +614,36 @@ int lfs_dir_rewind(lfs_t *lfs, lfs_dir_t *dir);
// blocks are in use or how much of the storage is available.
//
// Returns a negative error code on failure.
-int lfs_traverse(lfs_t *lfs, int (*cb)(void*, lfs_block_t), void *data);
+int lfs_fs_traverse(lfs_t *lfs, int (*cb)(void*, lfs_block_t), void *data);
-// Prunes any recoverable errors that may have occured in the filesystem
+// Get custom attributes on the filesystem
+//
+// Custom attributes are uniquely identified by an 8-bit type and limited
+// to LFS_ATTR_MAX bytes. When read, if the stored attribute is smaller than
+// the buffer, it will be padded with zeros. If the stored attribute is larger,
+// then it will be silently truncated.
+//
+// Note, filesystem-level attributes are not available for wear-leveling
+//
+// Returns the size of the attribute, or a negative error code on failure.
+// Note, the returned size is the size of the attribute on disk, irrespective
+// of the size of the buffer. This can be used to dynamically allocate a buffer
+// or check for existance.
+lfs_ssize_t lfs_fs_getattr(lfs_t *lfs,
+ uint8_t type, void *buffer, lfs_size_t size);
+
+// Set custom attributes on the filesystem
+//
+// Custom attributes are uniquely identified by an 8-bit type and limited
+// to LFS_ATTR_MAX bytes. If an attribute is not found, it will be
+// implicitly created, and setting the size of an attribute to zero deletes
+// the attribute.
//
-// Not needed to be called by user unless an operation is interrupted
-// but the filesystem is still mounted. This is already called on first
-// allocation.
+// Note, filesystem-level attributes are not available for wear-leveling
//
// Returns a negative error code on failure.
-int lfs_deorphan(lfs_t *lfs);
+int lfs_fs_setattr(lfs_t *lfs,
+ uint8_t type, const void *buffer, lfs_size_t size);
#ifdef __cplusplus
diff --git a/lfs_util.h b/lfs_util.h
index b2dc237..ed55a1c 100644
--- a/lfs_util.h
+++ b/lfs_util.h
@@ -23,6 +23,7 @@
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
+#include <inttypes.h>
#ifndef LFS_NO_MALLOC
#include <stdlib.h>
@@ -130,7 +131,7 @@ static inline int lfs_scmp(uint32_t a, uint32_t b) {
return (int)(unsigned)(a - b);
}
-// Convert from 32-bit little-endian to native order
+// Convert between 32-bit little-endian and native order
static inline uint32_t lfs_fromle32(uint32_t a) {
#if !defined(LFS_NO_INTRINSICS) && ( \
(defined( BYTE_ORDER ) && BYTE_ORDER == ORDER_LITTLE_ENDIAN ) || \
@@ -150,11 +151,41 @@ static inline uint32_t lfs_fromle32(uint32_t a) {
#endif
}
-// Convert to 32-bit little-endian from native order
static inline uint32_t lfs_tole32(uint32_t a) {
return lfs_fromle32(a);
}
+// Convert between 16-bit little-endian and native order
+static inline uint16_t lfs_fromle16(uint16_t a) {
+#if !defined(LFS_NO_INTRINSICS) && ( \
+ (defined( BYTE_ORDER ) && BYTE_ORDER == ORDER_LITTLE_ENDIAN ) || \
+ (defined(__BYTE_ORDER ) && __BYTE_ORDER == __ORDER_LITTLE_ENDIAN ) || \
+ (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__))
+ return a;
+#elif !defined(LFS_NO_INTRINSICS) && ( \
+ (defined( BYTE_ORDER ) && BYTE_ORDER == ORDER_BIG_ENDIAN ) || \
+ (defined(__BYTE_ORDER ) && __BYTE_ORDER == __ORDER_BIG_ENDIAN ) || \
+ (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__))
+ return __builtin_bswap16(a);
+#else
+ return (((uint8_t*)&a)[0] << 0) |
+ (((uint8_t*)&a)[1] << 8);
+#endif
+}
+
+static inline uint16_t lfs_tole16(uint16_t a) {
+ return lfs_fromle16(a);
+}
+
+// Align to nearest multiple of a size
+static inline uint32_t lfs_aligndown(uint32_t a, uint32_t alignment) {
+ return a - (a % alignment);
+}
+
+static inline uint32_t lfs_alignup(uint32_t a, uint32_t alignment) {
+ return lfs_aligndown(a + alignment-1, alignment);
+}
+
// Calculate CRC-32 with polynomial = 0x04c11db7
void lfs_crc(uint32_t *crc, const void *buffer, size_t size);
diff --git a/tests/corrupt.py b/tests/corrupt.py
new file mode 100755
index 0000000..76f07ce
--- /dev/null
+++ b/tests/corrupt.py
@@ -0,0 +1,39 @@
+#!/usr/bin/env python
+
+import struct
+import sys
+import os
+
+def main(*paths):
+ # find most recent block
+ file = None
+ rev = None
+ for path in paths:
+ try:
+ nfile = open(path, 'r+b')
+ nrev, = struct.unpack('<I', nfile.read(4))
+
+ assert rev != nrev
+ if not file or ((rev - nrev) & 0x80000000):
+ file = nfile
+ rev = nrev
+ except IOError:
+ pass
+
+ # go to last commit
+ tag = 0
+ while True:
+ try:
+ ntag, = struct.unpack('<I', file.read(4))
+ except struct.error:
+ break
+
+ tag ^= ntag
+ file.seek(tag & 0xfff, os.SEEK_CUR)
+
+ # lob off last 3 bytes
+ file.seek(-((tag & 0xfff) + 3), os.SEEK_CUR)
+ file.truncate()
+
+if __name__ == "__main__":
+ main(*sys.argv[1:])
diff --git a/tests/template.fmt b/tests/template.fmt
index a53f0c7..c745d24 100644
--- a/tests/template.fmt
+++ b/tests/template.fmt
@@ -66,7 +66,11 @@ uintmax_t test;
#endif
#ifndef LFS_PROG_SIZE
-#define LFS_PROG_SIZE 16
+#define LFS_PROG_SIZE LFS_READ_SIZE
+#endif
+
+#ifndef LFS_CACHE_SIZE
+#define LFS_CACHE_SIZE 64
#endif
#ifndef LFS_BLOCK_SIZE
@@ -90,6 +94,7 @@ const struct lfs_config cfg = {{
.read_size = LFS_READ_SIZE,
.prog_size = LFS_PROG_SIZE,
+ .cache_size = LFS_CACHE_SIZE,
.block_size = LFS_BLOCK_SIZE,
.block_count = LFS_BLOCK_COUNT,
.lookahead = LFS_LOOKAHEAD,
diff --git a/tests/test.py b/tests/test.py
index 24b0d1a..e93ccec 100755
--- a/tests/test.py
+++ b/tests/test.py
@@ -10,7 +10,7 @@ def generate(test):
template = file.read()
lines = []
- for line in re.split('(?<=[;{}])\n', test.read()):
+ for line in re.split('(?<=(?:.;| [{}]))\n', test.read()):
match = re.match('(?: *\n)*( *)(.*)=>(.*);', line, re.DOTALL | re.MULTILINE)
if match:
tab, test, expect = match.groups()
diff --git a/tests/test_alloc.sh b/tests/test_alloc.sh
index 8c81490..d3c8da4 100755
--- a/tests/test_alloc.sh
+++ b/tests/test_alloc.sh
@@ -274,9 +274,12 @@ TEST
tests/test.py << TEST
lfs_mount(&lfs, &cfg) => 0;
- // create one block whole for half a directory
+ // create one block hole for half a directory
lfs_file_open(&lfs, &file[0], "bump", LFS_O_WRONLY | LFS_O_CREAT) => 0;
- lfs_file_write(&lfs, &file[0], (void*)"hi", 2) => 2;
+ for (lfs_size_t i = 0; i < cfg.block_size; i += 2) {
+ memcpy(&buffer[i], "hi", 2);
+ }
+ lfs_file_write(&lfs, &file[0], buffer, cfg.block_size) => cfg.block_size;
lfs_file_close(&lfs, &file[0]) => 0;
lfs_file_open(&lfs, &file[0], "exhaustion", LFS_O_WRONLY | LFS_O_CREAT);
@@ -295,7 +298,10 @@ tests/test.py << TEST
lfs_mkdir(&lfs, "splitdir") => 0;
lfs_file_open(&lfs, &file[0], "splitdir/bump",
LFS_O_WRONLY | LFS_O_CREAT) => 0;
- lfs_file_write(&lfs, &file[0], buffer, size) => LFS_ERR_NOSPC;
+ for (lfs_size_t i = 0; i < cfg.block_size; i += 2) {
+ memcpy(&buffer[i], "hi", 2);
+ }
+ lfs_file_write(&lfs, &file[0], buffer, cfg.block_size) => LFS_ERR_NOSPC;
lfs_file_close(&lfs, &file[0]) => 0;
lfs_unmount(&lfs) => 0;
diff --git a/tests/test_attrs.sh b/tests/test_attrs.sh
new file mode 100755
index 0000000..cb4f4dc
--- /dev/null
+++ b/tests/test_attrs.sh
@@ -0,0 +1,262 @@
+#!/bin/bash
+set -eu
+
+echo "=== Attr tests ==="
+rm -rf blocks
+tests/test.py << TEST
+ lfs_format(&lfs, &cfg) => 0;
+
+ lfs_mount(&lfs, &cfg) => 0;
+ lfs_mkdir(&lfs, "hello") => 0;
+ lfs_file_open(&lfs, &file[0], "hello/hello",
+ LFS_O_WRONLY | LFS_O_CREAT) => 0;
+ lfs_file_write(&lfs, &file[0], "hello", strlen("hello"))
+ => strlen("hello");
+ lfs_file_close(&lfs, &file[0]);
+ lfs_unmount(&lfs) => 0;
+TEST
+
+echo "--- Set/get attribute ---"
+tests/test.py << TEST
+ lfs_mount(&lfs, &cfg) => 0;
+ lfs_setattr(&lfs, "hello", 'A', "aaaa", 4) => 0;
+ lfs_setattr(&lfs, "hello", 'B', "bbbbbb", 6) => 0;
+ lfs_setattr(&lfs, "hello", 'C', "ccccc", 5) => 0;
+ lfs_getattr(&lfs, "hello", 'A', buffer, 4) => 4;
+ lfs_getattr(&lfs, "hello", 'B', buffer+4, 6) => 6;
+ lfs_getattr(&lfs, "hello", 'C', buffer+10, 5) => 5;
+ memcmp(buffer, "aaaa", 4) => 0;
+ memcmp(buffer+4, "bbbbbb", 6) => 0;
+ memcmp(buffer+10, "ccccc", 5) => 0;
+
+ lfs_setattr(&lfs, "hello", 'B', "", 0) => 0;
+ lfs_getattr(&lfs, "hello", 'A', buffer, 4) => 4;
+ lfs_getattr(&lfs, "hello", 'B', buffer+4, 6) => 0;
+ lfs_getattr(&lfs, "hello", 'C', buffer+10, 5) => 5;
+ memcmp(buffer, "aaaa", 4) => 0;
+ memcmp(buffer+4, "\0\0\0\0\0\0", 6) => 0;
+ memcmp(buffer+10, "ccccc", 5) => 0;
+
+ lfs_setattr(&lfs, "hello", 'B', "dddddd", 6) => 0;
+ lfs_getattr(&lfs, "hello", 'A', buffer, 4) => 4;
+ lfs_getattr(&lfs, "hello", 'B', buffer+4, 6) => 6;
+ lfs_getattr(&lfs, "hello", 'C', buffer+10, 5) => 5;
+ memcmp(buffer, "aaaa", 4) => 0;
+ memcmp(buffer+4, "dddddd", 6) => 0;
+ memcmp(buffer+10, "ccccc", 5) => 0;
+
+ lfs_setattr(&lfs, "hello", 'B', "eee", 3) => 0;
+ lfs_getattr(&lfs, "hello", 'A', buffer, 4) => 4;
+ lfs_getattr(&lfs, "hello", 'B', buffer+4, 6) => 3;
+ lfs_getattr(&lfs, "hello", 'C', buffer+10, 5) => 5;
+ memcmp(buffer, "aaaa", 4) => 0;
+ memcmp(buffer+4, "eee\0\0\0", 6) => 0;
+ memcmp(buffer+10, "ccccc", 5) => 0;
+
+ lfs_setattr(&lfs, "hello", 'A', buffer, LFS_ATTR_MAX+1) => LFS_ERR_NOSPC;
+ lfs_setattr(&lfs, "hello", 'B', "fffffffff", 9) => 0;
+ lfs_getattr(&lfs, "hello", 'A', buffer, 4) => 4;
+ lfs_getattr(&lfs, "hello", 'B', buffer+4, 6) => 9;
+ lfs_getattr(&lfs, "hello", 'C', buffer+10, 5) => 5;
+
+ lfs_unmount(&lfs) => 0;
+TEST
+tests/test.py << TEST
+ lfs_mount(&lfs, &cfg) => 0;
+ lfs_getattr(&lfs, "hello", 'A', buffer, 4) => 4;
+ lfs_getattr(&lfs, "hello", 'B', buffer+4, 9) => 9;
+ lfs_getattr(&lfs, "hello", 'C', buffer+13, 5) => 5;
+ memcmp(buffer, "aaaa", 4) => 0;
+ memcmp(buffer+4, "fffffffff", 9) => 0;
+ memcmp(buffer+13, "ccccc", 5) => 0;
+
+ lfs_file_open(&lfs, &file[0], "hello/hello", LFS_O_RDONLY) => 0;
+ lfs_file_read(&lfs, &file[0], buffer, sizeof(buffer)) => strlen("hello");
+ memcmp(buffer, "hello", strlen("hello")) => 0;
+ lfs_file_close(&lfs, &file[0]);
+ lfs_unmount(&lfs) => 0;
+TEST
+
+echo "--- Set/get fs attribute ---"
+tests/test.py << TEST
+ lfs_mount(&lfs, &cfg) => 0;
+ lfs_fs_setattr(&lfs, 'A', "aaaa", 4) => 0;
+ lfs_fs_setattr(&lfs, 'B', "bbbbbb", 6) => 0;
+ lfs_fs_setattr(&lfs, 'C', "ccccc", 5) => 0;
+ lfs_fs_getattr(&lfs, 'A', buffer, 4) => 4;
+ lfs_fs_getattr(&lfs, 'B', buffer+4, 6) => 6;
+ lfs_fs_getattr(&lfs, 'C', buffer+10, 5) => 5;
+ memcmp(buffer, "aaaa", 4) => 0;
+ memcmp(buffer+4, "bbbbbb", 6) => 0;
+ memcmp(buffer+10, "ccccc", 5) => 0;
+
+ lfs_fs_setattr(&lfs, 'B', "", 0) => 0;
+ lfs_fs_getattr(&lfs, 'A', buffer, 4) => 4;
+ lfs_fs_getattr(&lfs, 'B', buffer+4, 6) => 0;
+ lfs_fs_getattr(&lfs, 'C', buffer+10, 5) => 5;
+ memcmp(buffer, "aaaa", 4) => 0;
+ memcmp(buffer+4, "\0\0\0\0\0\0", 6) => 0;
+ memcmp(buffer+10, "ccccc", 5) => 0;
+
+ lfs_fs_setattr(&lfs, 'B', "dddddd", 6) => 0;
+ lfs_fs_getattr(&lfs, 'A', buffer, 4) => 4;
+ lfs_fs_getattr(&lfs, 'B', buffer+4, 6) => 6;
+ lfs_fs_getattr(&lfs, 'C', buffer+10, 5) => 5;
+ memcmp(buffer, "aaaa", 4) => 0;
+ memcmp(buffer+4, "dddddd", 6) => 0;
+ memcmp(buffer+10, "ccccc", 5) => 0;
+
+ lfs_fs_setattr(&lfs, 'B', "eee", 3) => 0;
+ lfs_fs_getattr(&lfs, 'A', buffer, 4) => 4;
+ lfs_fs_getattr(&lfs, 'B', buffer+4, 6) => 3;
+ lfs_fs_getattr(&lfs, 'C', buffer+10, 5) => 5;
+ memcmp(buffer, "aaaa", 4) => 0;
+ memcmp(buffer+4, "eee\0\0\0", 6) => 0;
+ memcmp(buffer+10, "ccccc", 5) => 0;
+
+ lfs_fs_setattr(&lfs, 'A', buffer, LFS_ATTR_MAX+1) => LFS_ERR_NOSPC;
+ lfs_fs_setattr(&lfs, 'B', "fffffffff", 9) => 0;
+ lfs_fs_getattr(&lfs, 'A', buffer, 4) => 4;
+ lfs_fs_getattr(&lfs, 'B', buffer+4, 6) => 9;
+ lfs_fs_getattr(&lfs, 'C', buffer+10, 5) => 5;
+ lfs_unmount(&lfs) => 0;
+TEST
+tests/test.py << TEST
+ lfs_mount(&lfs, &cfg) => 0;
+ lfs_fs_getattr(&lfs, 'A', buffer, 4) => 4;
+ lfs_fs_getattr(&lfs, 'B', buffer+4, 9) => 9;
+ lfs_fs_getattr(&lfs, 'C', buffer+13, 5) => 5;
+ memcmp(buffer, "aaaa", 4) => 0;
+ memcmp(buffer+4, "fffffffff", 9) => 0;
+ memcmp(buffer+13, "ccccc", 5) => 0;
+
+ lfs_file_open(&lfs, &file[0], "hello/hello", LFS_O_RDONLY) => 0;
+ lfs_file_read(&lfs, &file[0], buffer, sizeof(buffer)) => strlen("hello");
+ memcmp(buffer, "hello", strlen("hello")) => 0;
+ lfs_file_close(&lfs, &file[0]);
+ lfs_unmount(&lfs) => 0;
+TEST
+
+echo "--- Set/get file attribute ---"
+tests/test.py << TEST
+ lfs_mount(&lfs, &cfg) => 0;
+ struct lfs_attr a1 = {'A', buffer, 4};
+ struct lfs_attr b1 = {'B', buffer+4, 6, &a1};
+ struct lfs_attr c1 = {'C', buffer+10, 5, &b1};
+ struct lfs_file_config cfg1 = {.attrs = &c1};
+
+ lfs_file_opencfg(&lfs, &file[0], "hello/hello", LFS_O_WRONLY, &cfg1) => 0;
+ memcpy(buffer, "aaaa", 4);
+ memcpy(buffer+4, "bbbbbb", 6);
+ memcpy(buffer+10, "ccccc", 5);
+ lfs_file_close(&lfs, &file[0]) => 0;
+ memset(buffer, 0, 15);
+ lfs_file_opencfg(&lfs, &file[0], "hello/hello", LFS_O_RDONLY, &cfg1) => 0;
+ lfs_file_close(&lfs, &file[0]) => 0;
+ memcmp(buffer, "aaaa", 4) => 0;
+ memcmp(buffer+4, "bbbbbb", 6) => 0;
+ memcmp(buffer+10, "ccccc", 5) => 0;
+
+ b1.size = 0;
+ lfs_file_opencfg(&lfs, &file[0], "hello/hello", LFS_O_WRONLY, &cfg1) => 0;
+ lfs_file_close(&lfs, &file[0]) => 0;
+ memset(buffer, 0, 15);
+ b1.size = 6;
+ lfs_file_opencfg(&lfs, &file[0], "hello/hello", LFS_O_RDONLY, &cfg1) => 0;
+ lfs_file_close(&lfs, &file[0]) => 0;
+ memcmp(buffer, "aaaa", 4) => 0;
+ memcmp(buffer+4, "\0\0\0\0\0\0", 6) => 0;
+ memcmp(buffer+10, "ccccc", 5) => 0;
+
+ b1.size = 6;
+ lfs_file_opencfg(&lfs, &file[0], "hello/hello", LFS_O_WRONLY, &cfg1) => 0;
+ memcpy(buffer+4, "dddddd", 6);
+ lfs_file_close(&lfs, &file[0]) => 0;
+ memset(buffer, 0, 15);
+ b1.size = 6;
+ lfs_file_opencfg(&lfs, &file[0], "hello/hello", LFS_O_RDONLY, &cfg1) => 0;
+ lfs_file_close(&lfs, &file[0]) => 0;
+ memcmp(buffer, "aaaa", 4) => 0;
+ memcmp(buffer+4, "dddddd", 6) => 0;
+ memcmp(buffer+10, "ccccc", 5) => 0;
+
+ b1.size = 3;
+ lfs_file_opencfg(&lfs, &file[0], "hello/hello", LFS_O_WRONLY, &cfg1) => 0;
+ memcpy(buffer+4, "eee", 3);
+ lfs_file_close(&lfs, &file[0]) => 0;
+ memset(buffer, 0, 15);
+ b1.size = 6;
+ lfs_file_opencfg(&lfs, &file[0], "hello/hello", LFS_O_RDONLY, &cfg1) => 0;
+ lfs_file_close(&lfs, &file[0]) => 0;
+ memcmp(buffer, "aaaa", 4) => 0;
+ memcmp(buffer+4, "eee\0\0\0", 6) => 0;
+ memcmp(buffer+10, "ccccc", 5) => 0;
+
+ a1.size = LFS_ATTR_MAX+1;
+ lfs_file_opencfg(&lfs, &file[0], "hello/hello", LFS_O_WRONLY, &cfg1)
+ => LFS_ERR_NOSPC;
+
+ struct lfs_attr a2 = {'A', buffer, 4};
+ struct lfs_attr b2 = {'B', buffer+4, 9, &a2};
+ struct lfs_attr c2 = {'C', buffer+13, 5, &b2};
+ struct lfs_file_config cfg2 = {.attrs = &c2};
+ lfs_file_opencfg(&lfs, &file[0], "hello/hello", LFS_O_RDWR, &cfg2) => 0;
+ memcpy(buffer+4, "fffffffff", 9);
+ lfs_file_close(&lfs, &file[0]) => 0;
+ a1.size = 4;
+ lfs_file_opencfg(&lfs, &file[0], "hello/hello", LFS_O_RDONLY, &cfg1) => 0;
+ lfs_file_close(&lfs, &file[0]) => 0;
+
+ lfs_unmount(&lfs) => 0;
+TEST
+tests/test.py << TEST
+ lfs_mount(&lfs, &cfg) => 0;
+ struct lfs_attr a2 = {'A', buffer, 4};
+ struct lfs_attr b2 = {'B', buffer+4, 9, &a2};
+ struct lfs_attr c2 = {'C', buffer+13, 5, &b2};
+ struct lfs_file_config cfg2 = {.attrs = &c2};
+
+ lfs_file_opencfg(&lfs, &file[0], "hello/hello", LFS_O_RDONLY, &cfg2) => 0;
+ lfs_file_close(&lfs, &file[0]) => 0;
+ memcmp(buffer, "aaaa", 4) => 0;
+ memcmp(buffer+4, "fffffffff", 9) => 0;
+ memcmp(buffer+13, "ccccc", 5) => 0;
+
+ lfs_file_open(&lfs, &file[0], "hello/hello", LFS_O_RDONLY) => 0;
+ lfs_file_read(&lfs, &file[0], buffer, sizeof(buffer)) => strlen("hello");
+ memcmp(buffer, "hello", strlen("hello")) => 0;
+ lfs_file_close(&lfs, &file[0]);
+ lfs_unmount(&lfs) => 0;
+TEST
+
+echo "--- Deferred file attributes ---"
+tests/test.py << TEST
+ lfs_mount(&lfs, &cfg) => 0;
+ struct lfs_attr a1 = {'B', "gggg", 4};
+ struct lfs_attr b1 = {'C', "", 0, &a1};
+ struct lfs_attr c1 = {'D', "hhhh", 4, &b1};
+ struct lfs_file_config cfg1 = {.attrs = &c1};
+
+ lfs_file_opencfg(&lfs, &file[0], "hello/hello", LFS_O_WRONLY, &cfg1) => 0;
+
+ lfs_getattr(&lfs, "hello/hello", 'B', buffer, 9) => 9;
+ lfs_getattr(&lfs, "hello/hello", 'C', buffer+9, 9) => 5;
+ lfs_getattr(&lfs, "hello/hello", 'D', buffer+18, 9) => 0;
+ memcmp(buffer, "fffffffff", 9) => 0;
+ memcmp(buffer+9, "ccccc\0\0\0\0", 9) => 0;
+ memcmp(buffer+18, "\0\0\0\0\0\0\0\0\0", 9) => 0;
+
+ lfs_file_sync(&lfs, &file[0]) => 0;
+ lfs_getattr(&lfs, "hello/hello", 'B', buffer, 9) => 4;
+ lfs_getattr(&lfs, "hello/hello", 'C', buffer+9, 9) => 0;
+ lfs_getattr(&lfs, "hello/hello", 'D', buffer+18, 9) => 4;
+ memcmp(buffer, "gggg\0\0\0\0\0", 9) => 0;
+ memcmp(buffer+9, "\0\0\0\0\0\0\0\0\0", 9) => 0;
+ memcmp(buffer+18, "hhhh\0\0\0\0\0", 9) => 0;
+
+ lfs_file_close(&lfs, &file[0]) => 0;
+ lfs_unmount(&lfs) => 0;
+TEST
+
+echo "--- Results ---"
+tests/stats.py
diff --git a/tests/test_corrupt.sh b/tests/test_corrupt.sh
index 44f1cae..1491dac 100755
--- a/tests/test_corrupt.sh
+++ b/tests/test_corrupt.sh
@@ -73,7 +73,7 @@ lfs_mktree
lfs_chktree
echo "--- Block corruption ---"
-for i in {0..33}
+for i in {2..33}
do
rm -rf blocks
mkdir blocks
@@ -83,12 +83,12 @@ do
done
echo "--- Block persistance ---"
-for i in {0..33}
+for i in {2..33}
do
rm -rf blocks
mkdir blocks
lfs_mktree
- chmod a-w blocks/$(printf '%x' $i)
+ chmod a-w blocks/$(printf '%x' $i) || true
lfs_mktree
lfs_chktree
done
diff --git a/tests/test_entries.sh b/tests/test_entries.sh
new file mode 100755
index 0000000..447d9bc
--- /dev/null
+++ b/tests/test_entries.sh
@@ -0,0 +1,220 @@
+#!/bin/bash
+set -eu
+
+# Note: These tests are intended for 512 byte inline size at different
+# inline sizes they should still pass, but won't be testing anything
+
+echo "=== Directory tests ==="
+function read_file {
+cat << TEST
+
+ size = $2;
+ lfs_file_open(&lfs, &file[0], "$1", LFS_O_RDONLY) => 0;
+ lfs_file_read(&lfs, &file[0], rbuffer, size) => size;
+ memcmp(rbuffer, wbuffer, size) => 0;
+ lfs_file_close(&lfs, &file[0]) => 0;
+TEST
+}
+
+function write_file {
+cat << TEST
+
+ size = $2;
+ lfs_file_open(&lfs, &file[0], "$1",
+ LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0;
+ memset(wbuffer, 'c', size);
+ lfs_file_write(&lfs, &file[0], wbuffer, size) => size;
+ lfs_file_close(&lfs, &file[0]) => 0;
+TEST
+}
+
+echo "--- Entry grow test ---"
+tests/test.py << TEST
+ lfs_format(&lfs, &cfg) => 0;
+
+ lfs_mount(&lfs, &cfg) => 0;
+ $(write_file "hi0" 20)
+ $(write_file "hi1" 20)
+ $(write_file "hi2" 20)
+ $(write_file "hi3" 20)
+
+ $(read_file "hi1" 20)
+ $(write_file "hi1" 200)
+
+ $(read_file "hi0" 20)
+ $(read_file "hi1" 200)
+ $(read_file "hi2" 20)
+ $(read_file "hi3" 20)
+ lfs_unmount(&lfs) => 0;
+TEST
+
+echo "--- Entry shrink test ---"
+tests/test.py << TEST
+ lfs_format(&lfs, &cfg) => 0;
+
+ lfs_mount(&lfs, &cfg) => 0;
+ $(write_file "hi0" 20)
+ $(write_file "hi1" 200)
+ $(write_file "hi2" 20)
+ $(write_file "hi3" 20)
+
+ $(read_file "hi1" 200)
+ $(write_file "hi1" 20)
+
+ $(read_file "hi0" 20)
+ $(read_file "hi1" 20)
+ $(read_file "hi2" 20)
+ $(read_file "hi3" 20)
+ lfs_unmount(&lfs) => 0;
+TEST
+
+echo "--- Entry spill test ---"
+tests/test.py << TEST
+ lfs_format(&lfs, &cfg) => 0;
+
+ lfs_mount(&lfs, &cfg) => 0;
+ $(write_file "hi0" 200)
+ $(write_file "hi1" 200)
+ $(write_file "hi2" 200)
+ $(write_file "hi3" 200)
+
+ $(read_file "hi0" 200)
+ $(read_file "hi1" 200)
+ $(read_file "hi2" 200)
+ $(read_file "hi3" 200)
+ lfs_unmount(&lfs) => 0;
+TEST
+
+echo "--- Entry push spill test ---"
+tests/test.py << TEST
+ lfs_format(&lfs, &cfg) => 0;
+
+ lfs_mount(&lfs, &cfg) => 0;
+ $(write_file "hi0" 200)
+ $(write_file "hi1" 20)
+ $(write_file "hi2" 200)
+ $(write_file "hi3" 200)
+
+ $(read_file "hi1" 20)
+ $(write_file "hi1" 200)
+
+ $(read_file "hi0" 200)
+ $(read_file "hi1" 200)
+ $(read_file "hi2" 200)
+ $(read_file "hi3" 200)
+ lfs_unmount(&lfs) => 0;
+TEST
+
+echo "--- Entry push spill two test ---"
+tests/test.py << TEST
+ lfs_format(&lfs, &cfg) => 0;
+
+ lfs_mount(&lfs, &cfg) => 0;
+ $(write_file "hi0" 200)
+ $(write_file "hi1" 20)
+ $(write_file "hi2" 200)
+ $(write_file "hi3" 200)
+ $(write_file "hi4" 200)
+
+ $(read_file "hi1" 20)
+ $(write_file "hi1" 200)
+
+ $(read_file "hi0" 200)
+ $(read_file "hi1" 200)
+ $(read_file "hi2" 200)
+ $(read_file "hi3" 200)
+ $(read_file "hi4" 200)
+ lfs_unmount(&lfs) => 0;
+TEST
+
+echo "--- Entry drop test ---"
+tests/test.py << TEST
+ lfs_format(&lfs, &cfg) => 0;
+
+ lfs_mount(&lfs, &cfg) => 0;
+ $(write_file "hi0" 200)
+ $(write_file "hi1" 200)
+ $(write_file "hi2" 200)
+ $(write_file "hi3" 200)
+
+ lfs_remove(&lfs, "hi1") => 0;
+ lfs_stat(&lfs, "hi1", &info) => LFS_ERR_NOENT;
+ $(read_file "hi0" 200)
+ $(read_file "hi2" 200)
+ $(read_file "hi3" 200)
+
+ lfs_remove(&lfs, "hi2") => 0;
+ lfs_stat(&lfs, "hi2", &info) => LFS_ERR_NOENT;
+ $(read_file "hi0" 200)
+ $(read_file "hi3" 200)
+
+ lfs_remove(&lfs, "hi3") => 0;
+ lfs_stat(&lfs, "hi3", &info) => LFS_ERR_NOENT;
+ $(read_file "hi0" 200)
+
+ lfs_remove(&lfs, "hi0") => 0;
+ lfs_stat(&lfs, "hi0", &info) => LFS_ERR_NOENT;
+ lfs_unmount(&lfs) => 0;
+TEST
+
+echo "--- Create too big ---"
+tests/test.py << TEST
+ lfs_format(&lfs, &cfg) => 0;
+
+ lfs_mount(&lfs, &cfg) => 0;
+ memset(buffer, 'm', 200);
+ buffer[200] = '\0';
+
+ size = 400;
+ lfs_file_open(&lfs, &file[0], (char*)buffer,
+ LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0;
+ memset(wbuffer, 'c', size);
+ lfs_file_write(&lfs, &file[0], wbuffer, size) => size;
+ lfs_file_close(&lfs, &file[0]) => 0;
+
+ size = 400;
+ lfs_file_open(&lfs, &file[0], (char*)buffer, LFS_O_RDONLY) => 0;
+ lfs_file_read(&lfs, &file[0], rbuffer, size) => size;
+ memcmp(rbuffer, wbuffer, size) => 0;
+ lfs_file_close(&lfs, &file[0]) => 0;
+ lfs_unmount(&lfs) => 0;
+TEST
+
+echo "--- Resize too big ---"
+tests/test.py << TEST
+ lfs_format(&lfs, &cfg) => 0;
+
+ lfs_mount(&lfs, &cfg) => 0;
+ memset(buffer, 'm', 200);
+ buffer[200] = '\0';
+
+ size = 40;
+ lfs_file_open(&lfs, &file[0], (char*)buffer,
+ LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0;
+ memset(wbuffer, 'c', size);
+ lfs_file_write(&lfs, &file[0], wbuffer, size) => size;
+ lfs_file_close(&lfs, &file[0]) => 0;
+
+ size = 40;
+ lfs_file_open(&lfs, &file[0], (char*)buffer, LFS_O_RDONLY) => 0;
+ lfs_file_read(&lfs, &file[0], rbuffer, size) => size;
+ memcmp(rbuffer, wbuffer, size) => 0;
+ lfs_file_close(&lfs, &file[0]) => 0;
+
+ size = 400;
+ lfs_file_open(&lfs, &file[0], (char*)buffer,
+ LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0;
+ memset(wbuffer, 'c', size);
+ lfs_file_write(&lfs, &file[0], wbuffer, size) => size;
+ lfs_file_close(&lfs, &file[0]) => 0;
+
+ size = 400;
+ lfs_file_open(&lfs, &file[0], (char*)buffer, LFS_O_RDONLY) => 0;
+ lfs_file_read(&lfs, &file[0], rbuffer, size) => size;
+ memcmp(rbuffer, wbuffer, size) => 0;
+ lfs_file_close(&lfs, &file[0]) => 0;
+ lfs_unmount(&lfs) => 0;
+TEST
+
+echo "--- Results ---"
+tests/stats.py
diff --git a/tests/test_format.sh b/tests/test_format.sh
index b907101..cff0bac 100755
--- a/tests/test_format.sh
+++ b/tests/test_format.sh
@@ -30,20 +30,10 @@ echo "--- Invalid mount ---"
tests/test.py << TEST
lfs_format(&lfs, &cfg) => 0;
TEST
-rm blocks/0 blocks/1
+rm -f blocks/0 blocks/1
tests/test.py << TEST
lfs_mount(&lfs, &cfg) => LFS_ERR_CORRUPT;
TEST
-echo "--- Valid corrupt mount ---"
-tests/test.py << TEST
- lfs_format(&lfs, &cfg) => 0;
-TEST
-rm blocks/0
-tests/test.py << TEST
- lfs_mount(&lfs, &cfg) => 0;
- lfs_unmount(&lfs) => 0;
-TEST
-
echo "--- Results ---"
tests/stats.py
diff --git a/tests/test_move.sh b/tests/test_move.sh
index 9e5abab..a36d058 100755
--- a/tests/test_move.sh
+++ b/tests/test_move.sh
@@ -59,7 +59,7 @@ tests/test.py << TEST
lfs_rename(&lfs, "b/hello", "c/hello") => 0;
lfs_unmount(&lfs) => 0;
TEST
-rm -v blocks/7
+tests/corrupt.py blocks/{6,7}
tests/test.py << TEST
lfs_mount(&lfs, &cfg) => 0;
lfs_dir_open(&lfs, &dir[0], "b") => 0;
@@ -86,8 +86,8 @@ tests/test.py << TEST
lfs_rename(&lfs, "c/hello", "d/hello") => 0;
lfs_unmount(&lfs) => 0;
TEST
-rm -v blocks/8
-rm -v blocks/a
+tests/corrupt.py blocks/{8,9}
+tests/corrupt.py blocks/{a,b}
tests/test.py << TEST
lfs_mount(&lfs, &cfg) => 0;
lfs_dir_open(&lfs, &dir[0], "c") => 0;
@@ -108,6 +108,32 @@ tests/test.py << TEST
lfs_unmount(&lfs) => 0;
TEST
+echo "--- Move file after corrupt ---"
+tests/test.py << TEST
+ lfs_mount(&lfs, &cfg) => 0;
+ lfs_rename(&lfs, "c/hello", "d/hello") => 0;
+ lfs_unmount(&lfs) => 0;
+TEST
+tests/test.py << TEST
+ lfs_mount(&lfs, &cfg) => 0;
+ lfs_dir_open(&lfs, &dir[0], "c") => 0;
+ lfs_dir_read(&lfs, &dir[0], &info) => 1;
+ strcmp(info.name, ".") => 0;
+ lfs_dir_read(&lfs, &dir[0], &info) => 1;
+ strcmp(info.name, "..") => 0;
+ lfs_dir_read(&lfs, &dir[0], &info) => 0;
+ lfs_dir_close(&lfs, &dir[0]) => 0;
+ lfs_dir_open(&lfs, &dir[0], "d") => 0;
+ lfs_dir_read(&lfs, &dir[0], &info) => 1;
+ strcmp(info.name, ".") => 0;
+ lfs_dir_read(&lfs, &dir[0], &info) => 1;
+ strcmp(info.name, "..") => 0;
+ lfs_dir_read(&lfs, &dir[0], &info) => 1;
+ strcmp(info.name, "hello") => 0;
+ lfs_dir_read(&lfs, &dir[0], &info) => 0;
+ lfs_unmount(&lfs) => 0;
+TEST
+
echo "--- Move dir ---"
tests/test.py << TEST
lfs_mount(&lfs, &cfg) => 0;
@@ -140,7 +166,7 @@ tests/test.py << TEST
lfs_rename(&lfs, "b/hi", "c/hi") => 0;
lfs_unmount(&lfs) => 0;
TEST
-rm -v blocks/7
+tests/corrupt.py blocks/{6,7}
tests/test.py << TEST
lfs_mount(&lfs, &cfg) => 0;
lfs_dir_open(&lfs, &dir[0], "b") => 0;
@@ -156,8 +182,6 @@ tests/test.py << TEST
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, "..") => 0;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
- strcmp(info.name, "hello") => 0;
- lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, "hi") => 0;
lfs_dir_read(&lfs, &dir[0], &info) => 0;
lfs_unmount(&lfs) => 0;
@@ -169,8 +193,8 @@ tests/test.py << TEST
lfs_rename(&lfs, "c/hi", "d/hi") => 0;
lfs_unmount(&lfs) => 0;
TEST
-rm -v blocks/9
-rm -v blocks/a
+tests/corrupt.py blocks/{8,9}
+tests/corrupt.py blocks/{a,b}
tests/test.py << TEST
lfs_mount(&lfs, &cfg) => 0;
lfs_dir_open(&lfs, &dir[0], "c") => 0;
@@ -179,9 +203,33 @@ tests/test.py << TEST
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, "..") => 0;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
+ strcmp(info.name, "hi") => 0;
+ lfs_dir_read(&lfs, &dir[0], &info) => 0;
+ lfs_dir_close(&lfs, &dir[0]) => 0;
+ lfs_dir_open(&lfs, &dir[0], "d") => 0;
+ lfs_dir_read(&lfs, &dir[0], &info) => 1;
+ strcmp(info.name, ".") => 0;
+ lfs_dir_read(&lfs, &dir[0], &info) => 1;
+ strcmp(info.name, "..") => 0;
+ lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, "hello") => 0;
+ lfs_dir_read(&lfs, &dir[0], &info) => 0;
+ lfs_unmount(&lfs) => 0;
+TEST
+
+echo "--- Move dir after corrupt ---"
+tests/test.py << TEST
+ lfs_mount(&lfs, &cfg) => 0;
+ lfs_rename(&lfs, "c/hi", "d/hi") => 0;
+ lfs_unmount(&lfs) => 0;
+TEST
+tests/test.py << TEST
+ lfs_mount(&lfs, &cfg) => 0;
+ lfs_dir_open(&lfs, &dir[0], "c") => 0;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
- strcmp(info.name, "hi") => 0;
+ strcmp(info.name, ".") => 0;
+ lfs_dir_read(&lfs, &dir[0], &info) => 1;
+ strcmp(info.name, "..") => 0;
lfs_dir_read(&lfs, &dir[0], &info) => 0;
lfs_dir_close(&lfs, &dir[0]) => 0;
lfs_dir_open(&lfs, &dir[0], "d") => 0;
@@ -189,6 +237,10 @@ tests/test.py << TEST
strcmp(info.name, ".") => 0;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, "..") => 0;
+ lfs_dir_read(&lfs, &dir[0], &info) => 1;
+ strcmp(info.name, "hello") => 0;
+ lfs_dir_read(&lfs, &dir[0], &info) => 1;
+ strcmp(info.name, "hi") => 0;
lfs_dir_read(&lfs, &dir[0], &info) => 0;
lfs_unmount(&lfs) => 0;
TEST
@@ -199,9 +251,9 @@ tests/test.py << TEST
lfs_dir_open(&lfs, &dir[0], "a/hi") => LFS_ERR_NOENT;
lfs_dir_open(&lfs, &dir[0], "b/hi") => LFS_ERR_NOENT;
- lfs_dir_open(&lfs, &dir[0], "d/hi") => LFS_ERR_NOENT;
+ lfs_dir_open(&lfs, &dir[0], "c/hi") => LFS_ERR_NOENT;
- lfs_dir_open(&lfs, &dir[0], "c/hi") => 0;
+ lfs_dir_open(&lfs, &dir[0], "d/hi") => 0;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, ".") => 0;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
@@ -217,9 +269,55 @@ tests/test.py << TEST
lfs_dir_open(&lfs, &dir[0], "a/hello") => LFS_ERR_NOENT;
lfs_dir_open(&lfs, &dir[0], "b/hello") => LFS_ERR_NOENT;
- lfs_dir_open(&lfs, &dir[0], "d/hello") => LFS_ERR_NOENT;
+ lfs_dir_open(&lfs, &dir[0], "c/hello") => LFS_ERR_NOENT;
+
+ lfs_file_open(&lfs, &file[0], "d/hello", LFS_O_RDONLY) => 0;
+ lfs_file_read(&lfs, &file[0], buffer, 5) => 5;
+ memcmp(buffer, "hola\n", 5) => 0;
+ lfs_file_read(&lfs, &file[0], buffer, 8) => 8;
+ memcmp(buffer, "bonjour\n", 8) => 0;
+ lfs_file_read(&lfs, &file[0], buffer, 6) => 6;
+ memcmp(buffer, "ohayo\n", 6) => 0;
+ lfs_file_close(&lfs, &file[0]) => 0;
+
+ lfs_unmount(&lfs) => 0;
+TEST
+
+echo "--- Move state stealing ---"
+tests/test.py << TEST
+ lfs_mount(&lfs, &cfg) => 0;
+
+ lfs_remove(&lfs, "b") => 0;
+ lfs_remove(&lfs, "c") => 0;
+
+ lfs_unmount(&lfs) => 0;
+TEST
+tests/test.py << TEST
+ lfs_mount(&lfs, &cfg) => 0;
+
+ lfs_dir_open(&lfs, &dir[0], "a/hi") => LFS_ERR_NOENT;
+ lfs_dir_open(&lfs, &dir[0], "b") => LFS_ERR_NOENT;
+ lfs_dir_open(&lfs, &dir[0], "c") => LFS_ERR_NOENT;
+
+ lfs_dir_open(&lfs, &dir[0], "d/hi") => 0;
+ lfs_dir_read(&lfs, &dir[0], &info) => 1;
+ strcmp(info.name, ".") => 0;
+ lfs_dir_read(&lfs, &dir[0], &info) => 1;
+ strcmp(info.name, "..") => 0;
+ lfs_dir_read(&lfs, &dir[0], &info) => 1;
+ strcmp(info.name, "hola") => 0;
+ lfs_dir_read(&lfs, &dir[0], &info) => 1;
+ strcmp(info.name, "bonjour") => 0;
+ lfs_dir_read(&lfs, &dir[0], &info) => 1;
+ strcmp(info.name, "ohayo") => 0;
+ lfs_dir_read(&lfs, &dir[0], &info) => 0;
+ lfs_dir_close(&lfs, &dir[0]) => 0;
+
+ lfs_dir_open(&lfs, &dir[0], "a/hello") => LFS_ERR_NOENT;
+ lfs_dir_open(&lfs, &dir[0], "b") => LFS_ERR_NOENT;
+ lfs_dir_open(&lfs, &dir[0], "c") => LFS_ERR_NOENT;
- lfs_file_open(&lfs, &file[0], "c/hello", LFS_O_RDONLY) => 0;
+ lfs_file_open(&lfs, &file[0], "d/hello", LFS_O_RDONLY) => 0;
lfs_file_read(&lfs, &file[0], buffer, 5) => 5;
memcmp(buffer, "hola\n", 5) => 0;
lfs_file_read(&lfs, &file[0], buffer, 8) => 8;
diff --git a/tests/test_orphan.sh b/tests/test_orphan.sh
index 71d6d4f..6aa36d9 100755
--- a/tests/test_orphan.sh
+++ b/tests/test_orphan.sh
@@ -15,25 +15,29 @@ tests/test.py << TEST
lfs_mkdir(&lfs, "parent/child") => 0;
lfs_remove(&lfs, "parent/orphan") => 0;
TEST
-# remove most recent file, this should be the update to the previous
+# corrupt most recent commit, this should be the update to the previous
# linked-list entry and should orphan the child
-rm -v blocks/8
-tests/test.py << TEST
+tests/corrupt.py blocks/{8,9}
+tests/test.py << TEST
lfs_mount(&lfs, &cfg) => 0;
+
+ lfs_stat(&lfs, "parent/orphan", &info) => LFS_ERR_NOENT;
+ lfs_ssize_t before = lfs_fs_size(&lfs);
+ before => 10;
+
+ lfs_unmount(&lfs) => 0;
+ lfs_mount(&lfs, &cfg) => 0;
+
lfs_stat(&lfs, "parent/orphan", &info) => LFS_ERR_NOENT;
- unsigned before = 0;
- lfs_traverse(&lfs, test_count, &before) => 0;
- test_log("before", before);
+ lfs_ssize_t orphaned = lfs_fs_size(&lfs);
+ orphaned => 10;
- lfs_deorphan(&lfs) => 0;
+ lfs_mkdir(&lfs, "parent/otherchild") => 0;
lfs_stat(&lfs, "parent/orphan", &info) => LFS_ERR_NOENT;
- unsigned after = 0;
- lfs_traverse(&lfs, test_count, &after) => 0;
- test_log("after", after);
+ lfs_ssize_t deorphaned = lfs_fs_size(&lfs);
+ deorphaned => 10;
- int diff = before - after;
- diff => 2;
lfs_unmount(&lfs) => 0;
TEST
diff --git a/tests/test_paths.sh b/tests/test_paths.sh
index 79c4e66..ea5eb21 100755
--- a/tests/test_paths.sh
+++ b/tests/test_paths.sh
@@ -139,5 +139,23 @@ tests/test.py << TEST
lfs_unmount(&lfs) => 0;
TEST
+echo "--- Max path test ---"
+tests/test.py << TEST
+ lfs_mount(&lfs, &cfg) => 0;
+ memset(buffer, 'w', LFS_NAME_MAX+1);
+ buffer[LFS_NAME_MAX+2] = '\0';
+ lfs_mkdir(&lfs, (char*)buffer) => LFS_ERR_NAMETOOLONG;
+ lfs_file_open(&lfs, &file[0], (char*)buffer,
+ LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_NAMETOOLONG;
+
+ memcpy(buffer, "coffee/", strlen("coffee/"));
+ memset(buffer+strlen("coffee/"), 'w', LFS_NAME_MAX+1);
+ buffer[strlen("coffee/")+LFS_NAME_MAX+2] = '\0';
+ lfs_mkdir(&lfs, (char*)buffer) => LFS_ERR_NAMETOOLONG;
+ lfs_file_open(&lfs, &file[0], (char*)buffer,
+ LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_NAMETOOLONG;
+ lfs_unmount(&lfs) => 0;
+TEST
+
echo "--- Results ---"
tests/stats.py