From 2ea8687d4cef0055fd6fbc30f887985307c13d86 Mon Sep 17 00:00:00 2001 From: Patrick Winnertz Date: Fri, 24 Apr 2026 14:37:16 +0200 Subject: [PATCH 1/2] Use a tempfile / rename for atomic writes in order to prevent dataloss on a HardFault on a repeater. --- src/helpers/ClientACL.cpp | 17 +++++++++++++---- src/helpers/CommonCLI.cpp | 15 +++++++++++++-- src/helpers/RegionMap.cpp | 19 +++++++++++++++---- 3 files changed, 41 insertions(+), 10 deletions(-) diff --git a/src/helpers/ClientACL.cpp b/src/helpers/ClientACL.cpp index 1282382737..58eb7861be 100644 --- a/src/helpers/ClientACL.cpp +++ b/src/helpers/ClientACL.cpp @@ -1,9 +1,9 @@ #include "ClientACL.h" -static File openWrite(FILESYSTEM* _fs, const char* filename) { +static File openWriteTemp(FILESYSTEM* _fs, const char* filename, const char* tmp_filename) { #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) - _fs->remove(filename); - return _fs->open(filename, FILE_O_WRITE); + _fs->remove(tmp_filename); + return _fs->open(tmp_filename, FILE_O_WRITE); #elif defined(RP2040_PLATFORM) return _fs->open(filename, "w"); #else @@ -54,7 +54,9 @@ void ClientACL::load(FILESYSTEM* fs, const mesh::LocalIdentity& self_id) { void ClientACL::save(FILESYSTEM* fs, bool (*filter)(ClientInfo*)) { _fs = fs; - File file = openWrite(_fs, "/s_contacts"); + const char* tmp_path = "/s_contacts.tmp"; + const char* real_path = "/s_contacts"; + File file = openWriteTemp(_fs, real_path, tmp_path); if (file) { uint8_t unused[2]; memset(unused, 0, sizeof(unused)); @@ -74,6 +76,13 @@ void ClientACL::save(FILESYSTEM* fs, bool (*filter)(ClientInfo*)) { if (!success) break; // write failed } file.close(); + +#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) + if (!_fs->rename(tmp_path, real_path)) { + _fs->remove(tmp_path); + MESH_DEBUG_PRINTLN("ERROR: ClientACL::save rename failed!"); + } +#endif } } diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index b71afc72e2..f68a6fc1eb 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -126,8 +126,12 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) { void CommonCLI::savePrefs(FILESYSTEM* fs) { #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) - fs->remove("/com_prefs"); - File file = fs->open("/com_prefs", FILE_O_WRITE); + // Atomic write: write to temp file, then rename over the real file. + // LittleFS rename() is a single metadata commit -- atomic even on power loss. + const char* tmp_path = "/com_prefs.tmp"; + const char* real_path = "/com_prefs"; + fs->remove(tmp_path); // clean up any stale temp from previous failed write + File file = fs->open(tmp_path, FILE_O_WRITE); #elif defined(RP2040_PLATFORM) File file = fs->open("/com_prefs", "w"); #else @@ -183,6 +187,13 @@ void CommonCLI::savePrefs(FILESYSTEM* fs) { // next: 291 file.close(); + +#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) + if (!fs->rename(tmp_path, real_path)) { + fs->remove(tmp_path); + MESH_DEBUG_PRINTLN("ERROR: savePrefs rename failed!"); + } +#endif } } diff --git a/src/helpers/RegionMap.cpp b/src/helpers/RegionMap.cpp index 7b8399e260..bd0d932b31 100644 --- a/src/helpers/RegionMap.cpp +++ b/src/helpers/RegionMap.cpp @@ -58,10 +58,10 @@ static const char* skip_hash(const char* name) { return *name == '#' ? name + 1 : name; } -static File openWrite(FILESYSTEM* _fs, const char* filename) { +static File openWriteTemp(FILESYSTEM* _fs, const char* filename, const char* tmp_filename) { #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) - _fs->remove(filename); - return _fs->open(filename, FILE_O_WRITE); + _fs->remove(tmp_filename); + return _fs->open(tmp_filename, FILE_O_WRITE); #elif defined(RP2040_PLATFORM) return _fs->open(filename, "w"); #else @@ -115,7 +115,10 @@ bool RegionMap::load(FILESYSTEM* _fs, const char* path) { } bool RegionMap::save(FILESYSTEM* _fs, const char* path) { - File file = openWrite(_fs, path ? path : "/regions2"); + const char* real_path = path ? path : "/regions2"; + char tmp_path[32]; + snprintf(tmp_path, sizeof(tmp_path), "%s.tmp", real_path); + File file = openWriteTemp(_fs, real_path, tmp_path); if (file) { uint8_t pad[128]; memset(pad, 0, sizeof(pad)); @@ -139,6 +142,14 @@ bool RegionMap::save(FILESYSTEM* _fs, const char* path) { } } file.close(); + +#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) + if (!_fs->rename(tmp_path, real_path)) { + _fs->remove(tmp_path); + MESH_DEBUG_PRINTLN("ERROR: RegionMap::save rename failed!"); + return false; + } +#endif return true; } return false; // failed From cb47439e185725514ac08ae26653e73d51c17fbe Mon Sep 17 00:00:00 2001 From: Patrick Winnertz Date: Sun, 26 Apr 2026 08:52:54 +0200 Subject: [PATCH 2/2] Don't change the function call but handle the tmp file within a separate function instead. --- src/helpers/ClientACL.cpp | 13 ++++++++++--- src/helpers/RegionMap.cpp | 16 +++++++++++----- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/src/helpers/ClientACL.cpp b/src/helpers/ClientACL.cpp index 58eb7861be..04b9439155 100644 --- a/src/helpers/ClientACL.cpp +++ b/src/helpers/ClientACL.cpp @@ -1,7 +1,14 @@ #include "ClientACL.h" -static File openWriteTemp(FILESYSTEM* _fs, const char* filename, const char* tmp_filename) { +static char* getTmpPath(const char* filename) { + static char tmp_filename[32]; + snprintf(tmp_filename, sizeof(tmp_filename), "%s.tmp", filename); + return tmp_filename; +} + +static File openWrite(FILESYSTEM* _fs, const char* filename) { #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) + char* tmp_filename = getTmpPath(filename); _fs->remove(tmp_filename); return _fs->open(tmp_filename, FILE_O_WRITE); #elif defined(RP2040_PLATFORM) @@ -54,9 +61,8 @@ void ClientACL::load(FILESYSTEM* fs, const mesh::LocalIdentity& self_id) { void ClientACL::save(FILESYSTEM* fs, bool (*filter)(ClientInfo*)) { _fs = fs; - const char* tmp_path = "/s_contacts.tmp"; const char* real_path = "/s_contacts"; - File file = openWriteTemp(_fs, real_path, tmp_path); + File file = openWrite(_fs, real_path); if (file) { uint8_t unused[2]; memset(unused, 0, sizeof(unused)); @@ -78,6 +84,7 @@ void ClientACL::save(FILESYSTEM* fs, bool (*filter)(ClientInfo*)) { file.close(); #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) + char* tmp_path = getTmpPath(real_path); if (!_fs->rename(tmp_path, real_path)) { _fs->remove(tmp_path); MESH_DEBUG_PRINTLN("ERROR: ClientACL::save rename failed!"); diff --git a/src/helpers/RegionMap.cpp b/src/helpers/RegionMap.cpp index bd0d932b31..7754e91d3f 100644 --- a/src/helpers/RegionMap.cpp +++ b/src/helpers/RegionMap.cpp @@ -1,6 +1,6 @@ #include "RegionMap.h" #include -#include +#include // helper class for region map exporter, we emulate Stream with a safe buffer writer. @@ -58,8 +58,15 @@ static const char* skip_hash(const char* name) { return *name == '#' ? name + 1 : name; } -static File openWriteTemp(FILESYSTEM* _fs, const char* filename, const char* tmp_filename) { +static char* getTmpPath(const char* filename) { + static char tmp_filename[32]; + snprintf(tmp_filename, sizeof(tmp_filename), "%s.tmp", filename); + return tmp_filename; +} + +static File openWrite(FILESYSTEM* _fs, const char* filename) { #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) + char* tmp_filename = getTmpPath(filename); _fs->remove(tmp_filename); return _fs->open(tmp_filename, FILE_O_WRITE); #elif defined(RP2040_PLATFORM) @@ -116,9 +123,7 @@ bool RegionMap::load(FILESYSTEM* _fs, const char* path) { bool RegionMap::save(FILESYSTEM* _fs, const char* path) { const char* real_path = path ? path : "/regions2"; - char tmp_path[32]; - snprintf(tmp_path, sizeof(tmp_path), "%s.tmp", real_path); - File file = openWriteTemp(_fs, real_path, tmp_path); + File file = openWrite(_fs, real_path); if (file) { uint8_t pad[128]; memset(pad, 0, sizeof(pad)); @@ -144,6 +149,7 @@ bool RegionMap::save(FILESYSTEM* _fs, const char* path) { file.close(); #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) + char* tmp_path = getTmpPath(real_path); if (!_fs->rename(tmp_path, real_path)) { _fs->remove(tmp_path); MESH_DEBUG_PRINTLN("ERROR: RegionMap::save rename failed!");