Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions arch/stm32/Adafruit_LittleFS_stm32/src/Adafruit_LittleFS.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
#include <Arduino.h>
#include <string.h>
#include "Adafruit_LittleFS.h"
#include <helpers/FsLastErr.h>

//#include <Adafruit_TinyUSB.h> // for Serial

Expand Down Expand Up @@ -193,6 +194,7 @@ bool Adafruit_LittleFS::remove (char const *filepath)
_lockFS();

int err = lfs_remove(&_lfs, filepath);
if (err != LFS_ERR_OK) fsLastErrSet(err);
PRINT_LFS_ERR(err);

_unlockFS();
Expand All @@ -205,6 +207,7 @@ bool Adafruit_LittleFS::rename (char const *oldfilepath, char const *newfilepath
_lockFS();

int err = lfs_rename(&_lfs, oldfilepath, newfilepath);
if (err != LFS_ERR_OK) fsLastErrSet(err);
PRINT_LFS_ERR(err);

_unlockFS();
Expand All @@ -217,6 +220,7 @@ bool Adafruit_LittleFS::rmdir (char const *filepath)
_lockFS();

int err = lfs_remove(&_lfs, filepath);
if (err != LFS_ERR_OK) fsLastErrSet(err);
PRINT_LFS_ERR(err);

_unlockFS();
Expand All @@ -235,6 +239,7 @@ bool Adafruit_LittleFS::rmdir_r (char const *filepath)
_lockFS();

int err = lfs_remove(&_lfs, filepath);
if (err != LFS_ERR_OK) fsLastErrSet(err);
PRINT_LFS_ERR(err);

_unlockFS();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
#include <Arduino.h>
#include "Adafruit_LittleFS.h"
#include "littlefs/lfs.h"
#include <helpers/FsLastErr.h>

//--------------------------------------------------------------------+
// MACRO TYPEDEF CONSTANT ENUM DECLARATION
Expand Down Expand Up @@ -66,6 +67,7 @@ bool File::_open_file (char const *filepath, uint8_t mode)
if ( rc )
{
// failed to open
fsLastErrSet(rc);
PRINT_LFS_ERR(rc);
// free memory
free(_file);
Expand All @@ -92,6 +94,7 @@ bool File::_open_dir (char const *filepath)
if ( rc )
{
// failed to open
fsLastErrSet(rc);
PRINT_LFS_ERR(rc);
// free memory
free(_dir);
Expand Down Expand Up @@ -167,6 +170,7 @@ size_t File::write (uint8_t const *buf, size_t size)
wrcount = lfs_file_write(_fs->_getFS(), _file, buf, size);
if (wrcount < 0)
{
fsLastErrSet((int) wrcount);
wrcount = 0;
}
}
Expand Down Expand Up @@ -340,7 +344,8 @@ void File::_close(void)
}
else
{
lfs_file_close(this->_fs->_getFS(), _file);
int rc = lfs_file_close(this->_fs->_getFS(), _file);
if (rc != 0) fsLastErrSet(rc);
free(_file);
_file = NULL;
}
Expand Down
15 changes: 15 additions & 0 deletions docs/cli_commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,21 @@ This document provides an overview of CLI commands that can be sent to MeshCore

---

## Prefs save errors

Prefs, ACL, regions, and companion contact/channel blobs save with atomic write (temp file + rename). When a `set …` or `password …` save fails, replies are specific instead of a generic write failure:

| Condition | Reply |
|-----------|--------|
| Partition critically full (≤2 free blocks on InternalFS) | `ERR no space left on device` |
| LittleFS returned NOSPC | Same as above |
| Other LFS error | `ERR prefs <stage> failed lfs=-NN` |
| JSON serialize failure | `ERR prefs serialize failed` |

Stages: `open`, `write`, `rename`, `serialize`, `nospc`.

---

## Logging

### Begin capture of rx log to node storage
Expand Down
174 changes: 97 additions & 77 deletions examples/companion_radio/DataStore.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include <Arduino.h>
#include "DataStore.h"
#include <helpers/ConfigSerializer.h>

#if defined(EXTRAFS) || defined(QSPIFLASH)
#define MAX_BLOBRECS 100
Expand Down Expand Up @@ -31,7 +32,8 @@ DataStore::DataStore(FILESYSTEM& fs, FILESYSTEM& fsExtra, mesh::RTCClock& clock)
}
#endif

static File openWrite(FILESYSTEM* fs, const char* filename) {
// One-time migration into an empty destination FS only.
static File migrateOpenWrite(FILESYSTEM* fs, const char* filename) {
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
fs->remove(filename);
return fs->open(filename, FILE_O_WRITE);
Expand Down Expand Up @@ -247,13 +249,7 @@ void DataStore::loadPrefsInt(const char *filename, NodePrefs& _prefs) {
}

bool DataStore::savePrefs(NodePrefs& _prefs) {
File file = openWrite(_fs, "/prefs.json");
if (file) {
bool success = _prefs.saveSerial(file);
file.close();
return success;
}
return false;
return saveConfigJsonAtomic(_fs, _prefs, "/prefs.json", "/.prefs.json.new");
}

void DataStore::loadContacts(DataStoreHost* host) {
Expand Down Expand Up @@ -287,37 +283,43 @@ File file = openRead(_getContactsChannelsFS(), "/contacts3");
}
}

void DataStore::saveContacts(DataStoreHost* host, bool (*filter)(const ContactInfo& c)) {
File file = openWrite(_getContactsChannelsFS(), "/contacts3");
if (file) {
uint32_t idx = 0;
ContactInfo c;
uint8_t unused = 0;

while (host->getContactForSave(idx, c)) {
if (filter && !filter(c)) {
idx++; // advance to next contact
continue;
}
bool success = (file.write(c.id.pub_key, 32) == 32);
success = success && (file.write((uint8_t *)&c.name, 32) == 32);
success = success && (file.write(&c.type, 1) == 1);
success = success && (file.write(&c.flags, 1) == 1);
success = success && (file.write(&unused, 1) == 1);
success = success && (file.write((uint8_t *)&c.sync_since, 4) == 4);
success = success && (file.write((uint8_t *)&c.out_path_len, 1) == 1);
success = success && (file.write((uint8_t *)&c.last_advert_timestamp, 4) == 4);
success = success && (file.write(c.out_path, 64) == 64);
success = success && (file.write((uint8_t *)&c.lastmod, 4) == 4);
success = success && (file.write((uint8_t *)&c.gps_lat, 4) == 4);
success = success && (file.write((uint8_t *)&c.gps_lon, 4) == 4);

if (!success) break; // write failed

idx++; // advance to next contact
struct SaveContactsCtx {
DataStoreHost* host;
bool (*filter)(const ContactInfo& c);
};

static bool writeContactsBody(File& file, void* ctx) {
SaveContactsCtx* c = (SaveContactsCtx*) ctx;
uint32_t idx = 0;
ContactInfo contact;
uint8_t unused = 0;

while (c->host->getContactForSave(idx, contact)) {
if (c->filter && !c->filter(contact)) {
idx++;
continue;
}
file.close();
bool success = (file.write(contact.id.pub_key, 32) == 32);
success = success && (file.write((uint8_t*) &contact.name, 32) == 32);
success = success && (file.write(&contact.type, 1) == 1);
success = success && (file.write(&contact.flags, 1) == 1);
success = success && (file.write(&unused, 1) == 1);
success = success && (file.write((uint8_t*) &contact.sync_since, 4) == 4);
success = success && (file.write((uint8_t*) &contact.out_path_len, 1) == 1);
success = success && (file.write((uint8_t*) &contact.last_advert_timestamp, 4) == 4);
success = success && (file.write(contact.out_path, 64) == 64);
success = success && (file.write((uint8_t*) &contact.lastmod, 4) == 4);
success = success && (file.write((uint8_t*) &contact.gps_lat, 4) == 4);
success = success && (file.write((uint8_t*) &contact.gps_lon, 4) == 4);
if (!success) return false;
idx++;
}
return true;
}

void DataStore::saveContacts(DataStoreHost* host, bool (*filter)(const ContactInfo& c)) {
SaveContactsCtx ctx = {host, filter};
writeFileAtomic(_getContactsChannelsFS(), "/contacts3", "/.contacts3.new", writeContactsBody, &ctx);
}

void DataStore::loadChannels(DataStoreHost* host) {
Expand Down Expand Up @@ -345,24 +347,30 @@ void DataStore::loadChannels(DataStoreHost* host) {
}
}

void DataStore::saveChannels(DataStoreHost* host) {
File file = openWrite(_getContactsChannelsFS(), "/channels2");
if (file) {
uint8_t channel_idx = 0;
ChannelDetails ch;
uint8_t unused[4];
memset(unused, 0, 4);

while (host->getChannelForSave(channel_idx, ch)) {
bool success = (file.write(unused, 4) == 4);
success = success && (file.write((uint8_t *)ch.name, 32) == 32);
success = success && (file.write((uint8_t *)ch.channel.secret, 32) == 32);

if (!success) break; // write failed
channel_idx++;
}
file.close();
struct SaveChannelsCtx {
DataStoreHost* host;
};

static bool writeChannelsBody(File& file, void* ctx) {
SaveChannelsCtx* c = (SaveChannelsCtx*) ctx;
uint8_t channel_idx = 0;
ChannelDetails ch;
uint8_t unused[4];
memset(unused, 0, 4);

while (c->host->getChannelForSave(channel_idx, ch)) {
bool success = (file.write(unused, 4) == 4);
success = success && (file.write((uint8_t*) ch.name, 32) == 32);
success = success && (file.write((uint8_t*) ch.channel.secret, 32) == 32);
if (!success) return false;
channel_idx++;
}
return true;
}

void DataStore::saveChannels(DataStoreHost* host) {
SaveChannelsCtx ctx = {host};
writeFileAtomic(_getContactsChannelsFS(), "/channels2", "/.channels2.new", writeChannelsBody, &ctx);
}

#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
Expand All @@ -376,17 +384,24 @@ struct BlobRec {
uint8_t data[MAX_ADVERT_PKT_LEN];
};

struct InitAdvBlobsCtx {
int max_recs;
};

static bool writeAdvBlobsInitBody(File& file, void* ctx) {
InitAdvBlobsCtx* c = (InitAdvBlobsCtx*) ctx;
BlobRec zeroes;
memset(&zeroes, 0, sizeof(zeroes));
for (int i = 0; i < c->max_recs; i++) {
if (file.write((uint8_t*) &zeroes, sizeof(zeroes)) != sizeof(zeroes)) return false;
}
return true;
}

void DataStore::checkAdvBlobFile() {
if (!_getContactsChannelsFS()->exists("/adv_blobs")) {
File file = openWrite(_getContactsChannelsFS(), "/adv_blobs");
if (file) {
BlobRec zeroes;
memset(&zeroes, 0, sizeof(zeroes));
for (int i = 0; i < MAX_BLOBRECS; i++) { // pre-allocate to fixed size
file.write((uint8_t *) &zeroes, sizeof(zeroes));
}
file.close();
}
InitAdvBlobsCtx ctx = {MAX_BLOBRECS};
writeFileAtomic(_getContactsChannelsFS(), "/adv_blobs", "/.adv_blobs.new", writeAdvBlobsInitBody, &ctx);
}
}

Expand All @@ -395,7 +410,7 @@ void DataStore::migrateToSecondaryFS() {
if (!_fsExtra->exists("/adv_blobs")) {
if (_fs->exists("/adv_blobs")) {
File oldAdvBlobs = openRead(_fs, "/adv_blobs");
File newAdvBlobs = openWrite(_fsExtra, "/adv_blobs");
File newAdvBlobs = migrateOpenWrite(_fsExtra, "/adv_blobs");

if (oldAdvBlobs && newAdvBlobs) {
BlobRec rec;
Expand All @@ -416,7 +431,7 @@ void DataStore::migrateToSecondaryFS() {
if (!_fsExtra->exists("/contacts3")) {
if (_fs->exists("/contacts3")) {
File oldFile = openRead(_fs, "/contacts3");
File newFile = openWrite(_fsExtra, "/contacts3");
File newFile = migrateOpenWrite(_fsExtra, "/contacts3");

if (oldFile && newFile) {
uint8_t buf[64];
Expand All @@ -433,7 +448,7 @@ void DataStore::migrateToSecondaryFS() {
if (!_fsExtra->exists("/channels2")) {
if (_fs->exists("/channels2")) {
File oldFile = openRead(_fs, "/channels2");
File newFile = openWrite(_fsExtra, "/channels2");
File newFile = migrateOpenWrite(_fsExtra, "/channels2");

if (oldFile && newFile) {
uint8_t buf[64];
Expand All @@ -451,7 +466,7 @@ void DataStore::migrateToSecondaryFS() {
if (_fsExtra->exists("/_main.id")) {
if (_fs->exists("/_main.id")) {_fs->remove("/_main.id");}
File oldFile = openRead(_fsExtra, "/_main.id");
File newFile = openWrite(_fs, "/_main.id");
File newFile = migrateOpenWrite(_fs, "/_main.id");

if (oldFile && newFile) {
uint8_t buf[64];
Expand All @@ -467,7 +482,7 @@ void DataStore::migrateToSecondaryFS() {
if (_fsExtra->exists("/new_prefs")) {
if (_fs->exists("/new_prefs")) {_fs->remove("/new_prefs");}
File oldFile = openRead(_fsExtra, "/new_prefs");
File newFile = openWrite(_fs, "/new_prefs");
File newFile = migrateOpenWrite(_fs, "/new_prefs");

if (oldFile && newFile) {
uint8_t buf[64];
Expand Down Expand Up @@ -578,19 +593,24 @@ uint8_t DataStore::getBlobByKey(const uint8_t key[], int key_len, uint8_t dest_b
return 0; // not found
}

struct BlobWriteCtx {
const uint8_t* buf;
uint8_t len;
};

static bool writeBlobBody(File& file, void* ctx) {
BlobWriteCtx* c = (BlobWriteCtx*) ctx;
return file.write(c->buf, c->len) == c->len;
}

bool DataStore::putBlobByKey(const uint8_t key[], int key_len, const uint8_t src_buf[], uint8_t len) {
char path[64];
makeBlobPath(key, key_len, path, sizeof(path));

File f = openWrite(_fs, path);
if (f) {
int n = f.write(src_buf, len);
f.close();
if (n == len) return true; // success!

_fs->remove(path); // blob was only partially written!
}
return false; // error
char tmp_path[72];
snprintf(tmp_path, sizeof(tmp_path), "%s.new", path);
BlobWriteCtx ctx = {src_buf, len};
return writeFileAtomic(_fs, path, tmp_path, writeBlobBody, &ctx);
}

bool DataStore::deleteBlobByKey(const uint8_t key[], int key_len) {
Expand Down
1 change: 1 addition & 0 deletions examples/simple_repeater/MyMesh.h
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks {
// CommonCLICallbacks
void applyTempRadioParams(float freq, float bw, uint8_t sf, uint8_t cr, int timeout_mins) override;
bool formatFileSystem() override;
FILESYSTEM* getFileSystem() override { return _fs; }
void sendSelfAdvertisement(int delay_millis, bool flood) override;
void updateAdvertTimer() override;
void updateFloodAdvertTimer() override;
Expand Down
1 change: 1 addition & 0 deletions examples/simple_room_server/MyMesh.h
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks {
// CommonCLICallbacks
void applyTempRadioParams(float freq, float bw, uint8_t sf, uint8_t cr, int timeout_mins) override;
bool formatFileSystem() override;
FILESYSTEM* getFileSystem() override { return _fs; }
void sendSelfAdvertisement(int delay_millis, bool flood) override;
void updateAdvertTimer() override;
void updateFloodAdvertTimer() override;
Expand Down
1 change: 1 addition & 0 deletions examples/simple_sensor/SensorMesh.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ class SensorMesh : public mesh::Mesh, public CommonCLICallbacks {
NodePrefs* getNodePrefs() { return &_prefs; }
void savePrefs() override { _cli.savePrefs(_fs); }
bool formatFileSystem() override;
FILESYSTEM* getFileSystem() override { return _fs; }
void sendSelfAdvertisement(int delay_millis, bool flood) override;
void updateAdvertTimer() override;
void updateFloodAdvertTimer() override;
Expand Down
Loading