diff options
-rw-r--r-- | .travis.yml | 7 | ||||
-rw-r--r-- | Makefile | 3 | ||||
-rw-r--r-- | lfs.c | 3152 | ||||
-rw-r--r-- | lfs.h | 381 | ||||
-rw-r--r-- | lfs_util.h | 35 | ||||
-rwxr-xr-x | tests/corrupt.py | 39 | ||||
-rw-r--r-- | tests/template.fmt | 7 | ||||
-rwxr-xr-x | tests/test.py | 2 | ||||
-rwxr-xr-x | tests/test_alloc.sh | 12 | ||||
-rwxr-xr-x | tests/test_attrs.sh | 262 | ||||
-rwxr-xr-x | tests/test_corrupt.sh | 6 | ||||
-rwxr-xr-x | tests/test_entries.sh | 220 | ||||
-rwxr-xr-x | tests/test_format.sh | 12 | ||||
-rwxr-xr-x | tests/test_move.sh | 124 | ||||
-rwxr-xr-x | tests/test_orphan.sh | 28 | ||||
-rwxr-xr-x | tests/test_paths.sh | 18 |
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: @@ -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 @@ -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; +} @@ -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 @@ -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 |