mirror of
https://github.com/MeshEnvy/lobbs.git
synced 2026-11-04 20:12:26 +01:00
977 lines
25 KiB
C++
977 lines
25 KiB
C++
#include "../LoBBSBootTrace.h"
|
|
#include "SPILock.h"
|
|
#include "configuration.h"
|
|
#include <lofs/LoFS.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <string>
|
|
|
|
#if LOBBS_EXTRA_QSPI
|
|
#if !defined(NRF52840_XXAA) || !defined(PIN_QSPI_SCK)
|
|
#error LOBBS_EXTRA_QSPI requires NRF52840_XXAA and PIN_QSPI_SCK
|
|
#endif
|
|
#include "LoFSQspi.h"
|
|
#endif
|
|
|
|
#if defined(HAS_SDCARD) && !defined(SDCARD_USE_SOFT_SPI)
|
|
#include <SD.h>
|
|
#include <SPI.h>
|
|
|
|
#ifdef SDCARD_USE_SPI1
|
|
extern SPIClass SPI_HSPI;
|
|
#define SDHandler SPI_HSPI
|
|
#else
|
|
#define SDHandler SPI
|
|
#endif
|
|
|
|
#ifndef SD_SPI_FREQUENCY
|
|
#define SD_SPI_FREQUENCY 4000000U
|
|
#endif
|
|
#endif
|
|
|
|
#if defined(ARCH_NRF52)
|
|
#include "flash/flash_nrf5x.h"
|
|
|
|
/** Second internal-flash partition bounds from the nRF52840 linker scripts. Weak: other nRF linker scripts get no `/flash2`. */
|
|
extern "C" uint8_t __flash2_start[] __attribute__((weak));
|
|
extern "C" uint8_t __flash2_end[] __attribute__((weak));
|
|
|
|
static constexpr uint32_t LOFS_FLASH2_BLOCK = 128;
|
|
|
|
// The flash_nrf5x page cache is shared with InternalFS (which the BLE task also writes), so /flash2 block IO holds its lock.
|
|
static int lofsFlash2Read(const struct lfs_config *c, lfs_block_t block, lfs_off_t off, void *buffer, lfs_size_t size)
|
|
{
|
|
InternalFS._lockFS();
|
|
int n = flash_nrf5x_read(buffer, (uint32_t)c->context + block * LOFS_FLASH2_BLOCK + off, size);
|
|
InternalFS._unlockFS();
|
|
return n > 0 ? 0 : -1;
|
|
}
|
|
|
|
static int lofsFlash2Prog(const struct lfs_config *c, lfs_block_t block, lfs_off_t off, const void *buffer, lfs_size_t size)
|
|
{
|
|
InternalFS._lockFS();
|
|
int n = flash_nrf5x_write((uint32_t)c->context + block * LOFS_FLASH2_BLOCK + off, buffer, size);
|
|
InternalFS._unlockFS();
|
|
return n > 0 ? 0 : -1;
|
|
}
|
|
|
|
static int lofsFlash2Erase(const struct lfs_config *c, lfs_block_t block)
|
|
{
|
|
uint8_t ff[LOFS_FLASH2_BLOCK];
|
|
memset(ff, 0xFF, sizeof(ff));
|
|
InternalFS._lockFS();
|
|
int n = flash_nrf5x_write((uint32_t)c->context + block * LOFS_FLASH2_BLOCK, ff, sizeof(ff));
|
|
InternalFS._unlockFS();
|
|
return n > 0 ? 0 : -1;
|
|
}
|
|
|
|
static int lofsFlash2Sync(const struct lfs_config *c)
|
|
{
|
|
(void)c;
|
|
InternalFS._lockFS();
|
|
flash_nrf5x_flush();
|
|
InternalFS._unlockFS();
|
|
return 0;
|
|
}
|
|
|
|
static struct lfs_config lofsFlash2Cfg;
|
|
static Adafruit_LittleFS lofsFlash2(&lofsFlash2Cfg);
|
|
|
|
static bool lofsFlash2Format()
|
|
{
|
|
const uint32_t start = (uint32_t)lofsFlash2Cfg.context;
|
|
const uint32_t end = start + lofsFlash2Cfg.block_count * LOFS_FLASH2_BLOCK;
|
|
LOG_WARN("LoFS: formatting /flash2 (0x%x-0x%x)", (unsigned)start, (unsigned)end);
|
|
LOBBS_BOOT_STEP("flash2: erase partition pages");
|
|
InternalFS._lockFS();
|
|
flash_nrf5x_flush();
|
|
for (uint32_t addr = start; addr < end; addr += FLASH_NRF52_PAGE_SIZE)
|
|
flash_nrf5x_erase(addr);
|
|
InternalFS._unlockFS();
|
|
LOBBS_BOOT_STEP("flash2: lfs format");
|
|
return lofsFlash2.format() && lofsFlash2.begin();
|
|
}
|
|
|
|
static bool lofsFlash2Begin()
|
|
{
|
|
LOBBS_BOOT_STEP("flash2: begin enter");
|
|
const uint32_t start = (uint32_t)__flash2_start;
|
|
const uint32_t end = (uint32_t)__flash2_end;
|
|
if (!start || end <= start)
|
|
return false;
|
|
lofsFlash2Cfg.context = (void *)start;
|
|
lofsFlash2Cfg.read = lofsFlash2Read;
|
|
lofsFlash2Cfg.prog = lofsFlash2Prog;
|
|
lofsFlash2Cfg.erase = lofsFlash2Erase;
|
|
lofsFlash2Cfg.sync = lofsFlash2Sync;
|
|
lofsFlash2Cfg.read_size = LOFS_FLASH2_BLOCK;
|
|
lofsFlash2Cfg.prog_size = LOFS_FLASH2_BLOCK;
|
|
lofsFlash2Cfg.block_size = LOFS_FLASH2_BLOCK;
|
|
lofsFlash2Cfg.block_count = (end - start) / LOFS_FLASH2_BLOCK;
|
|
lofsFlash2Cfg.lookahead = 128;
|
|
LOBBS_BOOT_STEP("flash2: lfs mount");
|
|
if (lofsFlash2.begin())
|
|
return true;
|
|
return lofsFlash2Format();
|
|
}
|
|
|
|
#if LOBBS_EXTRA_QSPI
|
|
static struct lfs_config lofsQspiCfg;
|
|
static Adafruit_LittleFS lofsQspi(&lofsQspiCfg);
|
|
|
|
static bool lofsQspiFormat()
|
|
{
|
|
LOG_WARN("LoFS: formatting /extra (QSPI)");
|
|
LOBBS_BOOT_STEP("extra: lfs format");
|
|
return lofsQspi.format() && lofsQspi.begin();
|
|
}
|
|
|
|
static bool lofsQspiBegin()
|
|
{
|
|
LOBBS_BOOT_STEP("extra: begin enter");
|
|
LOBBS_BOOT_STEP("extra: qspi config");
|
|
if (!lofsQspiConfig(lofsQspiCfg))
|
|
return false;
|
|
LOBBS_BOOT_STEP("extra: lfs mount");
|
|
if (lofsQspi.begin())
|
|
return true;
|
|
return lofsQspiFormat();
|
|
}
|
|
#endif
|
|
|
|
static Adafruit_LittleFS &lofsFs(LoFS::Backend b)
|
|
{
|
|
if (b == LoFS::Backend::Flash2)
|
|
return lofsFlash2;
|
|
#if LOBBS_EXTRA_QSPI
|
|
if (b == LoFS::Backend::Extra)
|
|
return lofsQspi;
|
|
#endif
|
|
return FSCom;
|
|
}
|
|
#else
|
|
static auto &lofsFs(bool flash2)
|
|
{
|
|
(void)flash2;
|
|
return FSCom;
|
|
}
|
|
#endif
|
|
|
|
#include "../apps/AppUtil.h"
|
|
#include "LoBBSStackGuard.h"
|
|
#include <stdio.h>
|
|
|
|
LoFS::Mount LoFS::mounts[4];
|
|
int LoFS::mountCount = 0;
|
|
bool LoFS::begun = false;
|
|
|
|
static bool lobfsSdPresent()
|
|
{
|
|
#if defined(HAS_SDCARD) && !defined(SDCARD_USE_SOFT_SPI)
|
|
uint8_t cardType = SD.cardType();
|
|
if (cardType == CARD_NONE) {
|
|
concurrency::LockGuard g(spiLock);
|
|
SDHandler.begin(SPI_SCK, SPI_MISO, SPI_MOSI);
|
|
if (SD.begin(SDCARD_CS, SDHandler, SD_SPI_FREQUENCY))
|
|
cardType = SD.cardType();
|
|
}
|
|
return cardType != CARD_NONE;
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
#if defined(HAS_SDCARD) && !defined(SDCARD_USE_SOFT_SPI)
|
|
#if defined(ARCH_ESP32) || defined(ARCH_RP2040) || defined(ARCH_PORTDUINO)
|
|
static const char *convertToSDMode(const char *modeStr)
|
|
{
|
|
return modeStr;
|
|
}
|
|
static const char *convertToSDMode(uint8_t mode)
|
|
{
|
|
return (mode == 0) ? "r" : "w";
|
|
}
|
|
#else
|
|
static uint8_t convertToSDMode(const char *modeStr)
|
|
{
|
|
return (strcmp(modeStr, "r") == 0) ? FILE_READ : FILE_WRITE;
|
|
}
|
|
static uint8_t convertToSDMode(uint8_t mode)
|
|
{
|
|
return (mode == 0) ? FILE_READ : FILE_WRITE;
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
void LoFS::begin()
|
|
{
|
|
if (begun)
|
|
return;
|
|
mountCount = 0;
|
|
bool hasFlash2 = false;
|
|
|
|
// Registration order is install preference order.
|
|
#if defined(HAS_SDCARD) && !defined(SDCARD_USE_SOFT_SPI)
|
|
LOBBS_BOOT_STEP("LoFS: sd present check");
|
|
if (lobfsSdPresent())
|
|
mounts[mountCount++] = Mount{"sd", Backend::Sd, true, false, true};
|
|
#endif
|
|
|
|
#if LOBBS_EXTRA_QSPI
|
|
LOBBS_BOOT_STEP("LoFS: lofsQspiBegin");
|
|
if (lofsQspiBegin())
|
|
mounts[mountCount++] = Mount{"extra", Backend::Extra, true, false, true};
|
|
#endif
|
|
|
|
#if defined(ARCH_NRF52)
|
|
LOBBS_BOOT_STEP("LoFS: lofsFlash2Begin");
|
|
if (lofsFlash2Begin()) {
|
|
mounts[mountCount++] = Mount{"flash2", Backend::Flash2, true, false, true};
|
|
hasFlash2 = true;
|
|
}
|
|
#endif
|
|
|
|
LOBBS_BOOT_STEP("LoFS: register flash mount");
|
|
mounts[mountCount++] = Mount{"flash", Backend::Flash, true, true, !hasFlash2};
|
|
begun = true;
|
|
LOBBS_BOOT_STEP("LoFS: begin done");
|
|
}
|
|
|
|
LoFS::Mount *LoFS::findByName(const char *name, size_t len)
|
|
{
|
|
for (int i = 0; i < mountCount; i++) {
|
|
if (strncmp(mounts[i].name, name, len) == 0 && mounts[i].name[len] == '\0' && mounts[i].present)
|
|
return &mounts[i];
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
bool LoFS::mountPresent(const char *name)
|
|
{
|
|
return findByName(name, strlen(name)) != nullptr;
|
|
}
|
|
|
|
bool LoFS::mountDbSafe(const char *name)
|
|
{
|
|
Mount *m = name ? findByName(name, strlen(name)) : nullptr;
|
|
return m && m->dbSafe;
|
|
}
|
|
|
|
bool LoFS::format(const char *name)
|
|
{
|
|
Mount *m = name ? findByName(name, strlen(name)) : nullptr;
|
|
if (!m)
|
|
return false;
|
|
#if defined(ARCH_NRF52)
|
|
if (m->backend == Backend::Flash2)
|
|
return lofsFlash2Format();
|
|
#endif
|
|
#if LOBBS_EXTRA_QSPI
|
|
if (m->backend == Backend::Extra)
|
|
return lofsQspiFormat();
|
|
#endif
|
|
#if defined(ARCH_NRF52) || defined(ARCH_ESP32) || defined(ARCH_RP2040)
|
|
if (m->backend == Backend::Flash) {
|
|
concurrency::LockGuard g(spiLock);
|
|
return FSCom.format();
|
|
}
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
void LoFS::eachPresentMount(void (*fn)(void *ctx, const char *name), void *ctx)
|
|
{
|
|
if (!fn)
|
|
return;
|
|
for (int i = 0; i < mountCount; i++) {
|
|
if (mounts[i].present)
|
|
fn(ctx, mounts[i].name);
|
|
}
|
|
}
|
|
|
|
bool LoFS::resolve(const char *filepath, Resolved &out)
|
|
{
|
|
out = {};
|
|
if (!filepath || filepath[0] != '/')
|
|
return false;
|
|
|
|
if (filepath[1] == '\0') {
|
|
out.kind = PathKind::VirtualRoot;
|
|
return true;
|
|
}
|
|
|
|
const char *p = filepath + 1;
|
|
const char *slash = strchr(p, '/');
|
|
size_t nameLen = slash ? (size_t)(slash - p) : strlen(p);
|
|
if (nameLen == 0 || nameLen >= 16)
|
|
return false;
|
|
|
|
Mount *m = findByName(p, nameLen);
|
|
if (!m)
|
|
return false;
|
|
|
|
out.backend = m->backend;
|
|
if (!slash || slash[1] == '\0') {
|
|
out.kind = PathKind::MountRoot;
|
|
out.rel = "/";
|
|
return true;
|
|
}
|
|
|
|
out.rel = slash;
|
|
out.kind = PathKind::Normal;
|
|
return true;
|
|
}
|
|
|
|
const char *LoFS::backendPath(const Resolved &r)
|
|
{
|
|
return r.backend == Backend::Sd ? r.rel + 1 : r.rel;
|
|
}
|
|
|
|
bool LoFS::isMountPoint(const char *path)
|
|
{
|
|
if (!path)
|
|
return false;
|
|
if (strcmp(path, "/") == 0)
|
|
return true;
|
|
Resolved r;
|
|
if (!resolve(path, r))
|
|
return false;
|
|
return r.kind == PathKind::MountRoot;
|
|
}
|
|
|
|
const char *LoFS::mountNameForPath(const char *path)
|
|
{
|
|
if (!path || path[0] != '/')
|
|
return nullptr;
|
|
if (path[1] == '\0')
|
|
return nullptr;
|
|
const char *p = path + 1;
|
|
const char *slash = strchr(p, '/');
|
|
size_t nameLen = slash ? (size_t)(slash - p) : strlen(p);
|
|
Mount *m = findByName(p, nameLen);
|
|
return m ? m->name : nullptr;
|
|
}
|
|
|
|
bool LoFS::isDirectory(const char *path)
|
|
{
|
|
Resolved r;
|
|
if (!resolve(path, r))
|
|
return false;
|
|
if (r.kind == PathKind::VirtualRoot || r.kind == PathKind::MountRoot)
|
|
return true;
|
|
|
|
const char *bp = backendPath(r);
|
|
|
|
concurrency::LockGuard g(spiLock);
|
|
#if defined(ARCH_ESP32) || defined(ARCH_RP2040) || defined(ARCH_PORTDUINO)
|
|
File f;
|
|
#else
|
|
File f(FSCom);
|
|
#endif
|
|
#if defined(ARCH_ESP32) || defined(ARCH_RP2040) || defined(ARCH_PORTDUINO)
|
|
if (r.backend == Backend::Sd) {
|
|
#if defined(HAS_SDCARD) && !defined(SDCARD_USE_SOFT_SPI)
|
|
f = SD.open(bp, FILE_O_READ);
|
|
#endif
|
|
} else {
|
|
f = lofsFs(r.backend == Backend::Flash2).open(bp, FILE_O_READ);
|
|
}
|
|
#else
|
|
if (r.backend == Backend::Sd) {
|
|
#if defined(HAS_SDCARD) && !defined(SDCARD_USE_SOFT_SPI)
|
|
f = SD.open(bp, FILE_O_READ);
|
|
#endif
|
|
} else {
|
|
f = lofsFs(r.backend).open(bp, FILE_O_READ);
|
|
}
|
|
#endif
|
|
if (!f)
|
|
return false;
|
|
bool isDir = f.isDirectory();
|
|
f.close();
|
|
return isDir;
|
|
}
|
|
|
|
bool LoFS::refuseMountPointMutation(const char *filepath)
|
|
{
|
|
return isMountPoint(filepath);
|
|
}
|
|
|
|
File LoFS::open(const char *filepath, uint8_t mode)
|
|
{
|
|
#if defined(ARCH_ESP32) || defined(ARCH_RP2040) || defined(ARCH_PORTDUINO)
|
|
const char *modeStr = (mode == 0) ? FILE_O_READ : FILE_O_WRITE;
|
|
return open(filepath, modeStr);
|
|
#else
|
|
Resolved r;
|
|
if (!resolve(filepath, r) || r.kind == PathKind::VirtualRoot) {
|
|
return File(FSCom);
|
|
}
|
|
|
|
const char *bp = backendPath(r);
|
|
|
|
concurrency::LockGuard g(spiLock);
|
|
if (r.backend == Backend::Sd) {
|
|
#if defined(HAS_SDCARD) && !defined(SDCARD_USE_SOFT_SPI)
|
|
return SD.open(bp, convertToSDMode(mode));
|
|
#endif
|
|
}
|
|
return lofsFs(r.backend).open(bp, mode);
|
|
#endif
|
|
}
|
|
|
|
File LoFS::open(const char *filepath, const char *mode)
|
|
{
|
|
Resolved r;
|
|
if (!resolve(filepath, r) || r.kind == PathKind::VirtualRoot) {
|
|
#if defined(ARCH_ESP32) || defined(ARCH_RP2040) || defined(ARCH_PORTDUINO)
|
|
return File();
|
|
#else
|
|
return File(FSCom);
|
|
#endif
|
|
}
|
|
|
|
const char *bp = backendPath(r);
|
|
|
|
concurrency::LockGuard g(spiLock);
|
|
if (r.backend == Backend::Sd) {
|
|
#if defined(HAS_SDCARD) && !defined(SDCARD_USE_SOFT_SPI)
|
|
#if defined(ARCH_ESP32) || defined(ARCH_RP2040) || defined(ARCH_PORTDUINO)
|
|
return SD.open(bp, mode);
|
|
#else
|
|
return SD.open(bp, convertToSDMode(mode));
|
|
#endif
|
|
#endif
|
|
}
|
|
#if defined(ARCH_ESP32) || defined(ARCH_RP2040) || defined(ARCH_PORTDUINO)
|
|
return lofsFs(r.backend == Backend::Flash2).open(bp, mode);
|
|
#else
|
|
uint8_t flashMode = (mode && strcmp(mode, "r") == 0) ? 0 : 1;
|
|
return lofsFs(r.backend).open(bp, flashMode);
|
|
#endif
|
|
}
|
|
|
|
bool LoFS::exists(const char *filepath)
|
|
{
|
|
Resolved r;
|
|
if (!resolve(filepath, r))
|
|
return false;
|
|
if (r.kind == PathKind::VirtualRoot)
|
|
return true;
|
|
if (r.kind == PathKind::MountRoot)
|
|
return true;
|
|
|
|
const char *bp = backendPath(r);
|
|
concurrency::LockGuard g(spiLock);
|
|
if (r.backend == Backend::Sd) {
|
|
#if defined(HAS_SDCARD) && !defined(SDCARD_USE_SOFT_SPI)
|
|
return SD.exists(bp);
|
|
#endif
|
|
}
|
|
#if defined(ARCH_NRF52)
|
|
return lofsFs(r.backend).exists(bp);
|
|
#else
|
|
return lofsFs(r.backend == Backend::Flash2).exists(bp);
|
|
#endif
|
|
}
|
|
|
|
bool LoFS::mkdir(const char *filepath)
|
|
{
|
|
if (refuseMountPointMutation(filepath))
|
|
return false;
|
|
|
|
Resolved r;
|
|
if (!resolve(filepath, r) || r.kind == PathKind::VirtualRoot)
|
|
return false;
|
|
|
|
const char *bp = backendPath(r);
|
|
|
|
concurrency::LockGuard g(spiLock);
|
|
if (r.backend == Backend::Sd) {
|
|
#if defined(HAS_SDCARD) && !defined(SDCARD_USE_SOFT_SPI)
|
|
return SD.mkdir(bp);
|
|
#endif
|
|
}
|
|
#if defined(ARCH_NRF52)
|
|
return lofsFs(r.backend).mkdir(bp);
|
|
#else
|
|
return lofsFs(r.backend == Backend::Flash2).mkdir(bp);
|
|
#endif
|
|
}
|
|
|
|
bool LoFS::remove(const char *filepath)
|
|
{
|
|
if (refuseMountPointMutation(filepath))
|
|
return false;
|
|
|
|
Resolved r;
|
|
if (!resolve(filepath, r) || r.kind == PathKind::VirtualRoot || r.kind == PathKind::MountRoot)
|
|
return false;
|
|
|
|
const char *bp = backendPath(r);
|
|
|
|
concurrency::LockGuard g(spiLock);
|
|
if (r.backend == Backend::Sd) {
|
|
#if defined(HAS_SDCARD) && !defined(SDCARD_USE_SOFT_SPI)
|
|
return SD.remove(bp);
|
|
#endif
|
|
}
|
|
#if defined(ARCH_NRF52)
|
|
return lofsFs(r.backend).remove(bp);
|
|
#else
|
|
return lofsFs(r.backend == Backend::Flash2).remove(bp);
|
|
#endif
|
|
}
|
|
|
|
bool LoFS::rename(const char *oldfilepath, const char *newfilepath)
|
|
{
|
|
if (refuseMountPointMutation(oldfilepath) || refuseMountPointMutation(newfilepath))
|
|
return false;
|
|
|
|
Resolved oldR;
|
|
Resolved newR;
|
|
if (!resolve(oldfilepath, oldR) || !resolve(newfilepath, newR))
|
|
return false;
|
|
if (oldR.kind == PathKind::VirtualRoot || newR.kind == PathKind::VirtualRoot)
|
|
return false;
|
|
if (oldR.backend != newR.backend)
|
|
return false;
|
|
|
|
const char *oldBp = backendPath(oldR);
|
|
const char *newBp = backendPath(newR);
|
|
|
|
concurrency::LockGuard g(spiLock);
|
|
if (oldR.backend == Backend::Sd) {
|
|
#if defined(HAS_SDCARD) && !defined(SDCARD_USE_SOFT_SPI)
|
|
return SD.rename(oldBp, newBp);
|
|
#endif
|
|
}
|
|
#if defined(ARCH_NRF52)
|
|
return lofsFs(oldR.backend).rename(oldBp, newBp);
|
|
#else
|
|
return lofsFs(oldR.backend == Backend::Flash2).rename(oldBp, newBp);
|
|
#endif
|
|
}
|
|
|
|
static bool lobfsEachDirEntry(File &dir, void *ctx, LoFS::ListCallback fn)
|
|
{
|
|
while (true) {
|
|
File file = dir.openNextFile();
|
|
if (!file)
|
|
break;
|
|
|
|
std::string pathFromFile = file.name();
|
|
bool isDir = file.isDirectory();
|
|
uint32_t size = isDir ? 0 : (uint32_t)file.size();
|
|
file.close();
|
|
|
|
size_t lastSlash = pathFromFile.rfind('/');
|
|
std::string entryName = (lastSlash != std::string::npos) ? pathFromFile.substr(lastSlash + 1) : pathFromFile;
|
|
|
|
if (entryName == "." || entryName == "..")
|
|
continue;
|
|
|
|
if (!fn(ctx, entryName.c_str(), isDir, size))
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool LoFS::list(const char *dirpath, void *ctx, ListCallback fn)
|
|
{
|
|
if (!dirpath || !fn)
|
|
return false;
|
|
|
|
Resolved r;
|
|
if (!resolve(dirpath, r))
|
|
return false;
|
|
|
|
if (r.kind == PathKind::VirtualRoot) {
|
|
for (int i = 0; i < mountCount; i++) {
|
|
if (!mounts[i].present)
|
|
continue;
|
|
if (!fn(ctx, mounts[i].name, true, 0))
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
File dir = open(dirpath, FILE_O_READ);
|
|
if (!dir)
|
|
return false;
|
|
if (!dir.isDirectory()) {
|
|
dir.close();
|
|
return false;
|
|
}
|
|
|
|
bool ok = lobfsEachDirEntry(dir, ctx, fn);
|
|
dir.close();
|
|
return ok;
|
|
}
|
|
|
|
bool LoFS::stat(const char *filepath, uint32_t *sizeOut, bool *isDirOut)
|
|
{
|
|
if (sizeOut)
|
|
*sizeOut = 0;
|
|
if (isDirOut)
|
|
*isDirOut = false;
|
|
|
|
Resolved r;
|
|
if (!resolve(filepath, r))
|
|
return false;
|
|
if (r.kind == PathKind::VirtualRoot || r.kind == PathKind::MountRoot) {
|
|
if (isDirOut)
|
|
*isDirOut = true;
|
|
return true;
|
|
}
|
|
|
|
File f = open(filepath, FILE_O_READ);
|
|
if (!f)
|
|
return false;
|
|
if (isDirOut)
|
|
*isDirOut = f.isDirectory();
|
|
if (sizeOut && !f.isDirectory())
|
|
*sizeOut = (uint32_t)f.size();
|
|
f.close();
|
|
return true;
|
|
}
|
|
|
|
bool LoFS::writeAt(const char *filepath, uint32_t offset, const uint8_t *data, size_t len)
|
|
{
|
|
if (!filepath || !data || len == 0)
|
|
return false;
|
|
if (refuseMountPointMutation(filepath))
|
|
return false;
|
|
|
|
Resolved r;
|
|
if (!resolve(filepath, r) || r.kind != PathKind::Normal)
|
|
return false;
|
|
|
|
const bool creating = !exists(filepath);
|
|
#if defined(ARCH_ESP32) || defined(ARCH_RP2040) || defined(ARCH_PORTDUINO)
|
|
File f = open(filepath, creating ? "w" : "r+");
|
|
#else
|
|
(void)creating;
|
|
File f = open(filepath, (uint8_t)1);
|
|
#endif
|
|
if (!f)
|
|
return false;
|
|
if (f.isDirectory()) {
|
|
f.close();
|
|
return false;
|
|
}
|
|
|
|
bool ok = false;
|
|
{
|
|
concurrency::LockGuard g(spiLock);
|
|
if (f.seek(offset)) {
|
|
size_t w = f.write(data, len);
|
|
ok = (w == len);
|
|
}
|
|
f.flush();
|
|
f.close();
|
|
}
|
|
return ok;
|
|
}
|
|
|
|
bool LoFS::copy(const char *src, const char *dst)
|
|
{
|
|
if (refuseMountPointMutation(src) || refuseMountPointMutation(dst))
|
|
return false;
|
|
if (exists(dst))
|
|
return false;
|
|
|
|
Resolved srcR;
|
|
Resolved dstR;
|
|
if (!resolve(src, srcR) || !resolve(dst, dstR))
|
|
return false;
|
|
if (srcR.kind == PathKind::VirtualRoot || srcR.kind == PathKind::MountRoot)
|
|
return false;
|
|
if (dstR.kind == PathKind::VirtualRoot || dstR.kind == PathKind::MountRoot)
|
|
return false;
|
|
|
|
File srcFile = open(src, FILE_O_READ);
|
|
if (!srcFile)
|
|
return false;
|
|
if (srcFile.isDirectory()) {
|
|
srcFile.close();
|
|
return false;
|
|
}
|
|
|
|
File dstFile = open(dst, FILE_O_WRITE);
|
|
if (!dstFile) {
|
|
srcFile.close();
|
|
return false;
|
|
}
|
|
|
|
unsigned char buffer[128];
|
|
bool ok = true;
|
|
while (true) {
|
|
size_t n = 0;
|
|
{
|
|
concurrency::LockGuard g(spiLock);
|
|
n = srcFile.read(buffer, sizeof(buffer));
|
|
}
|
|
if (n == 0)
|
|
break;
|
|
size_t w = 0;
|
|
{
|
|
concurrency::LockGuard g(spiLock);
|
|
w = dstFile.write(buffer, n);
|
|
}
|
|
if (w != n) {
|
|
ok = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
{
|
|
concurrency::LockGuard g(spiLock);
|
|
dstFile.flush();
|
|
dstFile.close();
|
|
srcFile.close();
|
|
}
|
|
|
|
if (!ok)
|
|
remove(dst);
|
|
return ok;
|
|
}
|
|
|
|
LoFSMoveResult LoFS::move(const char *src, const char *dst)
|
|
{
|
|
if (!src || !dst)
|
|
return LoFSMoveResult::Failed;
|
|
if (isMountPoint(src) || isMountPoint(dst))
|
|
return LoFSMoveResult::RefusedMount;
|
|
if (exists(dst))
|
|
return LoFSMoveResult::DstExists;
|
|
|
|
const char *srcMount = mountNameForPath(src);
|
|
const char *dstMount = mountNameForPath(dst);
|
|
if (srcMount && dstMount && strcmp(srcMount, dstMount) == 0) {
|
|
return rename(src, dst) ? LoFSMoveResult::Ok : LoFSMoveResult::Failed;
|
|
}
|
|
|
|
bool isDir = false;
|
|
uint32_t sz = 0;
|
|
if (!stat(src, &sz, &isDir))
|
|
return LoFSMoveResult::SrcMissing;
|
|
if (isDir)
|
|
return LoFSMoveResult::CrossMountDir;
|
|
|
|
if (!hasRoom(dst, sz))
|
|
return LoFSMoveResult::NoSpace;
|
|
|
|
if (!copy(src, dst))
|
|
return LoFSMoveResult::CopyFailed;
|
|
if (remove(src))
|
|
return LoFSMoveResult::Ok;
|
|
return LoFSMoveResult::SrcNotRemoved;
|
|
}
|
|
|
|
bool LoFS::crc32File(const char *filepath, uint32_t *crcOut)
|
|
{
|
|
if (!filepath || !crcOut)
|
|
return false;
|
|
File f = open(filepath, FILE_O_READ);
|
|
if (!f)
|
|
return false;
|
|
if (f.isDirectory()) {
|
|
f.close();
|
|
return false;
|
|
}
|
|
uint32_t crc = 0xffffffff;
|
|
uint8_t chunk[64];
|
|
while (true) {
|
|
size_t n = f.read(chunk, sizeof(chunk));
|
|
if (n == 0)
|
|
break;
|
|
crc = lobbsCrc32Update(crc, chunk, n);
|
|
}
|
|
f.close();
|
|
*crcOut = ~crc;
|
|
return true;
|
|
}
|
|
|
|
LoFSMoveResult LoFS::moveIfCrc32Matches(const char *src, const char *dst, uint32_t expectedCrc)
|
|
{
|
|
uint32_t crc = 0;
|
|
if (!crc32File(src, &crc))
|
|
return LoFSMoveResult::SrcMissing;
|
|
if (crc != expectedCrc)
|
|
return LoFSMoveResult::CrcMismatch;
|
|
return move(src, dst);
|
|
}
|
|
|
|
uint64_t LoFS::totalBytes(const char *mountRoot)
|
|
{
|
|
Resolved r;
|
|
if (!resolve(mountRoot, r) || r.kind != PathKind::MountRoot)
|
|
return 0;
|
|
concurrency::LockGuard g(spiLock);
|
|
if (r.backend == Backend::Sd) {
|
|
#if defined(HAS_SDCARD) && !defined(SDCARD_USE_SOFT_SPI)
|
|
return SD.totalBytes();
|
|
#endif
|
|
}
|
|
#if defined(ARCH_ESP32) || defined(ARCH_RP2040)
|
|
return FSCom.totalBytes();
|
|
#elif defined(ARCH_NRF52)
|
|
const lfs_config *cfg = lofsFs(r.backend)._getFS()->cfg;
|
|
return cfg ? (uint64_t)cfg->block_size * cfg->block_count : 0;
|
|
#else
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
#if defined(ARCH_NRF52)
|
|
static int lofsCountBlock(void *ctx, lfs_block_t block)
|
|
{
|
|
(void)block;
|
|
(*(uint32_t *)ctx)++;
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
uint64_t LoFS::usedBytes(const char *mountRoot)
|
|
{
|
|
Resolved r;
|
|
if (!resolve(mountRoot, r) || r.kind != PathKind::MountRoot)
|
|
return 0;
|
|
concurrency::LockGuard g(spiLock);
|
|
if (r.backend == Backend::Sd) {
|
|
#if defined(HAS_SDCARD) && !defined(SDCARD_USE_SOFT_SPI)
|
|
return SD.usedBytes();
|
|
#endif
|
|
}
|
|
#if defined(ARCH_ESP32) || defined(ARCH_RP2040)
|
|
return FSCom.usedBytes();
|
|
#elif defined(ARCH_NRF52)
|
|
Adafruit_LittleFS &fs = lofsFs(r.backend);
|
|
lfs_t *lfs = fs._getFS();
|
|
uint32_t blocks = 0;
|
|
fs._lockFS();
|
|
int err = lfs_traverse(lfs, lofsCountBlock, &blocks);
|
|
fs._unlockFS();
|
|
if (err || !lfs->cfg)
|
|
return 0;
|
|
return (uint64_t)blocks * lfs->cfg->block_size;
|
|
#else
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
uint64_t LoFS::freeBytes(const char *mountRoot)
|
|
{
|
|
uint64_t total = totalBytes(mountRoot);
|
|
uint64_t used = usedBytes(mountRoot);
|
|
if (total == 0 || used > total)
|
|
return 0;
|
|
return total - used;
|
|
}
|
|
|
|
uint32_t LoFS::mountReserve(const char *name)
|
|
{
|
|
Mount *m = name ? findByName(name, strlen(name)) : nullptr;
|
|
return (m && m->shared) ? LOFS_SHARED_RESERVE_BYTES : 0;
|
|
}
|
|
|
|
bool LoFS::hasRoom(const char *path, uint32_t bytes)
|
|
{
|
|
Resolved r;
|
|
const char *name = mountNameForPath(path);
|
|
if (!name || !resolve(path, r))
|
|
return true;
|
|
char root[20];
|
|
snprintf(root, sizeof(root), "/%s", name);
|
|
const uint64_t total = totalBytes(root);
|
|
if (total == 0)
|
|
return true;
|
|
const uint64_t used = usedBytes(root);
|
|
const uint64_t freeB = used < total ? total - used : 0;
|
|
|
|
uint32_t block = 4096;
|
|
uint32_t slack = 2 * 4096;
|
|
if (r.backend == Backend::Sd) {
|
|
block = 512;
|
|
slack = 64 * 1024;
|
|
}
|
|
#if defined(ARCH_NRF52)
|
|
if (r.backend == Backend::Flash || r.backend == Backend::Flash2) {
|
|
block = 128;
|
|
slack = 4 * 128;
|
|
}
|
|
#endif
|
|
const uint64_t need = (((uint64_t)bytes + block - 1) / block) * block + slack + mountReserve(name);
|
|
return freeB >= need;
|
|
}
|
|
|
|
bool LoFS::rmdir(const char *filepath, bool recursive)
|
|
{
|
|
if (refuseMountPointMutation(filepath))
|
|
return false;
|
|
|
|
if (!exists(filepath))
|
|
return true;
|
|
|
|
if (recursive) {
|
|
File dir = open(filepath, FILE_O_READ);
|
|
if (!dir)
|
|
return false;
|
|
|
|
if (!dir.isDirectory()) {
|
|
dir.close();
|
|
return remove(filepath);
|
|
}
|
|
|
|
bool result = true;
|
|
while (true) {
|
|
File file = dir.openNextFile();
|
|
if (!file)
|
|
break;
|
|
|
|
std::string pathFromFile = file.name();
|
|
bool isDir = file.isDirectory();
|
|
file.close();
|
|
|
|
size_t lastSlash = pathFromFile.rfind('/');
|
|
std::string entryName = (lastSlash != std::string::npos) ? pathFromFile.substr(lastSlash + 1) : pathFromFile;
|
|
|
|
if (entryName == "." || entryName == "..")
|
|
continue;
|
|
|
|
std::string fullPath = std::string(filepath) + "/" + entryName;
|
|
|
|
if (isDir) {
|
|
if (!rmdir(fullPath.c_str(), true))
|
|
result = false;
|
|
} else {
|
|
if (!remove(fullPath.c_str()))
|
|
result = false;
|
|
}
|
|
}
|
|
dir.close();
|
|
|
|
if (!result)
|
|
return false;
|
|
}
|
|
|
|
Resolved r;
|
|
if (!resolve(filepath, r) || r.kind == PathKind::VirtualRoot || r.kind == PathKind::MountRoot)
|
|
return false;
|
|
|
|
const char *bp = backendPath(r);
|
|
|
|
concurrency::LockGuard g(spiLock);
|
|
if (r.backend == Backend::Sd) {
|
|
#if defined(HAS_SDCARD) && !defined(SDCARD_USE_SOFT_SPI)
|
|
return SD.rmdir(bp);
|
|
#endif
|
|
}
|
|
#if defined(ARCH_NRF52)
|
|
return lofsFs(r.backend).rmdir(bp);
|
|
#else
|
|
return lofsFs(r.backend == Backend::Flash2).rmdir(bp);
|
|
#endif
|
|
}
|