diff --git a/extmod/extmod.mk b/extmod/extmod.mk index 8a48a4293..c4b2c3d9c 100644 --- a/extmod/extmod.mk +++ b/extmod/extmod.mk @@ -106,7 +106,7 @@ SRC_THIRDPARTY_C += $(addprefix $(LITTLEFS_DIR)/,\ lfs2_util.c \ ) -$(BUILD)/$(LITTLEFS_DIR)/lfs2.o: CFLAGS += -Wno-missing-field-initializers +$(BUILD)/$(LITTLEFS_DIR)/lfs2.o: CFLAGS += -Wno-shadow endif ################################################################################ diff --git a/lib/littlefs/lfs2.c b/lib/littlefs/lfs2.c index a9fcffafd..d89c42fd5 100644 --- a/lib/littlefs/lfs2.c +++ b/lib/littlefs/lfs2.c @@ -46,8 +46,8 @@ static int lfs2_bd_read(lfs2_t *lfs2, lfs2_block_t block, lfs2_off_t off, void *buffer, lfs2_size_t size) { uint8_t *data = buffer; - if (block >= lfs2->cfg->block_count || - off+size > lfs2->cfg->block_size) { + if (off+size > lfs2->cfg->block_size + || (lfs2->block_count && block >= lfs2->block_count)) { return LFS2_ERR_CORRUPT; } @@ -104,7 +104,7 @@ static int lfs2_bd_read(lfs2_t *lfs2, } // load to cache, first condition can no longer fail - LFS2_ASSERT(block < lfs2->cfg->block_count); + LFS2_ASSERT(!lfs2->block_count || block < lfs2->block_count); rcache->block = block; rcache->off = lfs2_aligndown(off, lfs2->cfg->read_size); rcache->size = lfs2_min( @@ -135,14 +135,14 @@ static int lfs2_bd_cmp(lfs2_t *lfs2, uint8_t dat[8]; diff = lfs2_min(size-i, sizeof(dat)); - int res = lfs2_bd_read(lfs2, + int err = lfs2_bd_read(lfs2, pcache, rcache, hint-i, block, off+i, &dat, diff); - if (res) { - return res; + if (err) { + return err; } - res = memcmp(dat, data + i, diff); + int res = memcmp(dat, data + i, diff); if (res) { return res < 0 ? LFS2_CMP_LT : LFS2_CMP_GT; } @@ -151,11 +151,32 @@ static int lfs2_bd_cmp(lfs2_t *lfs2, return LFS2_CMP_EQ; } +static int lfs2_bd_crc(lfs2_t *lfs2, + const lfs2_cache_t *pcache, lfs2_cache_t *rcache, lfs2_size_t hint, + lfs2_block_t block, lfs2_off_t off, lfs2_size_t size, uint32_t *crc) { + lfs2_size_t diff = 0; + + for (lfs2_off_t i = 0; i < size; i += diff) { + uint8_t dat[8]; + diff = lfs2_min(size-i, sizeof(dat)); + int err = lfs2_bd_read(lfs2, + pcache, rcache, hint-i, + block, off+i, &dat, diff); + if (err) { + return err; + } + + *crc = lfs2_crc(*crc, &dat, diff); + } + + return 0; +} + #ifndef LFS2_READONLY static int lfs2_bd_flush(lfs2_t *lfs2, lfs2_cache_t *pcache, lfs2_cache_t *rcache, bool validate) { if (pcache->block != LFS2_BLOCK_NULL && pcache->block != LFS2_BLOCK_INLINE) { - LFS2_ASSERT(pcache->block < lfs2->cfg->block_count); + LFS2_ASSERT(pcache->block < lfs2->block_count); lfs2_size_t diff = lfs2_alignup(pcache->size, lfs2->cfg->prog_size); int err = lfs2->cfg->prog(lfs2->cfg, pcache->block, pcache->off, pcache->buffer, diff); @@ -208,7 +229,7 @@ static int lfs2_bd_prog(lfs2_t *lfs2, lfs2_block_t block, lfs2_off_t off, const void *buffer, lfs2_size_t size) { const uint8_t *data = buffer; - LFS2_ASSERT(block == LFS2_BLOCK_INLINE || block < lfs2->cfg->block_count); + LFS2_ASSERT(block == LFS2_BLOCK_INLINE || block < lfs2->block_count); LFS2_ASSERT(off + size <= lfs2->cfg->block_size); while (size > 0) { @@ -252,7 +273,7 @@ static int lfs2_bd_prog(lfs2_t *lfs2, #ifndef LFS2_READONLY static int lfs2_bd_erase(lfs2_t *lfs2, lfs2_block_t block) { - LFS2_ASSERT(block < lfs2->cfg->block_count); + LFS2_ASSERT(block < lfs2->block_count); int err = lfs2->cfg->erase(lfs2->cfg, block); LFS2_ASSERT(err <= 0); return err; @@ -279,14 +300,12 @@ static inline int lfs2_pair_cmp( paira[0] == pairb[1] || paira[1] == pairb[0]); } -#ifndef LFS2_READONLY -static inline bool lfs2_pair_sync( +static inline bool lfs2_pair_issync( const lfs2_block_t paira[2], const lfs2_block_t pairb[2]) { return (paira[0] == pairb[0] && paira[1] == pairb[1]) || (paira[0] == pairb[1] && paira[1] == pairb[0]); } -#endif static inline void lfs2_pair_fromle32(lfs2_block_t pair[2]) { pair[0] = lfs2_fromle32(pair[0]); @@ -325,6 +344,10 @@ static inline uint16_t lfs2_tag_type1(lfs2_tag_t tag) { return (tag & 0x70000000) >> 20; } +static inline uint16_t lfs2_tag_type2(lfs2_tag_t tag) { + return (tag & 0x78000000) >> 20; +} + static inline uint16_t lfs2_tag_type3(lfs2_tag_t tag) { return (tag & 0x7ff00000) >> 20; } @@ -386,7 +409,7 @@ static inline bool lfs2_gstate_hasorphans(const lfs2_gstate_t *a) { } static inline uint8_t lfs2_gstate_getorphans(const lfs2_gstate_t *a) { - return lfs2_tag_size(a->tag); + return lfs2_tag_size(a->tag) & 0x1ff; } static inline bool lfs2_gstate_hasmove(const lfs2_gstate_t *a) { @@ -394,6 +417,10 @@ static inline bool lfs2_gstate_hasmove(const lfs2_gstate_t *a) { } #endif +static inline bool lfs2_gstate_needssuperblock(const lfs2_gstate_t *a) { + return lfs2_tag_size(a->tag) >> 9; +} + static inline bool lfs2_gstate_hasmovehere(const lfs2_gstate_t *a, const lfs2_block_t *pair) { return lfs2_tag_type1(a->tag) && lfs2_pair_cmp(a->pair, pair) == 0; @@ -413,6 +440,24 @@ static inline void lfs2_gstate_tole32(lfs2_gstate_t *a) { } #endif +// operations on forward-CRCs used to track erased state +struct lfs2_fcrc { + lfs2_size_t size; + uint32_t crc; +}; + +static void lfs2_fcrc_fromle32(struct lfs2_fcrc *fcrc) { + fcrc->size = lfs2_fromle32(fcrc->size); + fcrc->crc = lfs2_fromle32(fcrc->crc); +} + +#ifndef LFS2_READONLY +static void lfs2_fcrc_tole32(struct lfs2_fcrc *fcrc) { + fcrc->size = lfs2_tole32(fcrc->size); + fcrc->crc = lfs2_tole32(fcrc->crc); +} +#endif + // other endianness operations static void lfs2_ctz_fromle32(struct lfs2_ctz *ctz) { ctz->head = lfs2_fromle32(ctz->head); @@ -473,6 +518,28 @@ static void lfs2_mlist_append(lfs2_t *lfs2, struct lfs2_mlist *mlist) { lfs2->mlist = mlist; } +// some other filesystem operations +static uint32_t lfs2_fs_disk_version(lfs2_t *lfs2) { + (void)lfs2; +#ifdef LFS2_MULTIVERSION + if (lfs2->cfg->disk_version) { + return lfs2->cfg->disk_version; + } else +#endif + { + return LFS2_DISK_VERSION; + } +} + +static uint16_t lfs2_fs_disk_version_major(lfs2_t *lfs2) { + return 0xffff & (lfs2_fs_disk_version(lfs2) >> 16); + +} + +static uint16_t lfs2_fs_disk_version_minor(lfs2_t *lfs2) { + return 0xffff & (lfs2_fs_disk_version(lfs2) >> 0); +} + /// Internal operations predeclared here /// #ifndef LFS2_READONLY @@ -500,6 +567,8 @@ static lfs2_stag_t lfs2_fs_parent(lfs2_t *lfs2, const lfs2_block_t dir[2], static int lfs2_fs_forceconsistency(lfs2_t *lfs2); #endif +static void lfs2_fs_prepsuperblock(lfs2_t *lfs2, bool needssuperblock); + #ifdef LFS2_MIGRATE static int lfs21_traverse(lfs2_t *lfs2, int (*cb)(void*, lfs2_block_t), void *data); @@ -528,7 +597,7 @@ static int lfs2_rawunmount(lfs2_t *lfs2); static int lfs2_alloc_lookahead(void *p, lfs2_block_t block) { lfs2_t *lfs2 = (lfs2_t*)p; lfs2_block_t off = ((block - lfs2->free.off) - + lfs2->cfg->block_count) % lfs2->cfg->block_count; + + lfs2->block_count) % lfs2->block_count; if (off < lfs2->free.size) { lfs2->free.buffer[off / 32] |= 1U << (off % 32); @@ -542,7 +611,7 @@ static int lfs2_alloc_lookahead(void *p, lfs2_block_t block) { // is to prevent blocks from being garbage collected in the middle of a // commit operation static void lfs2_alloc_ack(lfs2_t *lfs2) { - lfs2->free.ack = lfs2->cfg->block_count; + lfs2->free.ack = lfs2->block_count; } // drop the lookahead buffer, this is done during mounting and failed @@ -553,6 +622,26 @@ static void lfs2_alloc_drop(lfs2_t *lfs2) { lfs2_alloc_ack(lfs2); } +#ifndef LFS2_READONLY +static int lfs2_fs_rawgc(lfs2_t *lfs2) { + // Move free offset at the first unused block (lfs2->free.i) + // lfs2->free.i is equal lfs2->free.size when all blocks are used + lfs2->free.off = (lfs2->free.off + lfs2->free.i) % lfs2->block_count; + lfs2->free.size = lfs2_min(8*lfs2->cfg->lookahead_size, lfs2->free.ack); + lfs2->free.i = 0; + + // find mask of free blocks from tree + memset(lfs2->free.buffer, 0, lfs2->cfg->lookahead_size); + int err = lfs2_fs_rawtraverse(lfs2, lfs2_alloc_lookahead, lfs2, true); + if (err) { + lfs2_alloc_drop(lfs2); + return err; + } + + return 0; +} +#endif + #ifndef LFS2_READONLY static int lfs2_alloc(lfs2_t *lfs2, lfs2_block_t *block) { while (true) { @@ -563,7 +652,7 @@ static int lfs2_alloc(lfs2_t *lfs2, lfs2_block_t *block) { if (!(lfs2->free.buffer[off / 32] & (1U << (off % 32)))) { // found a free block - *block = (lfs2->free.off + off) % lfs2->cfg->block_count; + *block = (lfs2->free.off + off) % lfs2->block_count; // eagerly find next off so an alloc ack can // discredit old lookahead blocks @@ -585,16 +674,8 @@ static int lfs2_alloc(lfs2_t *lfs2, lfs2_block_t *block) { return LFS2_ERR_NOSPC; } - lfs2->free.off = (lfs2->free.off + lfs2->free.size) - % lfs2->cfg->block_count; - lfs2->free.size = lfs2_min(8*lfs2->cfg->lookahead_size, lfs2->free.ack); - lfs2->free.i = 0; - - // find mask of free blocks from tree - memset(lfs2->free.buffer, 0, lfs2->cfg->lookahead_size); - int err = lfs2_fs_rawtraverse(lfs2, lfs2_alloc_lookahead, lfs2, true); - if (err) { - lfs2_alloc_drop(lfs2); + int err = lfs2_fs_rawgc(lfs2); + if(err) { return err; } } @@ -808,7 +889,7 @@ static int lfs2_dir_traverse(lfs2_t *lfs2, // iterate over directory and attrs lfs2_tag_t tag; const void *buffer; - struct lfs2_diskoff disk; + struct lfs2_diskoff disk = {0}; while (true) { { if (off+lfs2_tag_dsize(ptag) < dir->off) { @@ -998,7 +1079,8 @@ static lfs2_stag_t lfs2_dir_fetchmatch(lfs2_t *lfs2, // if either block address is invalid we return LFS2_ERR_CORRUPT here, // otherwise later writes to the pair could fail - if (pair[0] >= lfs2->cfg->block_count || pair[1] >= lfs2->cfg->block_count) { + if (lfs2->block_count + && (pair[0] >= lfs2->block_count || pair[1] >= lfs2->block_count)) { return LFS2_ERR_CORRUPT; } @@ -1035,6 +1117,11 @@ static lfs2_stag_t lfs2_dir_fetchmatch(lfs2_t *lfs2, bool tempsplit = false; lfs2_stag_t tempbesttag = besttag; + // assume not erased until proven otherwise + bool maybeerased = false; + bool hasfcrc = false; + struct lfs2_fcrc fcrc; + dir->rev = lfs2_tole32(dir->rev); uint32_t crc = lfs2_crc(0xffffffff, &dir->rev, sizeof(dir->rev)); dir->rev = lfs2_fromle32(dir->rev); @@ -1049,7 +1136,6 @@ static lfs2_stag_t lfs2_dir_fetchmatch(lfs2_t *lfs2, if (err) { if (err == LFS2_ERR_CORRUPT) { // can't continue? - dir->erased = false; break; } return err; @@ -1058,19 +1144,19 @@ static lfs2_stag_t lfs2_dir_fetchmatch(lfs2_t *lfs2, crc = lfs2_crc(crc, &tag, sizeof(tag)); tag = lfs2_frombe32(tag) ^ ptag; - // next commit not yet programmed or we're not in valid range + // next commit not yet programmed? if (!lfs2_tag_isvalid(tag)) { - dir->erased = (lfs2_tag_type1(ptag) == LFS2_TYPE_CRC && - dir->off % lfs2->cfg->prog_size == 0); + // we only might be erased if the last tag was a crc + maybeerased = (lfs2_tag_type2(ptag) == LFS2_TYPE_CCRC); break; + // out of range? } else if (off + lfs2_tag_dsize(tag) > lfs2->cfg->block_size) { - dir->erased = false; break; } ptag = tag; - if (lfs2_tag_type1(tag) == LFS2_TYPE_CRC) { + if (lfs2_tag_type2(tag) == LFS2_TYPE_CCRC) { // check the crc attr uint32_t dcrc; err = lfs2_bd_read(lfs2, @@ -1078,7 +1164,6 @@ static lfs2_stag_t lfs2_dir_fetchmatch(lfs2_t *lfs2, dir->pair[0], off+sizeof(tag), &dcrc, sizeof(dcrc)); if (err) { if (err == LFS2_ERR_CORRUPT) { - dir->erased = false; break; } return err; @@ -1086,7 +1171,6 @@ static lfs2_stag_t lfs2_dir_fetchmatch(lfs2_t *lfs2, dcrc = lfs2_fromle32(dcrc); if (crc != dcrc) { - dir->erased = false; break; } @@ -1108,26 +1192,21 @@ static lfs2_stag_t lfs2_dir_fetchmatch(lfs2_t *lfs2, dir->tail[1] = temptail[1]; dir->split = tempsplit; - // reset crc + // reset crc, hasfcrc crc = 0xffffffff; continue; } // crc the entry first, hopefully leaving it in the cache - for (lfs2_off_t j = sizeof(tag); j < lfs2_tag_dsize(tag); j++) { - uint8_t dat; - err = lfs2_bd_read(lfs2, - NULL, &lfs2->rcache, lfs2->cfg->block_size, - dir->pair[0], off+j, &dat, 1); - if (err) { - if (err == LFS2_ERR_CORRUPT) { - dir->erased = false; - break; - } - return err; + err = lfs2_bd_crc(lfs2, + NULL, &lfs2->rcache, lfs2->cfg->block_size, + dir->pair[0], off+sizeof(tag), + lfs2_tag_dsize(tag)-sizeof(tag), &crc); + if (err) { + if (err == LFS2_ERR_CORRUPT) { + break; } - - crc = lfs2_crc(crc, &dat, 1); + return err; } // directory modification tags? @@ -1154,11 +1233,24 @@ static lfs2_stag_t lfs2_dir_fetchmatch(lfs2_t *lfs2, dir->pair[0], off+sizeof(tag), &temptail, 8); if (err) { if (err == LFS2_ERR_CORRUPT) { - dir->erased = false; + break; + } + return err; + } + lfs2_pair_fromle32(temptail); + } else if (lfs2_tag_type3(tag) == LFS2_TYPE_FCRC) { + err = lfs2_bd_read(lfs2, + NULL, &lfs2->rcache, lfs2->cfg->block_size, + dir->pair[0], off+sizeof(tag), + &fcrc, sizeof(fcrc)); + if (err) { + if (err == LFS2_ERR_CORRUPT) { break; } } - lfs2_pair_fromle32(temptail); + + lfs2_fcrc_fromle32(&fcrc); + hasfcrc = true; } // found a match for our fetcher? @@ -1167,7 +1259,6 @@ static lfs2_stag_t lfs2_dir_fetchmatch(lfs2_t *lfs2, dir->pair[0], off+sizeof(tag)}); if (res < 0) { if (res == LFS2_ERR_CORRUPT) { - dir->erased = false; break; } return res; @@ -1189,35 +1280,67 @@ static lfs2_stag_t lfs2_dir_fetchmatch(lfs2_t *lfs2, } } - // consider what we have good enough - if (dir->off > 0) { - // synthetic move - if (lfs2_gstate_hasmovehere(&lfs2->gdisk, dir->pair)) { - if (lfs2_tag_id(lfs2->gdisk.tag) == lfs2_tag_id(besttag)) { - besttag |= 0x80000000; - } else if (besttag != -1 && - lfs2_tag_id(lfs2->gdisk.tag) < lfs2_tag_id(besttag)) { - besttag -= LFS2_MKTAG(0, 1, 0); + // found no valid commits? + if (dir->off == 0) { + // try the other block? + lfs2_pair_swap(dir->pair); + dir->rev = revs[(r+1)%2]; + continue; + } + + // did we end on a valid commit? we may have an erased block + dir->erased = false; + if (maybeerased && dir->off % lfs2->cfg->prog_size == 0) { + #ifdef LFS2_MULTIVERSION + // note versions < lfs22.1 did not have fcrc tags, if + // we're < lfs22.1 treat missing fcrc as erased data + // + // we don't strictly need to do this, but otherwise writing + // to lfs22.0 disks becomes very inefficient + if (lfs2_fs_disk_version(lfs2) < 0x00020001) { + dir->erased = true; + + } else + #endif + if (hasfcrc) { + // check for an fcrc matching the next prog's erased state, if + // this failed most likely a previous prog was interrupted, we + // need a new erase + uint32_t fcrc_ = 0xffffffff; + int err = lfs2_bd_crc(lfs2, + NULL, &lfs2->rcache, lfs2->cfg->block_size, + dir->pair[0], dir->off, fcrc.size, &fcrc_); + if (err && err != LFS2_ERR_CORRUPT) { + return err; } - } - // found tag? or found best id? - if (id) { - *id = lfs2_min(lfs2_tag_id(besttag), dir->count); - } - - if (lfs2_tag_isvalid(besttag)) { - return besttag; - } else if (lfs2_tag_id(besttag) < dir->count) { - return LFS2_ERR_NOENT; - } else { - return 0; + // found beginning of erased part? + dir->erased = (fcrc_ == fcrc.crc); } } - // failed, try the other block? - lfs2_pair_swap(dir->pair); - dir->rev = revs[(r+1)%2]; + // synthetic move + if (lfs2_gstate_hasmovehere(&lfs2->gdisk, dir->pair)) { + if (lfs2_tag_id(lfs2->gdisk.tag) == lfs2_tag_id(besttag)) { + besttag |= 0x80000000; + } else if (besttag != -1 && + lfs2_tag_id(lfs2->gdisk.tag) < lfs2_tag_id(besttag)) { + besttag -= LFS2_MKTAG(0, 1, 0); + } + } + + // found tag? or found best id? + if (id) { + *id = lfs2_min(lfs2_tag_id(besttag), dir->count); + } + + if (lfs2_tag_isvalid(besttag)) { + return besttag; + } else if (lfs2_tag_id(besttag) < dir->count) { + return LFS2_ERR_NOENT; + } else { + return 0; + } } LFS2_ERROR("Corrupted dir pair at {0x%"PRIx32", 0x%"PRIx32"}", @@ -1491,9 +1614,15 @@ static int lfs2_dir_commitattr(lfs2_t *lfs2, struct lfs2_commit *commit, #endif #ifndef LFS2_READONLY + static int lfs2_dir_commitcrc(lfs2_t *lfs2, struct lfs2_commit *commit) { // align to program units - const lfs2_off_t end = lfs2_alignup(commit->off + 2*sizeof(uint32_t), + // + // this gets a bit complex as we have two types of crcs: + // - 5-word crc with fcrc to check following prog (middle of block) + // - 2-word crc with no following prog (end of block) + const lfs2_off_t end = lfs2_alignup( + lfs2_min(commit->off + 5*sizeof(uint32_t), lfs2->cfg->block_size), lfs2->cfg->prog_size); lfs2_off_t off1 = 0; @@ -1503,89 +1632,128 @@ static int lfs2_dir_commitcrc(lfs2_t *lfs2, struct lfs2_commit *commit) { // padding is not crced, which lets fetches skip padding but // makes committing a bit more complicated while (commit->off < end) { - lfs2_off_t off = commit->off + sizeof(lfs2_tag_t); - lfs2_off_t noff = lfs2_min(end - off, 0x3fe) + off; + lfs2_off_t noff = ( + lfs2_min(end - (commit->off+sizeof(lfs2_tag_t)), 0x3fe) + + (commit->off+sizeof(lfs2_tag_t))); + // too large for crc tag? need padding commits if (noff < end) { - noff = lfs2_min(noff, end - 2*sizeof(uint32_t)); + noff = lfs2_min(noff, end - 5*sizeof(uint32_t)); } - // read erased state from next program unit - lfs2_tag_t tag = 0xffffffff; - int err = lfs2_bd_read(lfs2, - NULL, &lfs2->rcache, sizeof(tag), - commit->block, noff, &tag, sizeof(tag)); - if (err && err != LFS2_ERR_CORRUPT) { - return err; + // space for fcrc? + uint8_t eperturb = (uint8_t)-1; + if (noff >= end && noff <= lfs2->cfg->block_size - lfs2->cfg->prog_size) { + // first read the leading byte, this always contains a bit + // we can perturb to avoid writes that don't change the fcrc + int err = lfs2_bd_read(lfs2, + NULL, &lfs2->rcache, lfs2->cfg->prog_size, + commit->block, noff, &eperturb, 1); + if (err && err != LFS2_ERR_CORRUPT) { + return err; + } + + #ifdef LFS2_MULTIVERSION + // unfortunately fcrcs break mdir fetching < lfs22.1, so only write + // these if we're a >= lfs22.1 filesystem + if (lfs2_fs_disk_version(lfs2) <= 0x00020000) { + // don't write fcrc + } else + #endif + { + // find the expected fcrc, don't bother avoiding a reread + // of the eperturb, it should still be in our cache + struct lfs2_fcrc fcrc = { + .size = lfs2->cfg->prog_size, + .crc = 0xffffffff + }; + err = lfs2_bd_crc(lfs2, + NULL, &lfs2->rcache, lfs2->cfg->prog_size, + commit->block, noff, fcrc.size, &fcrc.crc); + if (err && err != LFS2_ERR_CORRUPT) { + return err; + } + + lfs2_fcrc_tole32(&fcrc); + err = lfs2_dir_commitattr(lfs2, commit, + LFS2_MKTAG(LFS2_TYPE_FCRC, 0x3ff, sizeof(struct lfs2_fcrc)), + &fcrc); + if (err) { + return err; + } + } } - // build crc tag - bool reset = ~lfs2_frombe32(tag) >> 31; - tag = LFS2_MKTAG(LFS2_TYPE_CRC + reset, 0x3ff, noff - off); + // build commit crc + struct { + lfs2_tag_t tag; + uint32_t crc; + } ccrc; + lfs2_tag_t ntag = LFS2_MKTAG( + LFS2_TYPE_CCRC + (((uint8_t)~eperturb) >> 7), 0x3ff, + noff - (commit->off+sizeof(lfs2_tag_t))); + ccrc.tag = lfs2_tobe32(ntag ^ commit->ptag); + commit->crc = lfs2_crc(commit->crc, &ccrc.tag, sizeof(lfs2_tag_t)); + ccrc.crc = lfs2_tole32(commit->crc); - // write out crc - uint32_t footer[2]; - footer[0] = lfs2_tobe32(tag ^ commit->ptag); - commit->crc = lfs2_crc(commit->crc, &footer[0], sizeof(footer[0])); - footer[1] = lfs2_tole32(commit->crc); - err = lfs2_bd_prog(lfs2, + int err = lfs2_bd_prog(lfs2, &lfs2->pcache, &lfs2->rcache, false, - commit->block, commit->off, &footer, sizeof(footer)); + commit->block, commit->off, &ccrc, sizeof(ccrc)); if (err) { return err; } // keep track of non-padding checksum to verify if (off1 == 0) { - off1 = commit->off + sizeof(uint32_t); + off1 = commit->off + sizeof(lfs2_tag_t); crc1 = commit->crc; } - commit->off += sizeof(tag)+lfs2_tag_size(tag); - commit->ptag = tag ^ ((lfs2_tag_t)reset << 31); - commit->crc = 0xffffffff; // reset crc for next "commit" + commit->off = noff; + // perturb valid bit? + commit->ptag = ntag ^ ((0x80UL & ~eperturb) << 24); + // reset crc for next commit + commit->crc = 0xffffffff; + + // manually flush here since we don't prog the padding, this confuses + // the caching layer + if (noff >= end || noff >= lfs2->pcache.off + lfs2->cfg->cache_size) { + // flush buffers + int err = lfs2_bd_sync(lfs2, &lfs2->pcache, &lfs2->rcache, false); + if (err) { + return err; + } + } } - // flush buffers - int err = lfs2_bd_sync(lfs2, &lfs2->pcache, &lfs2->rcache, false); + // successful commit, check checksums to make sure + // + // note that we don't need to check padding commits, worst + // case if they are corrupted we would have had to compact anyways + lfs2_off_t off = commit->begin; + uint32_t crc = 0xffffffff; + int err = lfs2_bd_crc(lfs2, + NULL, &lfs2->rcache, off1+sizeof(uint32_t), + commit->block, off, off1-off, &crc); if (err) { return err; } - // successful commit, check checksums to make sure - lfs2_off_t off = commit->begin; - lfs2_off_t noff = off1; - while (off < end) { - uint32_t crc = 0xffffffff; - for (lfs2_off_t i = off; i < noff+sizeof(uint32_t); i++) { - // check against written crc, may catch blocks that - // become readonly and match our commit size exactly - if (i == off1 && crc != crc1) { - return LFS2_ERR_CORRUPT; - } + // check non-padding commits against known crc + if (crc != crc1) { + return LFS2_ERR_CORRUPT; + } - // leave it up to caching to make this efficient - uint8_t dat; - err = lfs2_bd_read(lfs2, - NULL, &lfs2->rcache, noff+sizeof(uint32_t)-i, - commit->block, i, &dat, 1); - if (err) { - return err; - } + // make sure to check crc in case we happen to pick + // up an unrelated crc (frozen block?) + err = lfs2_bd_crc(lfs2, + NULL, &lfs2->rcache, sizeof(uint32_t), + commit->block, off1, sizeof(uint32_t), &crc); + if (err) { + return err; + } - crc = lfs2_crc(crc, &dat, 1); - } - - // detected write error? - if (crc != 0) { - return LFS2_ERR_CORRUPT; - } - - // skip padding - off = lfs2_min(end - noff, 0x3fe) + noff; - if (off < end) { - off = lfs2_min(off, end - 2*sizeof(uint32_t)); - } - noff = off + sizeof(uint32_t); + if (crc != 0) { + return LFS2_ERR_CORRUPT; } return 0; @@ -1926,11 +2094,20 @@ static int lfs2_dir_splittingcompact(lfs2_t *lfs2, lfs2_mdir_t *dir, return err; } - // space is complicated, we need room for tail, crc, gstate, - // cleanup delete, and we cap at half a block to give room - // for metadata updates. + // space is complicated, we need room for: + // + // - tail: 4+2*4 = 12 bytes + // - gstate: 4+3*4 = 16 bytes + // - move delete: 4 = 4 bytes + // - crc: 4+4 = 8 bytes + // total = 40 bytes + // + // And we cap at half a block to avoid degenerate cases with + // nearly-full metadata blocks. + // if (end - split < 0xff - && size <= lfs2_min(lfs2->cfg->block_size - 36, + && size <= lfs2_min( + lfs2->cfg->block_size - 40, lfs2_alignup( (lfs2->cfg->metadata_max ? lfs2->cfg->metadata_max @@ -1976,7 +2153,7 @@ static int lfs2_dir_splittingcompact(lfs2_t *lfs2, lfs2_mdir_t *dir, // do we have extra space? littlefs can't reclaim this space // by itself, so expand cautiously - if ((lfs2_size_t)size < lfs2->cfg->block_count/2) { + if ((lfs2_size_t)size < lfs2->block_count/2) { LFS2_DEBUG("Expanding superblock at rev %"PRIu32, dir->rev); int err = lfs2_dir_split(lfs2, dir, attrs, attrcount, source, begin, end); @@ -2594,11 +2771,6 @@ static int lfs2_dir_rawseek(lfs2_t *lfs2, lfs2_dir_t *dir, lfs2_off_t off) { dir->id = (off > 0 && lfs2_pair_cmp(dir->head, lfs2->root) == 0); while (off > 0) { - int diff = lfs2_min(dir->m.count - dir->id, off); - dir->id += diff; - dir->pos += diff; - off -= diff; - if (dir->id == dir->m.count) { if (!dir->m.split) { return LFS2_ERR_INVAL; @@ -2611,6 +2783,11 @@ static int lfs2_dir_rawseek(lfs2_t *lfs2, lfs2_dir_t *dir, lfs2_off_t off) { dir->id = 0; } + + int diff = lfs2_min(dir->m.count - dir->id, off); + dir->id += diff; + dir->pos += diff; + off -= diff; } return 0; @@ -3347,7 +3524,7 @@ static lfs2_ssize_t lfs2_file_flushedwrite(lfs2_t *lfs2, lfs2_file_t *file, // find out which block we're extending from int err = lfs2_ctz_find(lfs2, NULL, &file->cache, file->ctz.head, file->ctz.size, - file->pos-1, &file->block, &file->off); + file->pos-1, &file->block, &(lfs2_off_t){0}); if (err) { file->flags |= LFS2_F_ERRED; return err; @@ -3525,26 +3702,55 @@ static int lfs2_file_rawtruncate(lfs2_t *lfs2, lfs2_file_t *file, lfs2_off_t siz lfs2_off_t pos = file->pos; lfs2_off_t oldsize = lfs2_file_rawsize(lfs2, file); if (size < oldsize) { - // need to flush since directly changing metadata - int err = lfs2_file_flush(lfs2, file); - if (err) { - return err; - } + // revert to inline file? + if (size <= lfs2_min(0x3fe, lfs2_min( + lfs2->cfg->cache_size, + (lfs2->cfg->metadata_max ? + lfs2->cfg->metadata_max : lfs2->cfg->block_size) / 8))) { + // flush+seek to head + lfs2_soff_t res = lfs2_file_rawseek(lfs2, file, 0, LFS2_SEEK_SET); + if (res < 0) { + return (int)res; + } - // lookup new head in ctz skip list - err = lfs2_ctz_find(lfs2, NULL, &file->cache, - file->ctz.head, file->ctz.size, - size, &file->block, &file->off); - if (err) { - return err; - } + // read our data into rcache temporarily + lfs2_cache_drop(lfs2, &lfs2->rcache); + res = lfs2_file_flushedread(lfs2, file, + lfs2->rcache.buffer, size); + if (res < 0) { + return (int)res; + } - // need to set pos/block/off consistently so seeking back to - // the old position does not get confused - file->pos = size; - file->ctz.head = file->block; - file->ctz.size = size; - file->flags |= LFS2_F_DIRTY | LFS2_F_READING; + file->ctz.head = LFS2_BLOCK_INLINE; + file->ctz.size = size; + file->flags |= LFS2_F_DIRTY | LFS2_F_READING | LFS2_F_INLINE; + file->cache.block = file->ctz.head; + file->cache.off = 0; + file->cache.size = lfs2->cfg->cache_size; + memcpy(file->cache.buffer, lfs2->rcache.buffer, size); + + } else { + // need to flush since directly changing metadata + int err = lfs2_file_flush(lfs2, file); + if (err) { + return err; + } + + // lookup new head in ctz skip list + err = lfs2_ctz_find(lfs2, NULL, &file->cache, + file->ctz.head, file->ctz.size, + size-1, &file->block, &(lfs2_off_t){0}); + if (err) { + return err; + } + + // need to set pos/block/off consistently so seeking back to + // the old position does not get confused + file->pos = size; + file->ctz.head = file->block; + file->ctz.size = size; + file->flags |= LFS2_F_DIRTY | LFS2_F_READING; + } } else if (size > oldsize) { // flush+seek if not already at end lfs2_soff_t res = lfs2_file_rawseek(lfs2, file, 0, LFS2_SEEK_END); @@ -3902,8 +4108,24 @@ static int lfs2_rawremoveattr(lfs2_t *lfs2, const char *path, uint8_t type) { /// Filesystem operations /// static int lfs2_init(lfs2_t *lfs2, const struct lfs2_config *cfg) { lfs2->cfg = cfg; + lfs2->block_count = cfg->block_count; // May be 0 int err = 0; +#ifdef LFS2_MULTIVERSION + // this driver only supports minor version < current minor version + LFS2_ASSERT(!lfs2->cfg->disk_version || ( + (0xffff & (lfs2->cfg->disk_version >> 16)) + == LFS2_DISK_VERSION_MAJOR + && (0xffff & (lfs2->cfg->disk_version >> 0)) + <= LFS2_DISK_VERSION_MINOR)); +#endif + + // check that bool is a truthy-preserving type + // + // note the most common reason for this failure is a before-c99 compiler, + // which littlefs currently does not support + LFS2_ASSERT((bool)0x80000000); + // validate that the lfs2-cfg sizes were initiated properly before // performing any arithmetic logics with them LFS2_ASSERT(lfs2->cfg->read_size != 0); @@ -3916,7 +4138,10 @@ static int lfs2_init(lfs2_t *lfs2, const struct lfs2_config *cfg) { LFS2_ASSERT(lfs2->cfg->cache_size % lfs2->cfg->prog_size == 0); LFS2_ASSERT(lfs2->cfg->block_size % lfs2->cfg->cache_size == 0); - // check that the block size is large enough to fit ctz pointers + // check that the block size is large enough to fit all ctz pointers + LFS2_ASSERT(lfs2->cfg->block_size >= 128); + // this is the exact calculation for all ctz pointers, if this fails + // and the simpler assert above does not, math must be broken LFS2_ASSERT(4*lfs2_npw2(0xffffffff / (lfs2->cfg->block_size-2*4)) <= lfs2->cfg->block_size); @@ -4026,6 +4251,8 @@ static int lfs2_deinit(lfs2_t *lfs2) { return 0; } + + #ifndef LFS2_READONLY static int lfs2_rawformat(lfs2_t *lfs2, const struct lfs2_config *cfg) { int err = 0; @@ -4035,11 +4262,13 @@ static int lfs2_rawformat(lfs2_t *lfs2, const struct lfs2_config *cfg) { return err; } + LFS2_ASSERT(cfg->block_count != 0); + // create free lookahead memset(lfs2->free.buffer, 0, lfs2->cfg->lookahead_size); lfs2->free.off = 0; lfs2->free.size = lfs2_min(8*lfs2->cfg->lookahead_size, - lfs2->cfg->block_count); + lfs2->block_count); lfs2->free.i = 0; lfs2_alloc_ack(lfs2); @@ -4052,9 +4281,9 @@ static int lfs2_rawformat(lfs2_t *lfs2, const struct lfs2_config *cfg) { // write one superblock lfs2_superblock_t superblock = { - .version = LFS2_DISK_VERSION, + .version = lfs2_fs_disk_version(lfs2), .block_size = lfs2->cfg->block_size, - .block_count = lfs2->cfg->block_count, + .block_count = lfs2->block_count, .name_max = lfs2->name_max, .file_max = lfs2->file_max, .attr_max = lfs2->attr_max, @@ -4100,14 +4329,23 @@ static int lfs2_rawmount(lfs2_t *lfs2, const struct lfs2_config *cfg) { // scan directory blocks for superblock and any global updates lfs2_mdir_t dir = {.tail = {0, 1}}; - lfs2_block_t cycle = 0; + lfs2_block_t tortoise[2] = {LFS2_BLOCK_NULL, LFS2_BLOCK_NULL}; + lfs2_size_t tortoise_i = 1; + lfs2_size_t tortoise_period = 1; while (!lfs2_pair_isnull(dir.tail)) { - if (cycle >= lfs2->cfg->block_count/2) { - // loop detected + // detect cycles with Brent's algorithm + if (lfs2_pair_issync(dir.tail, tortoise)) { + LFS2_WARN("Cycle detected in tail list"); err = LFS2_ERR_CORRUPT; goto cleanup; } - cycle += 1; + if (tortoise_i == tortoise_period) { + tortoise[0] = dir.tail[0]; + tortoise[1] = dir.tail[1]; + tortoise_i = 0; + tortoise_period *= 2; + } + tortoise_i += 1; // fetch next block in tail list lfs2_stag_t tag = lfs2_dir_fetchmatch(lfs2, &dir, dir.tail, @@ -4141,14 +4379,33 @@ static int lfs2_rawmount(lfs2_t *lfs2, const struct lfs2_config *cfg) { // check version uint16_t major_version = (0xffff & (superblock.version >> 16)); uint16_t minor_version = (0xffff & (superblock.version >> 0)); - if ((major_version != LFS2_DISK_VERSION_MAJOR || - minor_version > LFS2_DISK_VERSION_MINOR)) { - LFS2_ERROR("Invalid version v%"PRIu16".%"PRIu16, - major_version, minor_version); + if (major_version != lfs2_fs_disk_version_major(lfs2) + || minor_version > lfs2_fs_disk_version_minor(lfs2)) { + LFS2_ERROR("Invalid version " + "v%"PRIu16".%"PRIu16" != v%"PRIu16".%"PRIu16, + major_version, + minor_version, + lfs2_fs_disk_version_major(lfs2), + lfs2_fs_disk_version_minor(lfs2)); err = LFS2_ERR_INVAL; goto cleanup; } + // found older minor version? set an in-device only bit in the + // gstate so we know we need to rewrite the superblock before + // the first write + if (minor_version < lfs2_fs_disk_version_minor(lfs2)) { + LFS2_DEBUG("Found older minor version " + "v%"PRIu16".%"PRIu16" < v%"PRIu16".%"PRIu16, + major_version, + minor_version, + lfs2_fs_disk_version_major(lfs2), + lfs2_fs_disk_version_minor(lfs2)); + // note this bit is reserved on disk, so fetching more gstate + // will not interfere here + lfs2_fs_prepsuperblock(lfs2, true); + } + // check superblock configuration if (superblock.name_max) { if (superblock.name_max > lfs2->name_max) { @@ -4183,16 +4440,20 @@ static int lfs2_rawmount(lfs2_t *lfs2, const struct lfs2_config *cfg) { lfs2->attr_max = superblock.attr_max; } - if (superblock.block_count != lfs2->cfg->block_count) { + // this is where we get the block_count from disk if block_count=0 + if (lfs2->cfg->block_count + && superblock.block_count != lfs2->cfg->block_count) { LFS2_ERROR("Invalid block count (%"PRIu32" != %"PRIu32")", superblock.block_count, lfs2->cfg->block_count); err = LFS2_ERR_INVAL; goto cleanup; } + lfs2->block_count = superblock.block_count; + if (superblock.block_size != lfs2->cfg->block_size) { LFS2_ERROR("Invalid block size (%"PRIu32" != %"PRIu32")", - superblock.block_count, lfs2->cfg->block_count); + superblock.block_size, lfs2->cfg->block_size); err = LFS2_ERR_INVAL; goto cleanup; } @@ -4205,12 +4466,6 @@ static int lfs2_rawmount(lfs2_t *lfs2, const struct lfs2_config *cfg) { } } - // found superblock? - if (lfs2_pair_isnull(lfs2->root)) { - err = LFS2_ERR_INVAL; - goto cleanup; - } - // update littlefs with gstate if (!lfs2_gstate_iszero(&lfs2->gstate)) { LFS2_DEBUG("Found pending gstate 0x%08"PRIx32"%08"PRIx32"%08"PRIx32, @@ -4223,7 +4478,7 @@ static int lfs2_rawmount(lfs2_t *lfs2, const struct lfs2_config *cfg) { // setup free lookahead, to distribute allocations uniformly across // boots, we start the allocator at a random location - lfs2->free.off = lfs2->seed % lfs2->cfg->block_count; + lfs2->free.off = lfs2->seed % lfs2->block_count; lfs2_alloc_drop(lfs2); return 0; @@ -4239,6 +4494,46 @@ static int lfs2_rawunmount(lfs2_t *lfs2) { /// Filesystem filesystem operations /// +static int lfs2_fs_rawstat(lfs2_t *lfs2, struct lfs2_fsinfo *fsinfo) { + // if the superblock is up-to-date, we must be on the most recent + // minor version of littlefs + if (!lfs2_gstate_needssuperblock(&lfs2->gstate)) { + fsinfo->disk_version = lfs2_fs_disk_version(lfs2); + + // otherwise we need to read the minor version on disk + } else { + // fetch the superblock + lfs2_mdir_t dir; + int err = lfs2_dir_fetch(lfs2, &dir, lfs2->root); + if (err) { + return err; + } + + lfs2_superblock_t superblock; + lfs2_stag_t tag = lfs2_dir_get(lfs2, &dir, LFS2_MKTAG(0x7ff, 0x3ff, 0), + LFS2_MKTAG(LFS2_TYPE_INLINESTRUCT, 0, sizeof(superblock)), + &superblock); + if (tag < 0) { + return tag; + } + lfs2_superblock_fromle32(&superblock); + + // read the on-disk version + fsinfo->disk_version = superblock.version; + } + + // filesystem geometry + fsinfo->block_size = lfs2->cfg->block_size; + fsinfo->block_count = lfs2->block_count; + + // other on-disk configuration, we cache all of these for internal use + fsinfo->name_max = lfs2->name_max; + fsinfo->file_max = lfs2->file_max; + fsinfo->attr_max = lfs2->attr_max; + + return 0; +} + int lfs2_fs_rawtraverse(lfs2_t *lfs2, int (*cb)(void *data, lfs2_block_t block), void *data, bool includeorphans) { @@ -4258,13 +4553,22 @@ int lfs2_fs_rawtraverse(lfs2_t *lfs2, } #endif - lfs2_block_t cycle = 0; + lfs2_block_t tortoise[2] = {LFS2_BLOCK_NULL, LFS2_BLOCK_NULL}; + lfs2_size_t tortoise_i = 1; + lfs2_size_t tortoise_period = 1; while (!lfs2_pair_isnull(dir.tail)) { - if (cycle >= lfs2->cfg->block_count/2) { - // loop detected + // detect cycles with Brent's algorithm + if (lfs2_pair_issync(dir.tail, tortoise)) { + LFS2_WARN("Cycle detected in tail list"); return LFS2_ERR_CORRUPT; } - cycle += 1; + if (tortoise_i == tortoise_period) { + tortoise[0] = dir.tail[0]; + tortoise[1] = dir.tail[1]; + tortoise_i = 0; + tortoise_period *= 2; + } + tortoise_i += 1; for (int i = 0; i < 2; i++) { int err = cb(data, dir.tail[i]); @@ -4343,13 +4647,22 @@ static int lfs2_fs_pred(lfs2_t *lfs2, // iterate over all directory directory entries pdir->tail[0] = 0; pdir->tail[1] = 1; - lfs2_block_t cycle = 0; + lfs2_block_t tortoise[2] = {LFS2_BLOCK_NULL, LFS2_BLOCK_NULL}; + lfs2_size_t tortoise_i = 1; + lfs2_size_t tortoise_period = 1; while (!lfs2_pair_isnull(pdir->tail)) { - if (cycle >= lfs2->cfg->block_count/2) { - // loop detected + // detect cycles with Brent's algorithm + if (lfs2_pair_issync(pdir->tail, tortoise)) { + LFS2_WARN("Cycle detected in tail list"); return LFS2_ERR_CORRUPT; } - cycle += 1; + if (tortoise_i == tortoise_period) { + tortoise[0] = pdir->tail[0]; + tortoise[1] = pdir->tail[1]; + tortoise_i = 0; + tortoise_period *= 2; + } + tortoise_i += 1; if (lfs2_pair_cmp(pdir->tail, pair) == 0) { return 0; @@ -4399,13 +4712,22 @@ static lfs2_stag_t lfs2_fs_parent(lfs2_t *lfs2, const lfs2_block_t pair[2], // use fetchmatch with callback to find pairs parent->tail[0] = 0; parent->tail[1] = 1; - lfs2_block_t cycle = 0; + lfs2_block_t tortoise[2] = {LFS2_BLOCK_NULL, LFS2_BLOCK_NULL}; + lfs2_size_t tortoise_i = 1; + lfs2_size_t tortoise_period = 1; while (!lfs2_pair_isnull(parent->tail)) { - if (cycle >= lfs2->cfg->block_count/2) { - // loop detected + // detect cycles with Brent's algorithm + if (lfs2_pair_issync(parent->tail, tortoise)) { + LFS2_WARN("Cycle detected in tail list"); return LFS2_ERR_CORRUPT; } - cycle += 1; + if (tortoise_i == tortoise_period) { + tortoise[0] = parent->tail[0]; + tortoise[1] = parent->tail[1]; + tortoise_i = 0; + tortoise_period *= 2; + } + tortoise_i += 1; lfs2_stag_t tag = lfs2_dir_fetchmatch(lfs2, parent, parent->tail, LFS2_MKTAG(0x7ff, 0, 0x3ff), @@ -4422,9 +4744,15 @@ static lfs2_stag_t lfs2_fs_parent(lfs2_t *lfs2, const lfs2_block_t pair[2], } #endif +static void lfs2_fs_prepsuperblock(lfs2_t *lfs2, bool needssuperblock) { + lfs2->gstate.tag = (lfs2->gstate.tag & ~LFS2_MKTAG(0, 0, 0x200)) + | (uint32_t)needssuperblock << 9; +} + #ifndef LFS2_READONLY static int lfs2_fs_preporphans(lfs2_t *lfs2, int8_t orphans) { - LFS2_ASSERT(lfs2_tag_size(lfs2->gstate.tag) > 0 || orphans >= 0); + LFS2_ASSERT(lfs2_tag_size(lfs2->gstate.tag) > 0x000 || orphans >= 0); + LFS2_ASSERT(lfs2_tag_size(lfs2->gstate.tag) < 0x1ff || orphans <= 0); lfs2->gstate.tag += orphans; lfs2->gstate.tag = ((lfs2->gstate.tag & ~LFS2_MKTAG(0x800, 0, 0)) | ((uint32_t)lfs2_gstate_hasorphans(&lfs2->gstate) << 31)); @@ -4443,6 +4771,45 @@ static void lfs2_fs_prepmove(lfs2_t *lfs2, } #endif +#ifndef LFS2_READONLY +static int lfs2_fs_desuperblock(lfs2_t *lfs2) { + if (!lfs2_gstate_needssuperblock(&lfs2->gstate)) { + return 0; + } + + LFS2_DEBUG("Rewriting superblock {0x%"PRIx32", 0x%"PRIx32"}", + lfs2->root[0], + lfs2->root[1]); + + lfs2_mdir_t root; + int err = lfs2_dir_fetch(lfs2, &root, lfs2->root); + if (err) { + return err; + } + + // write a new superblock + lfs2_superblock_t superblock = { + .version = lfs2_fs_disk_version(lfs2), + .block_size = lfs2->cfg->block_size, + .block_count = lfs2->block_count, + .name_max = lfs2->name_max, + .file_max = lfs2->file_max, + .attr_max = lfs2->attr_max, + }; + + lfs2_superblock_tole32(&superblock); + err = lfs2_dir_commit(lfs2, &root, LFS2_MKATTRS( + {LFS2_MKTAG(LFS2_TYPE_INLINESTRUCT, 0, sizeof(superblock)), + &superblock})); + if (err) { + return err; + } + + lfs2_fs_prepsuperblock(lfs2, false); + return 0; +} +#endif + #ifndef LFS2_READONLY static int lfs2_fs_demove(lfs2_t *lfs2) { if (!lfs2_gstate_hasmove(&lfs2->gdisk)) { @@ -4455,6 +4822,10 @@ static int lfs2_fs_demove(lfs2_t *lfs2) { lfs2->gdisk.pair[1], lfs2_tag_id(lfs2->gdisk.tag)); + // no other gstate is supported at this time, so if we found something else + // something most likely went wrong in gstate calculation + LFS2_ASSERT(lfs2_tag_type3(lfs2->gdisk.tag) == LFS2_TYPE_DELETE); + // fetch and delete the moved entry lfs2_mdir_t movedir; int err = lfs2_dir_fetch(lfs2, &movedir, lfs2->gdisk.pair); @@ -4481,12 +4852,20 @@ static int lfs2_fs_deorphan(lfs2_t *lfs2, bool powerloss) { return 0; } - int8_t found = 0; -restart: - { + // Check for orphans in two separate passes: + // - 1 for half-orphans (relocations) + // - 2 for full-orphans (removes/renames) + // + // Two separate passes are needed as half-orphans can contain outdated + // references to full-orphans, effectively hiding them from the deorphan + // search. + // + int pass = 0; + while (pass < 2) { // Fix any orphans lfs2_mdir_t pdir = {.split = true, .tail = {0, 1}}; lfs2_mdir_t dir; + bool moreorphans = false; // iterate over all directory directory entries while (!lfs2_pair_isnull(pdir.tail)) { @@ -4504,42 +4883,7 @@ restart: return tag; } - // note we only check for full orphans if we may have had a - // power-loss, otherwise orphans are created intentionally - // during operations such as lfs2_mkdir - if (tag == LFS2_ERR_NOENT && powerloss) { - // we are an orphan - LFS2_DEBUG("Fixing orphan {0x%"PRIx32", 0x%"PRIx32"}", - pdir.tail[0], pdir.tail[1]); - - // steal state - err = lfs2_dir_getgstate(lfs2, &dir, &lfs2->gdelta); - if (err) { - return err; - } - - // steal tail - lfs2_pair_tole32(dir.tail); - int state = lfs2_dir_orphaningcommit(lfs2, &pdir, LFS2_MKATTRS( - {LFS2_MKTAG(LFS2_TYPE_TAIL + dir.split, 0x3ff, 8), - dir.tail})); - lfs2_pair_fromle32(dir.tail); - if (state < 0) { - return state; - } - - found += 1; - - // did our commit create more orphans? - if (state == LFS2_OK_ORPHANED) { - goto restart; - } - - // refetch tail - continue; - } - - if (tag != LFS2_ERR_NOENT) { + if (pass == 0 && tag != LFS2_ERR_NOENT) { lfs2_block_t pair[2]; lfs2_stag_t state = lfs2_dir_get(lfs2, &parent, LFS2_MKTAG(0x7ff, 0x3ff, 0), tag, pair); @@ -4548,7 +4892,7 @@ restart: } lfs2_pair_fromle32(pair); - if (!lfs2_pair_sync(pair, pdir.tail)) { + if (!lfs2_pair_issync(pair, pdir.tail)) { // we have desynced LFS2_DEBUG("Fixing half-orphan " "{0x%"PRIx32", 0x%"PRIx32"} " @@ -4578,33 +4922,69 @@ restart: return state; } - found += 1; - // did our commit create more orphans? if (state == LFS2_OK_ORPHANED) { - goto restart; + moreorphans = true; } // refetch tail continue; } } + + // note we only check for full orphans if we may have had a + // power-loss, otherwise orphans are created intentionally + // during operations such as lfs2_mkdir + if (pass == 1 && tag == LFS2_ERR_NOENT && powerloss) { + // we are an orphan + LFS2_DEBUG("Fixing orphan {0x%"PRIx32", 0x%"PRIx32"}", + pdir.tail[0], pdir.tail[1]); + + // steal state + err = lfs2_dir_getgstate(lfs2, &dir, &lfs2->gdelta); + if (err) { + return err; + } + + // steal tail + lfs2_pair_tole32(dir.tail); + int state = lfs2_dir_orphaningcommit(lfs2, &pdir, LFS2_MKATTRS( + {LFS2_MKTAG(LFS2_TYPE_TAIL + dir.split, 0x3ff, 8), + dir.tail})); + lfs2_pair_fromle32(dir.tail); + if (state < 0) { + return state; + } + + // did our commit create more orphans? + if (state == LFS2_OK_ORPHANED) { + moreorphans = true; + } + + // refetch tail + continue; + } } pdir = dir; } + + pass = moreorphans ? 0 : pass+1; } // mark orphans as fixed - return lfs2_fs_preporphans(lfs2, -lfs2_min( - lfs2_gstate_getorphans(&lfs2->gstate), - found)); + return lfs2_fs_preporphans(lfs2, -lfs2_gstate_getorphans(&lfs2->gstate)); } #endif #ifndef LFS2_READONLY static int lfs2_fs_forceconsistency(lfs2_t *lfs2) { - int err = lfs2_fs_demove(lfs2); + int err = lfs2_fs_desuperblock(lfs2); + if (err) { + return err; + } + + err = lfs2_fs_demove(lfs2); if (err) { return err; } @@ -4618,6 +4998,36 @@ static int lfs2_fs_forceconsistency(lfs2_t *lfs2) { } #endif +#ifndef LFS2_READONLY +static int lfs2_fs_rawmkconsistent(lfs2_t *lfs2) { + // lfs2_fs_forceconsistency does most of the work here + int err = lfs2_fs_forceconsistency(lfs2); + if (err) { + return err; + } + + // do we have any pending gstate? + lfs2_gstate_t delta = {0}; + lfs2_gstate_xor(&delta, &lfs2->gdisk); + lfs2_gstate_xor(&delta, &lfs2->gstate); + if (!lfs2_gstate_iszero(&delta)) { + // lfs2_dir_commit will implicitly write out any pending gstate + lfs2_mdir_t root; + err = lfs2_dir_fetch(lfs2, &root, lfs2->root); + if (err) { + return err; + } + + err = lfs2_dir_commit(lfs2, &root, NULL, 0); + if (err) { + return err; + } + } + + return 0; +} +#endif + static int lfs2_fs_size_count(void *p, lfs2_block_t block) { (void)block; lfs2_size_t *size = p; @@ -4635,6 +5045,45 @@ static lfs2_ssize_t lfs2_fs_rawsize(lfs2_t *lfs2) { return size; } +#ifndef LFS2_READONLY +static int lfs2_fs_rawgrow(lfs2_t *lfs2, lfs2_size_t block_count) { + // shrinking is not supported + LFS2_ASSERT(block_count >= lfs2->block_count); + + if (block_count > lfs2->block_count) { + lfs2->block_count = block_count; + + // fetch the root + lfs2_mdir_t root; + int err = lfs2_dir_fetch(lfs2, &root, lfs2->root); + if (err) { + return err; + } + + // update the superblock + lfs2_superblock_t superblock; + lfs2_stag_t tag = lfs2_dir_get(lfs2, &root, LFS2_MKTAG(0x7ff, 0x3ff, 0), + LFS2_MKTAG(LFS2_TYPE_INLINESTRUCT, 0, sizeof(superblock)), + &superblock); + if (tag < 0) { + return tag; + } + lfs2_superblock_fromle32(&superblock); + + superblock.block_count = lfs2->block_count; + + lfs2_superblock_tole32(&superblock); + err = lfs2_dir_commit(lfs2, &root, LFS2_MKATTRS( + {tag, &superblock})); + if (err) { + return err; + } + } + + return 0; +} +#endif + #ifdef LFS2_MIGRATE ////// Migration from littelfs v1 below this ////// @@ -5058,6 +5507,10 @@ static int lfs21_unmount(lfs2_t *lfs2) { /// v1 migration /// static int lfs2_rawmigrate(lfs2_t *lfs2, const struct lfs2_config *cfg) { struct lfs21 lfs21; + + // Indeterminate filesystem size not allowed for migration. + LFS2_ASSERT(cfg->block_count != 0); + int err = lfs21_mount(lfs2, &lfs21, cfg); if (err) { return err; @@ -5754,6 +6207,20 @@ int lfs2_dir_rewind(lfs2_t *lfs2, lfs2_dir_t *dir) { return err; } +int lfs2_fs_stat(lfs2_t *lfs2, struct lfs2_fsinfo *fsinfo) { + int err = LFS2_LOCK(lfs2->cfg); + if (err) { + return err; + } + LFS2_TRACE("lfs2_fs_stat(%p, %p)", (void*)lfs2, (void*)fsinfo); + + err = lfs2_fs_rawstat(lfs2, fsinfo); + + LFS2_TRACE("lfs2_fs_stat -> %d", err); + LFS2_UNLOCK(lfs2->cfg); + return err; +} + lfs2_ssize_t lfs2_fs_size(lfs2_t *lfs2) { int err = LFS2_LOCK(lfs2->cfg); if (err) { @@ -5783,6 +6250,54 @@ int lfs2_fs_traverse(lfs2_t *lfs2, int (*cb)(void *, lfs2_block_t), void *data) return err; } +#ifndef LFS2_READONLY +int lfs2_fs_gc(lfs2_t *lfs2) { + int err = LFS2_LOCK(lfs2->cfg); + if (err) { + return err; + } + LFS2_TRACE("lfs2_fs_gc(%p)", (void*)lfs2); + + err = lfs2_fs_rawgc(lfs2); + + LFS2_TRACE("lfs2_fs_gc -> %d", err); + LFS2_UNLOCK(lfs2->cfg); + return err; +} +#endif + +#ifndef LFS2_READONLY +int lfs2_fs_mkconsistent(lfs2_t *lfs2) { + int err = LFS2_LOCK(lfs2->cfg); + if (err) { + return err; + } + LFS2_TRACE("lfs2_fs_mkconsistent(%p)", (void*)lfs2); + + err = lfs2_fs_rawmkconsistent(lfs2); + + LFS2_TRACE("lfs2_fs_mkconsistent -> %d", err); + LFS2_UNLOCK(lfs2->cfg); + return err; +} +#endif + +#ifndef LFS2_READONLY +int lfs2_fs_grow(lfs2_t *lfs2, lfs2_size_t block_count) { + int err = LFS2_LOCK(lfs2->cfg); + if (err) { + return err; + } + LFS2_TRACE("lfs2_fs_grow(%p, %"PRIu32")", (void*)lfs2, block_count); + + err = lfs2_fs_rawgrow(lfs2, block_count); + + LFS2_TRACE("lfs2_fs_grow -> %d", err); + LFS2_UNLOCK(lfs2->cfg); + return err; +} +#endif + #ifdef LFS2_MIGRATE int lfs2_migrate(lfs2_t *lfs2, const struct lfs2_config *cfg) { int err = LFS2_LOCK(cfg); diff --git a/lib/littlefs/lfs2.h b/lib/littlefs/lfs2.h index 715764f7c..4c426fc4c 100644 --- a/lib/littlefs/lfs2.h +++ b/lib/littlefs/lfs2.h @@ -8,8 +8,6 @@ #ifndef LFS2_H #define LFS2_H -#include -#include #include "lfs2_util.h" #ifdef __cplusplus @@ -23,14 +21,14 @@ extern "C" // Software library version // Major (top-nibble), incremented on backwards incompatible changes // Minor (bottom-nibble), incremented on feature additions -#define LFS2_VERSION 0x00020005 +#define LFS2_VERSION 0x00020008 #define LFS2_VERSION_MAJOR (0xffff & (LFS2_VERSION >> 16)) #define LFS2_VERSION_MINOR (0xffff & (LFS2_VERSION >> 0)) // Version of On-disk data structures // Major (top-nibble), incremented on backwards incompatible changes // Minor (bottom-nibble), incremented on feature additions -#define LFS2_DISK_VERSION 0x00020000 +#define LFS2_DISK_VERSION 0x00020001 #define LFS2_DISK_VERSION_MAJOR (0xffff & (LFS2_DISK_VERSION >> 16)) #define LFS2_DISK_VERSION_MINOR (0xffff & (LFS2_DISK_VERSION >> 0)) @@ -114,6 +112,8 @@ enum lfs2_type { LFS2_TYPE_SOFTTAIL = 0x600, LFS2_TYPE_HARDTAIL = 0x601, LFS2_TYPE_MOVESTATE = 0x7ff, + LFS2_TYPE_CCRC = 0x500, + LFS2_TYPE_FCRC = 0x5ff, // internal chip sources LFS2_FROM_NOOP = 0x000, @@ -263,6 +263,14 @@ struct lfs2_config { // can help bound the metadata compaction time. Must be <= block_size. // Defaults to block_size when zero. lfs2_size_t metadata_max; + +#ifdef LFS2_MULTIVERSION + // On-disk version to use when writing in the form of 16-bit major version + // + 16-bit minor version. This limiting metadata to what is supported by + // older minor versions. Note that some features will be lost. Defaults to + // to the most recent minor version when zero. + uint32_t disk_version; +#endif }; // File info structure @@ -280,6 +288,27 @@ struct lfs2_info { char name[LFS2_NAME_MAX+1]; }; +// Filesystem info structure +struct lfs2_fsinfo { + // On-disk version. + uint32_t disk_version; + + // Size of a logical block in bytes. + lfs2_size_t block_size; + + // Number of logical blocks in filesystem. + lfs2_size_t block_count; + + // Upper limit on the length of file names in bytes. + lfs2_size_t name_max; + + // Upper limit on the size of files in bytes. + lfs2_size_t file_max; + + // Upper limit on the size of custom attributes in bytes. + lfs2_size_t attr_max; +}; + // Custom attribute structure, used to describe custom attributes // committed atomically during file writes. struct lfs2_attr { @@ -410,6 +439,7 @@ typedef struct lfs2 { } free; const struct lfs2_config *cfg; + lfs2_size_t block_count; lfs2_size_t name_max; lfs2_size_t file_max; lfs2_size_t attr_max; @@ -534,8 +564,8 @@ int lfs2_file_open(lfs2_t *lfs2, lfs2_file_t *file, // are values from the enum lfs2_open_flags that are bitwise-ored together. // // The config struct provides additional config options per file as described -// above. The config struct must be allocated while the file is open, and the -// config struct must be zeroed for defaults and backwards compatibility. +// above. The config struct must remain allocated while the file is open, and +// the config struct must be zeroed for defaults and backwards compatibility. // // Returns a negative error code on failure. int lfs2_file_opencfg(lfs2_t *lfs2, lfs2_file_t *file, @@ -659,6 +689,12 @@ int lfs2_dir_rewind(lfs2_t *lfs2, lfs2_dir_t *dir); /// Filesystem-level filesystem operations +// Find on-disk info about the filesystem +// +// Fills out the fsinfo structure based on the filesystem found on-disk. +// Returns a negative error code on failure. +int lfs2_fs_stat(lfs2_t *lfs2, struct lfs2_fsinfo *fsinfo); + // Finds the current size of the filesystem // // Note: Result is best effort. If files share COW structures, the returned @@ -676,6 +712,40 @@ lfs2_ssize_t lfs2_fs_size(lfs2_t *lfs2); // Returns a negative error code on failure. int lfs2_fs_traverse(lfs2_t *lfs2, int (*cb)(void*, lfs2_block_t), void *data); +// Attempt to proactively find free blocks +// +// Calling this function is not required, but may allowing the offloading of +// the expensive block allocation scan to a less time-critical code path. +// +// Note: littlefs currently does not persist any found free blocks to disk. +// This may change in the future. +// +// Returns a negative error code on failure. Finding no free blocks is +// not an error. +int lfs2_fs_gc(lfs2_t *lfs2); + +#ifndef LFS2_READONLY +// Attempt to make the filesystem consistent and ready for writing +// +// Calling this function is not required, consistency will be implicitly +// enforced on the first operation that writes to the filesystem, but this +// function allows the work to be performed earlier and without other +// filesystem changes. +// +// Returns a negative error code on failure. +int lfs2_fs_mkconsistent(lfs2_t *lfs2); +#endif + +#ifndef LFS2_READONLY +// Grows the filesystem to a new size, updating the superblock with the new +// block count. +// +// Note: This is irreversible. +// +// Returns a negative error code on failure. +int lfs2_fs_grow(lfs2_t *lfs2, lfs2_size_t block_count); +#endif + #ifndef LFS2_READONLY #ifdef LFS2_MIGRATE // Attempts to migrate a previous version of littlefs diff --git a/lib/littlefs/lfs2_util.h b/lib/littlefs/lfs2_util.h index 6a4c8ffb5..dd2cbcc10 100644 --- a/lib/littlefs/lfs2_util.h +++ b/lib/littlefs/lfs2_util.h @@ -167,10 +167,9 @@ static inline int lfs2_scmp(uint32_t a, uint32_t b) { // Convert between 32-bit little-endian and native order static inline uint32_t lfs2_fromle32(uint32_t a) { -#if !defined(LFS2_NO_INTRINSICS) && ( \ - (defined( BYTE_ORDER ) && defined( ORDER_LITTLE_ENDIAN ) && BYTE_ORDER == ORDER_LITTLE_ENDIAN ) || \ +#if (defined( BYTE_ORDER ) && defined( ORDER_LITTLE_ENDIAN ) && BYTE_ORDER == ORDER_LITTLE_ENDIAN ) || \ (defined(__BYTE_ORDER ) && defined(__ORDER_LITTLE_ENDIAN ) && __BYTE_ORDER == __ORDER_LITTLE_ENDIAN ) || \ - (defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)) + (defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) return a; #elif !defined(LFS2_NO_INTRINSICS) && ( \ (defined( BYTE_ORDER ) && defined( ORDER_BIG_ENDIAN ) && BYTE_ORDER == ORDER_BIG_ENDIAN ) || \ @@ -196,10 +195,9 @@ static inline uint32_t lfs2_frombe32(uint32_t a) { (defined(__BYTE_ORDER ) && defined(__ORDER_LITTLE_ENDIAN ) && __BYTE_ORDER == __ORDER_LITTLE_ENDIAN ) || \ (defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)) return __builtin_bswap32(a); -#elif !defined(LFS2_NO_INTRINSICS) && ( \ - (defined( BYTE_ORDER ) && defined( ORDER_BIG_ENDIAN ) && BYTE_ORDER == ORDER_BIG_ENDIAN ) || \ +#elif (defined( BYTE_ORDER ) && defined( ORDER_BIG_ENDIAN ) && BYTE_ORDER == ORDER_BIG_ENDIAN ) || \ (defined(__BYTE_ORDER ) && defined(__ORDER_BIG_ENDIAN ) && __BYTE_ORDER == __ORDER_BIG_ENDIAN ) || \ - (defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)) + (defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) return a; #else return (((uint8_t*)&a)[0] << 24) |