From 76cd025bf51e6406be2d35eb4400ccfcef15fc52 Mon Sep 17 00:00:00 2001 From: Kaj Schittecat Date: Thu, 9 Jul 2026 13:00:54 +0200 Subject: [PATCH] tanmatsu: move identity + prefs to the SD card; open-probe the prefs load chain The P4's internal FFat 'locfd' has a broken FAT metadata layer: open/ read/write work but f_stat/exists()/size() return garbage - and WHICH garbage shifts with the build. At -Og the exists()-gated identity and NodePrefs loads worked by luck; the -Os switch flipped the lie and the device booted with a fresh identity and default name, and profile changes never survived a reboot (the loads failed, not the saves). Chat history was unaffected because it already lives on SD_MMC. - DataStore::useSdMmcStorage(): full-store adoption of the card (/meshcomod root, identity store included), mirroring the T-Deck's useSdStorage(). The old card-root contacts3/channels2/adv_blobs from the secondary-FS era are renamed under /meshcomod. - Tanmatsu boot: one-time rescue of identity + prefs off FFat by OPEN and READ probing (never exists()/size() on that FS), then adopt the card. FFat remains the no-card fallback only. - DataStore::loadPrefs() probes candidates by opening and reading a byte instead of exists() - truthful on the P4, identical elsewhere. Co-Authored-By: Claude Fable 5 --- src/DataStore.cpp | 52 +++++++++++++++++++++++++++++++++++++++--- src/DataStore.h | 6 +++++ tanmatsu/main/main.cpp | 41 +++++++++++++++++++++++++++++---- 3 files changed, 92 insertions(+), 7 deletions(-) diff --git a/src/DataStore.cpp b/src/DataStore.cpp index a3ee0b0..c898020 100644 --- a/src/DataStore.cpp +++ b/src/DataStore.cpp @@ -5,6 +5,9 @@ #include #include "helpers/esp32/WdtHeavyGuard.h" // suspend core-0 idle WDT during the (SPIFFS-GC-prone) contact write #endif +#if defined(HAS_TANMATSU) +#include +#endif #if defined(EXTRAFS) || defined(QSPIFLASH) #define MAX_BLOBRECS 100 @@ -240,16 +243,29 @@ bool DataStore::saveMainIdentity(const mesh::LocalIdentity &identity) { } void DataStore::loadPrefs(NodePrefs& prefs, double& node_lat, double& node_lon) { - if (_fs->exists(_rp("/new_prefs"))) { + // Probe by OPENING, never exists(): on the Tanmatsu's internal FFat the FAT + // metadata layer lies (f_stat garbage — the tile cache hit the same thing), + // and WHICH lie you get shifts with the build (the -Og to -Os switch turned + // "loaded fine by luck" into "no prefs found -> default name every boot"). + // open()+read is truthful there, and identical in behavior everywhere else. + auto probe = [this](const char* nm) -> bool { + File f = openRead(nm); + if (!f) return false; + uint8_t b; + const bool ok = f.read(&b, 1) == 1; // size() lies on the P4's FFat too — actually read + f.close(); + return ok; + }; + if (probe("/new_prefs")) { loadPrefsInt("/new_prefs", prefs, node_lat, node_lon); // new filename - } else if (_fs->exists(_rp("/new_prefs.tmp"))) { + } else if (probe("/new_prefs.tmp")) { // Main file gone but a staged copy exists: a reboot landed between the temp // write and the swap (or the swap was torn). Recover from it — this is the // "device booted with the default name once" failure mode. MESH_DEBUG_PRINTLN("DataStore: /new_prefs missing, recovering from .tmp"); loadPrefsInt("/new_prefs.tmp", prefs, node_lat, node_lon); savePrefs(prefs, node_lat, node_lon); // re-establish the main file - } else if (_fs->exists(_rp("/node_prefs"))) { + } else if (probe("/node_prefs")) { loadPrefsInt("/node_prefs", prefs, node_lat, node_lon); savePrefs(prefs, node_lat, node_lon); // save to new filename _fs->remove(_rp("/node_prefs")); // remove old @@ -818,3 +834,33 @@ bool DataStore::useSdStorage() { return true; } #endif + +#if defined(HAS_TANMATSU) +// Tanmatsu: same full-store adoption as useSdStorage(), but on the SD_MMC slot. +// The internal FFat 'locfd' has a broken FAT metadata layer (f_stat/exists lie — +// see the tile-cache notes), and the exists()-gated identity + prefs loads that +// "worked" at -Og read different garbage at -Os and came up empty: fresh node +// identity, default name, profile changes gone every reboot. The card's FAT +// metadata is truthful, so identity/prefs move there with everything else. +// The caller migrates any FFat-resident files first (open()-probed, never +// exists() on FFat). +bool DataStore::useSdMmcStorage() { + if (!SD_MMC.exists("/meshcomod")) SD_MMC.mkdir("/meshcomod"); + if (!SD_MMC.exists("/meshcomod/bl")) SD_MMC.mkdir("/meshcomod/bl"); + if (!SD_MMC.exists("/meshcomod/identity")) SD_MMC.mkdir("/meshcomod/identity"); + // The old secondary-FS layout kept contacts/channels/blobs at the CARD ROOT; + // pull them under /meshcomod so the rooted store keeps reading them. + static const char* k_move[] = { "/contacts3", "/channels2", "/adv_blobs" }; + for (const char* nm : k_move) { + char dst[40]; + snprintf(dst, sizeof dst, "/meshcomod%s", nm); + if (SD_MMC.exists(nm) && !SD_MMC.exists(dst)) SD_MMC.rename(nm, dst); + } + strncpy(_root, "/meshcomod", sizeof(_root) - 1); + _root[sizeof(_root) - 1] = '\0'; + _fs = &SD_MMC; + _fsExtra = nullptr; + identity_store.use(SD_MMC, "/meshcomod/identity"); + return true; +} +#endif diff --git a/src/DataStore.h b/src/DataStore.h index 9bc44cf..f04689e 100644 --- a/src/DataStore.h +++ b/src/DataStore.h @@ -42,6 +42,12 @@ public: // contacts, channels, blobs). Call after the SD card is mounted. Returns false // if the dir can't be created. No-op on non-ESP32. bool useSdStorage(); +#endif +#if defined(HAS_TANMATSU) + // Full-store adoption of the SD_MMC card (Tanmatsu): identity + prefs move off + // the broken-metadata internal FFat (its exists()/f_stat lie, which made the + // gated loads come up empty). Caller migrates FFat-resident files first. + bool useSdMmcStorage(); #endif FILESYSTEM* getPrimaryFS() const { return _fs; } FILESYSTEM* getSecondaryFS() const { return _fsExtra; } diff --git a/tanmatsu/main/main.cpp b/tanmatsu/main/main.cpp index 11092ae..8d88e25 100644 --- a/tanmatsu/main/main.cpp +++ b/tanmatsu/main/main.cpp @@ -375,12 +375,45 @@ static void wadameshSetup() { printf("[storage] persistence disabled this boot (DataStore opens fail gracefully)\n"); } // The internal FFat 'locfd' on this P4 has a broken FAT metadata layer (see the tile-cache notes): - // it keeps rarely-written data (identity/prefs) but LOSES the frequently-rewritten contacts + chat on - // reboot. So route contacts/channels (and chat history, see uiDataFsReady) to the microSD card, which - // is reliable FAT. Identity/NodePrefs stay on FFat — they persist there and survive a card-less boot. + // open/read/write work but f_stat/exists()/size() return garbage — and WHICH garbage shifts with + // the build (-Og "worked by luck"; -Os made the exists()-gated identity + prefs loads come up + // empty: fresh node identity, default name, profile changes lost every reboot). The card's FAT + // metadata is truthful, so the WHOLE store lives there now; FFat remains only the no-card fallback. g_sd_ok = SD_MMC.begin("/sdcard", false /*1-bit*/) && SD_MMC.cardType() != CARD_NONE; printf("[storage] SD_MMC.begin = %s\n", g_sd_ok ? "OK" : "no card"); - if (g_sd_ok) store.setSecondaryFS(&SD_MMC); // contacts + channels -> SD; identity/prefs stay on FFat + if (g_sd_ok) { + SD_MMC.mkdir("/meshcomod"); + SD_MMC.mkdir("/meshcomod/identity"); + SD_MMC.mkdir("/meshcomod/bl"); + if (g_fs_ok) { + // One-time rescue of identity + prefs off FFat: probe by OPEN + READ (never + // exists()/size() on this FS). Files the card already holds win — SD metadata + // is honest, so exists() is safe THERE. + static const char* k_mig[] = { "/new_prefs", "/new_prefs.tmp", "/node_prefs", + "/identity/_main.id" }; + for (const char* nm : k_mig) { + char dst[48]; + snprintf(dst, sizeof dst, "/meshcomod%s", nm); + if (SD_MMC.exists(dst)) continue; + File s = FFat.open(nm, FILE_READ); + if (!s) continue; + uint8_t buf[512]; + size_t n = s.read(buf, sizeof buf); + if (n == 0) { s.close(); continue; } // ghost/empty entry — nothing to keep + File d = SD_MMC.open(dst, FILE_WRITE); + if (!d) { s.close(); continue; } + size_t total = 0; + do { d.write(buf, n); total += n; n = s.read(buf, sizeof buf); } while (n > 0); + d.close(); + s.close(); + printf("[storage] migrated %s -> SD:/meshcomod (%u B)\n", nm, (unsigned)total); + } + } + store.useSdMmcStorage(); // identity/prefs/contacts/channels all on the card + } + // No card: FFat stays the store, as before — identity/prefs loads there depend + // on the broken metadata layer and may be unreliable, but it is the only + // persistent option on a card-less unit. store.begin(); bootLog("Mesh stack");