#include "../LoBBSBootTrace.h" #include "SPILock.h" #include "configuration.h" #include #include #include #include #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 #include #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 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 }