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 <noreply@anthropic.com>
This commit is contained in:
Kaj Schittecat
2026-07-09 13:00:54 +02:00
co-authored by Claude Fable 5
parent d20241627c
commit 76cd025bf5
3 changed files with 92 additions and 7 deletions
+49 -3
View File
@@ -5,6 +5,9 @@
#include <SD.h>
#include "helpers/esp32/WdtHeavyGuard.h" // suspend core-0 idle WDT during the (SPIFFS-GC-prone) contact write
#endif
#if defined(HAS_TANMATSU)
#include <SD_MMC.h>
#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