Bridges LoRa mesh and TCP/WiFi backbone networks using microReticulum. Based on microReticulum_Firmware with boundary mode additions: - BoundaryMode.h: State management and EEPROM persistence - BoundaryConfig.h: WiFi captive portal for configuration - TcpInterface.h: TCP backbone interface with HDLC framing - Display.h: Custom OLED layout with network status indicators - Transport/Identity library patches for embedded memory constraints
479 lines
12 KiB
C++
Executable File
479 lines
12 KiB
C++
Executable File
#include "FileSystem.h"
|
|
#include "FileStream.h"
|
|
#include "FileSystemType.h"
|
|
|
|
#ifdef HAS_RNS
|
|
|
|
#include <Log.h>
|
|
|
|
#if FS_TYPE == FS_TYPE_INTERNALFS
|
|
|
|
inline int _countLfsBlock(void *p, lfs_block_t block) {
|
|
lfs_size_t *size = (lfs_size_t*) p;
|
|
*size += 1;
|
|
return 0;
|
|
}
|
|
|
|
lfs_ssize_t usedBlocks() {
|
|
lfs_size_t size = 0;
|
|
lfs_traverse(FS._getFS(), _countLfsBlock, &size);
|
|
return size;
|
|
}
|
|
|
|
size_t usedBytes() {
|
|
const lfs_config* config = FS._getFS()->cfg;
|
|
const size_t usedBlockCount = usedBlocks();
|
|
return config->block_size * usedBlockCount;
|
|
}
|
|
|
|
size_t totalBytes() {
|
|
const lfs_config* config = FS._getFS()->cfg;
|
|
return config->block_size * config->block_count;
|
|
}
|
|
|
|
#elif FS_TYPE == FS_TYPE_FLASHFS
|
|
|
|
Adafruit_FlashTransport_SPI g_flashTransport(SS, SPI);
|
|
|
|
//Flash definition structure for GD25Q16C Flash (RAK15001)
|
|
Cached_SPIFlash g_flash(&g_flashTransport);
|
|
SPIFlash_Device_t g_RAK15001 {
|
|
.total_size = (1UL << 21),
|
|
.start_up_time_us = 5000,
|
|
.manufacturer_id = 0xc8,
|
|
.memory_type = 0x40,
|
|
.capacity = 0x15,
|
|
.max_clock_speed_mhz = 15,
|
|
.quad_enable_bit_mask = 0x00,
|
|
.has_sector_protection = false,
|
|
.supports_fast_read = true,
|
|
.supports_qspi = false,
|
|
.supports_qspi_writes = false,
|
|
.write_status_register_split = false,
|
|
.single_status_byte = true,
|
|
};
|
|
|
|
#endif
|
|
|
|
|
|
bool FileSystem::init() {
|
|
TRACE("Initializing filesystem...");
|
|
try {
|
|
#if FS_TYPE == FS_TYPE_SPIFFS
|
|
// Initialize SPIFFS
|
|
INFO("SPIFFS mounting filesystem");
|
|
if (!SPIFFS.begin(true, "")) {
|
|
ERROR("SPIFFS filesystem mount failed");
|
|
return false;
|
|
}
|
|
INFO("SPIFFS filesystem is ready");
|
|
#elif FS_TYPE == FS_TYPE_LITTLEFS
|
|
// Initialize LittleFS
|
|
INFO("LittleFS mounting filesystem");
|
|
if (!LittleFS.begin(true, "")) {
|
|
ERROR("LittleFS filesystem mount failed");
|
|
return false;
|
|
}
|
|
DEBUG("LittleFS filesystem is ready");
|
|
#elif FS_TYPE == FS_TYPE_INTERNALFS
|
|
// Initialize InternalFileSystem
|
|
INFO("InternalFS mounting filesystem");
|
|
if (!InternalFS.begin()) {
|
|
ERROR("InternalFS filesystem mount failed");
|
|
return false;
|
|
}
|
|
INFO("InternalFS filesystem is ready");
|
|
#elif FS_TYPE == FS_TYPE_FLASHFS
|
|
// Initialize FlashFileSystem
|
|
INFO("FlashFS mounting filesystem");
|
|
if (!g_flash.begin(&g_RAK15001)) {
|
|
ERROR("FlashFS failed to initialize");
|
|
return false;
|
|
}
|
|
if (!FlashFS.begin(&g_flash)) {
|
|
ERROR("FlashFS filesystem mount failed");
|
|
return false;
|
|
}
|
|
#endif
|
|
// Ensure filesystem is writable and reformat if not
|
|
RNS::Bytes test("test");
|
|
if (write_file("/test", test) < 4) {
|
|
HEAD("Failed to write test file, filesystem is being reformatted...", RNS::LOG_CRITICAL);
|
|
//FS.format();
|
|
reformat();
|
|
}
|
|
else {
|
|
remove_file("/test");
|
|
}
|
|
}
|
|
catch (std::exception& e) {
|
|
//ERROR("FileSystem init Exception: " + std::string(e.what()));
|
|
return false;
|
|
}
|
|
TRACE("Finished initializing");
|
|
return true;
|
|
}
|
|
|
|
bool FileSystem::format() {
|
|
INFO("Formatting filesystem...");
|
|
try {
|
|
if (!FS.format()) {
|
|
ERROR("Format failed!");
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
catch (std::exception& e) {
|
|
ERROR("FileSystem reformat Exception: " + std::string(e.what()));
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool FileSystem::reformat() {
|
|
INFO("Reformatting filesystem...");
|
|
try {
|
|
RNS::Bytes eeprom;
|
|
read_file("/eeprom", eeprom);
|
|
RNS::Bytes transport_identity;
|
|
read_file("/transport_identity", transport_identity);
|
|
//RNS::Bytes time_offset;
|
|
//read_file("/time_offset", time_offset);
|
|
if (!FS.format()) {
|
|
ERROR("Format failed!");
|
|
return false;
|
|
}
|
|
if (eeprom) {
|
|
write_file("/eeprom", eeprom);
|
|
}
|
|
if (transport_identity) {
|
|
write_file("/transport_identity", transport_identity);
|
|
}
|
|
//if (time_offset) {
|
|
// write_file("/time_offset", time_offset);
|
|
//}
|
|
return true;
|
|
}
|
|
catch (std::exception& e) {
|
|
ERROR("FileSystem reformat Exception: " + std::string(e.what()));
|
|
}
|
|
return false;
|
|
}
|
|
|
|
#ifndef NDEBUG
|
|
|
|
void FileSystem::listDir(const char* dir, const char* prefix /*= ""*/) {
|
|
Serial.print(prefix);
|
|
std::string full_dir(dir);
|
|
if (full_dir.compare("/") != 0) {
|
|
full_dir += "/";
|
|
}
|
|
Serial.println(full_dir.c_str());
|
|
std::string pre(prefix);
|
|
pre.append(" ");
|
|
try {
|
|
File root = FS.open(dir);
|
|
if (!root) {
|
|
Serial.print(pre.c_str());
|
|
Serial.println("(failed to open directory)");
|
|
return;
|
|
}
|
|
File file = root.openNextFile();
|
|
while (file) {
|
|
char* name = (char*)file.name();
|
|
std::string recurse_dir(full_dir);
|
|
if (file.isDirectory()) {
|
|
recurse_dir += name;
|
|
listDir(recurse_dir.c_str(), pre.c_str());
|
|
}
|
|
else {
|
|
Serial.print(pre.c_str());
|
|
//Serial.print("FILE: ");
|
|
Serial.print(name);
|
|
Serial.print(" (");
|
|
Serial.print(file.size());
|
|
Serial.println(" bytes)");
|
|
}
|
|
file.close();
|
|
file = root.openNextFile();
|
|
}
|
|
root.close();
|
|
}
|
|
catch (std::exception& e) {
|
|
Serial.print("listDir Exception: ");
|
|
Serial.println(e.what());
|
|
}
|
|
}
|
|
|
|
void FileSystem::dumpDir(const char* dir) {
|
|
Serial.print("DIR: ");
|
|
std::string full_dir(dir);
|
|
if (full_dir.compare("/") != 0) {
|
|
full_dir += "/";
|
|
}
|
|
Serial.println(full_dir.c_str());
|
|
try {
|
|
File root = FS.open(dir);
|
|
if (!root) {
|
|
Serial.println("(failed to open directory)");
|
|
return;
|
|
}
|
|
File file = root.openNextFile();
|
|
while (file) {
|
|
char* name = (char*)file.name();
|
|
if (file.isDirectory()) {
|
|
std::string recurse_dir(full_dir);
|
|
recurse_dir += name;
|
|
dumpDir(recurse_dir.c_str());
|
|
}
|
|
else {
|
|
Serial.print("\nFILE: ");
|
|
Serial.print(name);
|
|
Serial.print(" (");
|
|
Serial.print(file.size());
|
|
Serial.println(" bytes)");
|
|
char data[4096];
|
|
size_t size = file.size();
|
|
size_t read = file.readBytes(data, (size < sizeof(data)) ? size : sizeof(data));
|
|
Serial.write(data, read);
|
|
Serial.println("");
|
|
}
|
|
file.close();
|
|
file = root.openNextFile();
|
|
}
|
|
root.close();
|
|
}
|
|
catch (std::exception& e) {
|
|
Serial.print("dumpDir Exception: ");
|
|
Serial.println(e.what());
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
/*virtua*/ bool FileSystem::file_exists(const char* file_path) {
|
|
TRACEF("file_exists: checking for existence of file %s", file_path);
|
|
/*
|
|
#if FS_TYPE == FS_TYPE_INTERNALFS || FS_TYPE == FS_TYPE_FLASHFS
|
|
File file(FS);
|
|
if (file.open(file_path, FILE_O_READ)) {
|
|
#else
|
|
File file = FS.open(file_path, FILE_READ);
|
|
if (file) {
|
|
#endif
|
|
bool is_directory = file.isDirectory();
|
|
file.close();
|
|
return !is_directory;
|
|
}
|
|
return false;
|
|
*/
|
|
return FS.exists(file_path);
|
|
}
|
|
|
|
/*virtua*/ size_t FileSystem::read_file(const char* file_path, RNS::Bytes& data) {
|
|
TRACEF("read_file: reading from file %s", file_path);
|
|
size_t read = 0;
|
|
#if FS_TYPE == FS_TYPE_INTERNALFS || FS_TYPE == FS_TYPE_FLASHFS
|
|
File file(FS);
|
|
if (file.open(file_path, FILE_O_READ)) {
|
|
#else
|
|
File file = FS.open(file_path, FILE_READ);
|
|
if (file) {
|
|
#endif
|
|
size_t size = file.size();
|
|
read = file.readBytes((char*)data.writable(size), size);
|
|
TRACEF("read_file: read %u bytes from file %s", read, file_path);
|
|
if (read != size) {
|
|
ERRORF("read_file: failed to read file %s", file_path);
|
|
data.resize(read);
|
|
}
|
|
//TRACE("read_file: closing input file");
|
|
file.close();
|
|
}
|
|
else {
|
|
ERRORF("read_file: failed to open input file %s", file_path);
|
|
}
|
|
return read;
|
|
}
|
|
|
|
/*virtua*/ size_t FileSystem::write_file(const char* file_path, const RNS::Bytes& data) {
|
|
TRACEF("write_file: writing to file %s", file_path);
|
|
// CBA TODO Replace remove with working truncation
|
|
if (FS.exists(file_path)) {
|
|
FS.remove(file_path);
|
|
}
|
|
size_t wrote = 0;
|
|
#if FS_TYPE == FS_TYPE_INTERNALFS || FS_TYPE == FS_TYPE_FLASHFS
|
|
File file(FS);
|
|
if (file.open(file_path, FILE_O_WRITE)) {
|
|
#else
|
|
File file = FS.open(file_path, FILE_WRITE);
|
|
if (file) {
|
|
#endif
|
|
// Seek to beginning to overwrite
|
|
//file.seek(0);
|
|
//file.truncate(0);
|
|
wrote = file.write(data.data(), data.size());
|
|
TRACEF("write_file: wrote %u bytes to file %s", wrote, file_path);
|
|
if (wrote < data.size()) {
|
|
WARNINGF("write_file: not all data was written to file %s", file_path);
|
|
}
|
|
//TRACE("write_file: closing output file");
|
|
file.close();
|
|
}
|
|
else {
|
|
ERRORF("write_file: failed to open output file %s", file_path);
|
|
}
|
|
return wrote;
|
|
}
|
|
|
|
/*virtual*/ RNS::FileStream FileSystem::open_file(const char* file_path, RNS::FileStream::MODE file_mode) {
|
|
TRACEF("open_file: opening file %s", file_path);
|
|
#if FS_TYPE == FS_TYPE_INTERNALFS || FS_TYPE == FS_TYPE_FLASHFS
|
|
int mode;
|
|
if (file_mode == RNS::FileStream::MODE_READ) {
|
|
mode = FILE_O_READ;
|
|
}
|
|
else if (file_mode == RNS::FileStream::MODE_WRITE) {
|
|
mode = FILE_O_WRITE;
|
|
// CBA TODO Replace remove with working truncation
|
|
if (FS.exists(file_path)) {
|
|
FS.remove(file_path);
|
|
}
|
|
}
|
|
else if (file_mode == RNS::FileStream::MODE_APPEND) {
|
|
// CBA This is the default write mode for nrf52 littlefs
|
|
mode = FILE_O_WRITE;
|
|
}
|
|
else {
|
|
ERRORF("open_file: unsupported mode %d", file_mode);
|
|
return {RNS::Type::NONE};
|
|
}
|
|
File* file = new File(FS);
|
|
if (!file->open(file_path, mode)) {
|
|
ERRORF("open_file: failed to open output file %s", file_path);
|
|
return {RNS::Type::NONE};
|
|
}
|
|
// Seek to beginning to overwrite (this is failing on nrf52)
|
|
//if (file_mode == RNS::FileStream::MODE_WRITE) {
|
|
// file->seek(0);
|
|
// file->truncate(0);
|
|
//}
|
|
TRACEF("open_file: successfully opened file %s", file_path);
|
|
return RNS::FileStream(new FileStream(file));
|
|
#else
|
|
const char* mode;
|
|
if (file_mode == RNS::FileStream::MODE_READ) {
|
|
mode = FILE_READ;
|
|
}
|
|
else if (file_mode == RNS::FileStream::MODE_WRITE) {
|
|
mode = FILE_WRITE;
|
|
}
|
|
else if (file_mode == RNS::FileStream::MODE_APPEND) {
|
|
mode = FILE_APPEND;
|
|
}
|
|
else {
|
|
ERRORF("open_file: unsupported mode %d", file_mode);
|
|
return {RNS::Type::NONE};
|
|
}
|
|
TRACEF("open_file: opening file %s in mode %s", file_path, mode);
|
|
// CBA Using copy constructor to obtain File*
|
|
File* file = new File(FS.open(file_path, mode));
|
|
if (file == nullptr || !(*file)) {
|
|
ERRORF("open_file: failed to open output file %s", file_path);
|
|
return {RNS::Type::NONE};
|
|
}
|
|
TRACEF("open_file: successfully opened file %s", file_path);
|
|
return RNS::FileStream(new FileStream(file));
|
|
#endif
|
|
}
|
|
|
|
/*virtua*/ bool FileSystem::remove_file(const char* file_path) {
|
|
TRACEF("remove_file: removing file %s", file_path);
|
|
return FS.remove(file_path);
|
|
}
|
|
|
|
/*virtua*/ bool FileSystem::rename_file(const char* from_file_path, const char* to_file_path) {
|
|
TRACEF("rename_file: renaming file %s to %s", from_file_path, to_file_path);
|
|
return FS.rename(from_file_path, to_file_path);
|
|
}
|
|
|
|
/*virtua*/ bool FileSystem::directory_exists(const char* directory_path) {
|
|
TRACEF("directory_exists: checking for existence of directory %s", directory_path);
|
|
#if FS_TYPE == FS_TYPE_INTERNALFS || FS_TYPE == FS_TYPE_FLASHFS
|
|
File file(FS);
|
|
if (file.open(directory_path, FILE_O_READ)) {
|
|
#else
|
|
File file = FS.open(directory_path, FILE_READ);
|
|
if (file) {
|
|
#endif
|
|
bool is_directory = file.isDirectory();
|
|
file.close();
|
|
return is_directory;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/*virtua*/ bool FileSystem::create_directory(const char* directory_path) {
|
|
TRACEF("create_directory: creating directory %s", directory_path);
|
|
if (!FS.mkdir(directory_path)) {
|
|
ERROR("create_directory: failed to create directory " + std::string(directory_path));
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/*virtua*/ bool FileSystem::remove_directory(const char* directory_path) {
|
|
TRACEF("remove_directory: removing directory %s", directory_path);
|
|
#if FS_TYPE == FS_TYPE_INTERNALFS || FS_TYPE == FS_TYPE_FLASHFS
|
|
if (!FS.rmdir_r(directory_path)) {
|
|
#else
|
|
if (!FS.rmdir(directory_path)) {
|
|
#endif
|
|
ERROR("remove_directory: failed to remove directory " + std::string(directory_path));
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/*virtua*/ std::list<std::string> FileSystem::list_directory(const char* directory_path) {
|
|
TRACEF("list_directory: listing directory %s", directory_path);
|
|
std::list<std::string> files;
|
|
File root = FS.open(directory_path);
|
|
if (!root) {
|
|
ERROR("list_directory: failed to open directory " + std::string(directory_path));
|
|
return files;
|
|
}
|
|
File file = root.openNextFile();
|
|
while (file) {
|
|
if (!file.isDirectory()) {
|
|
char* name = (char*)file.name();
|
|
files.push_back(name);
|
|
}
|
|
// CBA Following close required to avoid leaking memory
|
|
file.close();
|
|
file = root.openNextFile();
|
|
}
|
|
root.close();
|
|
TRACE("list_directory: returning directory listing");
|
|
return files;
|
|
}
|
|
|
|
/*virtual*/ size_t FileSystem::storage_size() {
|
|
#if FS_TYPE == FS_TYPE_INTERNALFS
|
|
return totalBytes();
|
|
#else
|
|
return FS.totalBytes();
|
|
#endif
|
|
}
|
|
|
|
/*virtual*/ size_t FileSystem::storage_available() {
|
|
#if FS_TYPE == FS_TYPE_INTERNALFS
|
|
return (totalBytes() - usedBytes());
|
|
#else
|
|
return (FS.totalBytes() - FS.usedBytes());
|
|
#endif
|
|
}
|
|
|
|
#endif
|