From 376a9d3f9b68aa239769093890bac902749350b5 Mon Sep 17 00:00:00 2001 From: Ben Allfree Date: Mon, 20 Jul 2026 20:04:33 -0700 Subject: [PATCH] fix(companion): recover from corrupt littlefs at boot Co-authored-by: Cursor --- examples/companion_radio/DataStore.cpp | 48 ++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/examples/companion_radio/DataStore.cpp b/examples/companion_radio/DataStore.cpp index 0f3a0f9c5b..de85bbec50 100644 --- a/examples/companion_radio/DataStore.cpp +++ b/examples/companion_radio/DataStore.cpp @@ -44,6 +44,48 @@ static File openWrite(FILESYSTEM* fs, const char* filename) { #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) static uint32_t _ContactsChannelsTotalBlocks = 0; + +/* Boot-time filesystem integrity check. + * + * A littlefs whose superblock is intact but whose interior metadata is damaged (e.g. flash + * region partially overwritten, or interrupted writes) mounts successfully, then wedges the + * device on the FIRST write after boot: lfs_deorphan() -- run lazily by lfs_remove()/ + * lfs_file_open() once per power cycle -- walks every metadata block, and littlefs v1 + * follows corrupt tail pointers into an unbounded loop, or (with LFS_NO_ASSERT) reads/writes + * a wild out-of-range block address, hard-faulting the MCU. Either way the node freezes + * until manually reset, and every reboot repeats the cycle. + * + * Walking the whole tree up-front detects this state safely: lfs_traverse() invokes the + * callback on each block number BEFORE reading it, so out-of-range pointers are caught + * without touching bad addresses, and a visited-block count exceeding block_count can only + * mean a metadata cycle. On failure the filesystem is reformatted -- losing its contents, + * but recovering the node without a chip erase. + */ +struct FsckState { + lfs_size_t visited; + lfs_size_t block_count; +}; + +static int _fsckBlock(void *p, lfs_block_t block) { + FsckState *s = (FsckState *) p; + if (block >= s->block_count) return LFS_ERR_CORRUPT; // pointer outside the filesystem + if (++s->visited > s->block_count) return LFS_ERR_CORRUPT; // more blocks than exist -> cycle + return 0; +} + +static bool _isFilesystemIntact(FILESYSTEM* fs) { + FsckState s; + s.visited = 0; + s.block_count = fs->_getFS()->cfg->block_count; + return lfs_traverse(fs->_getFS(), _fsckBlock, &s) == 0; +} + +static void _fsckAndRecover(FILESYSTEM* fs, const char* name) { + if (!_isFilesystemIntact(fs)) { + MESH_DEBUG_PRINTLN("ERROR: %s filesystem is corrupted - reformatting!", name); + fs->format(); + } +} #endif void DataStore::begin() { @@ -52,6 +94,12 @@ void DataStore::begin() { #endif #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) + // must run before the first filesystem write (checkAdvBlobFile/migrateToSecondaryFS below) + _fsckAndRecover(_fs, "primary"); + if (_fsExtra != nullptr) { + _fsckAndRecover(_fsExtra, "secondary"); + } + _ContactsChannelsTotalBlocks = _getContactsChannelsFS()->_getFS()->cfg->block_count; checkAdvBlobFile(); #if defined(EXTRAFS) || defined(QSPIFLASH)